Full Code of vlad1m1r990/Lemniscate for AI

master 1e28235f6f4c cached
106 files
209.9 KB
57.3k tokens
1 requests
Download .txt
Showing preview only (241K chars total). Download the full file or copy to clipboard to get everything.
Repository: vlad1m1r990/Lemniscate
Branch: master
Commit: 1e28235f6f4c
Files: 106
Total size: 209.9 KB

Directory structure:
gitextract_ucgiast4/

├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── build.gradle
├── buildSrc/
│   ├── .gitignore
│   ├── build.gradle.kts
│   └── src/
│       └── main/
│           └── java/
│               └── Dependencies.kt
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── lemniscate/
│   ├── .gitignore
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src/
│       ├── main/
│       │   ├── AndroidManifest.xml
│       │   ├── java/
│       │   │   └── com/
│       │   │       └── vlad1m1r/
│       │   │           └── lemniscate/
│       │   │               ├── BernoullisBowProgressView.kt
│       │   │               ├── BernoullisProgressView.kt
│       │   │               ├── BernoullisSharpProgressView.kt
│       │   │               ├── GeronosProgressView.kt
│       │   │               ├── base/
│       │   │               │   ├── BaseCurveContract.kt
│       │   │               │   ├── BaseCurvePresenter.kt
│       │   │               │   ├── BaseCurveProgressView.kt
│       │   │               │   ├── models/
│       │   │               │   │   ├── DrawState.kt
│       │   │               │   │   ├── LineLength.kt
│       │   │               │   │   ├── Point.kt
│       │   │               │   │   ├── Points.kt
│       │   │               │   │   └── ViewSize.kt
│       │   │               │   └── settings/
│       │   │               │       ├── AnimationSettings.kt
│       │   │               │       └── CurveSettings.kt
│       │   │               ├── funny/
│       │   │               │   ├── CannabisProgressView.kt
│       │   │               │   └── HeartProgressView.kt
│       │   │               ├── other/
│       │   │               │   └── XProgressView.kt
│       │   │               ├── roulette/
│       │   │               │   ├── BaseRouletteProgressView.kt
│       │   │               │   ├── EpitrochoidProgressView.kt
│       │   │               │   ├── HypotrochoidProgressView.kt
│       │   │               │   ├── scribble/
│       │   │               │   │   ├── RoundScribbleProgressView.kt
│       │   │               │   │   └── ScribbleProgressView.kt
│       │   │               │   └── settings/
│       │   │               │       └── RouletteCurveSettings.kt
│       │   │               └── utils/
│       │   │                   └── CurveUtils.kt
│       │   └── res/
│       │       └── values/
│       │           ├── attrs.xml
│       │           └── dimens.xml
│       └── test/
│           ├── java/
│           │   └── com/
│           │       └── vlad1m1r/
│           │           └── lemniscate/
│           │               ├── BernoullisBowProgressViewTest.kt
│           │               ├── BernoullisProgressViewTest.kt
│           │               ├── BernoullisSharpProgressViewTest.kt
│           │               ├── GeronosProgressViewTest.kt
│           │               ├── base/
│           │               │   ├── BaseCurvePresenterTest.kt
│           │               │   ├── BaseCurveProgressViewTest.kt
│           │               │   ├── BaseProgressViewAttributesTest.kt
│           │               │   ├── models/
│           │               │   │   ├── DrawStateTest.kt
│           │               │   │   ├── LineLengthParcelableTest.kt
│           │               │   │   ├── LineLengthTest.kt
│           │               │   │   ├── PointTest.kt
│           │               │   │   └── PointsTest.kt
│           │               │   └── settings/
│           │               │       ├── AnimationSettingsParcelableTest.kt
│           │               │       ├── CurveSettingsParcelableTest.kt
│           │               │       └── CurveSettingsTest.kt
│           │               ├── funny/
│           │               │   ├── CannabisProgressViewTest.kt
│           │               │   └── HeartProgressViewTest.kt
│           │               ├── other/
│           │               │   └── XProgressViewTest.kt
│           │               ├── roulette/
│           │               │   ├── BaseRouletteProgressViewAttributesTest.kt
│           │               │   ├── EpitrochoidProgressViewTest.kt
│           │               │   ├── HypotrochoidProgressViewTest.kt
│           │               │   ├── scribble/
│           │               │   │   ├── RoundScribbleProgressViewTest.kt
│           │               │   │   └── ScribbleProgressViewTest.kt
│           │               │   └── settings/
│           │               │       └── RouletteCurveSettingsParcelableTest.kt
│           │               ├── testutils/
│           │               │   ├── CurveTestUtils.kt
│           │               │   ├── EqualUtils.kt
│           │               │   ├── TestConstants.kt
│           │               │   └── TestLayoutInflater.kt
│           │               └── utils/
│           │                   └── CurveUtilsTest.kt
│           └── resources/
│               └── mockito-extensions/
│                   └── org.mockito.plugins.MockMaker
├── remote_data/
│   └── legal/
│       ├── privacy_policy.html
│       └── terms_and_conditions.html
├── sample/
│   ├── .gitignore
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src/
│       └── main/
│           ├── AndroidManifest.xml
│           ├── java/
│           │   └── com/
│           │       └── vlad1m1r/
│           │           └── lemniscate/
│           │               └── sample/
│           │                   ├── CurveData.kt
│           │                   ├── FragmentCurve.kt
│           │                   ├── FragmentSettings.kt
│           │                   ├── MainActivity.kt
│           │                   └── PresentationActivity.kt
│           └── res/
│               ├── drawable/
│               │   ├── indicator.xml
│               │   ├── indicator_selected.xml
│               │   └── shadow.xml
│               ├── drawable-v26/
│               │   ├── ic_launcher_background.xml
│               │   └── ic_launcher_foreground.xml
│               ├── layout/
│               │   ├── activity_main.xml
│               │   ├── activity_presentation.xml
│               │   ├── fragment_curve.xml
│               │   ├── fragment_settings.xml
│               │   └── toolbar.xml
│               ├── layout-land/
│               │   ├── activity_main.xml
│               │   └── activity_presentation.xml
│               ├── menu/
│               │   └── menu_main_activity.xml
│               ├── mipmap-anydpi-v26/
│               │   ├── ic_launcher.xml
│               │   └── ic_launcher_round.xml
│               ├── values/
│               │   ├── colors.xml
│               │   ├── constants.xml
│               │   ├── dimens.xml
│               │   ├── strings.xml
│               │   └── styles.xml
│               └── values-w820dp/
│                   └── dimens.xml
└── settings.gradle

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
# Built application files
*.apk
*.ap_

# Files for the ART/Dalvik VM
*.dex

# Java class files
*.class

# Generated files
bin/
gen/
out/

# Gradle files
.gradle/
build/

# Local configuration file (sdk path, etc)
local.properties

# Proguard folder generated by Eclipse
proguard/

# Log Files
*.log

# Android Studio Navigation editor temp files
.navigation/

# Android Studio captures folder
captures/

# Intellij
*.iml
.idea/workspace.xml
.idea/

# Keystore files
*.jks


================================================
FILE: CHANGELOG.md
================================================
Change Log
==========

Version 2.0.2 *(2019-07-27)*
----------------------------

* Fix `lineMinLength` setter

Version 2.0.1 *(2019-07-21)*
----------------------------

* Remove `android:allowBackup` and `android:supportsRtl` from manifest
* Update dependencies
* Change package from `com.vlad1m1r.lemniscate.sample.lemniscate` to `com.vlad1m1r.lemniscate`

Version 2.0.0 *(2019-03-23)*
----------------------------

* Migration to AndroidX

Version 1.4.5 *(2019-03-23)*
----------------------------

* Update dependencies

Version 1.4.4 *(2018-04-05)*
----------------------------

* Replace `View.BaseSavedState` with `android.support.v4.view.AbsSavedState` in `BaseCurveProgressView`

Version 1.4.3 *(2018-04-04)*
----------------------------

* Fixed problem with restoring view's state
* Small code optimization

Version 1.4.2 *(2018-01-16)*
----------------------------

