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. Get it on Google Play 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 ``` ###### 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 ================================================ ================================================ 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 { override fun createFromParcel(source: Parcel): BaseCurveSavedState { return BaseCurveSavedState(source) } override fun newArray(size: Int): Array { 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, 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 { override fun createFromParcel(parcel: Parcel): LineLength { return LineLength(parcel) } override fun newArray(size: Int): Array { 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() val isEmpty: Boolean get() = points.isEmpty() fun getPoints(): List { 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 { override fun createFromParcel(parcel: Parcel): AnimationSettings { return AnimationSettings(parcel) } override fun newArray(size: Int): Array { 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 { override fun createFromParcel(source: Parcel): CurveSettings { return CurveSettings(source) } override fun newArray(size: Int): Array { 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 { override fun createFromParcel(source: Parcel): RouletteCurveSavedState { return RouletteCurveSavedState(source) } override fun newArray(size: Int): Array { 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 { override fun createFromParcel(parcel: Parcel): RouletteCurveSettings { return RouletteCurveSettings(parcel) } override fun newArray(size: Int): Array { 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 ================================================ ================================================ FILE: lemniscate/src/main/res/values/dimens.xml ================================================ 10dp 80dp 80dp ================================================ 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() @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() @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() @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() @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() private val curveSettings = mock() private val viewSize = mock() private val animationSettings = mock() private val drawState = mock() private val points = mock() 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() @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() 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() val viewSize = mock() 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() val viewSize = mock() 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() val viewSize = mock() 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() @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() @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() @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() @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() @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() @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() @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() @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 ================================================ Privacy Policy Privacy Policy

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.

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.

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.

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.

Information Collection and Use

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.

Log Data

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.

Cookies

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.

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.

Service Providers

I may employ third-party companies and individuals due to the following reasons:

  • To facilitate our Service;
  • To provide the Service on our behalf;
  • To perform Service-related services; or
  • To assist us in analyzing how our Service is used.

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.

Security

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.

Links to Other Sites

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.

Children’s Privacy

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).

Changes to This Privacy Policy

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.

This policy is effective as of 2022-06-18

Contact Us

If you have any questions or suggestions about my Privacy Policy, do not hesitate to contact me at write@vladimirj.dev.

This privacy policy page was created at privacypolicytemplate.net and modified/generated by App Privacy Policy Generator

================================================ FILE: remote_data/legal/terms_and_conditions.html ================================================ Terms & Conditions Terms & Conditions

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.

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.

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.

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.

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.

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.

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.

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.

Changes to This Terms and Conditions

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.

These terms and conditions are effective as of 2022-06-18

Contact Us

If you have any questions or suggestions about my Terms and Conditions, do not hesitate to contact me at write@vladimirj.dev.

This Terms and Conditions page was generated by App Privacy Policy Generator

================================================ 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 ================================================ ================================================ 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 { override fun createFromParcel(parcel: Parcel): CurveData { return CurveData(parcel) } override fun newArray(size: Int): Array { 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(R.id.indicator) pager.adapter = pagerAdapter indicator.setViewPager(pager) val rootView = findViewById(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(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 ================================================ ================================================ FILE: sample/src/main/res/drawable/indicator_selected.xml ================================================ ================================================ FILE: sample/src/main/res/drawable/shadow.xml ================================================ ================================================ FILE: sample/src/main/res/drawable-v26/ic_launcher_background.xml ================================================ ================================================ FILE: sample/src/main/res/drawable-v26/ic_launcher_foreground.xml ================================================ ================================================ FILE: sample/src/main/res/layout/activity_main.xml ================================================ ================================================ FILE: sample/src/main/res/layout/activity_presentation.xml ================================================ ================================================ FILE: sample/src/main/res/layout/fragment_curve.xml ================================================ ================================================ FILE: sample/src/main/res/layout/fragment_settings.xml ================================================ ================================================ FILE: sample/src/main/res/layout/toolbar.xml ================================================ ================================================ FILE: sample/src/main/res/layout-land/activity_main.xml ================================================ ================================================ FILE: sample/src/main/res/layout-land/activity_presentation.xml ================================================ ================================================ FILE: sample/src/main/res/menu/menu_main_activity.xml ================================================ ================================================ FILE: sample/src/main/res/mipmap-anydpi-v26/ic_launcher.xml ================================================ ================================================ FILE: sample/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml ================================================ ================================================ FILE: sample/src/main/res/values/colors.xml ================================================ #3F51B5 #303F9F #C5CAE9 #03A9F4 #212121 #757575 #BDBDBD #33888888 #00FFFFFF #4A148C #E91E63 #f44336 #4CAF50 #3F51B5 #9C27B0 ================================================ FILE: sample/src/main/res/values/constants.xml ================================================ https://github.com/vlad1m1r990/Lemniscate ================================================ FILE: sample/src/main/res/values/dimens.xml ================================================ 16dp 16dp 2dp 4dp 8dp 16dp 24dp 32dp 36dp ================================================ FILE: sample/src/main/res/values/strings.xml ================================================ Lemniscate Library Stroke width Has a hole? Is line length changeable? Line length Max length of line Min length of line Size multiplier Duration Precision Radius of fixed circle Radius of moving circle Distance from center of moving circle Number of cycles Github Open presentation screen Presentation %d%% %d ms %d points Stroke color ================================================ FILE: sample/src/main/res/values/styles.xml ================================================ ================================================ FILE: sample/src/main/res/values-w820dp/dimens.xml ================================================ 64dp ================================================ FILE: settings.gradle ================================================ include ':sample', ':lemniscate'