* Fixed bug where View would not show inside ScrollView.  [#5](https://github.com/VladimirWrites/Lemniscate/issues/5)

Version 1.4.1 *(2018-01-06)*
----------------------------

* Fixed bug where SizeMultiplier property was not working when set from `xml`.  [#4](https://github.com/VladimirWrites/Lemniscate/issues/4)

Version 1.4.0 *(2017-11-09)*
----------------------------

* Project rewritten in Kotlin.  
* Organization of base classes improved
* Fixed bugs in Sample app

Version 1.3.0 *(2017-11-03)*
----------------------------

* `lineLength` and `lineLengthChangeable` do not exist anymore. If `maxLineLength` and `minLineLength` are the same then `lineLengthChangeable==false`, otherwise line length will be changeable
`getGraphX` and `getGraphY` now return `float` and not `double`
* `mLemniscateParamX` and `mLemniscateParamY` are not used anymore and are replaced by `viewSize.getSize()`, where `mLemniscateParamX == mLemniscateParamY == viewSize.getSize()/2`
* `minSdkVersion` moved from 11 to 14

Version 1.2.0 *(2017-02-16)*
----------------------------

 * New curves added: `BernoullisBowProgressView`, `BernoullisSharpProgressView`, `XProgressView`, `RoundScribbleProgressView`, `ScribbleProgressView`
 * `colorAccent` is now being used as default line color

Version 1.1.1 *(2017-01-26)*
----------------------------

 * Optimization of function that is doing sampling of curve

Version 1.1.0 *(2017-01-26)*
----------------------------

 * Abstract functions `getGraphX()` and `getGraphY()` now receive value of `getT()`

Version 1.0.2 *(2017-01-24)*
----------------------------

 * Fix: Added `onSaveState` for Roulette curves
 * Fix: Precision is being saved `onSaveState` for all curves


Version 1.0.1 *(2017-01-23)*
----------------------------

 * Fix: Crash on `setColor(int color)` in `BaseCurveProgressBar`, when called from constructor.


Version 1.0.0 *(2017-01-23)*
----------------------------

Initial release.




================================================
FILE: LICENSE
================================================
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

================================================
FILE: README.md
================================================
![Lemniscate header](http://i.imgur.com/i9t5vUm.png)


[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/VladimirWrites/Lemniscate/blob/master/LICENSE)
[![](https://jitpack.io/v/VladimirWrites/Lemniscate.svg)](https://jitpack.io/#VladimirWrites/Lemniscate) 
[![API](https://img.shields.io/badge/API-14%2B-green.svg?style=flat)](https://android-arsenal.com/api?level-11) 
[![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-Lemniscate-green.svg?style=flat)](https://android-arsenal.com/details/1/5142)
[![Build Status](https://app.bitrise.io/app/a22f82dd1a84f058.svg?token=sufo7FQOqMK9NjUqcP4CzA&branch=master)](https://app.bitrise.io/app/a22f82dd1a84f058#/builds)
[![codecov](https://codecov.io/gh/VladimirWrites/Lemniscate/branch/master/graph/badge.svg)](https://codecov.io/gh/VladimirWrites/Lemniscate)

-----

Lemniscate is a library that will help you to make your progress view nice and sleek.

![Lemniscate gif](http://i.imgur.com/xPRHWdv.gif)

Demo
-----

Demo application is available on Google Play.

<a href='https://play.google.com/store/apps/details?id=com.vlad1m1r.lemniscate.sample'>
    <img alt='Get it on Google Play' src='http://i.imgur.com/tka3Exw.png'/>
</a>

The application is intentionally simple, without any libraries, to be understandable to more developers.

Setup
-----

Add to your module's `build.gradle`:

```groovy
allprojects {
    repositories {
        ...
        maven { url 'https://jitpack.io' }
    }
}
```

and to your app `build.gradle`:

###### AndroidX
```groovy
dependencies {
    implementation 'com.github.VladimirWrites:Lemniscate:2.0.4'
}
```
    
###### Android Support Library (Depricated)
```groovy
dependencies {
    implementation 'com.github.VladimirWrites:Lemniscate:1.4.5'
}
```

Usage
-----

Example of usage:
```xml
<com.vlad1m1r.lemniscate.BernoullisProgressView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:duration="1000"
    app:hasHole="false"
    app:lineColor="@color/colorPrimary"
    app:maxLineLength="0.8"
    app:minLineLength="0.4"
    app:sizeMultiplier="1"
    app:strokeWidth="5dp"/>
```

###### Params available in all views:

* **duration** (int) - duration of one animation cycle in millisecondes
* **lineColor** (color) - color of the line
* **maxLineLength** (float) - max length of line (in percentage; 1.0 is full length, 0.5 is half of length)
* **minLineLength** (float) - min length of line (in percentage; 1.0 is full length, 0.5 is half of length)
* **sizeMultiplier** (float) - default size of view will be multiplied with that number
* **strokeWidth** (dimension) - width of line 
* **precision** (int) - number of points in curve calculated in one cycle

#### Lemniscates
* `BernoullisProgressView` - [Lemniscate of Bernoulli](https://en.wikipedia.org/wiki/Lemniscate_of_Bernoulli),
* `GeronosProgressView` - [Lemniscate of Gerono](https://en.wikipedia.org/wiki/Lemniscate_of_Gerono)
* `BernoullisBowProgressView`
* `BernoullisSharpProgressView`

###### Additional params:
* **hasHole** (boolean) - hole in a middle of Lemniscates

#### Roulettes
* `EpitrochoidProgressView` - [Epitrochoid](https://en.wikipedia.org/wiki/Epitrochoid),
* `HypotrochoidProgressView` - [Hypotrochoid](https://en.wikipedia.org/wiki/Hypotrochoid)

###### Additional params:
* **radiusFixed** (float) - radius of fixed circle
* **radiusMoving** (float) - radius of moving circle
* **distanceFromCenter** (float) -  distance from the center of the moving circle
* **numberOfCycles** (float) - for one **duration** curve will be drawn on interval [0, 2 \* mNumberOfCycles \* π]

#### Scribble
* `RoundScribbleProgressView`
* `ScribbleProgressView`

#### Funny
* `HeartProgressView` - [Heart Curve](http://mathworld.wolfram.com/HeartCurve.html),
* `CannabisProgressView` - [Cannabis Curve](http://mathworld.wolfram.com/CannabisCurve.html)

#### Other
* `XProgressView`

Contributing
-------

Want to contribute? You are welcome! 
Note that all pull request should go to [`development`](https://github.com/VladimirWrites/Lemniscate/tree/development) branch.

Credits
-------

+ [Vladimir Jovanovic](https://github.com/VladimirWrites)

License
-------

    Copyright 2016 Vladimir Jovanovic

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

         http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.


================================================
FILE: build.gradle
================================================
buildscript {
    repositories {
        jcenter()
        google()
    }
    dependencies {
        classpath Deps.android_gradle_plugin
        classpath Deps.kotlin_gradle_plugin
    }
}

allprojects {
    repositories {
        jcenter()
        google()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}


================================================
FILE: buildSrc/.gitignore
================================================
/build


================================================
FILE: buildSrc/build.gradle.kts
================================================
/*
 * Copyright 2017 Vladimir Jovanovic
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

plugins {
    `kotlin-dsl`
}

repositories {
    mavenCentral()
}


================================================
FILE: buildSrc/src/main/java/Dependencies.kt
================================================

object Versions {
    const val kotlin = "2.0.20"

    const val android_x = "1.7.0"

    const val circleindicator = "2.1.6"

    const val junit = "4.13.2"
    const val mockito_core = "4.5.1"
    const val mockito_kotlin = "4.0.0"
    const val truth = "1.1.3"
    const val robolectric = "4.7.3"

    const val gradle_android = "8.6.0"
    const val jacoco = "0.8.8"

    const val min_sdk = 14
    const val sample_min_sdk = 21
    const val target_sdk = 35
    const val compile_sdk = 35

    const val lemniscate_version_code = 204
    const val lemniscate_version_name = "2.0.4"

    const val sample_version_code = 144
    const val sample_version_name = "1.4.4"
}

object Deps {
    const val kotlin_stdlib = "org.jetbrains.kotlin:kotlin-stdlib:${Versions.kotlin}"

    const val appcompat = "androidx.appcompat:appcompat:${Versions.android_x}"

    const val circleindicator = "me.relex:circleindicator:${Versions.circleindicator}"

    const val junit = "junit:junit:${Versions.junit}"
    const val mockito_core = "org.mockito:mockito-core:${Versions.mockito_core}"
    const val mockito_kotlin = "org.mockito.kotlin:mockito-kotlin:${Versions.mockito_kotlin}"
    const val truth = "com.google.truth:truth:${Versions.truth}"
    const val robolectric = "org.robolectric:robolectric:${Versions.robolectric}"

    const val android_gradle_plugin = "com.android.tools.build:gradle:${Versions.gradle_android}"
    const val kotlin_gradle_plugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlin}"
}


================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
#Sat Sep 07 13:09:29 CEST 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists


================================================
FILE: gradle.properties
================================================
# Project-wide Gradle settings.

# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.

# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html

# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx1536m
android.useAndroidX=true
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true


================================================
FILE: gradlew
================================================
#!/usr/bin/env bash

##############################################################################
##
##  Gradle start up script for UN*X
##
##############################################################################

# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""

APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`

# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"

warn ( ) {
    echo "$*"
}

die ( ) {
    echo
    echo "$*"
    echo
    exit 1
}

# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
  CYGWIN* )
    cygwin=true
    ;;
  Darwin* )
    darwin=true
    ;;
  MINGW* )
    msys=true
    ;;
esac

# Attempt to set APP_HOME
# Resolve links: $0 may be radiusFixed link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
    ls=`ls -ld "$PRG"`
    link=`expr "$ls" : '.*-> \(.*\)$'`
    if expr "$link" : '/.*' > /dev/null; then
        PRG="$link"
    else
        PRG=`dirname "$PRG"`"/$link"
    fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null

CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar

# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
        # IBM's JDK on AIX uses strange locations for the executables
        JAVACMD="$JAVA_HOME/jre/sh/java"
    else
        JAVACMD="$JAVA_HOME/bin/java"
    fi
    if [ ! -x "$JAVACMD" ] ; then
        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
    fi
else
    JAVACMD="java"
    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi

# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -radiusFixed "$darwin" = "false" ] ; then
    MAX_FD_LIMIT=`ulimit -H -n`
    if [ $? -eq 0 ] ; then
        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
            MAX_FD="$MAX_FD_LIMIT"
        fi
        ulimit -n $MAX_FD
        if [ $? -ne 0 ] ; then
            warn "Could not set maximum file descriptor limit: $MAX_FD"
        fi
    else
        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
    fi
fi

# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi

# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
    JAVACMD=`cygpath --unix "$JAVACMD"`

    # We build the pattern for arguments to be converted via cygpath
    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type distanceFromCenter 2>/dev/null`
    SEP=""
    for dir in $ROOTDIRSRAW ; do
        ROOTDIRS="$ROOTDIRS$SEP$dir"
        SEP="|"
    done
    OURCYGPATTERN="(^($ROOTDIRS))"
    # Add radiusFixed user-defined pattern to the cygpath arguments
    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
    fi
    # Now convert the arguments - kludge to limit ourselves to /bin/sh
    i=0
    for arg in "$@" ; do
        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option

        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added radiusFixed condition
            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
        else
            eval `echo args$i`="\"$arg\""
        fi
        i=$((i+1))
    done
    case $i in
        (0) set -- ;;
        (1) set -- "$args0" ;;
        (2) set -- "$args0" "$args1" ;;
        (3) set -- "$args0" "$args1" "$args2" ;;
        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
    esac
fi

# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
    JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"

exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"


================================================
FILE: gradlew.bat
================================================
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem  Gradle startup script for Windows
@rem
@rem ##########################################################################

@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal

@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=

set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%

@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome

set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init

echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.

goto fail

:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe

if exist "%JAVA_EXE%" goto init

echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.

goto fail

:init
@rem Get command-line arguments, handling Windowz variants

if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args

:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2

:win9xME_args_slurp
if "x%~1" == "x" goto execute

set CMD_LINE_ARGS=%*
goto execute

:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$

:execute
@rem Setup the command line

set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd

:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /radiusMoving 1

:mainEnd
if "%OS%"=="Windows_NT" endlocal

:omega


================================================
FILE: lemniscate/.gitignore
================================================
/build


================================================
FILE: lemniscate/build.gradle
================================================
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'jacoco'

jacoco {
    toolVersion = Versions.jacoco
}

android {
    namespace "com.vlad1m1r.lemniscate"
    defaultConfig {
        minSdkVersion Versions.min_sdk
        targetSdkVersion Versions.target_sdk
        compileSdk Versions.compile_sdk
        versionCode Versions.lemniscate_version_code
        versionName Versions.lemniscate_version_name
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        debug {
            testCoverageEnabled true
        }
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    sourceSets {
        main.java.srcDirs += 'src/main/kotlin'
    }
    testOptions {
        unitTests.all {
            jacoco {
                includeNoLocationClasses = true
            }
        }
        unitTests {
            includeAndroidResources = true
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_17
        targetCompatibility JavaVersion.VERSION_17
    }
    kotlinOptions {
        jvmTarget = JavaVersion.VERSION_17.toString()
    }
}

task jacocoDebugReport(type: JacocoReport, dependsOn: ['testDebugUnitTest']) {

    reports {
        csv {
            enabled true
        }
        xml {
            enabled true
        }
        html {
            enabled true
        }
    }

    def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', '**/*Test*.*', 'android/**/*.*']
    def debugTree = fileTree(dir: "${buildDir}/intermediates/classes/debug", excludes: fileFilter)
    def kotlinDebugTree = fileTree(dir: "${buildDir}/tmp/kotlin-classes/debug", excludes: fileFilter)
    def mainSrc = "${project.projectDir}/src/androidTest/java"

    getSourceDirectories().setFrom(files([mainSrc]))
    getClassDirectories().setFrom(files([debugTree], [kotlinDebugTree]))
    getExecutionData().setFrom(fileTree(dir: "$buildDir", includes: [
            "jacoco/testDebugUnitTest.exec",
            "outputs/code-coverage/connected/*coverage.ec"
    ]))
}

dependencies {
    implementation Deps.appcompat
    implementation Deps.kotlin_stdlib

    testImplementation Deps.junit
    testImplementation Deps.mockito_core
    testImplementation Deps.mockito_kotlin
    testImplementation Deps.truth
    testImplementation Deps.robolectric
}
repositories {
    mavenCentral()
}


================================================
FILE: lemniscate/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/vladimirjovanovic/Library/Android/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
#   http://developer.android.com/guide/developing/tools/proguard.html

# Add any project specific keep options here:

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
#   public *;
#}


================================================
FILE: lemniscate/src/main/AndroidManifest.xml
================================================
<manifest package="com.vlad1m1r.lemniscate"/>


================================================
FILE: lemniscate/src/main/java/com/vlad1m1r/lemniscate/BernoullisBowProgressView.kt
================================================
/*
 * Copyright 2016 Vladimir Jovanovic
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.vlad1m1r.lemniscate

import android.content.Context
import android.util.AttributeSet

import com.vlad1m1r.lemniscate.base.BaseCurveProgressView
import kotlin.math.cos
import kotlin.math.pow
import kotlin.math.sin

class BernoullisBowProgressView : BaseCurveProgressView {

    constructor(context: Context) : super(context)

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)

    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

    override fun getGraphX(t: Float): Float =
            size * 0.75f * cos(t) / (1 + cos(t).pow(6))

    override fun getGraphY(t: Float): Float =
         size * 0.75f * sin(t) * cos(t) / (1 + cos(t).pow(6))
}


================================================
FILE: lemniscate/src/main/java/com/vlad1m1r/lemniscate/BernoullisProgressView.kt
================================================
/*
 * Copyright 2016 Vladimir Jovanovic
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.vlad1m1r.lemniscate

import android.content.Context
import android.util.AttributeSet

import com.vlad1m1r.lemniscate.base.BaseCurveProgressView
import kotlin.math.cos
import kotlin.math.pow
import kotlin.math.sin

class BernoullisProgressView : BaseCurveProgressView {

    constructor(context: Context) : super(context)

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)

    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

    override fun getGraphX(t: Float): Float =
            size / 2 * cos(t) / (1 + sin(t).pow(2))

    override fun getGraphY(t: Float): Float =
        (size / 2) * sin(t) * cos(t) / (1 + sin(t).pow(2))
}


================================================
FILE: lemniscate/src/main/java/com/vlad1m1r/lemniscate/BernoullisSharpProgressView.kt
================================================
/*
 * Copyright 2016 Vladimir Jovanovic
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.vlad1m1r.lemniscate

import android.content.Context
import android.util.AttributeSet

import com.vlad1m1r.lemniscate.base.BaseCurveProgressView
import kotlin.math.cos
import kotlin.math.pow
import kotlin.math.sin

class BernoullisSharpProgressView : BaseCurveProgressView {

    constructor(context: Context) : super(context)

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)

    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

    override fun getGraphX(t: Float): Float =
            size * cos(t) / (1 + cos(t).pow(2))

    override fun getGraphY(t: Float): Float =
            size * sin(t) * cos(t) / (1 + cos(t).pow(2))
}


================================================
FILE: lemniscate/src/main/java/com/vlad1m1r/lemniscate/GeronosProgressView.kt
================================================
/*
 * Copyright 2016 Vladimir Jovanovic
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.vlad1m1r.lemniscate

import android.content.Context
import android.util.AttributeSet

import com.vlad1m1r.lemniscate.base.BaseCurveProgressView
import kotlin.math.cos
import kotlin.math.sin

class GeronosProgressView : BaseCurveProgressView {

    constructor(context: Context) : super(context)

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)

    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

    override fun getGraphX(t: Float): Float =
            size / 2 * sin(t)

    override fun getGraphY(t: Float): Float =
            (size / 2)* sin(t) * cos(t)
}


================================================
FILE: lemniscate/src/main/java/com/vlad1m1r/lemniscate/base/BaseCurveContract.kt
================================================
package com.vlad1m1r.lemniscate.base

import com.vlad1m1r.lemniscate.base.models.DrawState
import com.vlad1m1r.lemniscate.base.models.Points
import com.vlad1m1r.lemniscate.base.models.ViewSize
import com.vlad1m1r.lemniscate.base.settings.AnimationSettings
import com.vlad1m1r.lemniscate.base.settings.CurveSettings
import kotlin.math.PI

interface IBaseCurvePresenter {

    val view:IBaseCurveView
    var curveSettings: CurveSettings
    val viewSize: ViewSize
    var animationSettings: AnimationSettings
    val drawState: DrawState
    val points: Points

    fun recreatePoints()
    fun updateStartingPointOnCurve(point: Int)
}

interface IBaseCurveView {
    /**
     * This method should return values of x for t∈[0, upper limit of getT() function].
     * We should use parametric representation of curve for x.
     * Curve should be closed and periodic on interval that returns getT().
     * Resulting value should satisfy x∈[-viewSize.getWidth()/2, viewSize.getWidth()/2].
     */
    fun getGraphX(t: Float): Float

    /**
     * This method should return values of y for t∈[0, upper limit of getT() function].
     * We should use parametric representation of curve for y.
     * Curve should be closed and periodic on interval that returns getT().
     * Resulting value should satisfy y∈[-viewSize.getHeight()/2, viewSize.getHeight()/2].
     */
    fun getGraphY(t: Float): Float

    /**
     * @param i ∈ [0, mPrecision)
     * @return function is putting i∈[0, curveSettings.getPrecision()) points between [0, 2π]
     */
    fun getT(i: Int, precision: Int): Float {
        return i * 2f * PI.toFloat() / precision
    }

    fun invalidateProgressView()

    fun requestProgressViewLayout()
}



================================================
FILE: lemniscate/src/main/java/com/vlad1m1r/lemniscate/base/BaseCurvePresenter.kt
================================================
package com.vlad1m1r.lemniscate.base

import com.vlad1m1r.lemniscate.base.models.DrawState
import com.vlad1m1r.lemniscate.base.models.Point
import com.vlad1m1r.lemniscate.base.models.Points
import com.vlad1m1r.lemniscate.base.models.ViewSize
import com.vlad1m1r.lemniscate.base.settings.AnimationSettings
import com.vlad1m1r.lemniscate.base.settings.CurveSettings
import kotlin.math.round

class BaseCurvePresenter(override val view: IBaseCurveView,
                         override var curveSettings: CurveSettings,
                         override val viewSize: ViewSize,
                         override var animationSettings: AnimationSettings,
                         override val drawState: DrawState,
                         override val points: Points) : IBaseCurvePresenter {

    override fun updateStartingPointOnCurve(point: Int) {
        animationSettings.startingPointOnCurve = point
        drawState.recalculateLineLength(curveSettings.lineLength)
        view.invalidateProgressView()
    }

    internal val lineLengthToDraw: Int
        get() = round(curveSettings.precision * drawState.currentLineLength).toInt()

    override fun recreatePoints() {
        points.clear()
        createNewPoints()
        addPointsToPath()
    }

    internal fun createNewPoints() {
        var lineLengthToDraw = lineLengthToDraw

        while (lineLengthToDraw > 0) {
            lineLengthToDraw = addPointsToCurve(
                    getStartingPoint(),
                    lineLengthToDraw
            )
        }
    }

    internal fun getStartingPoint() = if (points.isEmpty) animationSettings.startingPointOnCurve else 0

    internal fun addPointsToPath() {
        drawState.addPointsToPath(points.getPoints(), curveSettings, viewSize)
    }

    internal fun addPointsToCurve(start: Int, remainingPoints: Int): Int {
        var remainingPointsTemp = remainingPoints
        for (i in start until curveSettings.precision) {

            points.addPoint(getPoint(i))

            if (--remainingPointsTemp == 0) {
                return remainingPointsTemp
            }
        }
        return remainingPointsTemp
    }

    internal fun getPoint(i: Int): Point {
        return Point(
                view.getGraphX(getT(i)),
                view.getGraphY(getT(i)),
                curveSettings.strokeWidth,
                viewSize.size
        )
    }

    internal fun getT(i: Int) = view.getT(i, curveSettings.precision)
}


================================================
FILE: lemniscate/src/main/java/com/vlad1m1r/lemniscate/base/BaseCurveProgressView.kt
================================================
/*
 * Copyright 2016 Vladimir Jovanovic
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.vlad1m1r.lemniscate.base

import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Canvas
import android.graphics.Path
import android.os.Parcel
import android.os.Parcelable
import android.util.AttributeSet
import android.view.View
import android.view.animation.LinearInterpolator
import androidx.customview.view.AbsSavedState
import com.vlad1m1r.lemniscate.R
import com.vlad1m1r.lemniscate.base.models.DrawState
import com.vlad1m1r.lemniscate.base.models.Points
import com.vlad1m1r.lemniscate.base.models.ViewSize
import com.vlad1m1r.lemniscate.base.settings.AnimationSettings
import com.vlad1m1r.lemniscate.base.settings.CurveSettings
import kotlin.math.min
import kotlin.math.round

abstract class BaseCurveProgressView : View, IBaseCurveView {

    protected var presenter: IBaseCurvePresenter = BaseCurvePresenter(
            this,
            CurveSettings(),
            ViewSize(), AnimationSettings(),
            DrawState(Path()),
            Points())

    private var valueAnimator: ValueAnimator? = null

    constructor(context: Context) : super(context)

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {

        val curveAttributes = context.obtainStyledAttributes(
                attrs,
                R.styleable.BaseCurveProgressView,
                0, 0)

        val colorAccentAttributes = context.obtainStyledAttributes(attrs, intArrayOf(android.R.attr.colorAccent))

        try {
            val colorAccent = colorAccentAttributes.getColor(0, 0)

            presenter.curveSettings.lineLength.lineMaxLength = curveAttributes.getFloat(R.styleable.BaseCurveProgressView_maxLineLength, 0.8f)
            presenter.curveSettings.lineLength.lineMinLength = curveAttributes.getFloat(R.styleable.BaseCurveProgressView_minLineLength, 0.4f)

            presenter.curveSettings.color = curveAttributes.getColor(R.styleable.BaseCurveProgressView_lineColor, colorAccent)
            presenter.curveSettings.hasHole = curveAttributes.getBoolean(R.styleable.BaseCurveProgressView_hasHole, false)
            presenter.curveSettings.strokeWidth = curveAttributes.getDimension(R.styleable.BaseCurveProgressView_strokeWidth, resources.getDimension(R.dimen.lemniscate_stroke_width))
            presenter.curveSettings.precision = curveAttributes.getInteger(R.styleable.BaseCurveProgressView_precision, 200)

            presenter.animationSettings.duration = curveAttributes.getInteger(R.styleable.BaseCurveProgressView_duration, 1000)
        } finally {
            curveAttributes.recycle()
            colorAccentAttributes.recycle()
        }
    }

    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        presenter.recreatePoints()
        canvas.drawPath(presenter.drawState.path, presenter.curveSettings.paint)
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)

        val defaultSize = resources.getDimension(R.dimen.lemniscate_preferred_height) * presenter.viewSize.sizeMultiplier

        val xPadding = paddingLeft + paddingRight
        val yPadding = paddingTop + paddingBottom

        val viewSize = getMaxViewSquareSize(
                measuredHeight,
                measuredWidth,
                xPadding,
                yPadding
        )

        presenter.viewSize.size = getViewDimension(
                MeasureSpec.getMode(widthMeasureSpec),
                viewSize.toFloat(),
                defaultSize
        )

        setMeasuredDimension(round(presenter.viewSize.size + xPadding).toInt(), round(presenter.viewSize.size + yPadding).toInt())
    }

    internal fun getMaxViewSquareSize(height: Int, width: Int, xPadding: Int, yPadding: Int): Int {
        return min(height - yPadding, width - xPadding)
    }

    internal fun getViewDimension(mode: Int, viewSize: Float, defaultSize: Float): Float {
        return when {
            viewSize == 0.0f -> defaultSize
            mode == MeasureSpec.EXACTLY -> viewSize
            mode == MeasureSpec.AT_MOST -> min(defaultSize, viewSize)
            else -> defaultSize
        }
    }

    override fun onAttachedToWindow() {
        super.onAttachedToWindow()
        animateLemniscate()
    }

    private fun animateLemniscate() {
        valueAnimator?.end()
        valueAnimator = ValueAnimator.ofInt(presenter.curveSettings.precision - 1, 0).apply {
            duration = presenter.animationSettings.duration.toLong()
            repeatCount = -1
            repeatMode = ValueAnimator.RESTART
            interpolator = LinearInterpolator()
            addUpdateListener { animation ->
                presenter.updateStartingPointOnCurve(animation.animatedValue as Int)
            }
            start()
        }
    }

    override fun onDetachedFromWindow() {
        super.onDetachedFromWindow()
        valueAnimator?.end()
    }

    var strokeWidth
        get() = presenter.curveSettings.strokeWidth
        set(strokeWidth) {
            presenter.curveSettings.strokeWidth = strokeWidth
        }

    var lineMaxLength
        get() = presenter.curveSettings.lineLength.lineMaxLength
        set(lineMaxLength) {
            presenter.curveSettings.lineLength.lineMaxLength = lineMaxLength
        }

    var lineMinLength
        get() = presenter.curveSettings.lineLength.lineMinLength
        set(lineMinLength) {
            presenter.curveSettings.lineLength.lineMinLength = lineMinLength
        }

    var color
        get() = presenter.curveSettings.color
        set(color) {
            presenter.curveSettings.color = color
        }

    var duration
        get() = presenter.animationSettings.duration
        set(duration) {
            presenter.animationSettings.duration = duration
            if (valueAnimator != null) valueAnimator!!.duration = duration.toLong()
        }

    var precision
        get() = presenter.curveSettings.precision
        set(precision) {
            presenter.curveSettings.precision = precision
            animateLemniscate()
            invalidate()
        }

    var sizeMultiplier
        get() = presenter.viewSize.sizeMultiplier
        set(sizeMultiplier) {
            presenter.viewSize.sizeMultiplier = sizeMultiplier
            requestLayout()
            invalidate()
        }

    var size = presenter.viewSize.size
        get() = presenter.viewSize.size
        private set

    open var hasHole
        get() = presenter.curveSettings.hasHole
        set(hasHole) {
            presenter.curveSettings.hasHole = hasHole
        }

    public override fun onSaveInstanceState(): Parcelable {
        val ss = BaseCurveSavedState(super.onSaveInstanceState()!!)
        ss.curveSettings = this.presenter.curveSettings
        ss.animationSettings = this.presenter.animationSettings
        return ss
    }

    public override fun onRestoreInstanceState(state: Parcelable) {
        if (state is BaseCurveSavedState) {
            super.onRestoreInstanceState(state.superState)
            state.curveSettings?.let {
                this.presenter.curveSettings = it
            }
            state.animationSettings?.let {
                this.presenter.animationSettings = it
            }
        } else {
            super.onRestoreInstanceState(state)
        }
    }

    protected open class BaseCurveSavedState : AbsSavedState {
        internal var curveSettings: CurveSettings? = null
        internal var animationSettings: AnimationSettings? = null

        constructor(superState: Parcelable) : super(superState)

        constructor(state: Parcel) : super(state, BaseCurveSavedState::class.java.classLoader) {
            this.curveSettings = state.readParcelable(CurveSettings::class.java.classLoader)
            this.animationSettings = state.readParcelable(AnimationSettings::class.java.classLoader)
        }

        override fun writeToParcel(out: Parcel, flags: Int) {
            super.writeToParcel(out, flags)
            out.writeParcelable(curveSettings, flags)
            out.writeParcelable(animationSettings, flags)
        }

        companion object {
            @JvmField
            val CREATOR = object : Parcelable.Creator<BaseCurveSavedState> {
                override fun createFromParcel(source: Parcel): BaseCurveSavedState {
                    return BaseCurveSavedState(source)
                }

                override fun newArray(size: Int): Array<BaseCurveSavedState?> {
                    return arrayOfNulls(size)
                }
            }
        }
    }

    override fun invalidateProgressView() {
        invalidate()
    }

    override fun requestProgressViewLayout() {
        requestLayout()
    }
}


================================================
FILE: lemniscate/src/main/java/com/vlad1m1r/lemniscate/base/models/DrawState.kt
================================================
/*
 * Copyright 2016 Vladimir Jovanovic
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.vlad1m1r.lemniscate.base.models

import android.graphics.Path
import com.vlad1m1r.lemniscate.base.settings.CurveSettings

import com.vlad1m1r.lemniscate.utils.CurveUtils

private const val STEP_SIZE = 0.001f
class DrawState(val path:Path) {

    internal var isExpanding = true
    var currentLineLength = 0.0f
        internal set

    internal fun addPairOfPointsToPath(start: Point?, end: Point?) {
        if (start != null && end != null) {
            path.moveTo(start.x, start.y)
            path.quadTo(start.x, start.y, end.x, end.y)
        } else if (start != null) {
            path.moveTo(start.x, start.y)
            path.lineTo(start.x, start.y)
        } else if (end != null) {
            path.moveTo(end.x, end.y)
        } 
    }

    internal fun isInRightDirectionToBeInHole(start: Point?, end: Point?)
            = start != null && end != null && start.x > end.x

    fun addPointsToPath(listOfPoints: List<Point>, curveSettings: CurveSettings, viewSize: ViewSize) {
        resetPath()

        val holeSize = curveSettings.strokeWidth

        //adds points to path and creates hole if curveSettings.hasHole()
        for (i in listOfPoints.indices) {
            var start: Point? = listOfPoints[i]
            var end: Point? = null

            if (listOfPoints.size > i + 1)
                end = listOfPoints[i + 1]

            if (curveSettings.hasHole) {
                if (isInRightDirectionToBeInHole(start, end)) {
                    start = CurveUtils.checkPointForHole(start, holeSize, viewSize.size)
                    end = CurveUtils.checkPointForHole(end, holeSize, viewSize.size)
                }
            }

            addPairOfPointsToPath(start, end)
        }
    }

    internal fun resetPath() {
        path.reset()
    }

    internal fun keepLineLengthInsideLimits(lineLength: LineLength){
        if (currentLineLength < lineLength.lineMinLength) {
            currentLineLength = lineLength.lineMinLength
        }
        if (currentLineLength > lineLength.lineMaxLength) {
            currentLineLength = lineLength.lineMaxLength
        }
    }

    internal fun calculateNewCurrentLineLength(lineLength: LineLength) {
        if (currentLineLength < lineLength.lineMaxLength && isExpanding) {
            currentLineLength += STEP_SIZE
        } else if (currentLineLength > lineLength.lineMinLength && !isExpanding) {
            currentLineLength -= STEP_SIZE
        } else if (currentLineLength == lineLength.lineMaxLength) {
            isExpanding = false
        } else if (currentLineLength == lineLength.lineMinLength) {
            isExpanding = true
        } else {
            throw IllegalArgumentException("currentLineLength is not inside limits")
        }
    }

    fun recalculateLineLength(lineLength: LineLength) {
        if (lineLength.lineMinLength < lineLength.lineMaxLength) {
            keepLineLengthInsideLimits(lineLength)
            calculateNewCurrentLineLength(lineLength)
        } else {
            currentLineLength = lineLength.lineMaxLength
        }
    }
}


================================================
FILE: lemniscate/src/main/java/com/vlad1m1r/lemniscate/base/models/LineLength.kt
================================================
/*
 * Copyright 2016 Vladimir Jovanovic
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.vlad1m1r.lemniscate.base.models

import android.os.Parcel
import android.os.Parcelable

class LineLength() : Parcelable {

    var lineMinLength = 0.4f
        set(value) {
            if (value > 0 && value <= 1) {
                field = value
            } else {
                throw IllegalArgumentException()
            }
        }

    var lineMaxLength = 0.8f
        set(value) {
            if (value > 0 && value <= 1) {
                field = value
            } else {
                throw IllegalArgumentException()
            }
        }

    internal constructor(state: Parcel) : this() {
        this.lineMinLength = state.readFloat()
        this.lineMaxLength = state.readFloat()
    }

    override fun describeContents(): Int {
        return 0
    }

    override fun writeToParcel(dest: Parcel, flags: Int) {
        dest.writeFloat(this.lineMinLength)
        dest.writeFloat(this.lineMaxLength)
    }

    companion object CREATOR : Parcelable.Creator<LineLength> {
        override fun createFromParcel(parcel: Parcel): LineLength {
            return LineLength(parcel)
        }

        override fun newArray(size: Int): Array<LineLength?> {
            return arrayOfNulls(size)
        }
    }
}


================================================
FILE: lemniscate/src/main/java/com/vlad1m1r/lemniscate/base/models/Point.kt
================================================
/*
 * Copyright 2016 Vladimir Jovanovic
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.vlad1m1r.lemniscate.base.models

class Point(x: Float, y: Float, strokeWidth: Float, viewSize: Float) {
    val x: Float = translateToPositiveCoordinates(x, strokeWidth, viewSize)
    val y: Float = translateToPositiveCoordinates(y, strokeWidth, viewSize)

    private fun compensateForStrokeWidth(coordinate: Float, strokeWidth: Float, viewSize: Float): Float {
        val ratio = viewSize / (viewSize + 2 * strokeWidth)
        return coordinate * ratio + strokeWidth * ratio
    }

    private fun translateToPositiveCoordinates(coordinate: Float, strokeWidth: Float, viewSize: Float): Float {
        return compensateForStrokeWidth(coordinate + viewSize / 2, strokeWidth, viewSize)
    }
}


================================================
FILE: lemniscate/src/main/java/com/vlad1m1r/lemniscate/base/models/Points.kt
================================================
/*
 * Copyright 2016 Vladimir Jovanovic
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.vlad1m1r.lemniscate.base.models

import java.util.*

class Points {
    private val points = ArrayList<Point>()

    val isEmpty: Boolean
        get() = points.isEmpty()

    fun getPoints(): List<Point> {
        return points.toList()
    }

    fun addPoint(point: Point) {
        points.add(point)
    }

    fun clear() {
        points.clear()
    }
}


================================================
FILE: lemniscate/src/main/java/com/vlad1m1r/lemniscate/base/models/ViewSize.kt
================================================
/*
 * Copyright 2016 Vladimir Jovanovic
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.vlad1m1r.lemniscate.base.models

class ViewSize {
    var size: Float = 0.0f
    var sizeMultiplier: Float = 1.0f
}


================================================
FILE: lemniscate/src/main/java/com/vlad1m1r/lemniscate/base/settings/AnimationSettings.kt
================================================
/*
 * Copyright 2016 Vladimir Jovanovic
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.vlad1m1r.lemniscate.base.settings

import android.os.Parcel
import android.os.Parcelable

class AnimationSettings(var startingPointOnCurve:Int = 0, var duration: Int = 1000) : Parcelable {

    constructor(state: Parcel) : this(
            state.readInt(),
            state.readInt())

    override fun writeToParcel(parcel: Parcel, flags: Int) {
        parcel.writeInt(startingPointOnCurve)
        parcel.writeInt(duration)
    }

    override fun describeContents(): Int {
        return 0
    }

    companion object CREATOR : Parcelable.Creator<AnimationSettings> {
        override fun createFromParcel(parcel: Parcel): AnimationSettings {
            return AnimationSettings(parcel)
        }

        override fun newArray(size: Int): Array<AnimationSettings?> {
            return arrayOfNulls(size)
        }
    }
}


================================================
FILE: lemniscate/src/main/java/com/vlad1m1r/lemniscate/base/settings/CurveSettings.kt
================================================
/*
 * Copyright 2016 Vladimir Jovanovic
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.vlad1m1r.lemniscate.base.settings

import android.graphics.Paint
import android.os.Parcel
import android.os.Parcelable
import com.vlad1m1r.lemniscate.base.models.LineLength

open class CurveSettings (val paint: Paint = Paint(Paint.ANTI_ALIAS_FLAG), var lineLength: LineLength = LineLength()) : Parcelable {

    init {
        paint.style = Paint.Style.STROKE
        paint.strokeCap = Paint.Cap.ROUND
    }

    var precision = 200
    var strokeWidth: Float = 0f
        @Throws(IllegalArgumentException::class)
        set(value) {
            if (value >= 0) {
                field = value
                this.paint.strokeWidth = value
            } else {
                throw IllegalArgumentException("\'strokeWidth\' must be positive!")
            }
        }

    var color: Int = 0
        set(value) {
            field = value
            paint.color = value
        }
    var hasHole = false

    internal constructor(state: Parcel) : this() {
        this.precision = state.readInt()
        this.strokeWidth = state.readFloat()
        this.color = state.readInt()
        this.lineLength = state.readParcelable(LineLength::class.java.classLoader)!!
        this.hasHole = state.readByte().toInt() != 0
    }

    override fun describeContents(): Int {
        return 0
    }

    override fun writeToParcel(dest: Parcel, flags: Int) {
        dest.writeInt(this.precision)
        dest.writeFloat(this.strokeWidth)
        dest.writeInt(this.color)
        dest.writeParcelable(this.lineLength, flags)
        dest.writeByte(if (this.hasHole) 1.toByte() else 0.toByte())
    }

    companion object CREATOR : Parcelable.Creator<CurveSettings> {
        override fun createFromParcel(source: Parcel): CurveSettings {
            return CurveSettings(source)
        }

        override fun newArray(size: Int): Array<CurveSettings?> {
            return arrayOfNulls(size)
        }
    }
}


================================================
FILE: lemniscate/src/main/java/com/vlad1m1r/lemniscate/funny/CannabisProgressView.kt
================================================
/*
 * Copyright 2016 Vladimir Jovanovic
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.vlad1m1r.lemniscate.funny

import android.content.Context
import android.util.AttributeSet

import com.vlad1m1r.lemniscate.base.BaseCurveProgressView
import kotlin.math.cos
import kotlin.math.sin

class CannabisProgressView : BaseCurveProgressView {

    constructor(context: Context) : super(context)

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)

    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

    override fun getGraphX(t: Float): Float =
            ((size / 6)
                    * (sin(t) + 1)
                    * cos(t)
                    * (9 / 10f * cos(8 * t) + 1)
                    * (1 / 10f * cos(24 * t) + 1)
                    * (1 / 10f * cos(200 * t) + 9 / 10f))

    override fun getGraphY(t: Float): Float =
            ((-size / 6)
                    * sin(t)
                    * (sin(t) + 1)
                    * (9 / 10f * cos(8 * t) + 1)
                    * (1 / 10f * cos(24 * t) + 1)
                    * (1 / 10f * cos(200 * t) + 9 / 10f)) + size / 4

    // Disable hasHole setter. Should stay false
    override var hasHole: Boolean = false
        set(hasHole) {
            super.hasHole = hasHole && false
            field = hasHole && false
        }
}


================================================
FILE: lemniscate/src/main/java/com/vlad1m1r/lemniscate/funny/HeartProgressView.kt
================================================
/*
 * Copyright 2016 Vladimir Jovanovic
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.vlad1m1r.lemniscate.funny

import android.content.Context
import android.util.AttributeSet

import com.vlad1m1r.lemniscate.base.BaseCurveProgressView
import kotlin.math.cos
import kotlin.math.pow
import kotlin.math.sin

class HeartProgressView : BaseCurveProgressView {

    constructor(context: Context) : super(context)

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)

    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

    override fun getGraphX(t: Float): Float =
            (size / 34) * 16 * sin(t).pow(3)

    override fun getGraphY(t: Float): Float =
            -size / 34 * (13 * cos(t)
                    - 5 * cos(2 * t)
                    - 2 * cos(3 * t)
                    - cos(4 * t))

    // Disable hasHole setter. Should stay false
    override var hasHole: Boolean = false
        set(hasHole) {
            super.hasHole = hasHole && false
            field = hasHole && false
        }
}


================================================
FILE: lemniscate/src/main/java/com/vlad1m1r/lemniscate/other/XProgressView.kt
================================================
/*
 * Copyright 2016 Vladimir Jovanovic
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.vlad1m1r.lemniscate.other

import android.content.Context
import android.util.AttributeSet

import com.vlad1m1r.lemniscate.base.BaseCurveProgressView
import kotlin.math.abs
import kotlin.math.cos
import kotlin.math.sin

class XProgressView : BaseCurveProgressView {

    constructor(context: Context) : super(context)

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)

    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

    override fun getGraphX(t: Float): Float =
            size * abs(sin(t)) * cos(t)

    override fun getGraphY(t: Float): Float =
            size * sin(t) * cos(t)

    // Disable hasHole setter. Should stay false
    override var hasHole: Boolean = false
        set(hasHole) {
            super.hasHole = hasHole && false
            field = hasHole && false
        }
}


================================================
FILE: lemniscate/src/main/java/com/vlad1m1r/lemniscate/roulette/BaseRouletteProgressView.kt
================================================
/*
 * Copyright 2016 Vladimir Jovanovic
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.vlad1m1r.lemniscate.roulette

import android.content.Context
import android.os.Parcel
import android.os.Parcelable
import android.util.AttributeSet
import com.vlad1m1r.lemniscate.R
import com.vlad1m1r.lemniscate.base.BaseCurveProgressView
import com.vlad1m1r.lemniscate.roulette.settings.RouletteCurveSettings
import kotlin.math.PI

abstract class BaseRouletteProgressView : BaseCurveProgressView {

    protected var rouletteCurveSettings: RouletteCurveSettings = RouletteCurveSettings()

    var radiusFixed: Float
        get() = rouletteCurveSettings.radiusFixed
        set(radiusFixed) {
            rouletteCurveSettings.radiusFixed = radiusFixed
            recalculateConstants()
        }

    var radiusMoving: Float
        get() = rouletteCurveSettings.radiusMoving
        set(radiusMoving) {
            rouletteCurveSettings.radiusMoving = radiusMoving
            recalculateConstants()
        }

    var distanceFromCenter: Float
        get() = rouletteCurveSettings.distanceFromCenter
        set(distanceFromCenter) {
            rouletteCurveSettings.distanceFromCenter = distanceFromCenter
            recalculateConstants()
        }

    var numberOfCycles: Float
        get() = rouletteCurveSettings.numberOfCycles
        set(numberOfCycles) {
            rouletteCurveSettings.numberOfCycles = numberOfCycles
        }

    constructor(context: Context) : super(context)

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
        val rouletteCurveAttributes = context.obtainStyledAttributes(
                attrs,
                R.styleable.RouletteCurveProgressView,
                0, 0)

        try {
            radiusFixed = rouletteCurveAttributes.getFloat(R.styleable.RouletteCurveProgressView_radiusFixed, rouletteCurveSettings.radiusFixed)
            radiusMoving = rouletteCurveAttributes.getFloat(R.styleable.RouletteCurveProgressView_radiusMoving, rouletteCurveSettings.radiusMoving)
            distanceFromCenter = rouletteCurveAttributes.getFloat(R.styleable.RouletteCurveProgressView_distanceFromCenter, rouletteCurveSettings.distanceFromCenter)
            numberOfCycles = rouletteCurveAttributes.getFloat(R.styleable.RouletteCurveProgressView_numberOfCycles, rouletteCurveSettings.numberOfCycles)
        } finally {
            rouletteCurveAttributes.recycle()
        }
    }

    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

    internal open fun recalculateConstants() {}

    // Disable hasHole setter. Should stay false
    override var hasHole: Boolean = false
        set(hasHole) {
            super.hasHole = hasHole && false
            field = hasHole && false
        }


    override fun getT(i: Int, precision: Int): Float {
        return i * rouletteCurveSettings.numberOfCycles * 2 * PI.toFloat() / precision
    }

    override fun onSaveInstanceState(): Parcelable {
        val ss = RouletteCurveSavedState(super.onSaveInstanceState())
        ss.rouletteCurveSettings = rouletteCurveSettings
        return ss
    }

    override fun onRestoreInstanceState(state: Parcelable) {
        if (state is RouletteCurveSavedState) {
            super.onRestoreInstanceState(state.superState!!)
            state.rouletteCurveSettings?.let {
                this.rouletteCurveSettings = it
            }
        } else {
            super.onRestoreInstanceState(state)
        }
    }

    protected open class RouletteCurveSavedState : BaseCurveSavedState {

        internal var rouletteCurveSettings: RouletteCurveSettings? = null

        constructor(superState: Parcelable) : super(superState)

        constructor(source: Parcel) : super(source) {
            this.rouletteCurveSettings = source.readParcelable(RouletteCurveSettings::class.java.classLoader)
        }

        override fun writeToParcel(out: Parcel, flags: Int) {
            super.writeToParcel(out, flags)
            out.writeParcelable(rouletteCurveSettings, flags)
        }

        companion object {
            @JvmField
            val CREATOR = object : Parcelable.Creator<RouletteCurveSavedState> {
                override fun createFromParcel(source: Parcel): RouletteCurveSavedState {
                    return RouletteCurveSavedState(source)
                }

                override fun newArray(size: Int): Array<RouletteCurveSavedState?> {
                    return arrayOfNulls(size)
                }
            }
        }
    }
}


================================================
FILE: lemniscate/src/main/java/com/vlad1m1r/lemniscate/roulette/EpitrochoidProgressView.kt
================================================
/*
 * Copyright 2016 Vladimir Jovanovic
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.vlad1m1r.lemniscate.roulette

import android.content.Context
import android.util.AttributeSet
import kotlin.math.cos
import kotlin.math.sin

class EpitrochoidProgressView : BaseRouletteProgressView {

    internal var radiusSum = 0f
        get() = radiusFixed + radiusMoving
        private set

    internal var sizeFactor = 0f
        get() = 2 * (radiusSum + distanceFromCenter)
        private set

    constructor(context: Context) : super(context)

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)

    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

    override fun getGraphX(t: Float): Float =
            size / sizeFactor * (radiusSum * cos(t) - distanceFromCenter * cos(radiusSum / radiusMoving * t))

    override fun getGraphY(t: Float): Float =
            size / sizeFactor * (radiusSum * sin(t) - distanceFromCenter * sin(radiusSum / radiusMoving * t))

    override fun recalculateConstants() {
        radiusSum = radiusFixed + radiusMoving
        sizeFactor = 2 * (radiusSum + distanceFromCenter)
    }
}


================================================
FILE: lemniscate/src/main/java/com/vlad1m1r/lemniscate/roulette/HypotrochoidProgressView.kt
================================================
/*
 * Copyright 2016 Vladimir Jovanovic
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.vlad1m1r.lemniscate.roulette

import android.content.Context
import android.util.AttributeSet
import kotlin.math.cos
import kotlin.math.sin

class HypotrochoidProgressView : BaseRouletteProgressView {

    internal var radiusDiff = 0f
        get() = radiusFixed - radiusMoving
        private set

    internal var sizeFactor = 0f
        get() = 2 * (radiusDiff + distanceFromCenter)
        private set

    // radiusFixed = 5, radiusMoving=3, distanceFromCenter=5, numberOfCycles = 3 to get pentagram

    constructor(context: Context) : super(context)

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)

    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

    override fun getGraphX(t: Float): Float =
            size / sizeFactor * (radiusDiff * cos(t) - distanceFromCenter * cos(radiusDiff / radiusMoving * t))

    override fun getGraphY(t: Float): Float =
            size / sizeFactor * (radiusDiff * sin(t) + distanceFromCenter * sin(radiusDiff / radiusMoving * t))

}


================================================
FILE: lemniscate/src/main/java/com/vlad1m1r/lemniscate/roulette/scribble/RoundScribbleProgressView.kt
================================================
/*
 * Copyright 2016 Vladimir Jovanovic
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.vlad1m1r.lemniscate.roulette.scribble

import android.content.Context
import android.util.AttributeSet

import com.vlad1m1r.lemniscate.roulette.BaseRouletteProgressView
import kotlin.math.cos
import kotlin.math.sin

class RoundScribbleProgressView : BaseRouletteProgressView {

    internal var radiusSum = 0.0f
        get() = radiusFixed + radiusMoving
        private set

    internal var sizeFactor = 0.0f
        get() = 2 * (radiusSum + distanceFromCenter)
        private set

    constructor(context: Context) : super(context)

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)

    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

    override fun getGraphX(t: Float): Float =
            size / sizeFactor * (radiusSum * cos(t) - distanceFromCenter * cos(radiusSum / radiusMoving * t))

    override fun getGraphY(t: Float): Float =
            size / sizeFactor * (radiusSum * cos(t) - distanceFromCenter * sin(radiusSum / radiusMoving * t))
}


================================================
FILE: lemniscate/src/main/java/com/vlad1m1r/lemniscate/roulette/scribble/ScribbleProgressView.kt
================================================
/*
 * Copyright 2016 Vladimir Jovanovic
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.vlad1m1r.lemniscate.roulette.scribble

import android.content.Context
import android.util.AttributeSet

import com.vlad1m1r.lemniscate.roulette.BaseRouletteProgressView
import kotlin.math.cos
import kotlin.math.sin

class ScribbleProgressView : BaseRouletteProgressView {

    internal var radiusSum: Float = 0f
        get() = radiusFixed + radiusMoving
        private set

    internal var sizeFactor: Float = 0f
        get() = 2 * (radiusSum + distanceFromCenter)
        private set

    constructor(context: Context) : super(context)

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)

    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

    override fun getGraphX(t: Float): Float =
         (size / sizeFactor * (radiusSum * cos(t) - distanceFromCenter * cos(radiusSum / radiusMoving * t)))

    override fun getGraphY(t: Float): Float =
            (size / sizeFactor * (radiusSum * sin(t) - distanceFromCenter * cos(radiusSum / radiusMoving * t)))
}


================================================
FILE: lemniscate/src/main/java/com/vlad1m1r/lemniscate/roulette/settings/RouletteCurveSettings.kt
================================================
package com.vlad1m1r.lemniscate.roulette.settings

import android.os.Parcel
import android.os.Parcelable

class RouletteCurveSettings() : Parcelable {

    /**
     * Radius of the non-moving circle
     */
    var radiusFixed = 3.0f
    /**
     * Radius of the moving circle
     */
    var radiusMoving = 1.0f
    /**
     * Distance from the center of the moving circle
     */
    var distanceFromCenter = 1.0f
    /**
     * Curve will be drawn on interval  [0, 2*numberOfCycles*π] before repeating
     */
    var numberOfCycles = 1.0f

    constructor(parcel: Parcel) : this() {
        radiusFixed = parcel.readFloat()
        radiusMoving = parcel.readFloat()
        distanceFromCenter = parcel.readFloat()
        numberOfCycles = parcel.readFloat()
    }

    override fun writeToParcel(parcel: Parcel, flags: Int) {
        parcel.writeFloat(radiusFixed)
        parcel.writeFloat(radiusMoving)
        parcel.writeFloat(distanceFromCenter)
        parcel.writeFloat(numberOfCycles)
    }

    override fun describeContents(): Int {
        return 0
    }

    companion object CREATOR : Parcelable.Creator<RouletteCurveSettings> {
        override fun createFromParcel(parcel: Parcel): RouletteCurveSettings {
            return RouletteCurveSettings(parcel)
        }

        override fun newArray(size: Int): Array<RouletteCurveSettings?> {
            return arrayOfNulls(size)
        }
    }
}


================================================
FILE: lemniscate/src/main/java/com/vlad1m1r/lemniscate/utils/CurveUtils.kt
================================================
/*
 * Copyright 2016 Vladimir Jovanovic
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.vlad1m1r.lemniscate.utils

import com.vlad1m1r.lemniscate.base.models.Point
import kotlin.math.abs

object CurveUtils {

    fun checkPointForHole(point: Point?, holeSize: Float, viewSize: Float): Point? {
        return if (point != null &&
                abs(point.x - viewSize / 2) < holeSize &&
                abs(point.y - viewSize / 2) < holeSize) {
            null
        } else point
    }
}


================================================
FILE: lemniscate/src/main/res/values/attrs.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="BaseCurveProgressView">
        <attr name="minLineLength" format="float"/>
        <attr name="maxLineLength" format="float"/>
        <attr name="lineColor" format="color"/>
        <attr name="duration" format="integer"/>
        <attr name="hasHole" format="boolean"/>
        <attr name="strokeWidth" format="dimension"/>
        <attr name="sizeMultiplier" format="float"/>
        <attr name="precision" format="integer"/>
    </declare-styleable>

    <declare-styleable name="RouletteCurveProgressView">
        <attr name="radiusFixed" format="float"/>
        <attr name="radiusMoving" format="float"/>
        <attr name="distanceFromCenter" format="float"/>
        <attr name="numberOfCycles" format="float"/>
    </declare-styleable>
</resources>

================================================
FILE: lemniscate/src/main/res/values/dimens.xml
================================================
<resources>
    <dimen name="lemniscate_stroke_width">10dp</dimen>
    <dimen name="lemniscate_preferred_width">80dp</dimen>
    <dimen name="lemniscate_preferred_height">80dp</dimen>
</resources>


================================================
FILE: lemniscate/src/test/java/com/vlad1m1r/lemniscate/BernoullisBowProgressViewTest.kt
================================================
package com.vlad1m1r.lemniscate

import com.google.common.truth.Truth.assertThat
import com.vlad1m1r.lemniscate.testutils.TestConstants
import com.vlad1m1r.lemniscate.testutils.isPeriodic
import com.vlad1m1r.lemniscate.testutils.setupDefaultMock
import org.junit.Before
import org.junit.Test
import org.mockito.kotlin.mock
import kotlin.math.PI

class BernoullisBowProgressViewTest {

    private val view = mock<BernoullisBowProgressView>()

    @Before
    fun setUp() {
        view.setupDefaultMock()
    }

    @Test
    fun getGraphX() {
        assertThat(view.getGraphX(0.0f)).isWithin(TestConstants.DELTA).of(37.5f)
        assertThat(view.getGraphX(0.1f)).isWithin(TestConstants.DELTA).of(37.873238f)
        assertThat(view.getGraphX(0.5f)).isWithin(TestConstants.DELTA).of(45.180264f)
        assertThat(view.getGraphX(1.0f)).isWithin(TestConstants.DELTA).of(39.53901f)
        assertThat(view.getGraphX(PI.toFloat())).isWithin(TestConstants.DELTA).of(-37.5f)
        assertThat(view.getGraphX(2.0f)).isWithin(TestConstants.DELTA).of(-31.049751f)
        assertThat(view.getGraphX(2 * PI.toFloat())).isWithin(TestConstants.DELTA).of(37.5f)
    }

    @Test
    fun getGraphY() {
        assertThat(view.getGraphY(0.0f)).isWithin(TestConstants.DELTA).of(0.0f)
        assertThat(view.getGraphY(0.1f)).isWithin(TestConstants.DELTA).of(3.7810147f)
        assertThat(view.getGraphY(0.5f)).isWithin(TestConstants.DELTA).of(21.660572f)
        assertThat(view.getGraphY(1.0f)).isWithin(TestConstants.DELTA).of(33.270927f)
        assertThat(view.getGraphY(PI.toFloat())).isWithin(TestConstants.DELTA).of(0.0f)
        assertThat(view.getGraphY(2.0f)).isWithin(TestConstants.DELTA).of(-28.233456f)
        assertThat(view.getGraphY(2 * PI.toFloat())).isWithin(TestConstants.DELTA).of(0.0f)
    }

    @Test
    fun isPeriodic() {
        view.isPeriodic(2 * PI.toFloat())
    }
}


================================================
FILE: lemniscate/src/test/java/com/vlad1m1r/lemniscate/BernoullisProgressViewTest.kt
================================================
package com.vlad1m1r.lemniscate

import com.google.common.truth.Truth.assertThat
import org.mockito.kotlin.mock
import com.vlad1m1r.lemniscate.testutils.TestConstants
import com.vlad1m1r.lemniscate.testutils.isPeriodic
import com.vlad1m1r.lemniscate.testutils.setupDefaultMock
import org.junit.Before
import org.junit.Test
import kotlin.math.PI

class BernoullisProgressViewTest {

    private val view = mock<BernoullisProgressView>()

    @Before
    fun setUp() {
        view.setupDefaultMock()
    }

    @Test
    fun getGraphX() {
        assertThat(view.getGraphX(0.0f)).isWithin(TestConstants.DELTA).of(50.0f)
        assertThat(view.getGraphX(0.1f)).isWithin(TestConstants.DELTA).of(49.259254f)
        assertThat(view.getGraphX(0.5f)).isWithin(TestConstants.DELTA).of(35.67847f)
        assertThat(view.getGraphX(1.0f)).isWithin(TestConstants.DELTA).of(15.816132f)
        assertThat(view.getGraphX(PI.toFloat())).isWithin(TestConstants.DELTA).of(-50.0f)
        assertThat(view.getGraphX(2.0f)).isWithin(TestConstants.DELTA).of(-11.389914f)
        assertThat(view.getGraphX(2 * PI.toFloat())).isWithin(TestConstants.DELTA).of(50.0f)
    }

    @Test
    fun getGraphY() {
        assertThat(view.getGraphY(0.0f)).isWithin(TestConstants.DELTA).of(0.0f)
        assertThat(view.getGraphY(0.1f)).isWithin(TestConstants.DELTA).of(4.91772f)
        assertThat(view.getGraphY(0.5f)).isWithin(TestConstants.DELTA).of(17.10517f)
        assertThat(view.getGraphY(1.0f)).isWithin(TestConstants.DELTA).of(13.308815f)
        assertThat(view.getGraphY(PI.toFloat())).isWithin(TestConstants.DELTA).of(0.0f)
        assertThat(view.getGraphY(2.0f)).isWithin(TestConstants.DELTA).of(-10.356819f)
        assertThat(view.getGraphY(2 * PI.toFloat())).isWithin(TestConstants.DELTA).of(0.0f)
    }

    @Test
    fun isPeriodic() {
        view.isPeriodic(2 * PI.toFloat())
    }
}


================================================
FILE: lemniscate/src/test/java/com/vlad1m1r/lemniscate/BernoullisSharpProgressViewTest.kt
================================================
package com.vlad1m1r.lemniscate

import com.google.common.truth.Truth.assertThat
import com.vlad1m1r.lemniscate.testutils.TestConstants
import com.vlad1m1r.lemniscate.testutils.isPeriodic
import com.vlad1m1r.lemniscate.testutils.setupDefaultMock
import org.junit.Before
import org.junit.Test
import org.mockito.kotlin.mock
import kotlin.math.PI

class BernoullisSharpProgressViewTest {

    private val view = mock<BernoullisSharpProgressView>()

    @Before
    fun setUp() {
        view.setupDefaultMock()
    }

    @Test
    fun getGraphX() {
        assertThat(view.getGraphX(0.0f)).isWithin(TestConstants.DELTA).of(50.0f)
        assertThat(view.getGraphX(0.1f)).isWithin(TestConstants.DELTA).of(49.99937f)
        assertThat(view.getGraphX(0.5f)).isWithin(TestConstants.DELTA).of(49.576702f)
        assertThat(view.getGraphX(1.0f)).isWithin(TestConstants.DELTA).of(41.821438f)
        assertThat(view.getGraphX(PI.toFloat())).isWithin(TestConstants.DELTA).of(-50.0f)
        assertThat(view.getGraphX(2.0f)).isWithin(TestConstants.DELTA).of(-35.471752f)
        assertThat(view.getGraphX(2 * PI.toFloat())).isWithin(TestConstants.DELTA).of(50.0f)
    }

    @Test
    fun getGraphY() {
        assertThat(view.getGraphY(0.0f)).isWithin(TestConstants.DELTA).of(0.0f)
        assertThat(view.getGraphY(0.1f)).isWithin(TestConstants.DELTA).of(4.991608f)
        assertThat(view.getGraphY(0.5f)).isWithin(TestConstants.DELTA).of(23.768335f)
        assertThat(view.getGraphY(1.0f)).isWithin(TestConstants.DELTA).of(35.191525f)
        assertThat(view.getGraphY(PI.toFloat())).isWithin(TestConstants.DELTA).of(0.0f)
        assertThat(view.getGraphY(2.0f)).isWithin(TestConstants.DELTA).of(-32.25437f)
        assertThat(view.getGraphY(2 * PI.toFloat())).isWithin(TestConstants.DELTA).of(0.0f)
    }

    @Test
    fun isPeriodic() {
        view.isPeriodic(2 * PI.toFloat())
    }
}


================================================
FILE: lemniscate/src/test/java/com/vlad1m1r/lemniscate/GeronosProgressViewTest.kt
================================================
package com.vlad1m1r.lemniscate

import com.google.common.truth.Truth.assertThat
import org.mockito.kotlin.mock
import com.vlad1m1r.lemniscate.testutils.TestConstants
import com.vlad1m1r.lemniscate.testutils.isPeriodic
import com.vlad1m1r.lemniscate.testutils.setupDefaultMock
import org.junit.Before
import org.junit.Test
import kotlin.math.PI

class GeronosProgressViewTest {

    private val view = mock<GeronosProgressView>()

    @Before
    fun setUp() {
        view.setupDefaultMock()
    }

    @Test
    fun getGraphX() {
        assertThat(view.getGraphX(0.0f)).isWithin(TestConstants.DELTA).of(0.0f)
        assertThat(view.getGraphX(0.1f)).isWithin(TestConstants.DELTA).of(4.991671f)
        assertThat(view.getGraphX(0.5f)).isWithin(TestConstants.DELTA).of(23.971277f)
        assertThat(view.getGraphX(1.0f)).isWithin(TestConstants.DELTA).of(42.073547f)
        assertThat(view.getGraphX(PI.toFloat())).isWithin(TestConstants.DELTA).of(0.0f)
        assertThat(view.getGraphX(2.0f)).isWithin(TestConstants.DELTA).of(45.46487f)
        assertThat(view.getGraphX(2 * PI.toFloat())).isWithin(TestConstants.DELTA).of(0.0f)
    }

    @Test
    fun getGraphY() {
        assertThat(view.getGraphY(0.0f)).isWithin(TestConstants.DELTA).of(0.0f)
        assertThat(view.getGraphY(0.1f)).isWithin(TestConstants.DELTA).of(4.9667335f)
        assertThat(view.getGraphY(0.5f)).isWithin(TestConstants.DELTA).of(21.036774f)
        assertThat(view.getGraphY(1.0f)).isWithin(TestConstants.DELTA).of(22.732433f)
        assertThat(view.getGraphY(PI.toFloat())).isWithin(TestConstants.DELTA).of(0.0f)
        assertThat(view.getGraphY(2.0f)).isWithin(TestConstants.DELTA).of(-18.920063f)
        assertThat(view.getGraphY(2 * PI.toFloat())).isWithin(TestConstants.DELTA).of(0.0f)
    }

    @Test
    fun isPeriodic() {
        view.isPeriodic(2 * PI.toFloat())
    }
}


================================================
FILE: lemniscate/src/test/java/com/vlad1m1r/lemniscate/base/BaseCurvePresenterTest.kt
================================================
package com.vlad1m1r.lemniscate.base

import com.google.common.truth.Truth.assertThat
import org.mockito.kotlin.*
import com.vlad1m1r.lemniscate.base.models.*
import com.vlad1m1r.lemniscate.base.settings.AnimationSettings
import com.vlad1m1r.lemniscate.base.settings.CurveSettings
import org.junit.Test

class BaseCurvePresenterTest {

    private val view = mock<IBaseCurveView>()
    private val curveSettings = mock<CurveSettings>()
    private val viewSize = mock<ViewSize>()
    private val animationSettings = mock<AnimationSettings>()
    private val drawState = mock<DrawState>()
    private val points = mock<Points>()

    val presenter = BaseCurvePresenter(view, curveSettings, viewSize, animationSettings, drawState, points)

    @Test
    fun updateStartingPointOnCurve() {
        whenever(curveSettings.lineLength).thenReturn(LineLength())

        presenter.updateStartingPointOnCurve(1)

        verify(animationSettings).startingPointOnCurve = 1
        verify(drawState).recalculateLineLength(curveSettings.lineLength)
        verify(view).invalidateProgressView()
    }

    @Test
    fun recreatePoints() {
        val presenterSpy = spy(presenter)

        presenterSpy.recreatePoints()

        verify(points).clear()
        verify(presenterSpy).createNewPoints()
        verify(presenterSpy).addPointsToPath()
    }

    @Test
    fun getCorrectLineLengthToDraw() {
        whenever(curveSettings.precision).thenReturn(99)
        whenever(drawState.currentLineLength).thenReturn(10.32f)

        assertThat(presenter.lineLengthToDraw).isEqualTo(1022)
    }

    @Test
    fun getCorrectStartingPoint_whenPointsIsEmpty() {
        whenever(animationSettings.startingPointOnCurve).thenReturn(10)
        whenever(points.isEmpty).thenReturn(true)

        assertThat(presenter.getStartingPoint()).isEqualTo(10)
    }

    @Test
    fun getCorrectStartingPoint_whenPointsIsNotEmpty() {
        whenever(points.isEmpty).thenReturn(false)
        assertThat(presenter.getStartingPoint()).isEqualTo(0)
    }

    @Test
    fun addPointsToPath() {
        whenever(points.getPoints()).thenReturn(ArrayList())
        presenter.addPointsToPath()
        verify(drawState).addPointsToPath(points.getPoints(), curveSettings, viewSize)
    }

    @Test
    fun addPointsToCurve_whenNotAllAddedStartO() {
        val presenterSpy = spy(presenter)
        val point = Point(1f,2f, 3f, 200f)
        doReturn(point).whenever(presenterSpy).getPoint(any())
        whenever(curveSettings.precision).thenReturn(20)
        val remainingPointsValue = 100
        val remainingPoints = presenterSpy.addPointsToCurve(0, remainingPointsValue)

        verify(points, times(curveSettings.precision)).addPoint(point)
        assertThat(remainingPoints).isEqualTo(remainingPointsValue - curveSettings.precision)
    }

    @Test
    fun addPointsToCurveWhenNotAllAddedStartNotO() {
        val presenterSpy = spy(presenter)
        val point = Point(1f,2f, 3f, 200f)
        doReturn(point).whenever(presenterSpy).getPoint(any())
        whenever(curveSettings.precision).thenReturn(20)
        val startValue = 10
        val remainingPointsValue = 100

        val remainingPoints = presenterSpy.addPointsToCurve(startValue, remainingPointsValue)

        verify(points, times(curveSettings.precision - startValue)).addPoint(point)
        assertThat(remainingPoints).isEqualTo(remainingPointsValue - curveSettings.precision + startValue)
    }

    @Test
    fun addPointsToCurveWhenAllAdded() {
        val presenterSpy = spy(presenter)
        val point = Point(1f,2f, 3f, 200f)
        doReturn(point).whenever(presenterSpy).getPoint(any())
        whenever(curveSettings.precision).thenReturn(200)
        val startValue = 10
        val remainingPointsValue = 100

        val remainingPoints = presenterSpy.addPointsToCurve(startValue, remainingPointsValue)

        verify(points, times(remainingPointsValue)).addPoint(point)
        assertThat(remainingPoints).isEqualTo(0)
    }

    @Test
    fun getCorrectPoint() {
        val presenterSpy = spy(presenter)
        whenever(presenterSpy.getT(1)).thenReturn(10.9f)

        presenterSpy.getPoint(1)

        verify(view).getGraphX(10.9f)
        verify(view).getGraphY(10.9f)
    }

    @Test
    fun getT() {
        whenever(curveSettings.precision).thenReturn(10)

        presenter.getT(1)

        verify(view).getT(1, curveSettings.precision)
    }
}


================================================
FILE: lemniscate/src/test/java/com/vlad1m1r/lemniscate/base/BaseCurveProgressViewTest.kt
================================================
package com.vlad1m1r.lemniscate.base

import android.view.View
import com.google.common.truth.Truth.assertThat
import org.mockito.kotlin.*
import org.junit.Before
import org.junit.Test

class BaseCurveProgressViewTest {

    val baseCurve: BaseCurve = BaseCurve()

    val baseCurveProgressView = mock<BaseCurveProgressView>()

    @Before
    fun setUp() {
        doCallRealMethod().whenever(baseCurveProgressView).getMaxViewSquareSize(any(), any(), any(), any())
        doCallRealMethod().whenever(baseCurveProgressView).getViewDimension(any(), any(), any())
    }

    @Test
    fun getMaxViewSquareSize() {
        assertThat(baseCurveProgressView.getMaxViewSquareSize(100, 200, 30, 50)).isEqualTo(100-50)
        assertThat(baseCurveProgressView.getMaxViewSquareSize(220, 150, 30, 50)).isEqualTo(150-30)
    }

    @Test
    fun getViewDimension_whenViewSizeIsZero() {
        val defaultSize = 10f
        assertThat(baseCurveProgressView.getViewDimension(View.MeasureSpec.AT_MOST, 0f, defaultSize)).isEqualTo(defaultSize)
    }

    @Test
    fun getViewDimension_whenMeasureSpecIsExactly() {
        val viewSize = 10f
        assertThat(baseCurveProgressView.getViewDimension(View.MeasureSpec.EXACTLY, viewSize, 10f)).isEqualTo(viewSize)
    }

    @Test
    fun getViewDimension_whenMeasureSpecIsAtMost() {
        assertThat(baseCurveProgressView.getViewDimension(View.MeasureSpec.AT_MOST, 10f, 20f)).isEqualTo(10f)
        assertThat(baseCurveProgressView.getViewDimension(View.MeasureSpec.AT_MOST, 30f, 20f)).isEqualTo(20f)
    }

    @Test
    fun getViewDimension_whenMeasureSpecIsUnspecified() {
        val defaultSize = 10f
        assertThat(baseCurveProgressView.getViewDimension(View.MeasureSpec.UNSPECIFIED, 20f, defaultSize)).isEqualTo(defaultSize)
    }

    @Test
    fun getT() {
        assertThat(baseCurve.getT(0, 10)).isEqualTo(0.0f)
        assertThat(baseCurve.getT(1, 10)).isEqualTo(0.62831855f)
        assertThat(baseCurve.getT(10, 10)).isEqualTo(6.2831855f)
        assertThat(baseCurve.getT(99, 8)).isEqualTo(77.75442f)
        assertThat(baseCurve.getT(-1, 10)).isEqualTo(-0.62831855f)
        assertThat(baseCurve.getT(-10, 10)).isEqualTo(-6.2831855f)
    }

    inner class BaseCurve : IBaseCurveView {
        override fun getGraphX(t: Float): Float {
            return 0f
        }

        override fun getGraphY(t: Float): Float {
            return 0f
        }

        override fun invalidateProgressView() {}

        override fun requestProgressViewLayout() {}
    }
}


================================================
FILE: lemniscate/src/test/java/com/vlad1m1r/lemniscate/base/BaseProgressViewAttributesTest.kt
================================================
package com.vlad1m1r.lemniscate.base

import android.graphics.Color
import android.os.Build
import androidx.test.platform.app.InstrumentationRegistry
import com.google.common.truth.Truth.assertThat
import com.vlad1m1r.lemniscate.BernoullisProgressView
import com.vlad1m1r.lemniscate.R
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.Robolectric
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config

@RunWith(RobolectricTestRunner::class)
@Config(sdk = [Build.VERSION_CODES.P])
class BaseProgressViewAttributesTest {

    val context = InstrumentationRegistry.getInstrumentation().targetContext

    val atributeSet =  Robolectric.buildAttributeSet()
            .addAttribute(R.attr.maxLineLength, "0.81")
            .addAttribute(R.attr.minLineLength, "0.23")
            .addAttribute(R.attr.lineColor, "#000000")
            .addAttribute(R.attr.hasHole, "true")
            .addAttribute(R.attr.strokeWidth, "33px")
            .addAttribute(R.attr.precision, "111")
            .addAttribute(R.attr.duration, "999")
            .build()

    @Test
    fun constructFromAttributeSet_whenProvided() {
        val bernoullisProgressView = BernoullisProgressView(context, atributeSet)

        assertThat(bernoullisProgressView.lineMaxLength).isEqualTo(0.81f)
        assertThat(bernoullisProgressView.lineMinLength).isEqualTo(0.23f)
        assertThat(bernoullisProgressView.color).isEqualTo(Color.BLACK)
        assertThat(bernoullisProgressView.hasHole).isTrue()
        assertThat(bernoullisProgressView.strokeWidth).isEqualTo(33f)
        assertThat(bernoullisProgressView.precision).isEqualTo(111)
        assertThat(bernoullisProgressView.duration).isEqualTo(999)
    }
}


================================================
FILE: lemniscate/src/test/java/com/vlad1m1r/lemniscate/base/models/DrawStateTest.kt
================================================
package com.vlad1m1r.lemniscate.base.models

import android.graphics.Path
import com.google.common.truth.Truth.assertThat
import org.mockito.kotlin.*
import com.vlad1m1r.lemniscate.base.settings.CurveSettings
import org.junit.Test
import org.mockito.Mockito.inOrder

class DrawStateTest {

    val path = mock<Path>()
    val drawState = DrawState(path)

    @Test
    fun addPairOfPoints() {
        val start = Point(0f,1f, 2f, 100f)
        val end = Point(2f, 3f, 2f, 100f)

        drawState.addPairOfPointsToPath(start, end)

        verify(path).moveTo(start.x, start.y)
        verify(path).quadTo(start.x, start.y, end.x, end.y)
        verifyNoMoreInteractions(path)
    }

    @Test
    fun addStartPoints() {
        val start = Point(0f,1f, 2f, 100f)

        drawState.addPairOfPointsToPath(start, null)

        verify(path).moveTo(start.x, start.y)
        verify(path).lineTo(start.x, start.y)
        verifyNoMoreInteractions(path)
    }

    @Test
    fun addEndPoints() {
        val end = Point(2f, 3f, 2f, 100f)

        drawState.addPairOfPointsToPath(null, end)

        verify(path).moveTo(end.x, end.y)
        verifyNoMoreInteractions(path)
    }

    @Test
    fun addPointsToPathWhenListEmpty() {
        val curveSettings = mock<CurveSettings>()
        val viewSize = mock<ViewSize>()

        drawState.addPointsToPath(emptyList(), curveSettings, viewSize)

        verify(path).reset()
        verifyNoMoreInteractions(path)
    }

    @Test
    fun addPointsToPathWhenListHasPointsOutOfHole() {
        val list = listOf(
                Point(5f, 0f, 1f, 100f),
                Point(3f, 0f, 1f, 100f)
        )
        val curveSettings = mock<CurveSettings>()
        val viewSize = mock<ViewSize>()
        whenever(curveSettings.hasHole).thenReturn(false)
        whenever(curveSettings.strokeWidth).thenReturn(1f)
        val drawStateSpy = spy(drawState)

        drawStateSpy.addPointsToPath(list, curveSettings, viewSize)

        verify(drawStateSpy).addPairOfPointsToPath(list[0], list[1])
        verify(drawStateSpy).addPairOfPointsToPath(list[1], null)
    }

    @Test
    fun addPointsToPathWhenListHasPointsInHole() {
        val list = listOf(
                Point(5f, 0f, 100f, 10f),
                Point(3f, 0f, 100f, 10f)
        )
        val curveSettings = mock<CurveSettings>()
        val viewSize = mock<ViewSize>()
        whenever(curveSettings.hasHole).thenReturn(true)
        whenever(curveSettings.strokeWidth).thenReturn(100f)
        val drawStateSpy = spy(drawState)

        drawStateSpy.addPointsToPath(list, curveSettings, viewSize)

        verify(drawStateSpy).addPairOfPointsToPath(null, null)
    }

    @Test
    fun recalculateLineLength() {
        val drawStateSpy = spy(drawState)
        val inOrder = inOrder(drawStateSpy)
        val lineLength = LineLength()

        drawStateSpy.recalculateLineLength(lineLength)

        inOrder.verify(drawStateSpy).keepLineLengthInsideLimits(lineLength)
        inOrder.verify(drawStateSpy).calculateNewCurrentLineLength(lineLength)
        inOrder.verifyNoMoreInteractions()
    }

    @Test
    fun recalculateLineLengthWhenMinGreaterThanMax() {
        val lineLength = LineLength()
        lineLength.lineMinLength = 0.8f
        lineLength.lineMaxLength = 0.6f

        drawState.recalculateLineLength(lineLength)

        assertThat(drawState.currentLineLength).isEqualTo(lineLength.lineMaxLength)
    }

    @Test
    fun keepLineLengthInsideLimitsWhenCurrentLessThanMin() {
        val lineLength = LineLength()
        lineLength.lineMinLength = 0.5f
        drawState.currentLineLength = 0.4f

        drawState.keepLineLengthInsideLimits(lineLength)

        assertThat(drawState.currentLineLength).isEqualTo(lineLength.lineMinLength)
    }

    @Test
    fun keepLineLengthInsideLimitsWhenCurrentGreaterThanMax() {
        val lineLength = LineLength()
        lineLength.lineMaxLength = 0.5f
        drawState.currentLineLength = 0.7f

        drawState.keepLineLengthInsideLimits(lineLength)

        assertThat(drawState.currentLineLength).isEqualTo(lineLength.lineMaxLength)
    }

    @Test
    fun calculateNewCurrentLineLengthWhenLessThanMaxAndExpanding() {
        val lineLength = LineLength()
        lineLength.lineMaxLength = 0.7f
        drawState.isExpanding = true
        drawState.currentLineLength = 0.5f

        drawState.calculateNewCurrentLineLength(lineLength)

        assertThat(drawState.currentLineLength).isEqualTo(0.501f)
    }

    @Test
    fun calculateNewCurrentLineLengthWhenGreaterThanMinAndNotExpanding() {
        val lineLength = LineLength()
        lineLength.lineMinLength = 0.2f
        drawState.isExpanding = false
        drawState.currentLineLength = 0.5f

        drawState.calculateNewCurrentLineLength(lineLength)

        assertThat(drawState.currentLineLength).isEqualTo(0.499f)
    }

    @Test
    fun calculateNewCurrentLineLengthWhenEqualMaxAndExpanding() {
        val lineLength = LineLength()
        drawState.currentLineLength = lineLength.lineMaxLength
        drawState.isExpanding = true

        drawState.calculateNewCurrentLineLength(lineLength)

        assertThat(drawState.currentLineLength).isEqualTo(lineLength.lineMaxLength)
        assertThat(drawState.isExpanding).isFalse()
    }

    @Test
    fun calculateNewCurrentLineLengthWhenEqualMinAndNotExpanding() {
        val lineLength = LineLength()
        drawState.currentLineLength = lineLength.lineMinLength
        drawState.isExpanding = false

        drawState.calculateNewCurrentLineLength(lineLength)

        assertThat(drawState.currentLineLength).isEqualTo(lineLength.lineMinLength)
        assertThat(drawState.isExpanding).isTrue()
    }

    @Test(expected = IllegalArgumentException::class)
    fun calculateNewCurrentLineLengthIsNotInsideLimits() {
        val lineLength = LineLength()
        drawState.currentLineLength = lineLength.lineMaxLength + 1

        drawState.calculateNewCurrentLineLength(lineLength)
    }

    @Test
    fun resetPath() {
        drawState.resetPath()

        verify(path).reset()
    }

}


================================================
FILE: lemniscate/src/test/java/com/vlad1m1r/lemniscate/base/models/LineLengthParcelableTest.kt
================================================
package com.vlad1m1r.lemniscate.base.models

import android.os.Build
import android.os.Parcel
import com.google.common.truth.Truth.assertThat
import com.vlad1m1r.lemniscate.testutils.isEqualTo
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config

@RunWith(RobolectricTestRunner::class)
@Config(sdk = [Build.VERSION_CODES.P])
class LineLengthParcelableTest {

    private lateinit var lineLength: LineLength

    @Before
    fun setUp() {
        lineLength = LineLength().apply {
            lineMinLength = 0.24f
            lineMaxLength = 0.83f
        }
    }

    @Test
    fun parcelable() {
        val parcel = Parcel.obtain()
        lineLength.writeToParcel(parcel, 0)
        parcel.setDataPosition(0)

        val copy = LineLength(parcel)
        parcel.recycle()

        assertThat(lineLength.isEqualTo(copy)).isTrue()
    }
}


================================================
FILE: lemniscate/src/test/java/com/vlad1m1r/lemniscate/base/models/LineLengthTest.kt
================================================
package com.vlad1m1r.lemniscate.base.models

import com.google.common.truth.Truth.assertThat
import org.junit.Test

class LineLengthTest {

    private val lineLength = LineLength()

    @Test
    fun getLineMaxLength() {
        lineLength.lineMaxLength = 0.9f
        assertThat(lineLength.lineMaxLength).isEqualTo(0.9f)
    }

    @Test(expected = IllegalArgumentException::class)
    fun setLineMaxLengthGreaterThan1ThrowsException() {
        lineLength.lineMaxLength = 1.1f
    }

    @Test(expected = IllegalArgumentException::class)
    fun setLineMaxLengthEqualTo0ThrowsException() {
        lineLength.lineMaxLength = 0.0f
    }

    @Test(expected = IllegalArgumentException::class)
    fun setLineMaxLengthLestThan0ThrowsException() {
        lineLength.lineMaxLength = -1.0f
    }

    @Test
    fun setLineMinLength() {
        lineLength.lineMinLength = 0.1f
        assertThat(lineLength.lineMinLength).isEqualTo(0.1f)
    }

    @Test(expected = IllegalArgumentException::class)
    fun setLineMinLengthLestThan0ThrowsException() {
        lineLength.lineMinLength = -0.1f
    }

    @Test(expected = IllegalArgumentException::class)
    fun setLineMinLengthGreaterThan1ThrowsException() {
        lineLength.lineMinLength = 1.1f
    }

    @Test(expected = IllegalArgumentException::class)
    fun setLineMinLengthEqualTo0ThrowsException() {
        lineLength.lineMinLength = 0.0f
    }
}

================================================
FILE: lemniscate/src/test/java/com/vlad1m1r/lemniscate/base/models/PointTest.kt
================================================
package com.vlad1m1r.lemniscate.base.models

import com.google.common.truth.Truth.assertThat
import org.junit.Test

class PointTest {
    @Test
    fun translatesPoints_whenCreated() {
        val point = Point(0.0f, 30.0f, 30.0f, 270.0f)
        assertThat(point.x).isEqualTo(135.0f)
        assertThat(point.y).isEqualTo(159.545455f)
    }
}


================================================
FILE: lemniscate/src/test/java/com/vlad1m1r/lemniscate/base/models/PointsTest.kt
================================================
package com.vlad1m1r.lemniscate.base.models

import com.google.common.truth.Truth.assertThat
import org.junit.Test

class PointsTest {

    val points = Points()
    val point  = Point(0f, 0f, 10f, 10f)

    @Test
    fun isNotEmpty_whenPointIsAdded() {
        assertThat(points.isEmpty).isTrue()
        points.addPoint(point)
        assertThat(points.isEmpty).isFalse()
    }

    @Test
    fun containsPoint_whenPointIsAdded() {
        points.addPoint(point)
        assertThat(points.getPoints()).containsExactly(point)
    }

    @Test
    fun isEmpty_whenClearIsCalled() {
        points.addPoint(point)
        points.clear()
        assertThat(points.isEmpty).isTrue()
    }
}


================================================
FILE: lemniscate/src/test/java/com/vlad1m1r/lemniscate/base/settings/AnimationSettingsParcelableTest.kt
================================================
package com.vlad1m1r.lemniscate.base.settings

import android.os.Build
import android.os.Parcel
import com.google.common.truth.Truth.assertThat
import com.vlad1m1r.lemniscate.testutils.isEqualTo
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config

@RunWith(RobolectricTestRunner::class)
@Config(sdk = [Build.VERSION_CODES.P])
class AnimationSettingsParcelableTest {

    private lateinit var animationSettings: AnimationSettings

    @Before
    fun setUp() {
        animationSettings = AnimationSettings(123, 987)
    }

    @Test
    fun parcelable() {
        val parcel = Parcel.obtain()
        animationSettings.writeToParcel(parcel, 0)
        parcel.setDataPosition(0)

        val copy = AnimationSettings(parcel)
        parcel.recycle()

        assertThat(animationSettings.isEqualTo(copy)).isTrue()
    }
}


================================================
FILE: lemniscate/src/test/java/com/vlad1m1r/lemniscate/base/settings/CurveSettingsParcelableTest.kt
================================================
package com.vlad1m1r.lemniscate.base.settings

import android.os.Build
import android.os.Parcel
import com.vlad1m1r.lemniscate.base.models.LineLength
import com.vlad1m1r.lemniscate.testutils.isEqualTo
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config

@RunWith(RobolectricTestRunner::class)
@Config(sdk = [Build.VERSION_CODES.P])
class CurveSettingsParcelableTest {

    private lateinit var curveSettings: CurveSettings

    @Before
    fun setUp() {
        curveSettings = CurveSettings().apply {
            color = 123
            hasHole = true
            lineLength = LineLength().apply {
                lineMaxLength = 0.85f
                lineMinLength = 0.26f
            }
            strokeWidth = 23.2f
            precision = 123
        }
    }

    @Test
    fun parcelable() {
        val parcel = Parcel.obtain()
        curveSettings.writeToParcel(parcel, 0)
        parcel.setDataPosition(0)

        val copy = CurveSettings(parcel)
        parcel.recycle()

        curveSettings.isEqualTo(copy)
    }
}


================================================
FILE: lemniscate/src/test/java/com/vlad1m1r/lemniscate/base/settings/CurveSettingsTest.kt
================================================
package com.vlad1m1r.lemniscate.base.settings

import android.graphics.Paint
import com.google.common.truth.Truth.assertThat
import org.mockito.kotlin.mock
import com.vlad1m1r.lemniscate.base.models.LineLength
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnitRunner

@RunWith(MockitoJUnitRunner::class)
class CurveSettingsTest {

    private lateinit var curveSettings: CurveSettings

    val paint = mock<Paint>()

    @Before
    fun setUp() {
        val lineLength = LineLength()
        curveSettings = CurveSettings(paint)
        curveSettings.lineLength = lineLength
    }

    @Test
    fun setStrokeWidth() {
        curveSettings.strokeWidth = 10.0f
        assertThat(curveSettings.strokeWidth).isEqualTo(10.0f)
        verify(paint).strokeWidth = 10f
    }

    @Test(expected = IllegalArgumentException::class)
    fun setStrokeWidthException() {
        curveSettings.strokeWidth = -1.0f
    }

    @Test
    fun setColor() {
        curveSettings.color = 123
        assertEquals(123, curveSettings.color.toLong())
        verify(paint).color = 123
    }
}


================================================
FILE: lemniscate/src/test/java/com/vlad1m1r/lemniscate/funny/CannabisProgressViewTest.kt
================================================
package com.vlad1m1r.lemniscate.funny

import android.os.Build
import androidx.test.platform.app.InstrumentationRegistry
import com.google.common.truth.Truth.assertThat
import org.mockito.kotlin.mock
import com.vlad1m1r.lemniscate.testutils.TestConstants
import com.vlad1m1r.lemniscate.testutils.isPeriodic
import com.vlad1m1r.lemniscate.testutils.setupDefaultMock
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import kotlin.math.PI

class CannabisProgressViewTest {

    private val view = mock<CannabisProgressView>()

    @Before
    fun setUp() {
        view.setupDefaultMock()
    }

    @Test
    fun getGraphX() {
        assertThat(view.getGraphX(0.0f)).isWithin(TestConstants.DELTA).of(34.833332f)
        assertThat(view.getGraphX(0.1f)).isWithin(TestConstants.DELTA).of(25.860207f)
        assertThat(view.getGraphX(0.5f)).isWithin(TestConstants.DELTA).of(9.527859f)
        assertThat(view.getGraphX(1.0f)).isWithin(TestConstants.DELTA).of(14.251956f)
        assertThat(view.getGraphX(PI.toFloat())).isWithin(TestConstants.DELTA).of(-34.83333f)
        assertThat(view.getGraphX(2.0f)).isWithin(TestConstants.DELTA).of(-1.4506966f)
        assertThat(view.getGraphX(2 * PI.toFloat())).isWithin(TestConstants.DELTA).of(34.833332f)
    }

    @Test
    fun getGraphY() {
        assertThat(view.getGraphY(0.0f)).isWithin(TestConstants.DELTA).of(25.0f)
        assertThat(view.getGraphY(0.1f)).isWithin(TestConstants.DELTA).of(22.405325f)
        assertThat(view.getGraphY(0.5f)).isWithin(TestConstants.DELTA).of(19.794907f)
        assertThat(view.getGraphY(1.0f)).isWithin(TestConstants.DELTA).of(2.803894f)
        assertThat(view.getGraphY(PI.toFloat())).isWithin(TestConstants.DELTA).of(25.0f)
        assertThat(view.getGraphY(2.0f)).isWithin(TestConstants.DELTA).of(21.83017f)
        assertThat(view.getGraphY(2 * PI.toFloat())).isWithin(TestConstants.DELTA).of(25.0f)
    }

    @Test
    fun isPeriodic() {
        view.isPeriodic(2 * PI.toFloat())
    }
}

@RunWith(RobolectricTestRunner::class)
@Config(sdk = [Build.VERSION_CODES.P])
class CannabisProgressViewHasHoleTest {

    val context = InstrumentationRegistry.getInstrumentation().targetContext
    private val view = CannabisProgressView(context)

    @Test
    fun hasHoleDisabled() {
        view.hasHole = true
        assertThat(view.hasHole).isFalse()
    }
}


================================================
FILE: lemniscate/src/test/java/com/vlad1m1r/lemniscate/funny/HeartProgressViewTest.kt
================================================
package com.vlad1m1r.lemniscate.funny

import android.os.Build
import androidx.test.platform.app.InstrumentationRegistry
import com.google.common.truth.Truth.assertThat
import org.mockito.kotlin.mock
import com.vlad1m1r.lemniscate.testutils.TestConstants
import com.vlad1m1r.lemniscate.testutils.isPeriodic
import com.vlad1m1r.lemniscate.testutils.setupDefaultMock
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import kotlin.math.PI

class HeartProgressViewTest{

    private val view = mock<HeartProgressView>()

    @Before
    fun setUp() {
        view.setupDefaultMock()
    }

    @Test
    fun getGraphX() {
        assertThat(view.getGraphX(0.0f)).isWithin(TestConstants.DELTA).of(0.0f)
        assertThat(view.getGraphX(0.1f)).isWithin(TestConstants.DELTA).of(0.046824045f)
        assertThat(view.getGraphX(0.5f)).isWithin(TestConstants.DELTA).of(5.1856666f)
        assertThat(view.getGraphX(1.0f)).isWithin(TestConstants.DELTA).of(28.038736f)
        assertThat(view.getGraphX(PI.toFloat())).isWithin(TestConstants.DELTA).of(0.0f)
        assertThat(view.getGraphX(2.0f)).isWithin(TestConstants.DELTA).of(35.38009f)
        assertThat(view.getGraphX(2 * PI.toFloat())).isWithin(TestConstants.DELTA).of(0.0f)
    }

    @Test
    fun getGraphY() {
        assertThat(view.getGraphY(0.0f)).isWithin(TestConstants.DELTA).of(-14.705882f)
        assertThat(view.getGraphY(0.1f)).isWithin(TestConstants.DELTA).of(-15.302903f)
        assertThat(view.getGraphY(0.5f)).isWithin(TestConstants.DELTA).of(-26.416864f)
        assertThat(view.getGraphY(1.0f)).isWithin(TestConstants.DELTA).of(-34.52439f)
        assertThat(view.getGraphY(PI.toFloat())).isWithin(TestConstants.DELTA).of(50.0f)
        assertThat(view.getGraphY(2.0f)).isWithin(TestConstants.DELTA).of(11.51921f)
        assertThat(view.getGraphY(2 * PI.toFloat())).isWithin(TestConstants.DELTA).of(-14.705882f)
    }

    @Test
    fun isPeriodic() {
        view.isPeriodic(2 * PI.toFloat())
    }
}

@RunWith(RobolectricTestRunner::class)
@Config(sdk = [Build.VERSION_CODES.P])
class HeartProgressViewHasHoleTest {

    val context = InstrumentationRegistry.getInstrumentation().targetContext
    private val view = HeartProgressView(context)

    @Test
    fun hasHoleDisabled() {
        view.hasHole = true
        assertThat(view.hasHole).isFalse()
    }
}


================================================
FILE: lemniscate/src/test/java/com/vlad1m1r/lemniscate/other/XProgressViewTest.kt
================================================
package com.vlad1m1r.lemniscate.other

import android.os.Build
import androidx.test.platform.app.InstrumentationRegistry
import com.google.common.truth.Truth.assertThat
import org.mockito.kotlin.mock
import com.vlad1m1r.lemniscate.testutils.TestConstants
import com.vlad1m1r.lemniscate.testutils.isPeriodic
import com.vlad1m1r.lemniscate.testutils.setupDefaultMock
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import kotlin.math.PI

class XProgressViewTest {

    private val view = mock<XProgressView>()

    @Before
    fun setUp() {
        view.setupDefaultMock()
    }

    @Test
    fun getGraphX() {
        assertThat(view.getGraphX(0.0f)).isWithin(TestConstants.DELTA).of(0.0f)
        assertThat(view.getGraphX(0.1f)).isWithin(TestConstants.DELTA).of(9.933467f)
        assertThat(view.getGraphX(0.5f)).isWithin(TestConstants.DELTA).of(42.073547f)
        assertThat(view.getGraphX(1.0f)).isWithin(TestConstants.DELTA).of(45.464867f)
        assertThat(view.getGraphX(PI.toFloat())).isWithin(TestConstants.DELTA).of(0.0f)
        assertThat(view.getGraphX(2.0f)).isWithin(TestConstants.DELTA).of(-37.840126f)
        assertThat(view.getGraphX(2 * PI.toFloat())).isWithin(TestConstants.DELTA).of(0.0f)
    }

    @Test
    fun getGraphY() {
        assertThat(view.getGraphY(0.0f)).isWithin(TestConstants.DELTA).of(0.0f)
        assertThat(view.getGraphY(0.1f)).isWithin(TestConstants.DELTA).of(9.933467f)
        assertThat(view.getGraphY(0.5f)).isWithin(TestConstants.DELTA).of(42.073547f)
        assertThat(view.getGraphY(1.0f)).isWithin(TestConstants.DELTA).of(45.464867f)
        assertThat(view.getGraphY(PI.toFloat())).isWithin(TestConstants.DELTA).of(0.0f)
        assertThat(view.getGraphY(2.0f)).isWithin(TestConstants.DELTA).of(-37.840126f)
        assertThat(view.getGraphY(2 * PI.toFloat())).isWithin(TestConstants.DELTA).of(0.0f)
    }

    @Test
    fun isPeriodic() {
        view.isPeriodic(2 * PI.toFloat())
    }
}

@RunWith(RobolectricTestRunner::class)
@Config(sdk = [Build.VERSION_CODES.P])
class XProgressViewHasHoleTest {

    val context = InstrumentationRegistry.getInstrumentation().targetContext
    private val view = XProgressView(context)

    @Test
    fun hasHoleDisabled() {
        view.hasHole = true
        assertThat(view.hasHole).isFalse()
    }
}


================================================
FILE: lemniscate/src/test/java/com/vlad1m1r/lemniscate/roulette/BaseRouletteProgressViewAttributesTest.kt
================================================
package com.vlad1m1r.lemniscate.roulette

import android.os.Build
import androidx.test.platform.app.InstrumentationRegistry
import com.google.common.truth.Truth.assertThat
import com.vlad1m1r.lemniscate.R
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.Robolectric
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config

@RunWith(RobolectricTestRunner::class)
@Config(sdk = [Build.VERSION_CODES.P])
class BaseRouletteProgressViewAttributesTest {

    val context = InstrumentationRegistry.getInstrumentation().targetContext
    val atributeSet =  Robolectric.buildAttributeSet()
            .addAttribute(R.attr.radiusFixed, "34")
            .addAttribute(R.attr.radiusMoving, "23")
            .addAttribute(R.attr.numberOfCycles, "43")
            .addAttribute(R.attr.distanceFromCenter, "31")
            .build()

    @Test
    fun constructorWithAttributeSet() {
        val epitrochoidProgressView = EpitrochoidProgressView(context, atributeSet)
        assertThat(epitrochoidProgressView.radiusFixed).isEqualTo(34f)
        assertThat(epitrochoidProgressView.radiusMoving).isEqualTo(23f)
        assertThat(epitrochoidProgressView.numberOfCycles).isEqualTo(43f)
        assertThat(epitrochoidProgressView.distanceFromCenter).isEqualTo(31f)
    }
}


================================================
FILE: lemniscate/src/test/java/com/vlad1m1r/lemniscate/roulette/EpitrochoidProgressViewTest.kt
================================================
package com.vlad1m1r.lemniscate.roulette

import android.os.Build
import androidx.test.platform.app.InstrumentationRegistry
import com.google.common.truth.Truth.assertThat
import org.mockito.kotlin.*
import com.vlad1m1r.lemniscate.testutils.TestConstants.DELTA
import com.vlad1m1r.lemniscate.testutils.isPeriodic
import com.vlad1m1r.lemniscate.testutils.setupDefaultMock
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import kotlin.math.PI

class EpitrochoidProgressViewTest {

    private val view = mock<EpitrochoidProgressView>()

    @Before
    fun setUp() {
        view.setupDefaultMock()
        doCallRealMethod().whenever(view).radiusSum
        doCallRealMethod().whenever(view).sizeFactor
    }

    @Test
    fun getGraphX() {
        assertThat(view.getGraphX(0.0f)).isWithin(DELTA).of(30.0f)
        assertThat(view.getGraphX(0.1f)).isWithin(DELTA).of(30.589556f)
        assertThat(view.getGraphX(0.5f)).isWithin(DELTA).of(39.26477f)
        assertThat(view.getGraphX(1.0f)).isWithin(DELTA).of(28.14853f)
        assertThat(view.getGraphX(PI.toFloat())).isWithin(DELTA).of(-50.0f)
        assertThat(view.getGraphX(2.0f)).isWithin(DELTA).of(-15.190873f)
        assertThat(view.getGraphX(2 * PI.toFloat())).isWithin(DELTA).of(30.0f)
    }

    @Test
    fun getGraphY() {
        assertThat(view.getGraphY(0.0f)).isWithin(DELTA).of(0.0f)
        assertThat(view.getGraphY(0.1f)).isWithin(DELTA).of(0.09915324f)
        assertThat(view.getGraphY(0.5f)).isWithin(DELTA).of(10.084047f)
        assertThat(view.getGraphY(1.0f)).isWithin(DELTA).of(41.226864f)
        assertThat(view.getGraphY(PI.toFloat())).isWithin(DELTA).of(9.797174E-15f)
        assertThat(view.getGraphY(2.0f)).isWithin(DELTA).of(26.478315f)
        assertThat(view.getGraphY(2 * PI.toFloat())).isWithin(DELTA).of(0.0f)
    }

    @Test
    fun isPeriodic() {
        view.isPeriodic(2 * PI.toFloat())
    }
}

@RunWith(RobolectricTestRunner::class)
@Config(sdk = [Build.VERSION_CODES.P])
class EpitrochoidProgressViewHasHoleTest {

    val context = InstrumentationRegistry.getInstrumentation().targetContext
    private val view = EpitrochoidProgressView(context)

    @Test
    fun hasHoleDisabled() {
        view.hasHole = true
        assertThat(view.hasHole).isFalse()
    }
}


================================================
FILE: lemniscate/src/test/java/com/vlad1m1r/lemniscate/roulette/HypotrochoidProgressViewTest.kt
================================================
package com.vlad1m1r.lemniscate.roulette

import android.os.Build
import androidx.test.platform.app.InstrumentationRegistry
import com.google.common.truth.Truth.assertThat
import org.mockito.kotlin.*
import com.vlad1m1r.lemniscate.testutils.TestConstants.DELTA
import com.vlad1m1r.lemniscate.testutils.isPeriodic
import com.vlad1m1r.lemniscate.testutils.setupDefaultMock
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import kotlin.math.PI

class HypotrochoidProgressViewTest {
    private val view = mock<HypotrochoidProgressView>()

    @Before
    fun setUp() {
        view.setupDefaultMock()
        doCallRealMethod().whenever(view).radiusDiff
        doCallRealMethod().whenever(view).sizeFactor
    }

    @Test
    fun getGraphX() {
        assertThat(view.getGraphX(0.0f)).isWithin(DELTA).of(16.666666f)
        assertThat(view.getGraphX(0.1f)).isWithin(DELTA).of(16.832361f)
        assertThat(view.getGraphX(0.5f)).isWithin(DELTA).of(20.247713f)
        assertThat(view.getGraphX(1.0f)).isWithin(DELTA).of(24.945856f)
        assertThat(view.getGraphX(PI.toFloat())).isWithin(DELTA).of(-50.0f)
        assertThat(view.getGraphX(2.0f)).isWithin(DELTA).of(-2.9775007f)
        assertThat(view.getGraphX(2 * PI.toFloat())).isWithin(DELTA).of(16.666666f)
    }

    @Test
    fun getGraphY() {
        assertThat(view.getGraphY(0.0f)).isWithin(DELTA).of(0.0f)
        assertThat(view.getGraphY(0.1f)).isWithin(DELTA).of(6.638936f)
        assertThat(view.getGraphY(0.5f)).isWithin(DELTA).of(30.005367f)
        assertThat(view.getGraphY(1.0f)).isWithin(DELTA).of(43.203987f)
        assertThat(view.getGraphY(PI.toFloat())).isWithin(DELTA).of(0.0f)
        assertThat(view.getGraphY(2.0f)).isWithin(DELTA).of(17.696539f)
        assertThat(view.getGraphY(2 * PI.toFloat())).isWithin(DELTA).of(0.0f)
    }

    @Test
    fun isPeriodic() {
        view.isPeriodic(2 * PI.toFloat())
    }
}

@RunWith(RobolectricTestRunner::class)
@Config(sdk = [Build.VERSION_CODES.P])
class HypotrochoidProgressViewHasHoleTest {

    val context = InstrumentationRegistry.getInstrumentation().targetContext
    private val view = HypotrochoidProgressView(context)

    @Test
    fun hasHoleDisabled() {
        view.hasHole = true
        assertThat(view.hasHole).isFalse()
    }
}


================================================
FILE: lemniscate/src/test/java/com/vlad1m1r/lemniscate/roulette/scribble/RoundScribbleProgressViewTest.kt
================================================
package com.vlad1m1r.lemniscate.roulette.scribble

import android.os.Build
import androidx.test.platform.app.InstrumentationRegistry
import com.google.common.truth.Truth.assertThat
import org.mockito.kotlin.*
import com.vlad1m1r.lemniscate.testutils.TestConstants.DELTA
import com.vlad1m1r.lemniscate.testutils.isPeriodic
import com.vlad1m1r.lemniscate.testutils.setupDefaultMock
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import kotlin.math.PI

class RoundScribbleProgressViewTest {

    private val view = mock<RoundScribbleProgressView>()

    @Before
    fun setUp() {
        view.setupDefaultMock()
        doCallRealMethod().whenever(view).radiusSum
        doCallRealMethod().whenever(view).sizeFactor
    }

    @Test
    fun getGraphX() {
        assertThat(view.getGraphX(0.0f)).isWithin(DELTA).of(30.0f)
        assertThat(view.getGraphX(0.1f)).isWithin(DELTA).of(30.589556f)
        assertThat(view.getGraphX(0.5f)).isWithin(DELTA).of(39.26477f)
        assertThat(view.getGraphX(1.0f)).isWithin(DELTA).of(28.14853f)
        assertThat(view.getGraphX(PI.toFloat())).isWithin(DELTA).of(-50.0f)
        assertThat(view.getGraphX(2.0f)).isWithin(DELTA).of(-15.190873f)
        assertThat(view.getGraphX(2 * PI.toFloat())).isWithin(DELTA).of(30.0f)
    }

    @Test
    fun getGraphY() {
        assertThat(view.getGraphY(0.0f)).isWithin(DELTA).of(40.0f)
        assertThat(view.getGraphY(0.1f)).isWithin(DELTA).of(35.905983f)
        assertThat(view.getGraphY(0.5f)).isWithin(DELTA).of(26.010328f)
        assertThat(view.getGraphY(1.0f)).isWithin(DELTA).of(29.180117f)
        assertThat(view.getGraphY(PI.toFloat())).isWithin(DELTA).of(-40.0f)
        assertThat(view.getGraphY(2.0f)).isWithin(DELTA).of(-26.539455f)
        assertThat(view.getGraphY(2 * PI.toFloat())).isWithin(DELTA).of(40.0f)
    }

    @Test
    fun isPeriodic() {
        view.isPeriodic(2 * PI.toFloat())
    }
}

@RunWith(RobolectricTestRunner::class)
@Config(sdk = [Build.VERSION_CODES.P])
class RoundScribbleProgressViewHasHoleTest {

    val context = InstrumentationRegistry.getInstrumentation().targetContext
    private val view = RoundScribbleProgressView(context)

    @Test
    fun hasHoleDisabled() {
        view.hasHole = true
        assertThat(view.hasHole).isFalse()
    }
}


================================================
FILE: lemniscate/src/test/java/com/vlad1m1r/lemniscate/roulette/scribble/ScribbleProgressViewTest.kt
================================================
package com.vlad1m1r.lemniscate.roulette.scribble

import android.os.Build
import androidx.test.platform.app.InstrumentationRegistry
import com.google.common.truth.Truth.assertThat
import org.mockito.kotlin.*
import com.vlad1m1r.lemniscate.testutils.TestConstants.DELTA
import com.vlad1m1r.lemniscate.testutils.isPeriodic
import com.vlad1m1r.lemniscate.testutils.setupDefaultMock
import org.junit.Before
import org.junit.Test

import org.junit.runner.RunWith
import org.mockito.junit.MockitoJUnitRunner
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import kotlin.math.PI

@RunWith(MockitoJUnitRunner::class)
class ScribbleProgressViewTest {

    private val view = mock<ScribbleProgressView>()

    @Before
    fun setUp() {
        view.setupDefaultMock()
        doCallRealMethod().whenever(view).radiusSum
        doCallRealMethod().whenever(view).sizeFactor
    }

    @Test
    fun getGraphX() {
        assertThat(view.getGraphX(0.0f)).isWithin(DELTA).of(30.0f)
        assertThat(view.getGraphX(0.1f)).isWithin(DELTA).of(30.589556f)
        assertThat(view.getGraphX(0.5f)).isWithin(DELTA).of(39.26477f)
        assertThat(view.getGraphX(1.0f)).isWithin(DELTA).of(28.14853f)
        assertThat(view.getGraphX(PI.toFloat())).isWithin(DELTA).of(-50.0f)
        assertThat(view.getGraphX(2.0f)).isWithin(DELTA).of(-15.190873f)
        assertThat(view.getGraphX(2 * PI.toFloat())).isWithin(DELTA).of(30.0f)
    }

    @Test
    fun getGraphY() {
        assertThat(view.getGraphY(0.0f)).isWithin(DELTA).of(-10.0f)
        assertThat(view.getGraphY(0.1f)).isWithin(DELTA).of(-5.217273f)
        assertThat(view.getGraphY(0.5f)).isWithin(DELTA).of(23.33849f)
        assertThat(view.getGraphY(1.0f)).isWithin(DELTA).of(40.195274f)
        assertThat(view.getGraphY(PI.toFloat())).isWithin(DELTA).of(-10.0f)
        assertThat(view.getGraphY(2.0f)).isWithin(DELTA).of(37.826897f)
        assertThat(view.getGraphY(2 * PI.toFloat())).isWithin(DELTA).of(-10.0f)
    }

    @Test
    fun isPeriodic() {
        view.isPeriodic(2 * PI.toFloat())
    }
}

@RunWith(RobolectricTestRunner::class)
@Config(sdk = [Build.VERSION_CODES.P])
class ScribbleProgressViewHasHoleTest {

    val context = InstrumentationRegistry.getInstrumentation().targetContext
    private val view = ScribbleProgressView(context)

    @Test
    fun hasHoleDisabled() {
        view.hasHole = true
        assertThat(view.hasHole).isFalse()
    }
}


================================================
FILE: lemniscate/src/test/java/com/vlad1m1r/lemniscate/roulette/settings/RouletteCurveSettingsParcelableTest.kt
================================================
package com.vlad1m1r.lemniscate.roulette.settings

import android.os.Build
import android.os.Parcel
import com.google.common.truth.Truth.assertThat
import com.vlad1m1r.lemniscate.testutils.isEqualTo
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config

@RunWith(RobolectricTestRunner::class)
@Config(sdk = [Build.VERSION_CODES.P])
class RouletteCurveSettingsParcelableTest {

    private lateinit var rouletteCurveSettings: RouletteCurveSettings

    @Before
    fun setUp() {
        rouletteCurveSettings = RouletteCurveSettings()
        rouletteCurveSettings.distanceFromCenter = 0.24f
        rouletteCurveSettings.numberOfCycles = 0.83f
        rouletteCurveSettings.radiusFixed = 1.41f
        rouletteCurveSettings.radiusMoving = 3.25f
    }

    @Test
    fun parcelable() {
        val parcel = Parcel.obtain()
        rouletteCurveSettings.writeToParcel(parcel, 0)
        parcel.setDataPosition(0)

        val copy = RouletteCurveSettings(parcel)
        parcel.recycle()

        assertThat(rouletteCurveSettings.isEqualTo(copy)).isTrue()
    }
}


================================================
FILE: lemniscate/src/test/java/com/vlad1m1r/lemniscate/testutils/CurveTestUtils.kt
================================================
package com.vlad1m1r.lemniscate.testutils

import com.google.common.truth.Truth
import org.mockito.kotlin.*
import com.vlad1m1r.lemniscate.base.BaseCurveProgressView
import com.vlad1m1r.lemniscate.roulette.BaseRouletteProgressView

fun BaseCurveProgressView.isPeriodic(period: Float) {
    for (i in 1..10) {
        val random = Math.random().toFloat()
        Truth.assertThat(getGraphX(random)).isWithin(TestConstants.DELTA).of(getGraphX(random + period))
        Truth.assertThat(getGraphY(random)).isWithin(TestConstants.DELTA).of(getGraphY(random + period))
    }
}

fun BaseRouletteProgressView.setupDefaultMock() {
    doCallRealMethod().whenever(this).getGraphX(any())
    doCallRealMethod().whenever(this).getGraphY(any())

    whenever(this.size).thenReturn(100f)
    whenever(this.radiusFixed).thenReturn(3f)
    whenever(this.radiusMoving).thenReturn(1f)
    whenever(this.distanceFromCenter).thenReturn(1f)
}

fun BaseCurveProgressView.setupDefaultMock() {
    doCallRealMethod().whenever(this).getGraphX(any())
    doCallRealMethod().whenever(this).getGraphY(any())
    whenever(this.size).thenReturn(100f)
}


================================================
FILE: lemniscate/src/test/java/com/vlad1m1r/lemniscate/testutils/EqualUtils.kt
================================================
package com.vlad1m1r.lemniscate.testutils

import com.vlad1m1r.lemniscate.base.models.LineLength
import com.vlad1m1r.lemniscate.base.settings.AnimationSettings
import com.vlad1m1r.lemniscate.base.settings.CurveSettings
import com.vlad1m1r.lemniscate.roulette.settings.RouletteCurveSettings

fun AnimationSettings.isEqualTo(animationSettings: AnimationSettings): Boolean {
    return this.duration == animationSettings.duration &&
            this.startingPointOnCurve == animationSettings.startingPointOnCurve
}

fun CurveSettings.isEqualTo(curveSettings: CurveSettings): Boolean {
    return this.hasHole == curveSettings.hasHole &&
            this.color == curveSettings.color &&
            this.precision == curveSettings.precision &&
            this.strokeWidth == curveSettings.strokeWidth &&
            this.lineLength.isEqualTo(curveSettings.lineLength)
}

fun LineLength.isEqualTo(lineLength: LineLength): Boolean {
    return this.lineMinLength == lineLength.lineMinLength &&
            this.lineMaxLength == lineLength.lineMaxLength
}

fun RouletteCurveSettings.isEqualTo(rouletteCurveSettings: RouletteCurveSettings): Boolean {
    return this.distanceFromCenter == rouletteCurveSettings.distanceFromCenter &&
            this.numberOfCycles == rouletteCurveSettings.numberOfCycles &&
            this.radiusFixed == rouletteCurveSettings.radiusFixed &&
            this.radiusMoving == rouletteCurveSettings.radiusMoving
}


================================================
FILE: lemniscate/src/test/java/com/vlad1m1r/lemniscate/testutils/TestConstants.kt
================================================
package com.vlad1m1r.lemniscate.testutils

object TestConstants {
    const val DELTA: Float = 0.001f
}


================================================
FILE: lemniscate/src/test/java/com/vlad1m1r/lemniscate/testutils/TestLayoutInflater.kt
================================================
package com.vlad1m1r.lemniscate.testutils

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup

class TestLayoutInflater internal constructor(context: Context) : LayoutInflater(context) {
    internal var resId: Int = 0
        private set
    internal var root: ViewGroup? = null
        private set

    override fun inflate(resource: Int, root: ViewGroup?): View? {
        this.resId = resource
        this.root = root
        return null
    }

    override fun inflate(resource: Int, root: ViewGroup?, attachToRoot: Boolean): View? {
        this.resId = resource
        this.root = root
        return null
    }

    override fun cloneInContext(newContext: Context): LayoutInflater? {
        return null
    }
}


================================================
FILE: lemniscate/src/test/java/com/vlad1m1r/lemniscate/utils/CurveUtilsTest.kt
================================================
package com.vlad1m1r.lemniscate.utils

import com.google.common.truth.Truth.assertThat
import com.vlad1m1r.lemniscate.base.models.Point
import org.junit.Test

class CurveUtilsTest {

    val point = Point(5f, 0f, 1f, 10f)

    @Test
    fun returnPoint_whenPointIsNotInHole() {
        assertThat(CurveUtils.checkPointForHole(point, 0.2f, 10f)).isSameInstanceAs(point)
    }

    @Test
    fun returnNull_whenPointIsInHole() {
        assertThat(CurveUtils.checkPointForHole(point, 5.0f, 10f)).isNull()
    }

    @Test
    fun returnNull_whenPointIsNull() {
        assertThat(CurveUtils.checkPointForHole(null, 0.2f, 10f)).isNull()
    }
}


================================================
FILE: lemniscate/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
================================================
mock-maker-inline

================================================
FILE: remote_data/legal/privacy_policy.html
================================================
<!DOCTYPE html>
<html>
<head>
    <meta charset='utf-8'>
    <meta name='viewport' content='width=device-width'>
    <title>Privacy Policy</title>
    <style> body { font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; padding:1em; } </style>
</head>
<body>
<strong>Privacy Policy</strong> <p>
    Vladimir Jovanovic built the Lemniscate app as
    an Open Source app. This SERVICE is provided by
    Vladimir Jovanovic at no cost and is intended for use as
    is.
</p> <p>
    This page is used to inform visitors regarding my
    policies with the collection, use, and disclosure of Personal
    Information if anyone decided to use my Service.
</p> <p>
    If you choose to use my Service, then you agree to
    the collection and use of information in relation to this
    policy. The Personal Information that I collect is
    used for providing and improving the Service. I will not use or share your information with
    anyone except as described in this Privacy Policy.
</p> <p>
    The terms used in this Privacy Policy have the same meanings
    as in our Terms and Conditions, which are accessible at
    Lemniscate unless otherwise defined in this Privacy Policy.
</p> <p><strong>Information Collection and Use</strong></p> <p>
    For a better experience, while using our Service, I
    may require you to provide us with certain personally
    identifiable information. The information that
    I request will be retained on your device and is not collected by me in any way.
</p> <!----> <p><strong>Log Data</strong></p> <p>
    I want to inform you that whenever you
    use my Service, in a case of an error in the app
    I collect data and information (through third-party
    products) on your phone called Log Data. This Log Data may
    include information such as your device Internet Protocol
    (“IP”) address, device name, operating system version, the
    configuration of the app when utilizing my Service,
    the time and date of your use of the Service, and other
    statistics.
</p> <p><strong>Cookies</strong></p> <p>
    Cookies are files with a small amount of data that are
    commonly used as anonymous unique identifiers. These are sent
    to your browser from the websites that you visit and are
    stored on your device's internal memory.
</p> <p>
    This Service does not use these “cookies” explicitly. However,
    the app may use third-party code and libraries that use
    “cookies” to collect information and improve their services.
    You have the option to either accept or refuse these cookies
    and know when a cookie is being sent to your device. If you
    choose to refuse our cookies, you may not be able to use some
    portions of this Service.
</p> <p><strong>Service Providers</strong></p> <p>
    I may employ third-party companies and
    individuals due to the following reasons:
</p> <ul><li>To facilitate our Service;</li> <li>To provide the Service on our behalf;</li> <li>To perform Service-related services; or</li> <li>To assist us in analyzing how our Service is used.</li></ul> <p>
    I want to inform users of this Service
    that these third parties have access to their Personal
    Information. The reason is to perform the tasks assigned to
    them on our behalf. However, they are obligated not to
    disclose or use the information for any other purpose.
</p> <p><strong>Security</strong></p> <p>
    I value your trust in providing us your
    Personal Information, thus we are striving to use commercially
    acceptable means of protecting it. But remember that no method
    of transmission over the internet, or method of electronic
    storage is 100% secure and reliable, and I cannot
    guarantee its absolute security.
</p> <p><strong>Links to Other Sites</strong></p> <p>
    This Service may contain links to other sites. If you click on
    a third-party link, you will be directed to that site. Note
    that these external sites are not operated by me.
    Therefore, I strongly advise you to review the
    Privacy Policy of these websites. I have
    no control over and assume no responsibility for the content,
    privacy policies, or practices of any third-party sites or
    services.
</p> <p><strong>Children’s Privacy</strong></p> <!----> <div><p>
    I do not knowingly collect personally
    identifiable information from children. I
    encourage all children to never submit any personally
    identifiable information through
    the Application and/or Services.
    I encourage parents and legal guardians to monitor
    their children's Internet usage and to help enforce this Policy by instructing
    their children never to provide personally identifiable information through the Application and/or Services without their permission. If you have reason to believe that a child
    has provided personally identifiable information to us through the Application and/or Services,
    please contact us. You must also be at least 16 years of age to consent to the processing
    of your personally identifiable information in your country (in some countries we may allow your parent
    or guardian to do so on your behalf).
</p></div> <p><strong>Changes to This Privacy Policy</strong></p> <p>
    I may update our Privacy Policy from
    time to time. Thus, you are advised to review this page
    periodically for any changes. I will
    notify you of any changes by posting the new Privacy Policy on
    this page.
</p> <p>This policy is effective as of 2022-06-18</p> <p><strong>Contact Us</strong></p> <p>
    If you have any questions or suggestions about my
    Privacy Policy, do not hesitate to contact me at write@vladimirj.dev.
</p> <p>This privacy policy page was created at <a href="https://privacypolicytemplate.net" target="_blank" rel="noopener noreferrer">privacypolicytemplate.net </a>and modified/generated by <a href="https://app-privacy-policy-generator.nisrulz.com/" target="_blank" rel="noopener noreferrer">App Privacy Policy Generator</a></p>
</body>
</html>


================================================
FILE: remote_data/legal/terms_and_conditions.html
================================================
<!DOCTYPE html>
<html>
<head>
    <meta charset='utf-8'>
    <meta name='viewport' content='width=device-width'>
    <title>Terms &amp; Conditions</title>
    <style> body { font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; padding:1em; } </style>
</head>
<body>
<strong>Terms &amp; Conditions</strong> <p>
    By downloading or using the app, these terms will
    automatically apply to you – you should make sure therefore
    that you read them carefully before using the app. You’re not
    allowed to copy or modify the app, any part of the app, or
    our trademarks in any way. You’re not allowed to attempt to
    extract the source code of the app, and you also shouldn’t try
    to translate the app into other languages or make derivative
    versions. The app itself, and all the trademarks, copyright,
    database rights, and other intellectual property rights related
    to it, still belong to Vladimir Jovanovic.
</p> <p>
    Vladimir Jovanovic is committed to ensuring that the app is
    as useful and efficient as possible. For that reason, we
    reserve the right to make changes to the app or to charge for
    its services, at any time and for any reason. We will never
    charge you for the app or its services without making it very
    clear to you exactly what you’re paying for.
</p> <p>
    The Lemniscate app stores and processes personal data that
    you have provided to us, to provide my
    Service. It’s your responsibility to keep your phone and
    access to the app secure. We therefore recommend that you do
    not jailbreak or root your phone, which is the process of
    removing software restrictions and limitations imposed by the
    official operating system of your device. It could make your
    phone vulnerable to malware/viruses/malicious programs,
    compromise your phone’s security features and it could mean
    that the Lemniscate app won’t work properly or at all.
</p> <!----> <p>
    You should be aware that there are certain things that
    Vladimir Jovanovic will not take responsibility for. Certain
    functions of the app will require the app to have an active
    internet connection. The connection can be Wi-Fi or provided
    by your mobile network provider, but Vladimir Jovanovic
    cannot take responsibility for the app not working at full
    functionality if you don’t have access to Wi-Fi, and you don’t
    have any of your data allowance left.
</p> <p></p> <p>
    If you’re using the app outside of an area with Wi-Fi, you
    should remember that the terms of the agreement with your
    mobile network provider will still apply. As a result, you may
    be charged by your mobile provider for the cost of data for
    the duration of the connection while accessing the app, or
    other third-party charges. In using the app, you’re accepting
    responsibility for any such charges, including roaming data
    charges if you use the app outside of your home territory
    (i.e. region or country) without turning off data roaming. If
    you are not the bill payer for the device on which you’re
    using the app, please be aware that we assume that you have
    received permission from the bill payer for using the app.
</p> <p>
    Along the same lines, Vladimir Jovanovic cannot always take
    responsibility for the way you use the app i.e. You need to
    make sure that your device stays charged – if it runs out of
    battery and you can’t turn it on to avail the Service,
    Vladimir Jovanovic cannot accept responsibility.
</p> <p>
    With respect to Vladimir Jovanovic’s responsibility for your
    use of the app, when you’re using the app, it’s important to
    bear in mind that although we endeavor to ensure that it is
    updated and correct at all times, we do rely on third parties
    to provide information to us so that we can make it available
    to you. Vladimir Jovanovic accepts no liability for any
    loss, direct or indirect, you experience as a result of
    relying wholly on this functionality of the app.
</p> <p>
    At some point, we may wish to update the app. The app is
    currently available on Android – the requirements for the
    system(and for any additional systems we
    decide to extend the availability of the app to) may change,
    and you’ll need to download the updates if you want to keep
    using the app. Vladimir Jovanovic does not promise that it
    will always update the app so that it is relevant to you
    and/or works with the Android version that you have
    installed on your device. However, you promise to always
    accept updates to the application when offered to you, We may
    also wish to stop providing the app, and may terminate use of
    it at any time without giving notice of termination to you.
    Unless we tell you otherwise, upon any termination, (a) the
    rights and licenses granted to you in these terms will end;
    (b) you must stop using the app, and (if needed) delete it
    from your device.
</p> <p><strong>Changes to This Terms and Conditions</strong></p> <p>
    I may update our Terms and Conditions
    from time to time. Thus, you are advised to review this page
    periodically for any changes. I will
    notify you of any changes by posting the new Terms and
    Conditions on this page.
</p> <p>
    These terms and conditions are effective as of 2022-06-18
</p> <p><strong>Contact Us</strong></p> <p>
    If you have any questions or suggestions about my
    Terms and Conditions, do not hesitate to contact me
    at write@vladimirj.dev.
</p> <p>This Terms and Conditions page was generated by <a href="https://app-privacy-policy-generator.nisrulz.com/" target="_blank" rel="noopener noreferrer">App Privacy Policy Generator</a></p>
</body>
</html>


================================================
FILE: sample/.gitignore
================================================
/build


================================================
FILE: sample/build.gradle
================================================
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'

android {
    namespace "com.vlad1m1r.lemniscate.sample"

    defaultConfig {
        applicationId "com.vlad1m1r.lemniscate.sample"
        minSdkVersion Versions.sample_min_sdk
        targetSdkVersion Versions.target_sdk
        compileSdk Versions.compile_sdk
        versionCode Versions.sample_version_code
        versionName Versions.sample_version_name
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_17
        targetCompatibility JavaVersion.VERSION_17
    }
    kotlinOptions {
        jvmTarget = JavaVersion.VERSION_17.toString()
    }
    buildFeatures {
        viewBinding true
    }
}

dependencies {
    implementation Deps.appcompat
    implementation Deps.circleindicator
    implementation Deps.kotlin_stdlib

    implementation project(':lemniscate')
}


================================================
FILE: sample/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/vladimirjovanovic/Library/Android/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
#   http://developer.android.com/guide/developing/tools/proguard.html

# Add any project specific keep options here:

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
#   public *;
#}


================================================
FILE: sample/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.vlad1m1r.lemniscate.sample">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <activity
            android:name=".PresentationActivity">
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value=".MainActivity" />
        </activity>
    </application>
</manifest>

================================================
FILE: sample/src/main/java/com/vlad1m1r/lemniscate/sample/CurveData.kt
================================================
/*
 * Copyright 2016 Vladimir Jovanovic
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.vlad1m1r.lemniscate.sample

import android.os.Parcel
import android.os.Parcelable

class CurveData(var precision: Int = 200,
                var strokeWidth: Float = 10.0f,
                var sizeMultiplier: Float = 1.0f,
                var lineMinLength: Float = 0.4f,
                var lineMaxLength: Float = 0.8f,
                var color: Int = 0,
                var duration: Int = 1000,
                var hasHole: Boolean = false,
                var radiusFixed: Float = 4.0f,
                var radiusMoving: Float = 1.0f,
                var distanceFromCenter: Float = 3.0f,
                var numberOfCycles: Int = 1) : Parcelable {

    constructor(parcel: Parcel) : this(
            parcel.readInt(),
            parcel.readFloat(),
            parcel.readFloat(),
            parcel.readFloat(),
            parcel.readFloat(),
            parcel.readInt(),
            parcel.readInt(),
            parcel.readByte() != 0.toByte(),
            parcel.readFloat(),
            parcel.readFloat(),
            parcel.readFloat(),
            parcel.readInt())

    override fun writeToParcel(parcel: Parcel, flags: Int) {
        parcel.writeInt(precision)
        parcel.writeFloat(strokeWidth)
        parcel.writeFloat(sizeMultiplier)
        parcel.writeFloat(lineMinLength)
        parcel.writeFloat(lineMaxLength)
        parcel.writeInt(color)
        parcel.writeInt(duration)
        parcel.writeByte(if (hasHole) 1 else 0)
        parcel.writeFloat(radiusFixed)
        parcel.writeFloat(radiusMoving)
        parcel.writeFloat(distanceFromCenter)
        parcel.writeInt(numberOfCycles)
    }

    override fun describeContents(): Int {
        return 0
    }

    companion object CREATOR : Parcelable.Creator<CurveData> {
        override fun createFromParcel(parcel: Parcel): CurveData {
            return CurveData(parcel)
        }

        override fun newArray(size: Int): Array<CurveData?> {
            return arrayOfNulls(size)
        }
    }
}


================================================
FILE: sample/src/main/java/com/vlad1m1r/lemniscate/sample/FragmentCurve.kt
================================================
/*
 * Copyright 2016 Vladimir Jovanovic
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.vlad1m1r.lemniscate.sample

import android.content.Context
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.TextView

import com.vlad1m1r.lemniscate.BernoullisBowProgressView
import com.vlad1m1r.lemniscate.BernoullisProgressView
import com.vlad1m1r.lemniscate.BernoullisSharpProgressView
import com.vlad1m1r.lemniscate.GeronosProgressView
import com.vlad1m1r.lemniscate.base.BaseCurveProgressView
import com.vlad1m1r.lemniscate.funny.CannabisProgressView
import com.vlad1m1r.lemniscate.funny.HeartProgressView
import com.vlad1m1r.lemniscate.other.XProgressView
import com.vlad1m1r.lemniscate.roulette.EpitrochoidProgressView
import com.vlad1m1r.lemniscate.roulette.HypotrochoidProgressView
import com.vlad1m1r.lemniscate.roulette.scribble.RoundScribbleProgressView
import com.vlad1m1r.lemniscate.roulette.scribble.ScribbleProgressView

class FragmentCurve : Fragment() {

    private var listener: OnViewCreated? = null

    private var baseCurveProgressView: BaseCurveProgressView? = null

    private lateinit var curveName: TextView
    private lateinit var layoutViewHolder: LinearLayout

    private var position: Int = 0

    interface OnViewCreated {
        fun onViewShown(position: Int, baseCurveProgressView: BaseCurveProgressView?)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        if (savedInstanceState != null && savedInstanceState.containsKey(KEY_POSITION))
            position = savedInstanceState.getInt(KEY_POSITION)

        if (baseCurveProgressView == null) {
            baseCurveProgressView = getViewForPosition(position).apply {
                id = position
                layoutParams = LinearLayout.LayoutParams(
                        LinearLayout.LayoutParams.WRAP_CONTENT,
                        LinearLayout.LayoutParams.WRAP_CONTENT)
            }
        }
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        val root = inflater.inflate(R.layout.fragment_curve, container, false) as ViewGroup

        curveName = root.findViewById(R.id.textCurveName)
        layoutViewHolder = root.findViewById(R.id.layoutViewHolder)

        (baseCurveProgressView?.parent as ViewGroup?)?.removeView(baseCurveProgressView)

        layoutViewHolder.addView(baseCurveProgressView)

        curveName.text = baseCurveProgressView?.javaClass?.simpleName

        return root
    }

    private fun getViewForPosition(position: Int): BaseCurveProgressView {
        when (position) {
            0 -> return BernoullisProgressView(context!!)
            1 -> return GeronosProgressView(context!!)
            2 -> return BernoullisBowProgressView(context!!)
            3 -> return BernoullisSharpProgressView(context!!)

            4 -> return EpitrochoidProgressView(context!!)
            5 -> return HypotrochoidProgressView(context!!)

            6 -> return XProgressView(context!!)

            7 -> return RoundScribbleProgressView(context!!)
            8 -> return ScribbleProgressView(context!!)

            9 -> return CannabisProgressView(context!!)
            10 -> return HeartProgressView(context!!)
            else -> return BernoullisProgressView(context!!)
        }
    }

    override fun onAttach(context: Context) {
        super.onAttach(context)
        listener = context as OnViewCreated
    }

    override fun onResume() {
        super.onResume()
        listener?.onViewShown(position, baseCurveProgressView)
    }

    override fun onDetach() {
        listener = null
        super.onDetach()
    }

    override fun onSaveInstanceState(outState: Bundle) {
        outState.putInt(KEY_POSITION, position)
        super.onSaveInstanceState(outState)
    }

    companion object {

        private const val KEY_POSITION = "position"

        fun getInstance(fragmentsPosition: Int) =
                FragmentCurve().apply {
                    position = fragmentsPosition
                    retainInstance = true
                }
    }
}


================================================
FILE: sample/src/main/java/com/vlad1m1r/lemniscate/sample/FragmentSettings.kt
================================================
/*
 * Copyright 2016 Vladimir Jovanovic
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.vlad1m1r.lemniscate.sample

import android.content.res.Resources
import android.os.Bundle
import android.view.LayoutInflater
import androidx.fragment.app.Fragment
import androidx.core.content.ContextCompat
import android.view.View
import android.view.ViewGroup
import android.widget.CompoundButton
import android.widget.SeekBar

import com.vlad1m1r.lemniscate.BernoullisBowProgressView
import com.vlad1m1r.lemniscate.BernoullisProgressView
import com.vlad1m1r.lemniscate.BernoullisSharpProgressView
import com.vlad1m1r.lemniscate.GeronosProgressView
import com.vlad1m1r.lemniscate.base.BaseCurveProgressView
import com.vlad1m1r.lemniscate.roulette.BaseRouletteProgressView
import com.vlad1m1r.lemniscate.sample.databinding.FragmentSettingsBinding
import kotlin.math.round

class FragmentSettings : Fragment(), SeekBar.OnSeekBarChangeListener, CompoundButton.OnCheckedChangeListener, View.OnClickListener {

    private lateinit var curveData: CurveData
    private var baseCurveProgressView: BaseCurveProgressView? = null

    private var _binding: FragmentSettingsBinding? = null
    private val binding get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = FragmentSettingsBinding.inflate(layoutInflater)
        return binding.root
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        curveData = if(savedInstanceState != null && savedInstanceState.containsKey("curve_data")) {
            savedInstanceState.getParcelable("curve_data")!!
        } else {
            CurveData(color = ContextCompat.getColor(requireContext(), R.color.picker_color_1))
        }

        setupViews()
    }

    private fun setupViews() {

        binding.seekBarStrokeWidth.max = 50
        binding.seekBarStrokeWidth.progress = curveData.strokeWidth.toInt()
        binding.seekBarStrokeWidth.setOnSeekBarChangeListener(this)

        binding.seekBarMaxLineLength.max = 99
        binding.seekBarMaxLineLength.progress = round(100 * curveData.lineMaxLength).toInt() - 1
        binding.seekBarMaxLineLength.setOnSeekBarChangeListener(this)

        binding.seekBarSizeMultiplier.max = 15
        binding.seekBarSizeMultiplier.progress = 5
        binding.seekBarSizeMultiplier.setOnSeekBarChangeListener(this)

        binding.seekBarMinLineLength.max = 99
        binding. seekBarMinLineLength.progress = round(100 * curveData.lineMinLength).toInt() - 1
        binding.seekBarMinLineLength.setOnSeekBarChangeListener(this)

        binding.seekBarAnimationDuration.max = 199
        binding.seekBarAnimationDuration.progress = curveData.duration / 10 - 1
        binding.seekBarAnimationDuration.setOnSeekBarChangeListener(this)

        binding.checkBoxHasHole.setOnCheckedChangeListener(this)

        binding.checkBoxHasHole.isChecked = curveData.hasHole

        binding.seekBarPrecision.max = 990
        binding.seekBarPrecision.progress = curveData.precision
        binding.seekBarPrecision.setOnSeekBarChangeListener(this)

        binding.seekBarA.max = 10
        binding.seekBarA.progress = (curveData.radiusFixed - 1).toInt()
        binding.seekBarA.setOnSeekBarChangeListener(this)

        binding.seekBarB.max = 10
        binding.seekBarB.progress = (curveData.radiusMoving - 1).toInt()
        binding.seekBarB.setOnSeekBarChangeListener(this)

        binding.seekBarD.max = 10
        binding.seekBarD.progress = (curveData.distanceFromCenter - 1).toInt()
        binding.seekBarD.setOnSeekBarChangeListener(this)

        binding.seekBarNumberOfCycles.max = 5
        binding.seekBarNumberOfCycles.progress = curveData.numberOfCycles - 1
        binding.seekBarNumberOfCycles.setOnSeekBarChangeListener(this)

        binding.viewColor1.setOnClickListener(this)
        binding.viewColor2.setOnClickListener(this)
        binding.viewColor3.setOnClickListener(this)
        binding.viewColor4.setOnClickListener(this)
        binding.viewColor5.setOnClickListener(this)
        binding.viewColor6.setOnClickListener(this)
    }

    fun setBaseCurveProgressView(baseCurveProgressView: BaseCurveProgressView) {
        this.baseCurveProgressView = baseCurveProgressView

        //Checkbox
        binding.checkBoxHasHole.isEnabled = this.baseCurveProgressView is BernoullisProgressView ||
                this.baseCurveProgressView is GeronosProgressView ||
                this.baseCurveProgressView is BernoullisBowProgressView ||
                this.baseCurveProgressView is BernoullisSharpProgressView

        //Roulette params
        if (this.baseCurveProgressView is BaseRouletteProgressView) {
            binding.seekBarA.isEnabled = true
            binding.seekBarB.isEnabled = true
            binding.seekBarD.isEnabled = true
            binding.seekBarNumberOfCycles!!.isEnabled = true
        } else {
            binding.seekBarA.isEnabled = false
            binding.seekBarB.isEnabled = false
            binding.seekBarD.isEnabled = false
            binding.seekBarNumberOfCycles.isEnabled = false
        }

        invalidateView(this.baseCurveProgressView)
        updateValues()
    }

    override fun onProgressChanged(seekBar: SeekBar, i: Int, fromUser: Boolean) {
        when (seekBar.id) {
            R.id.seekBarStrokeWidth -> curveData.strokeWidth = resources.dpToPx(i / 3.0f)
            R.id.seekBarMaxLineLength -> if (i <  binding.seekBarMinLineLength.progress) {
                binding.seekBarMaxLineLength.progress =  binding.seekBarMinLineLength.progress
            } else
                curveData.lineMaxLength = (i + 1) / 100.0f
            R.id.seekBarMinLineLength -> if (i >  binding.seekBarMaxLineLength.progress) {
                binding.seekBarMinLineLength.progress =  binding.seekBarMaxLineLength.progress
            } else
                curveData.lineMinLength = (i + 1) / 100.0f
            R.id.seekBarSizeMultiplier -> curveData.sizeMultiplier = (i + 5) / 10.0f
            R.id.seekBarAnimationDuration -> curveData.duration = (i + 1) * 10
            R.id.seekBarPrecision -> curveData.precision = i + 10
            R.id.seekBarA -> curveData.radiusFixed = (i + 1).toFloat()
            R.id.seekBarB -> curveData.radiusMoving = (i + 1).toFloat()
            R.id.seekBarD -> curveData.distanceFromCenter = (i + 1).toFloat()
            R.id.seekBarNumberOfCycles -> curveData.numberOfCycles = i + 1
        }
        invalidateView(baseCurveProgressView)
        updateValues()
    }

    private fun updateValues() {
        binding.textStrokeWidth.text = curveData.strokeWidth.toString()
        binding.textMaxLineLength.text = String.format(resources.getString(R.string.format_percentage), (curveData.lineMaxLength * 100).toInt())
        binding.textMinLineLength.text = String.format(resources.getString(R.string.format_percentage), (curveData.lineMinLength * 100).toInt())
        binding.textSizeMultiplier.text = curveData.sizeMultiplier.toString()
        binding.textAnimationDuration.text = String.format(resources.getString(R.string.format_ms), curveData.duration)
        binding.textPrecision.text = String.format(resources.getString(R.string.format_points), curveData.precision)
    }

    private fun invalidateView(baseCurveProgressView: BaseCurveProgressView?) {
        baseCurveProgressView?.apply {
            precision = curveData.precision
            strokeWidth = curveData.strokeWidth
            lineMaxLength = curveData.lineMaxLength
            lineMinLength = curveData.lineMinLength
            duration = curveData.duration
            hasHole = curveData.hasHole
            color = curveData.color
            sizeMultiplier = curveData.sizeMultiplier

            if (this is BaseRouletteProgressView) {
                radiusFixed = curveData.radiusFixed
                radiusMoving = curveData.radiusMoving
                distanceFromCenter = curveData.distanceFromCenter

                numberOfCycles = curveData.numberOfCycles.toFloat()
            }
        }
    }

    override fun onStartTrackingTouch(seekBar: SeekBar) {}

    override fun onStopTrackingTouch(seekBar: SeekBar) {}

    override fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) {
        when (buttonView.id) {
            R.id.checkBoxHasHole -> curveData.hasHole = isChecked
        }
        invalidateView(baseCurveProgressView)
    }

    fun applySettings(baseCurveProgressView: BaseCurveProgressView) {
        invalidateView(baseCurveProgressView)
    }

    override fun onClick(v: View) {
        when (v.id) {
            R.id.viewColor1 -> curveData.color = ContextCompat.getColor(requireContext(), R.color.picker_color_1)
            R.id.viewColor2 -> curveData.color = ContextCompat.getColor(requireContext(), R.color.picker_color_2)
            R.id.viewColor3 -> curveData.color = ContextCompat.getColor(requireContext(), R.color.picker_color_3)
            R.id.viewColor4 -> curveData.color = ContextCompat.getColor(requireContext(), R.color.picker_color_4)
            R.id.viewColor5 -> curveData.color = ContextCompat.getColor(requireContext(), R.color.picker_color_5)
            R.id.viewColor6 -> curveData.color = ContextCompat.getColor(requireContext(), R.color.picker_color_6)
        }
        invalidateView(baseCurveProgressView)
    }

    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        outState.putParcelable("curve_data", curveData)
    }
}

fun Resources.dpToPx(dp: Float): Float {
    return dp * this.displayMetrics.density
}


================================================
FILE: sample/src/main/java/com/vlad1m1r/lemniscate/sample/MainActivity.kt
================================================
/*
 * Copyright 2016 Vladimir Jovanovic
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.vlad1m1r.lemniscate.sample

import android.content.Intent
import android.net.Uri
import android.os.Bundle
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentStatePagerAdapter
import androidx.viewpager.widget.ViewPager
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import android.view.Menu
import android.view.MenuItem
import android.view.View
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat

import com.vlad1m1r.lemniscate.base.BaseCurveProgressView
import me.relex.circleindicator.CircleIndicator

private const val NUM_PAGES = 11

class MainActivity : AppCompatActivity(), FragmentCurve.OnViewCreated {

    private lateinit var fragmentSettings: FragmentSettings
    private lateinit var pager: ViewPager

    private lateinit var pagerAdapter: CurvesPagerAdapter
    private lateinit var toolbar: Toolbar


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        toolbar = findViewById(R.id.toolbar)
        setSupportActionBar(toolbar)
        fragmentSettings = supportFragmentManager.findFragmentById(R.id.fragment_settings) as FragmentSettings
        pager = findViewById(R.id.viewPager)
        pagerAdapter = CurvesPagerAdapter(supportFragmentManager)
        val indicator = findViewById<CircleIndicator>(R.id.indicator)
        pager.adapter = pagerAdapter
        indicator.setViewPager(pager)

        val rootView = findViewById<View>(R.id.root_view)
        ViewCompat.setOnApplyWindowInsetsListener(rootView) { view, insets ->
            val systemInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            view.setPadding(0, systemInsets.top, 0, systemInsets.bottom)
            insets
        }
    }

    override fun onViewShown(position: Int, baseCurveProgressView: BaseCurveProgressView?) {
        if (pager.currentItem == position) {
            fragmentSettings.setBaseCurveProgressView(baseCurveProgressView!!)
        }
    }

    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        menuInflater.inflate(R.menu.menu_main_activity, menu)
        return true
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {

        when (item.itemId) {
            R.id.action_github -> {
                val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.url_github)))
                startActivity(browserIntent)
                return true
            }
            R.id.action_presentation -> {
                startActivity(Intent(this, PresentationActivity::class.java))
                return true
            }
        }
        return super.onOptionsItemSelected(item)
    }

    private inner class CurvesPagerAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm,  BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {

        override fun getItem(position: Int): Fragment {
            return FragmentCurve.getInstance(position)
        }

        override fun getCount(): Int {
            return NUM_PAGES
        }
    }
}


================================================
FILE: sample/src/main/java/com/vlad1m1r/lemniscate/sample/PresentationActivity.kt
================================================
/*
 * Copyright 2016 Vladimir Jovanovic
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.vlad1m1r.lemniscate.sample

import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat

class PresentationActivity : AppCompatActivity() {

    private lateinit var toolbar: Toolbar

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_presentation)
        toolbar = findViewById(R.id.toolbar)
        setSupportActionBar(toolbar)
        supportActionBar?.apply {
            setTitle(R.string.screen_presentation)
            setDisplayHomeAsUpEnabled(true)
            setDisplayShowHomeEnabled(true)
        }

        val rootView = findViewById<View>(R.id.root_view)
        ViewCompat.setOnApplyWindowInsetsListener(rootView) { view, insets ->
            val systemInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            view.setPadding(0, systemInsets.top, 0, systemInsets.bottom)
            insets
        }
    }
}



================================================
FILE: sample/src/main/res/drawable/indicator.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
    <solid
        android:color="@color/color_primary"/>
</shape>


================================================
FILE: sample/src/main/res/drawable/indicator_selected.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
    <solid
        android:color="@color/color_primary_dark"/>
</shape>


================================================
FILE: sample/src/main/res/drawable/shadow.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
       android:shape="rectangle" >

    <gradient
        android:angle="90"
        android:startColor="@color/shadow_start"
        android:endColor="@color/shadow_end"
        android:type="linear" />

</shape>

================================================
FILE: sample/src/main/res/drawable-v26/ic_launcher_background.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<vector
    android:height="108dp"
    android:width="108dp"
    android:viewportHeight="108"
    android:viewportWidth="108"
    xmlns:android="http://schemas.android.com/apk/res/android">
    <path android:fillColor="@color/color_primary"
          android:pathData="M0,0h108v108h-108z"/>
</vector>


================================================
FILE: sample/src/main/res/drawable-v26/ic_launcher_foreground.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="108dp"
        android:height="108dp"
        android:viewportWidth="108.0"
        android:viewportHeight="108.0">
    <path
        android:pathData="m85.76,70.11c0.16,-0.08 0.62,-0.33 0.48,-0.23 -0.26,0.18 -1.06,0.67 -0.82,0.47 0.33,-0.28 0.73,-0.46 1.09,-0.7 0.48,-0.32 0.95,-0.64 1.42,-0.97 2.12,-1.46 1.64,-1.13 3.87,-2.73 3.19,-2.37 6.3,-4.91 8.86,-7.97 1.8,-2.15 1.95,-2.62 3.38,-4.99 1.25,-2.45 2.19,-5.04 2.71,-7.75 0.16,-0.84 0.24,-1.69 0.36,-2.54 0,0 1.49,-0.73 1.49,-0.73v0c-0.12,0.85 -0.2,1.71 -0.36,2.56 -0.52,2.72 -1.47,5.34 -2.72,7.81 -1.42,2.38 -1.59,2.87 -3.38,5.03 -2.54,3.06 -5.63,5.6 -8.8,7.98 -2.36,1.71 -1.56,1.15 -3.78,2.7 -1.71,1.19 -3.43,2.38 -5.32,3.28 0,0 1.51,-1.2 1.51,-1.2z"
        android:strokeLineCap="square"
        android:fillAlpha="0"
        android:strokeColor="#00000000"
        android:fillColor="#000000"
        android:strokeWidth="1.51181102"
        android:strokeLineJoin="miter"
        android:strokeAlpha="0.5"/>
    <path
        android:pathData="m101.76,107.99 l-27.78,-0.09 -45.61,-45.61 11.63,3.12 11.09,-8.09 50.68,50.68v0"
        android:strokeLineCap="butt"
        android:fillAlpha="0.14117648"
        android:strokeColor="#00000000"
        android:fillColor="#000000"
        android:strokeWidth="1"
        android:strokeLineJoin="miter"
        android:strokeAlpha="1"/>
    <path
        android:pathData="m51.09,57.31 l-13.77,-13.77 -7.66,2.05 -3.13,11.68 6.97,6.97h9.72z"
        android:strokeLineCap="butt"
        android:fillAlpha="0.08058824"
        android:strokeColor="#00000000"
        android:fillColor="#000000"
        android:strokeWidth="1"
        android:strokeLineJoin="miter"
        android:strokeAlpha="1"/>
    <path
        android:pathData="m56.79,50.81 l13.61,13.57 7.3,-1.93 3.23,-12.07 -8.08,-8.08 -10.91,2.92z"
        android:strokeLineCap="butt"
        android:fillAlpha="0.08058824"
        android:strokeColor="#00000000"
        android:fillColor="#000000"
        android:strokeWidth="1"
        android:strokeLineJoin="miter"
        android:strokeAlpha="1"/>
    <path
        android:pathData="m70.4,64.38 l37.67,37.67v6.07l-6.31,-0.14 -64.44,-64.44 7.15,2.11 17.52,17.52z"
        android:strokeLineCap="butt"
        android:fillAlpha="0.08058824"
        android:strokeColor="#00000000"
        android:fillColor="#000000"
        android:strokeWidth="1"
        android:strokeLineJoin="miter"
        android:strokeAlpha="1"/>
    <path
        android:pathData="m80.01,46.12 l27.98,27.98 0.09,27.95 -37.67,-37.67 8.52,-3.09 2.67,-9.96z"
        android:strokeLineCap="butt"
        android:fillAlpha="0.14117647"
        android:strokeColor="#00000000"
        android:fillColor="#000000"
        android:strokeWidth="1"
        android:strokeLineJoin="miter"
        android:strokeAlpha="1"/>

    <path
        android:pathData="m70.11,41.31c-5.48,0 -9.58,3.84 -12.87,6.93l-0.36,0.36c-0.64,0.6 -0.67,1.6 -0.06,2.23 0.6,0.63 1.61,0.66 2.25,0.06l0.36,-0.36c3.18,-2.98 6.47,-6.07 10.68,-6.07 4.56,0 9.28,3.57 9.28,9.54 0,5.97 -4.72,9.54 -9.28,9.54C63.64,63.54 55.96,53.83 55.06,52.8 54.24,51.88 46.11,41.31 37.89,41.31 31.77,41.31 25.44,46.05 25.44,54c0,7.95 6.33,12.69 12.46,12.69 5.48,0 9.58,-3.84 12.87,-6.93l0.28,-0.26c0.64,-0.6 0.67,-1.6 0.06,-2.23 -0.6,-0.63 -1.61,-0.66 -2.25,-0.06l-0.28,0.27c-3.18,2.98 -6.47,6.07 -10.68,6.07 -4.56,0 -9.28,-3.57 -9.28,-9.54 0,-5.97 4.72,-9.54 9.28,-9.54 6.57,0 13.91,9.44 14.74,10.38C53.53,55.87 62,66.69 70.11,66.69 76.23,66.69 82.56,61.95 82.56,54c0,-7.95 -6.33,-12.69 -12.46,-12.69z"
        android:fillColor="#ffffff"/>

</vector>


================================================
FILE: sample/src/main/res/layout/activity_main.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/root_view"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    tools:context="com.vlad1m1r.lemniscate.sample.MainActivity">

    <include layout="@layout/toolbar" />

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <androidx.viewpager.widget.ViewPager
            android:id="@+id/viewPager"
            android:layout_width="wrap_content"
            android:layout_height="200dp" />

        <me.relex.circleindicator.CircleIndicator
            android:id="@+id/indicator"
            android:layout_width="match_parent"
            android:layout_height="48dp"
            android:layout_gravity="bottom"
            app:ci_drawable="@drawable/indicator_selected"
            app:ci_drawable_unselected="@drawable/indicator" />
    </FrameLayout>

    <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="@color/divider" />

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <fragment
            android:id="@+id/fragment_settings"
            android:name="com.vlad1m1r.lemniscate.sample.FragmentSettings"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

        <View
            android:layout_width="match_parent"
            android:layout_height="5dp"
            android:layout_gravity="top"
            android:background="@drawable/shadow" />
    </FrameLayout>

</LinearLayout>


================================================
FILE: sample/src/main/res/layout/activity_presentation.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/root_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:orientation="horizontal">

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_gravity="center"
            android:gravity="center"
            android:orientation="vertical">

            <com.vlad1m1r.lemniscate.BernoullisProgressView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_margin="@dimen/margin_normal"
                app:duration="1250"
                app:hasHole="true"
                app:lineColor="@color/color_primary"
                app:maxLineLength="0.8"
                app:minLineLength="0.8"
                app:precision="80"
                app:strokeWidth="5dp" />

            <com.vlad1m1r.lemniscate.roulette.EpitrochoidProgressView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_margin="@dimen/margin_normal"
                app:distanceFromCenter="1"
                app:duration="1250"
                app:hasHole="true"
                app:lineColor="@color/color_primary"
                app:maxLineLength="0.8"
                app:minLineLength="0.8"
                app:numberOfCycles="1"
                app:precision="150"
                app:radiusFixed="3"
                app:radiusMoving="1"
                app:strokeWidth="5dp" />

            <com.vlad1m1r.lemniscate.funny.HeartProgressView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_margin="@dimen/margin_normal"
                app:duration="1250"
                app:lineColor="@color/color_primary"
                app:maxLineLength="0.8"
                app:minLineLength="0.8"
                app:precision="150"
                app:strokeWidth="5dp" />

            <com.vlad1m1r.lemniscate.roulette.HypotrochoidProgressView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_margin="@dimen/margin_normal"
                app:distanceFromCenter="2"
                app:duration="1250"
                app:lineColor="@color/color_primary"
                app:maxLineLength="0.8"
                app:minLineLength="0.8"
                app:numberOfCycles="2"
                app:precision="80"
                app:radiusFixed="5"
                app:radiusMoving="2"
                app:strokeWidth="5dp" />

        </LinearLayout>

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_gravity="center"
            android:gravity="center"
          
Download .txt
gitextract_ucgiast4/

├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── build.gradle
├── buildSrc/
│   ├── .gitignore
│   ├── build.gradle.kts
│   └── src/
│       └── main/
│           └── java/
│               └── Dependencies.kt
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── lemniscate/
│   ├── .gitignore
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src/
│       ├── main/
│       │   ├── AndroidManifest.xml
│       │   ├── java/
│       │   │   └── com/
│       │   │       └── vlad1m1r/
│       │   │           └── lemniscate/
│       │   │               ├── BernoullisBowProgressView.kt
│       │   │               ├── BernoullisProgressView.kt
│       │   │               ├── BernoullisSharpProgressView.kt
│       │   │               ├── GeronosProgressView.kt
│       │   │               ├── base/
│       │   │               │   ├── BaseCurveContract.kt
│       │   │               │   ├── BaseCurvePresenter.kt
│       │   │               │   ├── BaseCurveProgressView.kt
│       │   │               │   ├── models/
│       │   │               │   │   ├── DrawState.kt
│       │   │               │   │   ├── LineLength.kt
│       │   │               │   │   ├── Point.kt
│       │   │               │   │   ├── Points.kt
│       │   │               │   │   └── ViewSize.kt
│       │   │               │   └── settings/
│       │   │               │       ├── AnimationSettings.kt
│       │   │               │       └── CurveSettings.kt
│       │   │               ├── funny/
│       │   │               │   ├── CannabisProgressView.kt
│       │   │               │   └── HeartProgressView.kt
│       │   │               ├── other/
│       │   │               │   └── XProgressView.kt
│       │   │               ├── roulette/
│       │   │               │   ├── BaseRouletteProgressView.kt
│       │   │               │   ├── EpitrochoidProgressView.kt
│       │   │               │   ├── HypotrochoidProgressView.kt
│       │   │               │   ├── scribble/
│       │   │               │   │   ├── RoundScribbleProgressView.kt
│       │   │               │   │   └── ScribbleProgressView.kt
│       │   │               │   └── settings/
│       │   │               │       └── RouletteCurveSettings.kt
│       │   │               └── utils/
│       │   │                   └── CurveUtils.kt
│       │   └── res/
│       │       └── values/
│       │           ├── attrs.xml
│       │           └── dimens.xml
│       └── test/
│           ├── java/
│           │   └── com/
│           │       └── vlad1m1r/
│           │           └── lemniscate/
│           │               ├── BernoullisBowProgressViewTest.kt
│           │               ├── BernoullisProgressViewTest.kt
│           │               ├── BernoullisSharpProgressViewTest.kt
│           │               ├── GeronosProgressViewTest.kt
│           │               ├── base/
│           │               │   ├── BaseCurvePresenterTest.kt
│           │               │   ├── BaseCurveProgressViewTest.kt
│           │               │   ├── BaseProgressViewAttributesTest.kt
│           │               │   ├── models/
│           │               │   │   ├── DrawStateTest.kt
│           │               │   │   ├── LineLengthParcelableTest.kt
│           │               │   │   ├── LineLengthTest.kt
│           │               │   │   ├── PointTest.kt
│           │               │   │   └── PointsTest.kt
│           │               │   └── settings/
│           │               │       ├── AnimationSettingsParcelableTest.kt
│           │               │       ├── CurveSettingsParcelableTest.kt
│           │               │       └── CurveSettingsTest.kt
│           │               ├── funny/
│           │               │   ├── CannabisProgressViewTest.kt
│           │               │   └── HeartProgressViewTest.kt
│           │               ├── other/
│           │               │   └── XProgressViewTest.kt
│           │               ├── roulette/
│           │               │   ├── BaseRouletteProgressViewAttributesTest.kt
│           │               │   ├── EpitrochoidProgressViewTest.kt
│           │               │   ├── HypotrochoidProgressViewTest.kt
│           │               │   ├── scribble/
│           │               │   │   ├── RoundScribbleProgressViewTest.kt
│           │               │   │   └── ScribbleProgressViewTest.kt
│           │               │   └── settings/
│           │               │       └── RouletteCurveSettingsParcelableTest.kt
│           │               ├── testutils/
│           │               │   ├── CurveTestUtils.kt
│           │               │   ├── EqualUtils.kt
│           │               │   ├── TestConstants.kt
│           │               │   └── TestLayoutInflater.kt
│           │               └── utils/
│           │                   └── CurveUtilsTest.kt
│           └── resources/
│               └── mockito-extensions/
│                   └── org.mockito.plugins.MockMaker
├── remote_data/
│   └── legal/
│       ├── privacy_policy.html
│       └── terms_and_conditions.html
├── sample/
│   ├── .gitignore
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src/
│       └── main/
│           ├── AndroidManifest.xml
│           ├── java/
│           │   └── com/
│           │       └── vlad1m1r/
│           │           └── lemniscate/
│           │               └── sample/
│           │                   ├── CurveData.kt
│           │                   ├── FragmentCurve.kt
│           │                   ├── FragmentSettings.kt
│           │                   ├── MainActivity.kt
│           │                   └── PresentationActivity.kt
│           └── res/
│               ├── drawable/
│               │   ├── indicator.xml
│               │   ├── indicator_selected.xml
│               │   └── shadow.xml
│               ├── drawable-v26/
│               │   ├── ic_launcher_background.xml
│               │   └── ic_launcher_foreground.xml
│               ├── layout/
│               │   ├── activity_main.xml
│               │   ├── activity_presentation.xml
│               │   ├── fragment_curve.xml
│               │   ├── fragment_settings.xml
│               │   └── toolbar.xml
│               ├── layout-land/
│               │   ├── activity_main.xml
│               │   └── activity_presentation.xml
│               ├── menu/
│               │   └── menu_main_activity.xml
│               ├── mipmap-anydpi-v26/
│               │   ├── ic_launcher.xml
│               │   └── ic_launcher_round.xml
│               ├── values/
│               │   ├── colors.xml
│               │   ├── constants.xml
│               │   ├── dimens.xml
│               │   ├── strings.xml
│               │   └── styles.xml
│               └── values-w820dp/
│                   └── dimens.xml
└── settings.gradle
Condensed preview — 106 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (233K chars).
[
  {
    "path": ".gitignore",
    "chars": 472,
    "preview": "# Built application files\n*.apk\n*.ap_\n\n# Files for the ART/Dalvik VM\n*.dex\n\n# Java class files\n*.class\n\n# Generated file"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 2926,
    "preview": "Change Log\r\n==========\r\n\r\nVersion 2.0.2 *(2019-07-27)*\r\n----------------------------\r\n\r\n* Fix `lineMinLength` setter\r\n\r\n"
  },
  {
    "path": "LICENSE",
    "chars": 10172,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004\n                   "
  },
  {
    "path": "README.md",
    "chars": 4957,
    "preview": "![Lemniscate header](http://i.imgur.com/i9t5vUm.png)\r\n\r\n\r\n[![License](https://img.shields.io/badge/License-Apache%202.0-"
  },
  {
    "path": "build.gradle",
    "chars": 329,
    "preview": "buildscript {\n    repositories {\n        jcenter()\n        google()\n    }\n    dependencies {\n        classpath Deps.andr"
  },
  {
    "path": "buildSrc/.gitignore",
    "chars": 7,
    "preview": "/build\n"
  },
  {
    "path": "buildSrc/build.gradle.kts",
    "chars": 670,
    "preview": "/*\n * Copyright 2017 Vladimir Jovanovic\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may"
  },
  {
    "path": "buildSrc/src/main/java/Dependencies.kt",
    "chars": 1522,
    "preview": "\nobject Versions {\n    const val kotlin = \"2.0.20\"\n\n    const val android_x = \"1.7.0\"\n\n    const val circleindicator = \""
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "chars": 231,
    "preview": "#Sat Sep 07 13:09:29 CEST 2024\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\:/"
  },
  {
    "path": "gradle.properties",
    "chars": 754,
    "preview": "# Project-wide Gradle settings.\n\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will o"
  },
  {
    "path": "gradlew",
    "chars": 5028,
    "preview": "#!/usr/bin/env bash\n\n##############################################################################\n##\n##  Gradle start "
  },
  {
    "path": "gradlew.bat",
    "chars": 2415,
    "preview": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@r"
  },
  {
    "path": "lemniscate/.gitignore",
    "chars": 7,
    "preview": "/build\n"
  },
  {
    "path": "lemniscate/build.gradle",
    "chars": 2522,
    "preview": "apply plugin: 'com.android.library'\napply plugin: 'kotlin-android'\napply plugin: 'jacoco'\n\njacoco {\n    toolVersion = Ve"
  },
  {
    "path": "lemniscate/proguard-rules.pro",
    "chars": 675,
    "preview": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /U"
  },
  {
    "path": "lemniscate/src/main/AndroidManifest.xml",
    "chars": 46,
    "preview": "<manifest package=\"com.vlad1m1r.lemniscate\"/>\n"
  },
  {
    "path": "lemniscate/src/main/java/com/vlad1m1r/lemniscate/BernoullisBowProgressView.kt",
    "chars": 1346,
    "preview": "/*\n * Copyright 2016 Vladimir Jovanovic\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may"
  },
  {
    "path": "lemniscate/src/main/java/com/vlad1m1r/lemniscate/BernoullisProgressView.kt",
    "chars": 1336,
    "preview": "/*\n * Copyright 2016 Vladimir Jovanovic\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may"
  },
  {
    "path": "lemniscate/src/main/java/com/vlad1m1r/lemniscate/BernoullisSharpProgressView.kt",
    "chars": 1335,
    "preview": "/*\n * Copyright 2016 Vladimir Jovanovic\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may"
  },
  {
    "path": "lemniscate/src/main/java/com/vlad1m1r/lemniscate/GeronosProgressView.kt",
    "chars": 1269,
    "preview": "/*\n * Copyright 2016 Vladimir Jovanovic\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may"
  },
  {
    "path": "lemniscate/src/main/java/com/vlad1m1r/lemniscate/base/BaseCurveContract.kt",
    "chars": 1720,
    "preview": "package com.vlad1m1r.lemniscate.base\n\nimport com.vlad1m1r.lemniscate.base.models.DrawState\nimport com.vlad1m1r.lemniscat"
  },
  {
    "path": "lemniscate/src/main/java/com/vlad1m1r/lemniscate/base/BaseCurvePresenter.kt",
    "chars": 2457,
    "preview": "package com.vlad1m1r.lemniscate.base\n\nimport com.vlad1m1r.lemniscate.base.models.DrawState\nimport com.vlad1m1r.lemniscat"
  },
  {
    "path": "lemniscate/src/main/java/com/vlad1m1r/lemniscate/base/BaseCurveProgressView.kt",
    "chars": 9476,
    "preview": "/*\n * Copyright 2016 Vladimir Jovanovic\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may"
  },
  {
    "path": "lemniscate/src/main/java/com/vlad1m1r/lemniscate/base/models/DrawState.kt",
    "chars": 3684,
    "preview": "/*\n * Copyright 2016 Vladimir Jovanovic\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may"
  },
  {
    "path": "lemniscate/src/main/java/com/vlad1m1r/lemniscate/base/models/LineLength.kt",
    "chars": 1846,
    "preview": "/*\n * Copyright 2016 Vladimir Jovanovic\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may"
  },
  {
    "path": "lemniscate/src/main/java/com/vlad1m1r/lemniscate/base/models/Point.kt",
    "chars": 1310,
    "preview": "/*\n * Copyright 2016 Vladimir Jovanovic\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may"
  },
  {
    "path": "lemniscate/src/main/java/com/vlad1m1r/lemniscate/base/models/Points.kt",
    "chars": 974,
    "preview": "/*\n * Copyright 2016 Vladimir Jovanovic\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may"
  },
  {
    "path": "lemniscate/src/main/java/com/vlad1m1r/lemniscate/base/models/ViewSize.kt",
    "chars": 730,
    "preview": "/*\n * Copyright 2016 Vladimir Jovanovic\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may"
  },
  {
    "path": "lemniscate/src/main/java/com/vlad1m1r/lemniscate/base/settings/AnimationSettings.kt",
    "chars": 1445,
    "preview": "/*\n * Copyright 2016 Vladimir Jovanovic\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may"
  },
  {
    "path": "lemniscate/src/main/java/com/vlad1m1r/lemniscate/base/settings/CurveSettings.kt",
    "chars": 2523,
    "preview": "/*\n * Copyright 2016 Vladimir Jovanovic\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may"
  },
  {
    "path": "lemniscate/src/main/java/com/vlad1m1r/lemniscate/funny/CannabisProgressView.kt",
    "chars": 1915,
    "preview": "/*\n * Copyright 2016 Vladimir Jovanovic\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may"
  },
  {
    "path": "lemniscate/src/main/java/com/vlad1m1r/lemniscate/funny/HeartProgressView.kt",
    "chars": 1624,
    "preview": "/*\n * Copyright 2016 Vladimir Jovanovic\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may"
  },
  {
    "path": "lemniscate/src/main/java/com/vlad1m1r/lemniscate/other/XProgressView.kt",
    "chars": 1504,
    "preview": "/*\n * Copyright 2016 Vladimir Jovanovic\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may"
  },
  {
    "path": "lemniscate/src/main/java/com/vlad1m1r/lemniscate/roulette/BaseRouletteProgressView.kt",
    "chars": 5109,
    "preview": "/*\n * Copyright 2016 Vladimir Jovanovic\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may"
  },
  {
    "path": "lemniscate/src/main/java/com/vlad1m1r/lemniscate/roulette/EpitrochoidProgressView.kt",
    "chars": 1733,
    "preview": "/*\n * Copyright 2016 Vladimir Jovanovic\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may"
  },
  {
    "path": "lemniscate/src/main/java/com/vlad1m1r/lemniscate/roulette/HypotrochoidProgressView.kt",
    "chars": 1686,
    "preview": "/*\n * Copyright 2016 Vladimir Jovanovic\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may"
  },
  {
    "path": "lemniscate/src/main/java/com/vlad1m1r/lemniscate/roulette/scribble/RoundScribbleProgressView.kt",
    "chars": 1660,
    "preview": "/*\n * Copyright 2016 Vladimir Jovanovic\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may"
  },
  {
    "path": "lemniscate/src/main/java/com/vlad1m1r/lemniscate/roulette/scribble/ScribbleProgressView.kt",
    "chars": 1666,
    "preview": "/*\n * Copyright 2016 Vladimir Jovanovic\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may"
  },
  {
    "path": "lemniscate/src/main/java/com/vlad1m1r/lemniscate/roulette/settings/RouletteCurveSettings.kt",
    "chars": 1415,
    "preview": "package com.vlad1m1r.lemniscate.roulette.settings\n\nimport android.os.Parcel\nimport android.os.Parcelable\n\nclass Roulette"
  },
  {
    "path": "lemniscate/src/main/java/com/vlad1m1r/lemniscate/utils/CurveUtils.kt",
    "chars": 1019,
    "preview": "/*\n * Copyright 2016 Vladimir Jovanovic\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may"
  },
  {
    "path": "lemniscate/src/main/res/values/attrs.xml",
    "chars": 841,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <declare-styleable name=\"BaseCurveProgressView\">\n        <attr na"
  },
  {
    "path": "lemniscate/src/main/res/values/dimens.xml",
    "chars": 197,
    "preview": "<resources>\n    <dimen name=\"lemniscate_stroke_width\">10dp</dimen>\n    <dimen name=\"lemniscate_preferred_width\">80dp</di"
  },
  {
    "path": "lemniscate/src/test/java/com/vlad1m1r/lemniscate/BernoullisBowProgressViewTest.kt",
    "chars": 1886,
    "preview": "package com.vlad1m1r.lemniscate\n\nimport com.google.common.truth.Truth.assertThat\nimport com.vlad1m1r.lemniscate.testutil"
  },
  {
    "path": "lemniscate/src/test/java/com/vlad1m1r/lemniscate/BernoullisProgressViewTest.kt",
    "chars": 1877,
    "preview": "package com.vlad1m1r.lemniscate\n\nimport com.google.common.truth.Truth.assertThat\nimport org.mockito.kotlin.mock\nimport c"
  },
  {
    "path": "lemniscate/src/test/java/com/vlad1m1r/lemniscate/BernoullisSharpProgressViewTest.kt",
    "chars": 1888,
    "preview": "package com.vlad1m1r.lemniscate\n\nimport com.google.common.truth.Truth.assertThat\nimport com.vlad1m1r.lemniscate.testutil"
  },
  {
    "path": "lemniscate/src/test/java/com/vlad1m1r/lemniscate/GeronosProgressViewTest.kt",
    "chars": 1868,
    "preview": "package com.vlad1m1r.lemniscate\n\nimport com.google.common.truth.Truth.assertThat\nimport org.mockito.kotlin.mock\nimport c"
  },
  {
    "path": "lemniscate/src/test/java/com/vlad1m1r/lemniscate/base/BaseCurvePresenterTest.kt",
    "chars": 4422,
    "preview": "package com.vlad1m1r.lemniscate.base\n\nimport com.google.common.truth.Truth.assertThat\nimport org.mockito.kotlin.*\nimport"
  },
  {
    "path": "lemniscate/src/test/java/com/vlad1m1r/lemniscate/base/BaseCurveProgressViewTest.kt",
    "chars": 2521,
    "preview": "package com.vlad1m1r.lemniscate.base\n\nimport android.view.View\nimport com.google.common.truth.Truth.assertThat\nimport or"
  },
  {
    "path": "lemniscate/src/test/java/com/vlad1m1r/lemniscate/base/BaseProgressViewAttributesTest.kt",
    "chars": 1743,
    "preview": "package com.vlad1m1r.lemniscate.base\n\nimport android.graphics.Color\nimport android.os.Build\nimport androidx.test.platfor"
  },
  {
    "path": "lemniscate/src/test/java/com/vlad1m1r/lemniscate/base/models/DrawStateTest.kt",
    "chars": 6103,
    "preview": "package com.vlad1m1r.lemniscate.base.models\n\nimport android.graphics.Path\nimport com.google.common.truth.Truth.assertTha"
  },
  {
    "path": "lemniscate/src/test/java/com/vlad1m1r/lemniscate/base/models/LineLengthParcelableTest.kt",
    "chars": 953,
    "preview": "package com.vlad1m1r.lemniscate.base.models\n\nimport android.os.Build\nimport android.os.Parcel\nimport com.google.common.t"
  },
  {
    "path": "lemniscate/src/test/java/com/vlad1m1r/lemniscate/base/models/LineLengthTest.kt",
    "chars": 1407,
    "preview": "package com.vlad1m1r.lemniscate.base.models\n\nimport com.google.common.truth.Truth.assertThat\nimport org.junit.Test\n\nclas"
  },
  {
    "path": "lemniscate/src/test/java/com/vlad1m1r/lemniscate/base/models/PointTest.kt",
    "chars": 344,
    "preview": "package com.vlad1m1r.lemniscate.base.models\n\nimport com.google.common.truth.Truth.assertThat\nimport org.junit.Test\n\nclas"
  },
  {
    "path": "lemniscate/src/test/java/com/vlad1m1r/lemniscate/base/models/PointsTest.kt",
    "chars": 688,
    "preview": "package com.vlad1m1r.lemniscate.base.models\n\nimport com.google.common.truth.Truth.assertThat\nimport org.junit.Test\n\nclas"
  },
  {
    "path": "lemniscate/src/test/java/com/vlad1m1r/lemniscate/base/settings/AnimationSettingsParcelableTest.kt",
    "chars": 933,
    "preview": "package com.vlad1m1r.lemniscate.base.settings\n\nimport android.os.Build\nimport android.os.Parcel\nimport com.google.common"
  },
  {
    "path": "lemniscate/src/test/java/com/vlad1m1r/lemniscate/base/settings/CurveSettingsParcelableTest.kt",
    "chars": 1143,
    "preview": "package com.vlad1m1r.lemniscate.base.settings\n\nimport android.os.Build\nimport android.os.Parcel\nimport com.vlad1m1r.lemn"
  },
  {
    "path": "lemniscate/src/test/java/com/vlad1m1r/lemniscate/base/settings/CurveSettingsTest.kt",
    "chars": 1210,
    "preview": "package com.vlad1m1r.lemniscate.base.settings\n\nimport android.graphics.Paint\nimport com.google.common.truth.Truth.assert"
  },
  {
    "path": "lemniscate/src/test/java/com/vlad1m1r/lemniscate/funny/CannabisProgressViewTest.kt",
    "chars": 2464,
    "preview": "package com.vlad1m1r.lemniscate.funny\n\nimport android.os.Build\nimport androidx.test.platform.app.InstrumentationRegistry"
  },
  {
    "path": "lemniscate/src/test/java/com/vlad1m1r/lemniscate/funny/HeartProgressViewTest.kt",
    "chars": 2449,
    "preview": "package com.vlad1m1r.lemniscate.funny\n\nimport android.os.Build\nimport androidx.test.platform.app.InstrumentationRegistry"
  },
  {
    "path": "lemniscate/src/test/java/com/vlad1m1r/lemniscate/other/XProgressViewTest.kt",
    "chars": 2417,
    "preview": "package com.vlad1m1r.lemniscate.other\n\nimport android.os.Build\nimport androidx.test.platform.app.InstrumentationRegistry"
  },
  {
    "path": "lemniscate/src/test/java/com/vlad1m1r/lemniscate/roulette/BaseRouletteProgressViewAttributesTest.kt",
    "chars": 1317,
    "preview": "package com.vlad1m1r.lemniscate.roulette\n\nimport android.os.Build\nimport androidx.test.platform.app.InstrumentationRegis"
  },
  {
    "path": "lemniscate/src/test/java/com/vlad1m1r/lemniscate/roulette/EpitrochoidProgressViewTest.kt",
    "chars": 2385,
    "preview": "package com.vlad1m1r.lemniscate.roulette\n\nimport android.os.Build\nimport androidx.test.platform.app.InstrumentationRegis"
  },
  {
    "path": "lemniscate/src/test/java/com/vlad1m1r/lemniscate/roulette/HypotrochoidProgressViewTest.kt",
    "chars": 2390,
    "preview": "package com.vlad1m1r.lemniscate.roulette\n\nimport android.os.Build\nimport androidx.test.platform.app.InstrumentationRegis"
  },
  {
    "path": "lemniscate/src/test/java/com/vlad1m1r/lemniscate/roulette/scribble/RoundScribbleProgressViewTest.kt",
    "chars": 2397,
    "preview": "package com.vlad1m1r.lemniscate.roulette.scribble\n\nimport android.os.Build\nimport androidx.test.platform.app.Instrumenta"
  },
  {
    "path": "lemniscate/src/test/java/com/vlad1m1r/lemniscate/roulette/scribble/ScribbleProgressViewTest.kt",
    "chars": 2458,
    "preview": "package com.vlad1m1r.lemniscate.roulette.scribble\n\nimport android.os.Build\nimport androidx.test.platform.app.Instrumenta"
  },
  {
    "path": "lemniscate/src/test/java/com/vlad1m1r/lemniscate/roulette/settings/RouletteCurveSettingsParcelableTest.kt",
    "chars": 1172,
    "preview": "package com.vlad1m1r.lemniscate.roulette.settings\n\nimport android.os.Build\nimport android.os.Parcel\nimport com.google.co"
  },
  {
    "path": "lemniscate/src/test/java/com/vlad1m1r/lemniscate/testutils/CurveTestUtils.kt",
    "chars": 1124,
    "preview": "package com.vlad1m1r.lemniscate.testutils\n\nimport com.google.common.truth.Truth\nimport org.mockito.kotlin.*\nimport com.v"
  },
  {
    "path": "lemniscate/src/test/java/com/vlad1m1r/lemniscate/testutils/EqualUtils.kt",
    "chars": 1440,
    "preview": "package com.vlad1m1r.lemniscate.testutils\n\nimport com.vlad1m1r.lemniscate.base.models.LineLength\nimport com.vlad1m1r.lem"
  },
  {
    "path": "lemniscate/src/test/java/com/vlad1m1r/lemniscate/testutils/TestConstants.kt",
    "chars": 104,
    "preview": "package com.vlad1m1r.lemniscate.testutils\n\nobject TestConstants {\n    const val DELTA: Float = 0.001f\n}\n"
  },
  {
    "path": "lemniscate/src/test/java/com/vlad1m1r/lemniscate/testutils/TestLayoutInflater.kt",
    "chars": 792,
    "preview": "package com.vlad1m1r.lemniscate.testutils\n\nimport android.content.Context\nimport android.view.LayoutInflater\nimport andr"
  },
  {
    "path": "lemniscate/src/test/java/com/vlad1m1r/lemniscate/utils/CurveUtilsTest.kt",
    "chars": 642,
    "preview": "package com.vlad1m1r.lemniscate.utils\n\nimport com.google.common.truth.Truth.assertThat\nimport com.vlad1m1r.lemniscate.ba"
  },
  {
    "path": "lemniscate/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker",
    "chars": 17,
    "preview": "mock-maker-inline"
  },
  {
    "path": "remote_data/legal/privacy_policy.html",
    "chars": 6014,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n    <meta charset='utf-8'>\n    <meta name='viewport' content='width=device-width'>\n    <ti"
  },
  {
    "path": "remote_data/legal/terms_and_conditions.html",
    "chars": 5770,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n    <meta charset='utf-8'>\n    <meta name='viewport' content='width=device-width'>\n    <ti"
  },
  {
    "path": "sample/.gitignore",
    "chars": 7,
    "preview": "/build\n"
  },
  {
    "path": "sample/build.gradle",
    "chars": 1053,
    "preview": "apply plugin: 'com.android.application'\napply plugin: 'kotlin-android'\n\nandroid {\n    namespace \"com.vlad1m1r.lemniscate"
  },
  {
    "path": "sample/proguard-rules.pro",
    "chars": 675,
    "preview": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /U"
  },
  {
    "path": "sample/src/main/AndroidManifest.xml",
    "chars": 997,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n          pa"
  },
  {
    "path": "sample/src/main/java/com/vlad1m1r/lemniscate/sample/CurveData.kt",
    "chars": 2607,
    "preview": "/*\n * Copyright 2016 Vladimir Jovanovic\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may"
  },
  {
    "path": "sample/src/main/java/com/vlad1m1r/lemniscate/sample/FragmentCurve.kt",
    "chars": 4822,
    "preview": "/*\n * Copyright 2016 Vladimir Jovanovic\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may"
  },
  {
    "path": "sample/src/main/java/com/vlad1m1r/lemniscate/sample/FragmentSettings.kt",
    "chars": 10362,
    "preview": "/*\n * Copyright 2016 Vladimir Jovanovic\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may"
  },
  {
    "path": "sample/src/main/java/com/vlad1m1r/lemniscate/sample/MainActivity.kt",
    "chars": 3784,
    "preview": "/*\n * Copyright 2016 Vladimir Jovanovic\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may"
  },
  {
    "path": "sample/src/main/java/com/vlad1m1r/lemniscate/sample/PresentationActivity.kt",
    "chars": 1714,
    "preview": "/*\n * Copyright 2016 Vladimir Jovanovic\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may"
  },
  {
    "path": "sample/src/main/res/drawable/indicator.xml",
    "chars": 198,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:sha"
  },
  {
    "path": "sample/src/main/res/drawable/indicator_selected.xml",
    "chars": 203,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:sha"
  },
  {
    "path": "sample/src/main/res/drawable/shadow.xml",
    "chars": 318,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n       android:"
  },
  {
    "path": "sample/src/main/res/drawable-v26/ic_launcher_background.xml",
    "chars": 340,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n    android:height=\"108dp\"\n    android:width=\"108dp\"\n    android:viewport"
  },
  {
    "path": "sample/src/main/res/drawable-v26/ic_launcher_foreground.xml",
    "chars": 3684,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:width=\"108dp\"\n        android:height="
  },
  {
    "path": "sample/src/main/res/layout/activity_main.xml",
    "chars": 1848,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmln"
  },
  {
    "path": "sample/src/main/res/layout/activity_presentation.xml",
    "chars": 5843,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    x"
  },
  {
    "path": "sample/src/main/res/layout/fragment_curve.xml",
    "chars": 998,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    a"
  },
  {
    "path": "sample/src/main/res/layout/fragment_settings.xml",
    "chars": 13707,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ScrollView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:"
  },
  {
    "path": "sample/src/main/res/layout/toolbar.xml",
    "chars": 516,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.appcompat.widget.Toolbar\n    xmlns:android=\"http://schemas.android.com/"
  },
  {
    "path": "sample/src/main/res/layout-land/activity_main.xml",
    "chars": 1836,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmln"
  },
  {
    "path": "sample/src/main/res/layout-land/activity_presentation.xml",
    "chars": 5845,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    x"
  },
  {
    "path": "sample/src/main/res/menu/menu_main_activity.xml",
    "chars": 719,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:ap"
  },
  {
    "path": "sample/src/main/res/mipmap-anydpi-v26/ic_launcher.xml",
    "chars": 270,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <b"
  },
  {
    "path": "sample/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml",
    "chars": 270,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <b"
  },
  {
    "path": "sample/src/main/res/values/colors.xml",
    "chars": 796,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"color_primary\">#3F51B5</color>\n    <color name=\"colo"
  },
  {
    "path": "sample/src/main/res/values/constants.xml",
    "chars": 144,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"url_github\">https://github.com/vlad1m1r990/Lemnisca"
  },
  {
    "path": "sample/src/main/res/values/dimens.xml",
    "chars": 520,
    "preview": "<resources>\n    <!-- Default screen margins, per the Android Design guidelines. -->\n    <dimen name=\"activity_horizontal"
  },
  {
    "path": "sample/src/main/res/values/strings.xml",
    "chars": 1238,
    "preview": "<resources>\n    <string name=\"app_name\">Lemniscate Library</string>\n\n    <string name=\"stroke_width\">Stroke width</strin"
  },
  {
    "path": "sample/src/main/res/values/styles.xml",
    "chars": 385,
    "preview": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.AppCompat.Light.NoActionBar\">"
  },
  {
    "path": "sample/src/main/res/values-w820dp/dimens.xml",
    "chars": 358,
    "preview": "<resources>\n    <!-- Example customization of dimensions originally defined in res/values/dimens.xml\n         (such as s"
  },
  {
    "path": "settings.gradle",
    "chars": 33,
    "preview": "include ':sample', ':lemniscate'\n"
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the vlad1m1r990/Lemniscate GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 106 files (209.9 KB), approximately 57.3k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!