Compare commits
79 Commits
v2.0.2
...
feature/sy
| Author | SHA1 | Date | |
|---|---|---|---|
| e8a4a9740e | |||
| f15c660d33 | |||
| 1866743c47 | |||
| 6b9a7917b4 | |||
| 2a5725f382 | |||
| edeabb6ee3 | |||
| 24a9fbe414 | |||
| e5b8c4c3c4 | |||
|
|
a781a1f947 | ||
|
|
13e57b5026 | ||
|
|
f8c7abfff4 | ||
|
|
7fe3ce970c | ||
| 9c395243f4 | |||
|
|
b9eb244b0b | ||
|
|
420a99f1cf | ||
| e756a639ae | |||
|
|
91ff5f7a0c | ||
|
|
67b55a4ecf | ||
|
|
a5ae2eaa63 | ||
|
|
0bc2a8b6d4 | ||
|
|
95a1786c4a | ||
|
|
caa1c9d72e | ||
|
|
a7afe0b309 | ||
|
|
9c03174eef | ||
|
|
da02926fa6 | ||
|
|
250dabfe58 | ||
|
|
0bba3b76bc | ||
|
|
c2479278ba | ||
|
|
499a403a06 | ||
|
|
af5d622339 | ||
|
|
5eeb54bc47 | ||
|
|
e09e899aad | ||
| 69f0fc6c3a | |||
| 705dfb9cbd | |||
| d0ef749f19 | |||
| b54711243f | |||
| 37f03aca37 | |||
|
|
271de59a94 | ||
|
|
0ab55f6f5a | ||
|
|
43921721d4 | ||
|
|
33c88cded3 | ||
|
|
aecce891ea | ||
|
|
2ea98a7756 | ||
|
|
e667872d83 | ||
|
|
3602a614c4 | ||
|
|
d8c5f4d93c | ||
|
|
c9f4df9dae | ||
|
|
feb384bca6 | ||
|
|
71e9160460 | ||
|
|
64966d3c86 | ||
|
|
4787df4074 | ||
| 92291fd919 | |||
| be51538704 | |||
| 89b24911ba | |||
| 1cf71b3973 | |||
| 2fe3b15806 | |||
| 33468bfc1c | |||
|
|
5908692a5c | ||
|
|
693dce8719 | ||
| b232827dfd | |||
|
|
02f9f411ce | ||
| bbf9da44e1 | |||
|
|
ee896fb4f9 | ||
|
|
4d7d8b6206 | ||
|
|
55b841a8b4 | ||
| 7fac86b617 | |||
|
|
c31d42be2d | ||
| 31c09b9c0b | |||
|
|
db91dce57f | ||
| 12c76245e6 | |||
| 7f1a1add8c | |||
| 59a4d7552c | |||
| 9d7840bdd1 | |||
|
|
32db4e363b | ||
|
|
004bb8d71c | ||
|
|
21a1e88c47 | ||
|
|
86fb718896 | ||
|
|
a4e9b2f874 | ||
|
|
ac924470b8 |
18
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -6,11 +6,16 @@ labels: ''
|
|||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
<!--
|
||||||
|
Please use the template below for your bug report. Issues that do not follow this
|
||||||
|
template, or that do not contain all necessary information (namely, description of
|
||||||
|
the problem, steps to reproduce, phone, phone operating system, and app version)
|
||||||
|
will be closed without further consideration.
|
||||||
|
-->
|
||||||
|
|
||||||
## Pre-submission checklist
|
## Pre-submission checklist
|
||||||
- [ ] I am submitting a bug report, not a feature request.
|
- [ ] I am submitting a bug report, not a feature request.
|
||||||
- [ ] I am running the latest version of Loop Habit Tracker.
|
- [ ] I have searched for similar issues, but did not find any matches.
|
||||||
- [ ] I have have searched for similar issues, but did not find any matches.
|
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
A clear and concise description of what the problem is.
|
A clear and concise description of what the problem is.
|
||||||
@@ -21,10 +26,11 @@ A clear and concise description of what the problem is.
|
|||||||
3. Scroll down to ....
|
3. Scroll down to ....
|
||||||
4. See error
|
4. See error
|
||||||
|
|
||||||
## Screenshots
|
|
||||||
If applicable, add screenshots to help explain your problem.
|
|
||||||
|
|
||||||
## System information
|
## System information
|
||||||
- Phone: [e.g. Google Pixel 4]
|
- Phone: [e.g. Google Pixel 4]
|
||||||
- Phone Operating System: [e.g. Android 10]
|
- Phone Operating System: [e.g. Android 10]
|
||||||
- App version: [e.g. 1.8.9]
|
- App version: [e.g. 2.0.2]
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
|
|||||||
2
.github/dependabot.yml
vendored
@@ -3,4 +3,4 @@ updates:
|
|||||||
- package-ecosystem: "gradle"
|
- package-ecosystem: "gradle"
|
||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "daily"
|
interval: "monthly"
|
||||||
|
|||||||
5
.github/workflows/main.yml
vendored
@@ -55,6 +55,11 @@ jobs:
|
|||||||
name: uhabits-android
|
name: uhabits-android
|
||||||
path: uhabits-android/build/outputs/
|
path: uhabits-android/build/outputs/
|
||||||
|
|
||||||
|
- name: Install flock
|
||||||
|
run: |
|
||||||
|
brew install util-linux
|
||||||
|
echo "/usr/local/opt/util-linux/bin" >> $GITHUB_PATH
|
||||||
|
|
||||||
- name: Run Android Tests
|
- name: Run Android Tests
|
||||||
run: ./build.sh android-tests ${{ matrix.api }}
|
run: ./build.sh android-tests ${{ matrix.api }}
|
||||||
|
|
||||||
|
|||||||
10
CHANGELOG.md
@@ -1,5 +1,15 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [2.0.3] - 2021-08-21
|
||||||
|
### Fixed
|
||||||
|
- Improve automatic checkmarks for monthly habits (@iSoron, #947)
|
||||||
|
- Fix small theme issues (@iSoron)
|
||||||
|
- Fix ANR on some Samsung phones (@iSoron, #962)
|
||||||
|
- Fix dates before the year 2000 (@iSoron, #967)
|
||||||
|
- Fix notification adding checkmarks to the wrong day (@hiqua, #969)
|
||||||
|
- Fix crashes in widgets (@hiqua, @iSoron, #907, #966, #965)
|
||||||
|
- Fix crash when moving habits (@hiqua, #968)
|
||||||
|
|
||||||
## [2.0.2] - 2021-05-23
|
## [2.0.2] - 2021-05-23
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ source.
|
|||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://play.google.com/store/apps/details?id=org.isoron.uhabits&utm_source=global_co&utm_medium=prtnr&utm_content=Mar2515&utm_campaign=PartBadge&pcampaignid=MKT-AC-global-none-all-co-pr-py-PartBadges-Oct1515-1"><img alt="Get it on Google Play" src="https://play.google.com/intl/en_us/badges/images/apps/en-play-badge-border.png" height="75px"/></a>
|
<a href="https://play.google.com/store/apps/details?id=org.isoron.uhabits&utm_source=global_co&utm_medium=prtnr&utm_content=Mar2515&utm_campaign=PartBadge&pcampaignid=MKT-AC-global-none-all-co-pr-py-PartBadges-Oct1515-1"><img alt="Get it on Google Play" src="https://play.google.com/intl/en_us/badges/images/apps/en-play-badge-border.png" height="75px"/></a>
|
||||||
<a href="http://f-droid.org/app/org.isoron.uhabits"><img alt="Get it on F-Droid" src="http://i.imgur.com/baSPE7X.png" height="75px"/></a>
|
<a href="https://f-droid.org/app/org.isoron.uhabits"><img alt="Get it on F-Droid" src="https://i.imgur.com/baSPE7X.png" height="75px"/></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
@@ -109,7 +109,7 @@ contribute, even if you are not a software developer.
|
|||||||
more details.
|
more details.
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License along
|
You should have received a copy of the GNU General Public License along
|
||||||
with this program. If not, see <http://www.gnu.org/licenses/>.
|
with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
[screen1]: screenshots/1.png
|
[screen1]: screenshots/1.png
|
||||||
[screen2]: screenshots/2.png
|
[screen2]: screenshots/2.png
|
||||||
@@ -123,9 +123,9 @@ contribute, even if you are not a software developer.
|
|||||||
[screen4th]: screenshots/4.thumb.png
|
[screen4th]: screenshots/4.thumb.png
|
||||||
[screen5th]: screenshots/5.thumb.png
|
[screen5th]: screenshots/5.thumb.png
|
||||||
[screen6th]: screenshots/6.thumb.png
|
[screen6th]: screenshots/6.thumb.png
|
||||||
[poedit]: http://translate.loophabits.org
|
[poedit]: https://translate.loophabits.org
|
||||||
[playstore]: https://play.google.com/store/apps/details?id=org.isoron.uhabits
|
[playstore]: https://play.google.com/store/apps/details?id=org.isoron.uhabits
|
||||||
[releases]: https://github.com/iSoron/uhabits/releases
|
[releases]: https://github.com/iSoron/uhabits/releases
|
||||||
[fdroid]: http://f-droid.org/app/org.isoron.uhabits
|
[fdroid]: https://f-droid.org/app/org.isoron.uhabits
|
||||||
[build]: https://github.com/iSoron/uhabits/blob/dev/docs/BUILD.md
|
[build]: https://github.com/iSoron/uhabits/blob/dev/docs/BUILD.md
|
||||||
[beta]: https://play.google.com/apps/testing/org.isoron.uhabits
|
[beta]: https://play.google.com/apps/testing/org.isoron.uhabits
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
plugins {
|
plugins {
|
||||||
val kotlinVersion = "1.4.21"
|
val kotlinVersion = "1.5.0"
|
||||||
id("com.android.application") version ("4.1.0") apply (false)
|
id("com.android.application") version ("4.2.0") apply (false)
|
||||||
id("org.jetbrains.kotlin.android") version kotlinVersion apply (false)
|
id("org.jetbrains.kotlin.android") version kotlinVersion apply (false)
|
||||||
id("org.jetbrains.kotlin.kapt") version kotlinVersion apply (false)
|
id("org.jetbrains.kotlin.kapt") version kotlinVersion apply (false)
|
||||||
id("org.jetbrains.kotlin.android.extensions") version kotlinVersion apply (false)
|
id("org.jetbrains.kotlin.android.extensions") version kotlinVersion apply (false)
|
||||||
id("org.jetbrains.kotlin.multiplatform") version kotlinVersion apply (false)
|
id("org.jetbrains.kotlin.multiplatform") version kotlinVersion apply (false)
|
||||||
id("org.jlleitschuh.gradle.ktlint") version "10.0.0"
|
id("org.jlleitschuh.gradle.ktlint") version "10.1.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
apply {
|
apply {
|
||||||
|
|||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
3
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,5 @@
|
|||||||
#Sat Nov 28 09:55:24 CST 2020
|
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-all.zip
|
|
||||||
|
|||||||
2
gradlew
vendored
@@ -82,6 +82,7 @@ esac
|
|||||||
|
|
||||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
# Determine the Java command to use to start the JVM.
|
# Determine the Java command to use to start the JVM.
|
||||||
if [ -n "$JAVA_HOME" ] ; then
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
@@ -129,6 +130,7 @@ fi
|
|||||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
|
|
||||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
|
|
||||||
# We build the pattern for arguments to be converted via cygpath
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
|
|||||||
25
gradlew.bat
vendored
@@ -29,6 +29,9 @@ if "%DIRNAME%" == "" set DIRNAME=.
|
|||||||
set APP_BASE_NAME=%~n0
|
set APP_BASE_NAME=%~n0
|
||||||
set APP_HOME=%DIRNAME%
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||||
|
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||||
|
|
||||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
@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="-Xmx64m" "-Xms64m"
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
@@ -37,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
|||||||
|
|
||||||
set JAVA_EXE=java.exe
|
set JAVA_EXE=java.exe
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if "%ERRORLEVEL%" == "0" goto init
|
if "%ERRORLEVEL%" == "0" goto execute
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
@@ -51,7 +54,7 @@ goto fail
|
|||||||
set JAVA_HOME=%JAVA_HOME:"=%
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
if exist "%JAVA_EXE%" goto init
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
@@ -61,28 +64,14 @@ echo location of your Java installation.
|
|||||||
|
|
||||||
goto fail
|
goto fail
|
||||||
|
|
||||||
:init
|
|
||||||
@rem Get command-line arguments, handling Windows variants
|
|
||||||
|
|
||||||
if not "%OS%" == "Windows_NT" goto win9xME_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=%*
|
|
||||||
|
|
||||||
:execute
|
:execute
|
||||||
@rem Setup the command line
|
@rem Setup the command line
|
||||||
|
|
||||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
@rem Execute Gradle
|
@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%
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||||
|
|
||||||
:end
|
:end
|
||||||
@rem End local scope for the variables with windows NT shell
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
|||||||
@@ -1,350 +1,377 @@
|
|||||||
Name,Languages,"Translated (Words)","Target Words","Approved (Words)",Voted,"""+"" votes received","""-"" votes received","Winning (Words)"
|
Name,Languages,"Translated (Words)","Target Words","Approved (Words)",Voted,"""+"" votes received","""-"" votes received","Winning (Words)",Joined
|
||||||
"Alinson Xavier (iSoron)","Portuguese, Brazilian; Japanese; Spanish; Portuguese; Italian; Chinese Simplified; French; Hungarian; German; Arabic; Hindi; Slovenian; Catalan; Greek; Korean; Bulgarian; Chinese Traditional; Polish; Russian; Serbian (Cyrillic); Turkish; Ukrainian; Czech; Indonesian; Croatian; Danish; Dutch; Romanian; Swedish; Basque; Persian; Vietnamese; Telugu; Finnish; Tamil; Afrikaans; Esperanto; Hebrew",14790,17211,1272,0,1707,70,4259
|
"Alinson Xavier (iSoron)","Portuguese, Brazilian; Japanese; Spanish; Portuguese; Italian; Chinese Simplified; French; Hungarian; German; Arabic; Hindi; Slovenian; Catalan; Greek; Korean; Bulgarian; Chinese Traditional; Polish; Russian; Serbian (Cyrillic); Turkish; Ukrainian; Czech; Indonesian; Croatian; Danish; Dutch; Romanian; Swedish; Basque; Persian; Finnish; Vietnamese; Telugu; Tamil; Afrikaans; Esperanto; Hebrew",14808,17227,1282,0,1779,80,4274,"2016-03-05 18:35:27"
|
||||||
"Slobodan Simić (Слободан Симић) (slsimic)","Serbian (Latin); Serbian (Cyrillic)",2053,1830,2105,12,33,0,1982
|
"Slobodan Simić (Слободан Симић) (slsimic)","Serbian (Latin); Serbian (Cyrillic)",2054,1831,2114,12,33,0,1991,"2021-02-03 14:26:07"
|
||||||
"Oglaigh Rystard (oglaignaheireann)","Ukrainian; Portuguese; Catalan; Greek; Basque; Romanian; Italian",1103,1037,1327,1,13,6,954
|
"Oglaigh Rystard (oglaignaheireann)","Ukrainian; Portuguese; Catalan; Greek; Basque; Romanian; Italian",1103,1037,1327,1,13,6,954,"2017-03-31 09:13:19"
|
||||||
"David (Cliff122)",Swedish,1040,1019,725,6,0,0,700
|
"David (Cliff122)",Swedish,1040,1019,725,6,0,0,700,"2020-01-21 13:56:55"
|
||||||
dukelc,Slovak,919,880,0,0,0,0,0
|
"Omer I.S. (omeritzics)",Hebrew,1000,900,1097,14,1,0,946,"2020-10-11 20:10:51"
|
||||||
KMakoto,"Chinese Traditional",745,1146,949,0,0,0,745
|
dukelc,Slovak,919,880,0,0,0,0,0,"2020-08-27 14:02:41"
|
||||||
"Evren (evrenkiymaz)",Turkish,688,604,0,71,4,0,0
|
"Intan Ayunda (Intan_Ayunda)",Indonesian,800,793,962,0,0,0,711,"2020-10-14 07:51:58"
|
||||||
"David Nos (david.nos)","Catalan; Spanish",667,731,0,0,0,0,0
|
KMakoto,"Chinese Traditional",745,1146,949,0,0,0,745,"2019-10-22 04:19:52"
|
||||||
androide74,Italian,644,659,0,2,0,0,0
|
"Evren (evrenkiymaz)",Turkish,688,604,0,71,5,1,0,"2020-10-04 03:39:16"
|
||||||
Tomairuka,Japanese,633,1636,909,43,0,0,564
|
"David Nos (david.nos)","Catalan; Spanish",667,731,0,0,1,0,0,"2020-01-04 10:15:36"
|
||||||
"Dmitriy Bogdanov (di72nn)",Russian,625,572,1175,0,34,0,519
|
"Antti Kallio (antti.kallio)",Finnish,650,525,0,0,0,0,0,"2021-07-03 05:54:44"
|
||||||
reyhoon,Persian,624,759,0,1,1,0,0
|
androide74,Italian,644,659,0,2,0,0,0,"2020-02-06 15:46:28"
|
||||||
Osoitz,Basque,610,545,0,9,0,0,3
|
Tomairuka,Japanese,633,1636,909,43,0,0,564,"2020-12-12 12:14:22"
|
||||||
fabian.bouchal,German,548,527,0,6,0,3,72
|
"Dmitriy Bogdanov (di72nn)",Russian,625,572,1197,0,36,0,515,"2017-03-31 10:00:48"
|
||||||
boban77,Czech,509,461,0,2,0,0,0
|
reyhoon,Persian,624,759,0,1,3,1,0,"2020-10-01 18:17:23"
|
||||||
"Yoav Argov (YoavArgov)",Hebrew,501,461,0,0,1,8,3
|
Osoitz,Basque,610,545,0,9,0,0,3,"2018-01-23 14:07:47"
|
||||||
REMOVED_USER,Norwegian,501,498,501,0,148,0,501
|
"Saeed Esmaili (saaeed.es20)",Persian,568,774,0,5,4,0,0,"2020-11-26 15:41:15"
|
||||||
"Martim Parente (Sharlimar)",Portuguese,497,534,0,38,0,0,0
|
fabian.bouchal,German,548,527,0,6,0,3,72,"2020-01-07 06:43:37"
|
||||||
"chrrris1987 (Chrrris1987)",Dutch,467,478,0,23,0,0,0
|
boban77,Czech,509,461,0,2,0,0,0,"2020-04-30 13:18:24"
|
||||||
"Huy Ngo (huyngo)",Vietnamese,461,695,0,1,0,0,0
|
"Yoav Argov (YoavArgov)",Hebrew,501,461,0,0,1,8,103,"2017-04-28 07:23:01"
|
||||||
"黄克 (hk13127)","Chinese Simplified",461,765,0,1,0,0,0
|
REMOVED_USER,Norwegian,501,498,501,0,148,0,501,"2017-07-05 19:02:25"
|
||||||
"Arkadiusz Bubak (epitek)",Polish,458,416,29,24,0,3,0
|
"Martim Parente (Sharlimar)",Portuguese,497,534,0,38,0,0,0,"2020-08-26 10:22:11"
|
||||||
marco.baturan,Esperanto,452,452,0,0,0,0,0
|
"chrrris1987 (Chrrris1987)",Dutch,467,478,0,23,0,0,0,"2020-02-03 05:26:04"
|
||||||
"Sief Tarek (sieftarek135)",Arabic,447,455,0,0,0,0,0
|
"Huy Ngo (huyngo)",Vietnamese,461,695,0,1,0,0,0,"2020-01-26 11:58:36"
|
||||||
"Omer I.S. (omeritzics)",Hebrew,428,366,0,14,1,0,0
|
"黄克 (hk13127)","Chinese Simplified",461,765,0,1,0,0,24,"2020-01-17 23:16:03"
|
||||||
"Samuel Guay (SamGuay)",French,426,486,0,6,0,0,0
|
"Arkadiusz Bubak (epitek)",Polish,458,416,29,24,0,3,0,"2020-11-05 05:11:58"
|
||||||
"Diana Karaseva (Sun_Dianka)",Russian,399,373,0,10,1,0,221
|
marco.baturan,Esperanto,452,452,0,0,0,0,0,"2020-06-23 02:49:46"
|
||||||
"Alexander Jansson (dalecarlian)",Swedish,396,406,507,0,0,3,399
|
"Sief Tarek (sieftarek135)",Arabic,447,455,0,0,0,0,0,"2021-02-07 14:35:21"
|
||||||
luiandresgonzalez,Spanish,383,403,0,1,0,0,0
|
"Samuel Guay (SamGuay)",French,426,486,0,6,0,0,0,"2020-06-25 07:14:38"
|
||||||
"Thamara Andrade (tkcandrade)","Portuguese, Brazilian",380,387,0,0,1,0,252
|
"Diana Karaseva (Sun_Dianka)",Russian,399,373,0,10,1,0,209,"2020-01-30 06:40:02"
|
||||||
"Sølv Ræven (soelvraeven)",Danish,370,370,0,0,0,0,0
|
"Alexander Jansson (dalecarlian)",Swedish,396,406,507,0,0,3,399,"2017-06-21 01:37:32"
|
||||||
"Isti (eisti)",Hungarian,367,329,0,0,0,0,0
|
luiandresgonzalez,Spanish,383,403,0,1,28,0,0,"2020-07-11 14:20:44"
|
||||||
"Anh Quân (dangquanuet)",Vietnamese,362,530,0,42,1,0,0
|
"Thamara Andrade (tkcandrade)","Portuguese, Brazilian",380,387,0,0,1,0,252,"2020-01-09 19:35:48"
|
||||||
gapszi,Hungarian,348,301,0,86,0,0,0
|
"Sølv Ræven (soelvraeven)",Danish,370,370,0,0,0,0,0,"2020-11-28 16:46:18"
|
||||||
"Mahdi Nasiri (mahdi.nasiri)",Persian,343,465,0,39,3,0,0
|
"Isti (eisti)",Hungarian,367,329,0,0,0,0,0,"2020-12-03 12:02:51"
|
||||||
Seoyul,Korean,339,825,0,0,27,0,0
|
"Anh Quân (dangquanuet)",Vietnamese,362,530,0,42,2,0,0,"2017-10-29 12:27:44"
|
||||||
"Magimai Prakasam (magimai)",Tamil,336,831,0,12,0,0,0
|
gapszi,Hungarian,348,301,0,86,0,0,0,"2019-04-08 01:35:54"
|
||||||
"Michael Malak (MichaelKMalak)",Arabic,304,271,0,0,0,0,0
|
JY3,"Chinese Simplified",345,585,278,0,1,0,207,"2021-03-08 08:53:35"
|
||||||
"Elina Salminen (salminen.elina.m)",Finnish,297,227,0,0,0,0,0
|
"Mahdi Nasiri (mahdi.nasiri)",Persian,343,465,0,39,3,1,0,"2017-07-14 09:17:25"
|
||||||
ayane.m,Japanese,292,863,0,1,2,0,22
|
Seoyul,Korean,339,825,0,0,27,0,0,"2017-06-21 08:11:39"
|
||||||
"Marius Teufelweich (teufelweich)",German,249,256,606,4,0,0,146
|
"Magimai Prakasam (magimai)",Tamil,336,831,0,12,0,0,0,"2018-04-15 21:16:08"
|
||||||
cobalt59,German,237,234,0,1,23,1,132
|
"Michael Malak (MichaelKMalak)",Arabic,304,271,0,0,0,0,0,"2020-05-26 19:47:58"
|
||||||
"QWERT (lurenjia01)","Chinese Simplified",236,407,0,0,0,0,0
|
"Elina Salminen (salminen.elina.m)",Finnish,297,227,0,0,0,0,0,"2021-01-06 01:28:57"
|
||||||
beriain,Basque,234,235,0,0,2,0,0
|
ayane.m,Japanese,292,863,0,1,3,0,22,"2019-11-20 03:28:26"
|
||||||
"Intan Ayunda (Intan_Ayunda)",Indonesian,228,239,336,0,0,0,135
|
Blinkin,Dutch,284,318,0,1,0,0,0,"2021-06-14 10:30:05"
|
||||||
pnhpnh,Vietnamese,225,343,0,1,3,0,0
|
"Marius Teufelweich (teufelweich)",German,249,256,606,4,2,0,146,"2021-03-12 04:11:38"
|
||||||
"Dika Fitrian Dwi Putra (OsamuDazai)",Indonesian,221,215,0,0,0,0,33
|
cobalt59,German,237,234,0,1,24,1,132,"2017-06-05 05:18:33"
|
||||||
easyrepro,Telugu,214,297,0,0,0,0,0
|
"QWERT (lurenjia01)","Chinese Simplified",236,407,0,0,8,0,19,"2020-03-08 01:46:25"
|
||||||
taras-ko,Ukrainian,211,183,0,1,4,0,19
|
beriain,Basque,234,235,0,0,2,0,0,"2017-03-31 15:42:28"
|
||||||
sojusnik,German,207,200,1,0,30,0,66
|
pnhpnh,Vietnamese,225,343,0,1,3,0,0,"2017-11-27 12:06:07"
|
||||||
axmed99,Ukrainian,203,177,0,40,0,0,53
|
"Dika Fitrian Dwi Putra (OsamuDazai)",Indonesian,221,215,0,0,0,0,48,"2020-07-13 04:40:27"
|
||||||
"Heru Yen (heruyen)",Indonesian,201,201,0,0,0,0,2
|
easyrepro,Telugu,214,297,0,0,0,0,0,"2020-06-12 12:52:10"
|
||||||
Ishmaeel,Turkish,193,174,0,129,6,0,0
|
taras-ko,Ukrainian,211,183,0,1,4,0,19,"2017-10-26 16:52:22"
|
||||||
fbruna17,Danish,181,179,0,1,0,0,0
|
sojusnik,German,207,200,1,0,30,0,66,"2017-04-03 17:11:56"
|
||||||
"Omry Cohen (omrycohen)",Hebrew,175,156,0,1,0,0,0
|
axmed99,Ukrainian,203,177,0,40,0,0,53,"2021-04-01 03:56:20"
|
||||||
Bryanx,Dutch,174,165,0,5,0,0,0
|
"Heru Yen (heruyen)",Indonesian,201,201,0,0,0,0,25,"2020-06-29 18:39:15"
|
||||||
"Pierre GALIEGUE (pierre.galiegue)",French,171,194,0,24,2,0,0
|
"Vijaykumar Borkar (vjkumar)",Hindi,200,364,0,11,0,0,0,"2021-08-06 16:12:15"
|
||||||
DionysosDV,Greek,165,153,0,0,0,0,0
|
Ishmaeel,Turkish,193,174,0,129,6,0,0,"2017-10-04 03:54:00"
|
||||||
"Gustavo Lima (GustavoLima)",Portuguese,158,177,0,1,4,10,0
|
"Aputsiak Niels Janussen (aputtu)",Danish,187,200,0,0,0,0,0,"2019-08-28 05:47:42"
|
||||||
"Aputsiak Niels Janussen (aputtu)",Danish,153,171,0,0,0,0,0
|
_translator,French,181,206,0,11,0,0,0,"2021-07-06 07:54:12"
|
||||||
plitwin,Polish,145,128,0,1,16,0,26
|
fbruna17,Danish,181,179,0,1,0,0,0,"2021-01-28 15:48:47"
|
||||||
"Lương Vĩnh Khang (LuongVinhKhang)",Vietnamese,144,256,0,0,46,1,0
|
"Omry Cohen (omrycohen)",Hebrew,175,156,0,1,0,0,33,"2021-01-18 07:33:23"
|
||||||
azzamsa,Indonesian,142,136,0,48,0,1,50
|
Bryanx,Dutch,174,165,0,5,0,0,0,"2019-11-21 17:08:12"
|
||||||
"Neysa Nasywa (neysanasywa)",Indonesian,140,141,0,0,0,0,35
|
"Pierre GALIEGUE (pierre.galiegue)",French,171,194,0,24,4,0,0,"2020-08-16 11:41:35"
|
||||||
JY3,"Chinese Simplified",136,217,80,0,0,0,80
|
bruhwut,Vietnamese,171,268,0,1,0,0,0,"2021-05-21 07:16:30"
|
||||||
"Mohammed Imthath (mimthath4)",Tamil,136,274,0,0,11,0,0
|
DionysosDV,Greek,165,153,0,0,0,0,0,"2021-02-27 19:05:25"
|
||||||
roptat,French,132,154,0,112,88,5,0
|
"Gustavo Lima (GustavoLima)",Portuguese,158,177,0,1,4,10,0,"2020-08-26 10:35:05"
|
||||||
"Trần Thái (tranhoangthai2001)",Vietnamese,127,186,0,8,1,0,0
|
oscfd,Spanish,155,166,0,1,4,0,0,"2021-05-21 17:58:22"
|
||||||
"OP Smosher (teenwolffan44)","Serbian (Cyrillic)",124,122,0,0,0,0,18
|
plitwin,Polish,145,128,0,1,16,0,26,"2021-01-20 06:18:37"
|
||||||
4001982248998,Esperanto,122,119,0,0,0,0,0
|
"Lương Vĩnh Khang (LuongVinhKhang)",Vietnamese,144,256,0,0,46,1,0,"2017-08-10 10:05:58"
|
||||||
"StoP4Me (Lcqp)",Romanian,121,119,0,0,3,0,0
|
azzamsa,Indonesian,142,136,0,48,0,1,26,"2017-06-16 18:29:45"
|
||||||
Sebastian05067,Spanish,114,133,0,55,18,0,0
|
"yoding (yodingc)","Chinese Traditional; Chinese Simplified",141,271,0,10,0,0,0,"2021-07-07 01:45:45"
|
||||||
"Tanya (MagicUnderHood)",Russian,114,98,0,19,0,0,32
|
"Neysa Nasywa (neysanasywa)",Indonesian,140,141,0,0,0,0,60,"2020-11-18 10:32:10"
|
||||||
REMOVED_USER,Arabic,111,106,0,22,21,2,0
|
"Mohammed Imthath (mimthath4)",Tamil,136,274,0,0,11,0,0,"2018-02-15 22:41:15"
|
||||||
mohmans,Arabic,109,103,0,2,0,0,0
|
roptat,French,132,154,0,112,89,5,0,"2017-04-19 16:54:47"
|
||||||
"Ivan Krušlin (krux3r)",Croatian,108,122,503,0,0,0,108
|
"Trần Thái (tranhoangthai2001)",Vietnamese,127,186,0,8,1,0,0,"2018-03-01 10:51:39"
|
||||||
"Iabin Arteaga (iabin)",Spanish,108,111,0,4,20,0,0
|
"OP Smosher (teenwolffan44)","Serbian (Cyrillic)",124,122,0,0,0,0,18,"2020-11-05 09:41:35"
|
||||||
2kaafone,Finnish,105,90,0,0,0,0,0
|
4001982248998,Esperanto,122,119,0,0,0,0,0,"2017-10-08 04:13:02"
|
||||||
"Adam Jurkiewicz (hasztagg)",Polish,104,105,529,0,0,0,104
|
"StoP4Me (Lcqp)",Romanian,121,119,0,0,3,0,0,"2018-05-06 18:51:59"
|
||||||
"Saeed Esmaili (saaeed.es20)",Persian,104,141,0,0,0,0,0
|
alalloush,Arabic,118,129,0,2,2,0,0,"2017-03-31 12:37:17"
|
||||||
"just a name bro (justanamebr0)",Danish,98,109,0,0,1,0,0
|
"Eilif Adelvice (adelvice)",Spanish,116,126,0,96,0,0,0,"2021-08-05 07:20:21"
|
||||||
"Nam Nguyen (namnl2706)",Vietnamese,95,137,0,0,0,0,0
|
Sebastian05067,Spanish,114,133,0,55,28,0,0,"2017-05-14 00:48:16"
|
||||||
"손유정 (yuwon1213)",Korean,95,57,0,0,0,0,0
|
"Tanya (MagicUnderHood)",Russian,114,98,0,19,0,0,54,"2019-04-21 10:44:03"
|
||||||
ranmagen,Hebrew,91,78,0,0,0,0,0
|
REMOVED_USER,Arabic,111,106,0,22,21,2,0,"2018-01-05 07:01:45"
|
||||||
LoneWanderer,"Chinese Traditional",90,137,0,4,0,0,0
|
mohmans,Arabic,109,103,0,2,0,0,0,"2020-11-23 02:48:00"
|
||||||
"Vo - (voyl)","Chinese Traditional",89,126,0,0,5,0,0
|
"Iabin Arteaga (iabin)",Spanish,108,111,0,4,21,0,0,"2017-08-26 21:08:54"
|
||||||
ikkaz,Indonesian,89,84,0,5,0,0,4
|
"Ivan Krušlin (krux3r)",Croatian,108,122,503,0,0,0,108,"2017-03-31 09:15:24"
|
||||||
"Irene K (Heaun)",Korean,88,75,0,25,0,0,0
|
2kaafone,Finnish,105,90,0,0,0,0,0,"2019-08-12 06:58:48"
|
||||||
"Kumar Anand (kumar0500)",Hindi,87,125,0,0,0,0,0
|
"Adam Jurkiewicz (hasztagg)",Polish,104,105,529,0,0,0,104,"2017-03-31 09:50:51"
|
||||||
Prosta4ok_ua,Ukrainian,87,84,0,1,0,0,17
|
"just a name bro (justanamebr0)",Danish,98,109,0,0,1,0,0,"2019-06-19 11:57:55"
|
||||||
"Ohad Edri (ohadalte)",Hebrew,85,79,0,0,1,3,0
|
"Nam Nguyen (namnl2706)",Vietnamese,95,137,0,0,0,0,0,"2020-08-18 23:02:33"
|
||||||
helectron,Persian,84,102,0,1,0,0,0
|
"손유정 (yuwon1213)",Korean,95,57,0,0,0,0,0,"2021-03-30 05:25:33"
|
||||||
"Radu Cebotari (wildProgrammer)",Romanian,84,92,0,1,0,0,0
|
ranmagen,Hebrew,91,78,0,0,0,0,0,"2021-02-16 05:44:31"
|
||||||
"Israa Z (sosozozo)",Arabic,79,87,0,43,11,0,3
|
LoneWanderer,"Chinese Traditional",90,137,0,4,0,0,0,"2020-09-29 05:24:48"
|
||||||
"Sofia Neves (sofiasonev)","Portuguese, Brazilian",79,84,0,1,0,0,46
|
"Vo - (voyl)","Chinese Traditional",89,126,0,0,5,0,0,"2020-09-02 23:34:42"
|
||||||
Tiralka,French,79,91,0,92,1,0,0
|
ikkaz,Indonesian,89,84,0,5,0,0,4,"2019-09-02 19:58:54"
|
||||||
"Jacob Roller (jdr28070)",Korean,79,61,0,0,0,0,0
|
"Irene K (Heaun)",Korean,88,75,0,25,0,0,0,"2020-03-16 11:31:12"
|
||||||
"Toni Mustonen (toni.mustonen)",Finnish,78,72,0,0,0,0,0
|
Prosta4ok_ua,Ukrainian,87,84,0,1,0,0,17,"2020-01-23 19:43:41"
|
||||||
"Michael (quelbs)",German,76,75,0,1,0,0,39
|
"Kumar Anand (kumar0500)",Hindi,87,125,0,0,0,0,0,"2020-11-07 02:46:09"
|
||||||
"Fauz Aladeem (topfauz)",Arabic,76,77,0,0,0,1,0
|
"Ohad Edri (ohadalte)",Hebrew,85,79,0,0,1,3,18,"2020-07-04 03:42:09"
|
||||||
RealDonald,Dutch,67,69,0,121,10,0,0
|
helectron,Persian,84,102,0,1,0,0,0,"2021-03-02 04:10:51"
|
||||||
sirekanyan,"Armenian; Russian",66,65,0,0,0,0,0
|
"Radu Cebotari (wildProgrammer)",Romanian,84,92,0,1,0,0,0,"2020-02-05 01:20:00"
|
||||||
"Константин К. (kocyak1991)",Russian,64,60,0,0,1,2,0
|
"Sofia Neves (sofiasonev)","Portuguese, Brazilian",79,84,0,1,0,0,46,"2020-03-12 18:19:46"
|
||||||
"Laura Sophie (laurasophie20)",German,62,67,0,4,0,0,0
|
"Jacob Roller (jdr28070)",Korean,79,61,0,0,0,0,0,"2020-01-03 11:36:40"
|
||||||
raden20,Indonesian,61,62,177,0,1,0,64
|
Tiralka,French,79,91,0,92,1,0,0,"2018-02-09 18:39:01"
|
||||||
"Jan Wojtecki (j4nw)",Polish,58,46,0,0,0,0,26
|
"Israa Z (sosozozo)",Arabic,79,87,0,43,12,0,3,"2017-11-27 14:10:50"
|
||||||
"Deepak Bharathi (deepakbharathi1994)",Tamil,56,107,0,0,11,4,0
|
"Toni Mustonen (toni.mustonen)",Finnish,78,72,0,0,0,0,0,"2017-09-02 05:34:12"
|
||||||
"Андрій Козицький (andriikozytskyi1108)",Ukrainian,52,52,0,0,1,0,0
|
"Fauz Aladeem (topfauz)",Arabic,76,77,0,0,0,1,0,"2020-02-21 22:46:12"
|
||||||
REMOVED_USER,Italian,51,52,0,2,0,0,0
|
"Michael (quelbs)",German,76,75,0,1,0,0,39,"2020-08-18 07:39:26"
|
||||||
govindap,"Japanese; Hindi",51,114,0,6,1,0,0
|
"Oliver Gronowski (OliverGronowski)",German,70,69,0,5,0,0,0,"2021-05-14 16:37:10"
|
||||||
"Mare Geldenhuys (mare.geldenhuys)",Afrikaans,50,57,0,0,0,0,0
|
RealDonald,Dutch,67,69,0,121,10,0,0,"2017-06-23 20:10:12"
|
||||||
"Peter Williams (williamspete001)",Japanese,49,133,0,1,0,0,3
|
sirekanyan,"Armenian; Russian",66,65,0,0,0,0,0,"2020-04-18 11:32:52"
|
||||||
"Behnood HRazy (behnoodhr)",Persian,49,70,0,0,0,0,0
|
"Константин К. (kocyak1991)",Russian,64,60,0,0,1,2,0,"2018-06-10 13:39:37"
|
||||||
J3ll3nl,Dutch,48,48,0,0,17,1,3
|
"Laura Sophie (laurasophie20)",German,62,67,0,4,0,0,0,"2018-01-06 14:21:24"
|
||||||
"tat bz (Tat_i)",German,48,56,0,55,0,0,27
|
"Alparslan Sakci (sakci)",Turkish,61,55,0,11,0,0,0,"2021-06-10 11:59:22"
|
||||||
vach,Armenian,47,36,0,0,0,0,0
|
raden20,Indonesian,61,62,177,0,1,0,64,"2017-04-09 22:04:23"
|
||||||
andowero,Czech,47,38,0,0,0,0,0
|
"Jan Wojtecki (j4nw)",Polish,58,46,0,0,0,0,26,"2017-11-02 05:42:14"
|
||||||
"Andrew Firnes (Anechan)",Russian,47,47,0,3,0,0,29
|
"Deepak Bharathi (deepakbharathi1994)",Tamil,56,107,0,0,11,4,0,"2017-09-17 08:00:31"
|
||||||
"Coni Ragni (coni2ragnii)",Spanish,46,46,0,0,0,0,0
|
"Peter Williams (williamspete001)",Japanese,55,147,0,2,0,0,3,"2020-01-01 13:17:44"
|
||||||
"Rahul Shishodia (rahul.shishodia.10)",Hindi,46,85,0,6,0,0,0
|
"Андрій Козицький (andriikozytskyi1108)",Ukrainian,52,52,0,0,1,0,0,"2018-10-22 01:45:08"
|
||||||
Cp0204,"Chinese Simplified",45,72,0,0,0,0,0
|
"Nil riera (nilriera2000)",Catalan,52,61,0,1,0,0,0,"2021-06-22 16:37:44"
|
||||||
"cc (cavaz)",Italian,44,41,0,0,0,0,0
|
REMOVED_USER,Italian,51,52,0,2,0,0,0,"2017-08-21 05:15:31"
|
||||||
"Kamil Dziadek (prso94)",Polish,43,39,0,0,2,0,0
|
govindap,"Japanese; Hindi",51,114,0,6,1,0,0,"2020-06-02 20:15:52"
|
||||||
"Boban Jagertraum (boban40)",Czech,43,38,0,2,1,1,0
|
"Mare Geldenhuys (mare.geldenhuys)",Afrikaans,50,57,0,0,0,0,0,"2017-10-20 18:00:14"
|
||||||
andreea.muscalagiu,Romanian,42,52,0,1,0,0,0
|
"Behnood HRazy (behnoodhr)",Persian,49,70,0,0,0,0,0,"2017-11-25 10:57:21"
|
||||||
"Me Me (gentelwom)",Arabic,42,40,0,0,0,0,0
|
"tat bz (Tat_i)",German,48,56,0,55,0,0,27,"2021-03-26 05:12:54"
|
||||||
"Balázs Keresztury (belidzs)",Hungarian,42,41,501,0,7,0,38
|
J3ll3nl,Dutch,48,48,0,0,17,1,3,"2017-03-31 11:56:09"
|
||||||
MStefanov,Bulgarian,41,55,2,0,2,0,2
|
"Andrew Firnes (Anechan)",Russian,47,47,0,3,0,0,29,"2019-09-18 09:51:59"
|
||||||
"Ali Zali (stm19951995)",Persian,40,60,0,0,0,0,0
|
andowero,Czech,47,38,0,0,0,0,0,"2020-01-20 02:29:01"
|
||||||
"Sofia Veijonen (Suklaa) (sofia.veijonen)",Finnish,40,33,0,0,0,0,0
|
vach,Armenian,47,36,0,0,0,0,0,"2020-04-18 16:53:12"
|
||||||
dusanstrgar,Slovenian,39,41,0,0,0,0,0
|
"Rahul Shishodia (rahul.shishodia.10)",Hindi,46,85,0,6,5,1,0,"2018-12-24 22:18:19"
|
||||||
"Limin Lu (liminlu)","Chinese Simplified",39,79,503,0,0,0,39
|
"Coni Ragni (coni2ragnii)",Spanish,46,46,0,0,0,0,0,"2021-02-28 20:18:37"
|
||||||
Anshoe,Tamil,38,65,0,14,0,0,0
|
Cp0204,"Chinese Simplified",45,72,0,0,0,0,0,"2019-08-20 11:04:27"
|
||||||
anasshm,Arabic,37,36,0,9,0,0,0
|
"cc (cavaz)",Italian,44,41,0,0,0,0,0,"2017-04-01 04:21:08"
|
||||||
hrexen,Armenian,37,37,0,0,0,0,0
|
"Boban Jagertraum (boban40)",Czech,43,38,0,2,1,1,0,"2017-03-31 09:39:16"
|
||||||
REMOVED_USER,Swedish,36,33,0,5,1,0,0
|
"Kamil Dziadek (prso94)",Polish,43,39,0,0,2,0,0,"2020-04-06 17:12:06"
|
||||||
"Abdulrahman (D7M)",Arabic,36,39,0,0,0,0,0
|
"Me Me (gentelwom)",Arabic,42,40,0,0,0,0,0,"2020-11-08 20:44:01"
|
||||||
"長谷川知里 (chase0213)",Japanese,34,138,0,13,0,0,24
|
"Ali Elsheikh (aelsheikh1987)",Arabic,42,41,0,0,0,0,0,"2021-06-16 10:17:26"
|
||||||
"Piotr Łuczyński (peterluczynski)",Polish,33,30,0,6,5,0,2
|
"Balázs Keresztury (belidzs)",Hungarian,42,41,501,0,7,0,38,"2017-04-06 02:40:24"
|
||||||
"milad farahani (miladfarmahini90)",Persian,33,44,0,18,0,0,3
|
andreea.muscalagiu,Romanian,42,52,0,1,0,0,0,"2017-10-22 07:19:49"
|
||||||
"Luis E. Perichon (luisperichon)",Spanish,33,40,0,104,0,0,0
|
"Mateusz Duda (MateuszDuda)",Polish,42,42,0,0,0,0,0,"2021-08-17 11:27:11"
|
||||||
andriikozytskyi2625,Ukrainian,31,23,0,0,0,0,0
|
MStefanov,Bulgarian,41,55,2,0,2,0,2,"2017-03-31 16:09:02"
|
||||||
JoeLi,"Chinese Traditional",31,70,0,12,0,0,24
|
"Sofia Veijonen (Suklaa) (sofia.veijonen)",Finnish,40,33,0,0,0,0,0,"2018-03-07 09:24:22"
|
||||||
Moastafa,Arabic,31,25,0,0,0,0,0
|
"Ali Zali (stm19951995)",Persian,40,60,0,0,0,0,0,"2020-03-23 19:57:26"
|
||||||
REMOVED_USER,Russian,31,30,0,2,4,0,3
|
"Limin Lu (liminlu)","Chinese Simplified",39,79,503,0,0,0,39,"2017-03-31 09:49:35"
|
||||||
"hamza gamal (hamzagamal4444)",Arabic,31,28,0,0,0,0,0
|
dusanstrgar,Slovenian,39,41,0,0,0,0,0,"2017-03-31 10:30:28"
|
||||||
yancyn,"Chinese Simplified",30,40,0,0,0,0,0
|
Anshoe,Tamil,38,65,0,14,0,0,0,"2018-01-02 11:06:52"
|
||||||
"비니몬youtube (khj01025276475)",Korean,29,25,0,0,0,0,0
|
anasshm,Arabic,37,36,0,9,0,0,0,"2019-01-27 04:07:22"
|
||||||
"Ruud Schouten (ruudschouten)",Dutch,29,32,0,41,3,0,0
|
hrexen,Armenian,37,37,0,0,0,0,0,"2020-12-09 02:30:34"
|
||||||
"Guillaume Collic (gcollic)",French,26,28,0,126,11,0,0
|
"Abdulrahman (D7M)",Arabic,36,39,0,0,0,0,0,"2020-01-29 18:55:30"
|
||||||
"Niraj Yadav (neverforgetniraj)",Hindi,26,48,0,0,0,0,0
|
"Maria Chushnyakova (maria.ch)",Russian,36,31,0,3,0,0,0,"2021-08-17 03:23:58"
|
||||||
"Radoslaw Biernacki (radoslaw.biernacki)",Polish,26,24,0,8,0,0,1
|
REMOVED_USER,Swedish,36,33,0,5,1,0,0,"2018-09-29 17:47:33"
|
||||||
"Jonny I (jonny99dj)",Italian,26,26,0,5,0,0,0
|
"長谷川知里 (chase0213)",Japanese,34,138,0,13,0,0,24,"2018-12-14 10:52:44"
|
||||||
"Aaron Dalton (Perlkonig)",French,26,25,0,141,1,0,0
|
"Luis E. Perichon (luisperichon)",Spanish,33,40,0,104,0,0,0,"2017-09-04 13:46:06"
|
||||||
"Eddie (eddieattaboy)","Chinese Traditional",25,34,0,1,0,0,0
|
"Piotr Łuczyński (peterluczynski)",Polish,33,30,0,6,5,0,2,"2020-01-29 07:27:40"
|
||||||
Pan_Filuta,Czech,25,21,0,5,4,0,3
|
"milad farahani (miladfarmahini90)",Persian,33,44,0,18,1,0,3,"2017-08-31 16:09:00"
|
||||||
"eduard83 (barbany.eduard)",Catalan,24,25,0,2,0,0,0
|
andriikozytskyi2625,Ukrainian,31,23,0,0,0,0,0,"2019-07-08 00:16:41"
|
||||||
"Ľuboš Čaky (lubos.caky)",Slovak,23,22,0,0,0,0,0
|
Moastafa,Arabic,31,25,0,0,0,0,0,"2020-07-06 11:37:53"
|
||||||
"Caner Başaran (basarancaner)",Turkish,23,21,0,0,20,0,0
|
"hamza gamal (hamzagamal4444)",Arabic,31,28,0,0,0,0,0,"2020-08-03 15:23:34"
|
||||||
hodanli,Turkish,22,26,0,0,1,0,0
|
REMOVED_USER,Russian,31,30,0,2,4,0,3,"2018-12-03 23:55:47"
|
||||||
"Neeraj Verma (verma.neeraj.in)",Hindi,22,37,0,0,1,0,0
|
JoeLi,"Chinese Traditional",31,70,0,12,0,0,24,"2017-06-25 05:32:48"
|
||||||
"Alcarkse (alexis.brusle)",French,21,25,0,7,11,0,0
|
yancyn,"Chinese Simplified",30,40,0,0,0,0,1,"2020-05-18 20:06:03"
|
||||||
"Shashwat (goforgold)",Hindi,20,33,0,0,0,0,0
|
"비니몬youtube (khj01025276475)",Korean,29,25,0,0,0,0,0,"2020-02-09 20:44:35"
|
||||||
"사자솥 (toke1597)",Korean,19,19,0,0,0,0,0
|
"Ruud Schouten (ruudschouten)",Dutch,29,32,0,41,3,0,0,"2017-07-22 17:49:17"
|
||||||
"İsa Eş (IsaEs)",Turkish,19,17,0,0,6,1,0
|
"Aaron Dalton (Perlkonig)",French,26,25,0,141,1,0,0,"2018-01-14 12:58:19"
|
||||||
sheeCesu,French,19,18,0,48,2,0,0
|
"Niraj Yadav (neverforgetniraj)",Hindi,26,48,0,0,0,0,0,"2017-04-11 02:26:50"
|
||||||
can13,Turkish,19,14,0,8,0,0,0
|
"Guillaume Collic (gcollic)",French,26,28,0,126,11,0,0,"2017-05-05 16:13:00"
|
||||||
"Magdalena Urbańczyk (madziia139)",Polish,19,19,0,0,0,0,0
|
"Radoslaw Biernacki (radoslaw.biernacki)",Polish,26,24,0,8,0,0,1,"2020-12-15 17:55:31"
|
||||||
axikman11111,Uyghur,18,19,0,0,0,0,0
|
"Jonny I (jonny99dj)",Italian,26,26,0,5,0,0,0,"2017-10-07 07:35:34"
|
||||||
Adeline31,French,17,20,0,3,0,0,0
|
"Eddie (eddieattaboy)","Chinese Traditional",25,34,0,1,0,0,0,"2020-11-04 21:48:05"
|
||||||
"Hoon Jung (hooni100)",Korean,17,10,0,0,0,0,0
|
Pan_Filuta,Czech,25,21,0,5,4,0,3,"2017-04-29 12:55:14"
|
||||||
"Ceara Lopez (cealopez)",Spanish,17,18,0,0,5,1,0
|
"eduard83 (barbany.eduard)",Catalan,24,25,0,2,0,0,0,"2019-06-26 14:59:47"
|
||||||
takoyakibento,Korean,17,13,0,3,0,0,0
|
"Ľuboš Čaky (lubos.caky)",Slovak,23,22,0,0,0,0,0,"2019-07-02 16:51:44"
|
||||||
bretzel15,German,16,20,0,0,0,0,0
|
"Caner Başaran (basarancaner)",Turkish,23,21,0,0,21,0,0,"2017-04-09 06:34:59"
|
||||||
engineeringforgood,Russian,16,15,0,0,0,0,0
|
hodanli,Turkish,22,26,0,0,1,0,0,"2017-11-03 14:33:41"
|
||||||
"Şamil Ateşoğlu (m.samilatesoglu)",Turkish,16,22,0,11,6,3,0
|
gnu-ewm,Polish,22,23,0,6,0,0,0,"2021-02-24 03:42:01"
|
||||||
DebatablySane,Bulgarian,16,15,0,48,0,0,0
|
"Neeraj Verma (verma.neeraj.in)",Hindi,22,37,0,0,1,0,0,"2018-07-23 07:16:41"
|
||||||
"Bhava Tharini (bhavidanush)",Tamil,15,37,0,0,0,0,0
|
"Alcarkse (alexis.brusle)",French,21,25,0,7,11,0,0,"2017-08-06 09:32:29"
|
||||||
"Anastasia Borchuk (al2.borchuk)",Russian,14,14,0,0,0,0,0
|
olbotta,Italian,20,25,0,2,0,0,0,"2021-06-06 04:22:55"
|
||||||
"EuiHo Hwang (euiho.hwang)",Korean,14,16,0,0,0,0,0
|
"Shashwat (goforgold)",Hindi,20,33,0,0,0,0,0,"2020-05-17 10:34:42"
|
||||||
iamsurajbobade,Hindi,14,30,0,0,0,0,0
|
"Magdalena Urbańczyk (madziia139)",Polish,19,19,0,0,0,0,0,"2017-10-21 03:01:04"
|
||||||
"Zeynep Esen (nezihaesen50)",Turkish,14,13,0,0,0,0,0
|
sheeCesu,French,19,18,0,48,4,0,0,"2017-12-21 17:01:39"
|
||||||
"Fikret Bilici (fikretbilici)",Turkish,14,13,0,0,0,0,0
|
can13,Turkish,19,14,0,8,0,0,0,"2021-01-03 10:39:03"
|
||||||
"Zeeshan Rabbani (Zeera)",Hindi,14,25,0,0,0,0,0
|
"İsa Eş (IsaEs)",Turkish,19,17,0,0,6,1,0,"2017-06-20 07:30:22"
|
||||||
"Sanji Vinsmock (mukanzhanbolat4)",Russian,14,14,0,0,0,0,0
|
"사자솥 (toke1597)",Korean,19,19,0,0,0,0,0,"2020-02-04 13:36:11"
|
||||||
"Nenad Vukotic (vukotic.nenad)","Serbian (Cyrillic)",13,13,0,1,2,6,0
|
axikman11111,Uyghur,18,19,0,0,0,0,0,"2018-10-13 12:25:31"
|
||||||
"Uwe Mönks (schirinowski)",German,13,12,0,0,0,0,0
|
"Hoon Jung (hooni100)",Korean,17,10,0,0,0,0,0,"2021-01-03 02:26:54"
|
||||||
"Dave (xdave)",Hungarian,13,11,0,0,0,0,0
|
"Ceara Lopez (cealopez)",Spanish,17,18,0,0,5,1,0,"2017-08-22 22:56:13"
|
||||||
soura2,Arabic,12,13,0,0,0,0,0
|
takoyakibento,Korean,17,13,0,3,0,0,0,"2020-08-01 08:44:15"
|
||||||
"shreyas (techiespace)",Hindi,12,20,0,0,0,0,0
|
Adeline31,French,17,20,0,3,0,0,0,"2019-12-06 00:00:11"
|
||||||
"Jo Chuang (josephch405)","Chinese Traditional",11,24,0,0,0,0,11
|
engineeringforgood,Russian,16,15,0,0,0,0,16,"2021-01-22 03:32:35"
|
||||||
Vmrc,French,11,12,0,2,0,0,0
|
bretzel15,German,16,20,0,0,0,0,0,"2020-04-06 02:49:14"
|
||||||
"sathvic k (sathvictripleseven)",Telugu,10,17,0,0,0,0,0
|
"Şamil Ateşoğlu (m.samilatesoglu)",Turkish,16,22,0,11,6,3,0,"2017-07-05 18:37:08"
|
||||||
"Edwin van Rooij (edwinvrooij)",Dutch,10,13,0,17,0,0,0
|
DebatablySane,Bulgarian,16,15,0,48,0,0,0,"2017-07-10 15:13:18"
|
||||||
"Brian Camacho (bmcamacho)",Polish,10,11,0,0,1,0,0
|
"Bhava Tharini (bhavidanush)",Tamil,15,37,0,0,0,0,0,"2019-10-09 05:43:11"
|
||||||
"Ahmed Mosaad (ahmed.mosaad2018)",Arabic,10,12,0,6,0,0,0
|
"Zeynep Esen (nezihaesen50)",Turkish,14,13,0,0,0,0,0,"2020-01-28 07:05:15"
|
||||||
"Mihael Wagner (miha.wagner)",Slovenian,10,9,0,7,0,0,0
|
"Fikret Bilici (fikretbilici)",Turkish,14,13,0,0,0,0,0,"2020-06-21 17:16:11"
|
||||||
"Anonymous edgy nerd (yamentaad)",Arabic,10,13,0,1,0,0,0
|
"EuiHo Hwang (euiho.hwang)",Korean,14,16,0,0,0,0,0,"2020-06-23 02:40:01"
|
||||||
"Martin Vostatek (martinvostatek)",Czech,9,8,0,32,2,0,0
|
"Zeeshan Rabbani (Zeera)",Hindi,14,25,0,0,0,0,0,"2020-09-15 11:32:01"
|
||||||
"Sourire Lucide (sourire_lucide)",Russian,9,10,0,0,1,0,0
|
"Faiz Ahamed (faiznewton)",Tamil,14,31,0,0,0,0,0,"2021-05-06 23:06:46"
|
||||||
"Suhaili Hassan (kucingsyg96)",Indonesian,9,10,0,0,0,0,0
|
"Anastasia Borchuk (al2.borchuk)",Russian,14,14,0,0,0,0,0,"2020-04-14 13:22:49"
|
||||||
"Seweryn Piotrowski (Draxxsx)",Polish,9,10,0,0,19,0,0
|
iamsurajbobade,Hindi,14,30,0,0,0,0,0,"2018-05-21 11:23:27"
|
||||||
Rex123,Persian,8,8,0,0,0,0,0
|
"Sanji Vinsmock (mukanzhanbolat4)",Russian,14,14,0,0,0,0,0,"2020-02-18 12:38:54"
|
||||||
"Konstantin (KZhidovinov)",Russian,7,7,0,0,0,0,0
|
"Maro Chr (caprisunglasses)",Greek,14,17,0,0,0,0,0,"2021-08-17 06:53:33"
|
||||||
pkorove,Greek,7,7,0,0,0,0,0
|
"Nenad Vukotic (vukotic.nenad)","Serbian (Cyrillic)",13,13,0,1,2,6,0,"2019-01-31 14:29:15"
|
||||||
ftfoi,Norwegian,7,6,0,0,0,0,0
|
"Uwe Mönks (schirinowski)",German,13,12,0,0,0,0,0,"2021-02-18 04:00:41"
|
||||||
"Андрій Козицький (andriikozytskyi3807)",Ukrainian,7,12,0,2,0,0,0
|
"Dave (xdave)",Hungarian,13,11,0,0,0,0,0,"2020-03-02 20:56:50"
|
||||||
"Vladimir Pavlychev (KeyJoo)",Russian,7,9,0,0,0,0,0
|
soura2,Arabic,12,13,0,0,0,0,0,"2020-01-13 19:23:47"
|
||||||
"Andrey ZaXeLoN (waragaa)",Russian,7,7,0,8,1,0,0
|
"shreyas (techiespace)",Hindi,12,20,0,0,0,0,0,"2018-06-10 01:14:26"
|
||||||
erfan2927,Persian,6,6,0,0,0,0,0
|
"Sonu Sharma (riteetude)",Hindi,11,23,0,0,0,0,0,"2021-05-30 19:38:00"
|
||||||
dragnus,Arabic,6,6,0,1,1,0,0
|
Vmrc,French,11,12,0,2,0,0,0,"2020-11-02 05:35:06"
|
||||||
"Sam (SorodonSorodon)",German,6,6,0,13,0,0,0
|
"Jo Chuang (josephch405)","Chinese Traditional",11,24,0,0,0,0,11,"2017-06-16 20:21:06"
|
||||||
ChloeLiang,Japanese,6,22,0,0,1,0,3
|
"sathvic k (sathvictripleseven)",Telugu,10,17,0,0,0,0,0,"2020-09-11 08:11:32"
|
||||||
"Burak Ceylan (7burakceylan)",Turkish,6,6,0,0,0,0,0
|
"Brian Camacho (bmcamacho)",Polish,10,11,0,0,1,0,0,"2020-08-03 02:27:28"
|
||||||
"닉닉 (seohu9466)",Korean,6,14,0,13,0,0,0
|
"Anonymous edgy nerd (yamentaad)",Arabic,10,13,0,1,0,0,0,"2018-05-06 09:23:57"
|
||||||
"Matthias Joly (joly.matt12)",French,5,8,0,27,1,0,0
|
"Edwin van Rooij (edwinvrooij)",Dutch,10,13,0,17,0,0,0,"2018-11-05 03:59:10"
|
||||||
"Guerra Ivaneth (rossanaiva-04)",Spanish,5,7,0,0,0,0,0
|
"Mihael Wagner (miha.wagner)",Slovenian,10,9,0,7,0,0,0,"2017-10-18 18:26:29"
|
||||||
"Manuel Tassi (Mannivu)",Italian,5,6,0,0,0,0,0
|
"Ahmed Mosaad (ahmed.mosaad2018)",Arabic,10,12,0,6,0,0,0,"2021-02-03 18:45:43"
|
||||||
"Micaela Pighin (micaelapiighin)",Spanish,5,6,0,1,0,0,0
|
"Suhaili Hassan (kucingsyg96)",Indonesian,9,10,0,0,0,0,0,"2018-06-10 11:55:09"
|
||||||
"Дмитрий Хапенков (d.khapenkov)",Russian,5,5,0,6,4,0,2
|
"Martin Vostatek (martinvostatek)",Czech,9,8,0,32,2,0,0,"2019-01-21 13:52:36"
|
||||||
andriikozytskyi2018,Ukrainian,5,5,0,0,0,0,0
|
"Seweryn Piotrowski (Draxxsx)",Polish,9,10,0,0,19,0,0,"2020-01-02 09:55:48"
|
||||||
"Neko123 (emandic11)","Serbian (Cyrillic)",4,4,0,57,0,0,0
|
"Sourire Lucide (sourire_lucide)",Russian,9,10,0,0,1,0,0,"2018-03-22 01:37:55"
|
||||||
marmo,German,4,4,0,0,0,0,0
|
Rex123,Persian,8,8,0,0,0,0,0,"2017-07-01 00:47:42"
|
||||||
"Eli Besirov (elibesirov07)",Turkish,4,4,0,0,0,0,0
|
"Andrey ZaXeLoN (waragaa)",Russian,7,7,0,8,1,0,0,"2017-09-18 21:37:42"
|
||||||
"Lopo Isaac Fernández (rocapata)",Spanish,4,3,0,0,0,0,0
|
"Konstantin (KZhidovinov)",Russian,7,7,0,0,0,0,0,"2020-01-29 13:35:12"
|
||||||
bziuum,Polish,4,4,0,0,0,0,0
|
"Андрій Козицький (andriikozytskyi3807)",Ukrainian,7,12,0,2,0,0,0,"2020-09-26 20:31:56"
|
||||||
"Thoum Ptrgnt (thomas.petrignet)",French,3,3,0,2,0,3,0
|
ftfoi,Norwegian,7,6,0,0,0,0,0,"2020-04-11 20:42:35"
|
||||||
"Vagner Roberto (vagner.trompete)","Portuguese, Brazilian",3,3,0,0,0,0,0
|
"Vladimir Pavlychev (KeyJoo)",Russian,7,9,0,0,0,0,0,"2017-12-18 02:46:56"
|
||||||
"Unnie Here (Carb)",Hindi,3,8,0,0,0,0,0
|
pkorove,Greek,7,7,0,0,0,0,0,"2020-03-07 11:36:12"
|
||||||
carsten_kafke,German,3,3,0,43,0,0,3
|
erfan2927,Persian,6,6,0,0,0,0,0,"2018-04-09 02:12:44"
|
||||||
Magidxz,Arabic,3,3,0,0,0,0,0
|
"Burak Ceylan (7burakceylan)",Turkish,6,6,0,0,0,0,0,"2018-05-20 17:24:19"
|
||||||
atomjani,Hungarian,3,3,0,0,0,0,0
|
"Sam (SorodonSorodon)",German,6,6,0,13,0,0,0,"2017-04-14 11:09:27"
|
||||||
"Péter Bernát (bernatp)",Hungarian,3,2,0,0,0,0,0
|
"닉닉 (seohu9466)",Korean,6,14,0,13,0,0,0,"2017-10-09 23:08:15"
|
||||||
"Igor Piskun (i_piskun)",Ukrainian,3,3,0,0,0,0,0
|
"Sarita Cajas (sarayanacajas)",Spanish,6,4,0,0,1,0,0,"2021-05-14 14:27:59"
|
||||||
"joabe gabriel (joabegabrielcma1)","Portuguese, Brazilian",3,4,0,0,0,0,0
|
ChloeLiang,Japanese,6,22,0,0,1,0,3,"2017-08-08 05:02:59"
|
||||||
"Oleg Kogut (kogut_oleg)",Ukrainian,3,3,0,0,0,0,0
|
"Manuel Tassi (Mannivu)",Italian,5,6,0,0,0,0,0,"2021-01-03 11:00:33"
|
||||||
"Andrea Bianchi (andreawhite1597)",Italian,3,1,0,1,0,0,0
|
"Tomáš Hrabáček (Hrabyyy)",Czech,5,3,0,0,0,0,0,"2021-05-27 11:58:11"
|
||||||
"Gabriel Cavalcante (gabrielc.alves14)","Portuguese, Brazilian",3,4,0,0,0,0,0
|
"Guerra Ivaneth (rossanaiva-04)",Spanish,5,7,0,0,0,0,0,"2019-02-03 16:48:59"
|
||||||
REMOVED_USER,"Portuguese, Brazilian",3,4,0,0,0,0,0
|
"Дмитрий Хапенков (d.khapenkov)",Russian,5,5,0,6,4,0,2,"2018-01-06 23:00:43"
|
||||||
"Martin Zimdahl (zimdahlmartin)",Swedish,3,2,0,0,1,0,3
|
"Matthias Joly (joly.matt12)",French,5,8,0,27,1,0,0,"2017-08-28 09:53:59"
|
||||||
"Hiohana Rilary (hiohanarilary)","Portuguese, Brazilian",3,4,0,0,0,0,0
|
"Micaela Pighin (micaelapiighin)",Spanish,5,6,0,1,0,0,0,"2019-10-09 23:32:42"
|
||||||
"Sarath S (CyberShark)",Tamil,3,7,0,0,0,0,0
|
andriikozytskyi2018,Ukrainian,5,5,0,0,0,0,0,"2017-09-03 05:24:43"
|
||||||
"Cláudio Bernardo (claudiobernardo.ti)","Portuguese, Brazilian",3,4,0,1,0,0,0
|
marmo,German,4,4,0,0,0,0,0,"2021-01-13 01:16:35"
|
||||||
"hesamiranii (esam.matouri)",Persian,2,2,0,0,0,0,0
|
"Eli Besirov (elibesirov07)",Turkish,4,4,0,0,0,0,0,"2019-03-25 07:12:34"
|
||||||
"조화정 (yunjoo337)",Korean,2,2,0,0,0,0,0
|
"Lopo Isaac Fernández (rocapata)",Spanish,4,3,0,0,0,0,0,"2018-09-20 11:46:22"
|
||||||
"Walid Baazia (walidbaazia2005)",Arabic,2,1,0,0,0,0,0
|
bziuum,Polish,4,4,0,0,0,0,0,"2020-09-01 09:08:01"
|
||||||
LNDDYL,"Chinese Traditional",2,4,0,0,0,0,2
|
"Neko123 (emandic11)","Serbian (Cyrillic)",4,4,0,57,0,0,0,"2021-04-21 15:33:29"
|
||||||
REMOVED_USER,Ukrainian,2,2,0,0,0,0,0
|
Magidxz,Arabic,3,3,0,0,0,0,0,"2021-01-05 05:02:54"
|
||||||
"fatemeh s (fargolseifoori3)",Persian,2,2,0,0,0,0,0
|
"mohammadali barati (mabaraty)",Persian,3,3,0,0,0,0,0,"2021-07-10 05:54:44"
|
||||||
"أم محمد تقي (souadboudia19)",Arabic,2,2,0,0,0,0,0
|
"Sarath S (CyberShark)",Tamil,3,7,0,0,0,0,0,"2020-08-27 22:43:16"
|
||||||
"Alex Stein (diefaust1993)",Russian,2,2,0,4,4,0,2
|
"Unnie Here (Carb)",Hindi,3,8,0,0,0,0,0,"2020-03-18 23:34:35"
|
||||||
"Danial Agh (danialagh)",Persian,2,3,0,0,0,0,0
|
REMOVED_USER,"Portuguese, Brazilian",3,4,0,0,0,0,0,"2018-11-18 09:02:37"
|
||||||
omerfarukbas,Turkish,2,3,0,19,2,0,0
|
"Thoum Ptrgnt (thomas.petrignet)",French,3,3,0,2,0,3,0,"2017-09-23 19:25:52"
|
||||||
"Valerij D (vala.dobler)",German,2,2,0,0,0,0,0
|
"Oleg Kogut (kogut_oleg)",Ukrainian,3,3,0,0,0,0,0,"2018-12-28 14:31:02"
|
||||||
"Naveen jai krishna (njsbpolymer1)",Tamil,2,5,0,0,0,0,0
|
carsten_kafke,German,3,3,0,43,0,0,3,"2017-10-27 13:27:47"
|
||||||
"Balthazar Aubard (Balatzar)",French,2,5,0,0,1,0,0
|
"Vagner Roberto (vagner.trompete)","Portuguese, Brazilian",3,3,0,0,0,0,0,"2017-12-30 17:54:26"
|
||||||
"FAy FAy (fayfayfay52)","Chinese Traditional",2,5,0,0,0,0,0
|
"Igor Piskun (i_piskun)",Ukrainian,3,3,0,0,0,0,0,"2018-01-19 15:20:27"
|
||||||
Soroor_SI,Persian,2,2,0,0,0,0,0
|
"Andrea Bianchi (andreawhite1597)",Italian,3,1,0,1,0,0,0,"2018-01-21 17:45:48"
|
||||||
chavs1997,Russian,2,2,0,9,0,0,0
|
"Cláudio Bernardo (claudiobernardo.ti)","Portuguese, Brazilian",3,4,0,1,0,0,0,"2019-01-08 14:41:10"
|
||||||
amei,"Portuguese, Brazilian",2,2,0,0,0,0,0
|
"Hiohana Rilary (hiohanarilary)","Portuguese, Brazilian",3,4,0,0,0,0,0,"2019-07-31 20:42:20"
|
||||||
"Ilyas Fekhar (il47yas)",Arabic,2,2,0,0,0,0,0
|
"joabe gabriel (joabegabrielcma1)","Portuguese, Brazilian",3,4,0,0,0,0,0,"2018-08-21 09:08:59"
|
||||||
"Ali Zaida (alizaeda92)",Arabic,2,2,0,0,0,0,0
|
"Péter Bernát (bernatp)",Hungarian,3,2,0,0,0,0,0,"2019-11-30 15:50:33"
|
||||||
gnu-ewm,Polish,1,1,0,6,0,0,0
|
"Martin Zimdahl (zimdahlmartin)",Swedish,3,2,0,0,1,0,3,"2018-09-15 04:39:22"
|
||||||
"Luca Gori (grolcu)",Italian,1,2,0,0,0,0,0
|
"Gabriel Cavalcante (gabrielc.alves14)","Portuguese, Brazilian",3,4,0,0,0,0,0,"2018-08-06 22:24:54"
|
||||||
"Alan Jeon (skyisle)",Korean,1,2,0,8,0,0,0
|
atomjani,Hungarian,3,3,0,0,0,0,0,"2019-01-19 00:49:25"
|
||||||
iSoron2,"Portuguese, Brazilian",1,1,0,0,0,0,0
|
"أم محمد تقي (souadboudia19)",Arabic,2,2,0,0,0,0,0,"2020-06-13 15:24:17"
|
||||||
"Anastasiia Bondarenko (nastasya.bondarenko.97)",Russian,1,1,0,0,0,0,0
|
"FAy FAy (fayfayfay52)","Chinese Traditional",2,5,0,0,0,0,0,"2017-10-06 08:53:21"
|
||||||
"Patrick Pimenta (trickap1)","Portuguese, Brazilian",1,1,0,0,0,0,0
|
chavs1997,Russian,2,2,0,9,0,0,0,"2018-05-18 16:58:19"
|
||||||
axd,Spanish,1,1,0,15,0,0,0
|
Soroor_SI,Persian,2,2,0,0,0,0,0,"2018-06-10 06:28:27"
|
||||||
jonesses,German,1,1,0,1,0,0,1
|
"Ilyas Fekhar (il47yas)",Arabic,2,2,0,0,0,0,0,"2018-04-17 22:00:41"
|
||||||
"박찌 (perpact20)",Korean,1,1,0,0,0,0,0
|
"hesamiranii (esam.matouri)",Persian,2,2,0,0,0,0,0,"2018-09-22 16:33:36"
|
||||||
"Kan Black (kanblack.va)",Vietnamese,1,2,0,0,0,1,0
|
"fatemeh s (fargolseifoori3)",Persian,2,2,0,0,0,0,0,"2019-01-31 12:06:57"
|
||||||
REMOVED_USER,Russian,1,2,0,6,1,0,1
|
amei,"Portuguese, Brazilian",2,2,0,0,0,0,0,"2018-04-19 19:42:28"
|
||||||
"Wibi Cahyo (wbcahyoh)",Indonesian,1,3,0,0,0,0,0
|
"Naveen jai krishna (njsbpolymer1)",Tamil,2,5,0,0,0,0,0,"2020-01-10 14:19:41"
|
||||||
"Anton (tT0NG)","Chinese Traditional",1,2,0,0,0,0,1
|
"Danial Agh (danialagh)",Persian,2,3,0,0,0,0,0,"2019-03-30 13:24:16"
|
||||||
"Maria Fefelova (mashafefel)",Russian,1,1,0,0,0,0,0
|
"Walid Baazia (walidbaazia2005)",Arabic,2,1,0,0,0,0,0,"2021-01-27 12:47:34"
|
||||||
Itch,,0,0,0,0,0,0,0
|
"Ali Zaida (alizaeda92)",Arabic,2,2,0,0,0,0,0,"2019-12-01 11:47:00"
|
||||||
"Muhammet Furkan ALMACI (furkan.almaci)",Turkish,0,0,0,1,0,0,0
|
LNDDYL,"Chinese Traditional",2,4,0,0,0,0,2,"2018-04-22 04:00:19"
|
||||||
Sandhu564.,,0,0,0,0,0,0,0
|
"Ño Bí Tã (pt614553)",Arabic,2,8,0,1,0,0,0,"2021-05-22 20:41:01"
|
||||||
"Quentin Hibon (hiq)",,0,0,0,0,0,0,0
|
"Judith Ayala (Azul1612)",Spanish,2,1,0,0,0,1,0,"2021-05-18 17:07:19"
|
||||||
mushin,,0,0,0,0,0,0,0
|
REMOVED_USER,Ukrainian,2,2,0,0,0,0,0,"2017-06-15 12:24:44"
|
||||||
"Balaji Jayaraman (jkbalaji1103)",,0,0,0,0,0,0,0
|
"Valerij D (vala.dobler)",German,2,2,0,0,0,0,0,"2018-09-22 09:38:27"
|
||||||
"Wellington Ribeiro (wellington.rib)",,0,0,0,0,0,0,0
|
"Alex Stein (diefaust1993)",Russian,2,2,0,4,4,0,2,"2017-07-13 06:56:17"
|
||||||
"Javid IRAN (twitteriran98)",Persian,0,0,0,1,0,0,0
|
"조화정 (yunjoo337)",Korean,2,2,0,0,0,0,0,"2019-06-16 22:25:31"
|
||||||
"박인호 (wphestiraid)",Korean,0,0,0,2,0,0,0
|
omerfarukbas,Turkish,2,3,0,19,2,0,0,"2017-08-14 16:10:35"
|
||||||
"Ahmed Nazir (ahmednazir333)",,0,0,0,0,0,0,0
|
"Balthazar Aubard (Balatzar)",French,2,5,0,0,1,0,0,"2017-09-23 01:42:57"
|
||||||
dimateos,,0,0,0,0,0,0,0
|
"Luca Gori (grolcu)",Italian,1,2,0,0,0,0,0,"2020-09-26 23:26:15"
|
||||||
AhmedDz,Arabic,0,0,0,1,0,0,0
|
axd,Spanish,1,1,0,15,0,0,0,"2017-09-12 05:48:51"
|
||||||
"reza golestanzadeh (reza.golestanzadeh)",Persian,0,0,0,1,0,0,0
|
iSoron2,"Portuguese, Brazilian",1,1,0,0,0,0,0,"2017-03-18 17:56:29"
|
||||||
"Petros Bleyan (coolbleyan)",Russian,0,0,0,14,0,0,0
|
REMOVED_USER,Russian,1,2,0,6,1,0,1,"2019-12-26 05:37:01"
|
||||||
Kamalakannan,,0,0,0,0,0,0,0
|
"Wibi Cahyo (wbcahyoh)",Indonesian,1,3,0,0,0,0,0,"2017-12-14 06:35:58"
|
||||||
"Dagna Q (dagnaq)",,0,0,0,0,0,0,0
|
jonesses,German,1,1,0,1,0,0,1,"2021-01-01 08:03:18"
|
||||||
"Thomas Orlita (Thomas995)",Czech,0,0,0,1,0,0,0
|
"Anton (tT0NG)","Chinese Traditional",1,2,0,0,0,0,1,"2017-07-06 14:18:39"
|
||||||
Fazy1380,,0,0,0,0,0,0,0
|
"박찌 (perpact20)",Korean,1,1,0,0,0,0,0,"2018-02-10 10:11:44"
|
||||||
"Rivo Zängov (Eraser)",,0,0,0,0,0,0,0
|
"Alan Jeon (skyisle)",Korean,1,2,0,8,0,0,0,"2018-01-09 10:46:00"
|
||||||
T-v-Gerwen,Dutch,0,0,0,47,0,0,0
|
"Maria Fefelova (mashafefel)",Russian,1,1,0,0,0,0,0,"2019-05-18 02:03:56"
|
||||||
"عبد الناصر سعيد الثبيتي (asaeed)",,0,0,0,0,0,0,0
|
"Anastasiia Bondarenko (nastasya.bondarenko.97)",Russian,1,1,0,0,0,0,0,"2019-06-07 17:43:08"
|
||||||
"Edmunds Edmundam (edmundam)",,0,0,0,0,0,0,0
|
"Kan Black (kanblack.va)",Vietnamese,1,2,0,0,0,1,0,"2019-01-15 03:50:10"
|
||||||
"vi ve (VimalV)",,0,0,0,0,0,0,0
|
"Patrick Pimenta (trickap1)","Portuguese, Brazilian",1,1,0,0,0,0,0,"2018-12-01 14:31:21"
|
||||||
"LeMeD (LeMeS)",French,0,0,0,2,0,0,0
|
"Dagna Q (dagnaq)",,0,0,0,0,0,0,0,"2017-08-06 01:42:52"
|
||||||
"Éjbãss Übbeî (littlebittlebottle)",Norwegian,0,0,0,152,0,0,0
|
Kamalakannan,,0,0,0,0,0,0,0,"2017-05-14 11:40:23"
|
||||||
EmanAmini,,0,0,0,0,0,0,0
|
"Éjbãss Übbeî (littlebittlebottle)",Norwegian,0,0,0,152,0,0,0,"2017-07-05 21:12:02"
|
||||||
AnggaRifandi,,0,0,0,0,0,0,0
|
"Равиль Мифтахов (ravilmif47)",Russian,0,0,0,1,0,0,0,"2019-08-12 21:58:30"
|
||||||
"Mateusz Teteruk (mttet)",Polish,0,0,0,1,0,0,0
|
sanyoniket,,0,0,0,0,0,0,0,"2019-07-23 12:58:40"
|
||||||
"Lori Amico (lavodkaclyde2323)",Italian,0,0,0,1,0,0,0
|
REMOVED_USER,,0,0,0,0,0,0,0,"2020-02-01 03:47:48"
|
||||||
"Florian Stuhlmann (stuhlmann)",German,0,0,0,10,0,0,0
|
"vi ve (VimalV)",,0,0,0,0,0,0,0,"2021-02-08 02:35:45"
|
||||||
"Yasin Okumus (lacivert)",Turkish,0,0,0,1,0,0,0
|
"George Merkulov (george142.emarket)",Russian,0,0,0,11,0,0,0,"2019-06-09 19:47:02"
|
||||||
NairaDNV,Spanish,0,0,0,9,0,0,0
|
"Yasin Okumus (lacivert)",Turkish,0,0,0,1,0,0,0,"2018-02-07 04:13:51"
|
||||||
REMOVED_USER,,0,0,0,0,0,0,0
|
"Petros Bleyan (coolbleyan)",Russian,0,0,0,14,0,0,0,"2017-08-18 18:37:18"
|
||||||
farbod66,Persian,0,0,0,1,0,0,0
|
"LeMeD (LeMeS)",French,0,0,0,2,0,0,0,"2021-02-06 15:35:00"
|
||||||
Raulbertassi,,0,0,0,0,0,0,0
|
ava_rfie,Persian,0,0,0,1,0,0,0,"2019-06-09 16:19:24"
|
||||||
"Равиль Мифтахов (ravilmif47)",Russian,0,0,0,1,0,0,0
|
"Mateusz Teteruk (mttet)",Polish,0,0,0,1,0,0,0,"2021-01-23 13:09:59"
|
||||||
"Pumpith Ungsupanit (pumpithu)",,0,0,0,0,0,0,0
|
EwanB,,0,0,0,0,0,0,0,"2019-11-19 10:04:38"
|
||||||
"Nat Fomicheva (natac)",Russian,0,0,0,3,0,0,0
|
Fazy1380,,0,0,0,0,0,0,0,"2021-04-10 11:02:53"
|
||||||
EwanB,,0,0,0,0,0,0,0
|
"Lori Amico (lavodkaclyde2323)",Italian,0,0,0,1,0,0,0,"2017-04-09 10:08:13"
|
||||||
ava_rfie,Persian,0,0,0,1,0,0,0
|
"Florian Stuhlmann (stuhlmann)",German,0,0,0,10,0,0,0,"2017-04-15 04:04:00"
|
||||||
"George Merkulov (george142.emarket)",Russian,0,0,0,11,0,0,0
|
"عبد الناصر سعيد الثبيتي (asaeed)",,0,0,0,0,0,0,0,"2018-03-13 02:09:35"
|
||||||
REMOVED_USER,,0,0,0,0,0,0,0
|
"Rivo Zängov (Eraser)",,0,0,0,0,0,0,0,"2020-10-13 04:38:26"
|
||||||
sanyoniket,,0,0,0,0,0,0,0
|
Hayder21,,0,0,0,0,0,0,0,"2019-12-31 10:56:24"
|
||||||
"Shuvashish Sahoo (shuvashish76)",,0,0,0,0,0,0,0
|
T-v-Gerwen,Dutch,0,0,0,47,0,0,0,"2018-03-02 10:26:33"
|
||||||
martyaberger,,0,0,0,0,0,0,0
|
"Eduard Boboc (edi.boboc33)",Romanian,0,0,0,4,0,0,0,"2019-12-16 09:08:39"
|
||||||
"Карлен Шаухаев (KarlenShaukhaev)",,0,0,0,0,0,0,0
|
"Samuel Przeździęk (samek22)",Polish,0,0,0,1,0,0,0,"2021-08-01 00:49:01"
|
||||||
"Elmo (oberknecht)",,0,0,0,0,0,0,0
|
"Saiprasath B (Saiprasath)",,0,0,0,0,0,0,0,"2021-07-11 11:10:41"
|
||||||
Irsgram,Russian,0,0,0,1,0,0,0
|
shuvo786,,0,0,0,0,0,0,0,"2019-11-13 00:18:12"
|
||||||
"Matus Zdansky (matuszdansky)",,0,0,0,0,0,0,0
|
"Edmunds Edmundam (edmundam)",,0,0,0,0,0,0,0,"2020-06-01 14:18:18"
|
||||||
shuvo786,,0,0,0,0,0,0,0
|
Itch,,0,0,0,0,0,0,0,"2017-10-16 09:18:42"
|
||||||
"Eduard Boboc (edi.boboc33)",Romanian,0,0,0,4,0,0,0
|
"Manny Farsangy (manifarsangi)",Persian,0,0,0,12,0,0,0,"2021-08-10 05:32:28"
|
||||||
Hayder21,,0,0,0,0,0,0,0
|
"Matus Zdansky (matuszdansky)",,0,0,0,0,0,0,0,"2019-10-20 13:52:24"
|
||||||
AsadullahIlyas,,0,0,0,0,0,0,0
|
"Thomas Orlita (Thomas995)",Czech,0,0,0,1,0,0,0,"2017-12-24 04:08:27"
|
||||||
"Никита Карамов (nikita.karamoff)",Russian,0,0,0,10,0,0,0
|
Irsgram,Russian,0,0,0,1,0,0,0,"2019-09-30 16:42:20"
|
||||||
"Katherine Alexandra Flórez Ramírez (katherine.florez12)",Spanish,0,0,0,46,0,0,0
|
EmanAmini,,0,0,0,0,0,0,0,"2017-03-31 13:27:43"
|
||||||
mdrobulis,,0,0,0,0,0,0,0
|
mushin,,0,0,0,0,0,0,0,"2020-02-02 04:08:05"
|
||||||
"Eliška Roubalová (roubaeli)",Czech,0,0,0,6,0,0,0
|
"Elmo (oberknecht)",,0,0,0,0,0,0,0,"2020-04-16 08:45:50"
|
||||||
droidahmed,Arabic,0,0,0,7,0,0,0
|
AnggaRifandi,,0,0,0,0,0,0,0,"2017-03-31 19:28:35"
|
||||||
"Arjun K. (arjunkdot)",,0,0,0,0,0,0,0
|
"darkkingredian (rediancool)",,0,0,0,0,0,0,0,"2021-07-27 16:04:32"
|
||||||
"Sarah BCNN (fsarahboucenna)",French,0,0,0,16,0,0,0
|
"Sri Harsha Bhogi (sriharshabhogi)",,0,0,0,0,0,0,0,"2018-09-02 05:31:53"
|
||||||
REMOVED_USER,Czech,0,0,0,18,0,0,0
|
"Nat Fomicheva (natac)",Russian,0,0,0,3,0,0,0,"2019-01-25 14:35:02"
|
||||||
BongTran,Vietnamese,0,0,0,2,0,0,0
|
mdrobulis,,0,0,0,0,0,0,0,"2018-05-24 01:40:42"
|
||||||
"Arttu Ylhävuori (arttu.ylhavuori)",,0,0,0,0,0,0,0
|
"Sarah BCNN (fsarahboucenna)",French,0,0,0,16,0,0,0,"2018-02-11 11:07:36"
|
||||||
valney.faria,"Portuguese, Brazilian",0,0,0,1,0,0,0
|
"Arjun K. (arjunkdot)",,0,0,0,0,0,0,0,"2020-09-20 11:16:18"
|
||||||
REMOVED_USER,,0,0,0,0,0,0,0
|
REMOVED_USER,Czech,0,0,0,18,0,0,0,"2018-03-27 06:19:52"
|
||||||
rooban23,,0,0,0,0,0,0,0
|
martyaberger,,0,0,0,0,0,0,0,"2019-01-01 18:48:08"
|
||||||
REMOVED_USER,,0,0,0,0,0,0,0
|
BongTran,Vietnamese,0,0,0,2,0,0,0,"2018-04-24 05:16:07"
|
||||||
"Алтынбек Наурызғали (altinbeknaurizgali)",Russian,0,0,0,1,0,0,0
|
"Arttu Ylhävuori (arttu.ylhavuori)",,0,0,0,0,0,0,0,"2019-07-24 15:03:42"
|
||||||
"Sri Harsha Bhogi (sriharshabhogi)",,0,0,0,0,0,0,0
|
"Никита Карамов (nikita.karamoff)",Russian,0,0,0,10,0,0,0,"2018-10-29 03:57:21"
|
||||||
"Ahnaf Tajwar (atn4404)",,0,0,0,0,0,0,0
|
rooban23,,0,0,0,0,0,0,0,"2020-09-15 11:49:14"
|
||||||
Elham1361,,0,0,0,0,0,0,0
|
"Eliška Roubalová (roubaeli)",Czech,0,0,0,6,0,0,0,"2019-12-31 12:47:29"
|
||||||
dongchen.yue,German,0,0,0,4,0,0,0
|
valney.faria,"Portuguese, Brazilian",0,0,0,1,0,0,0,"2020-02-02 14:45:02"
|
||||||
philfr49,French,0,0,0,2,0,0,0
|
"Алтынбек Наурызғали (altinbeknaurizgali)",Russian,0,0,0,1,0,0,0,"2020-08-12 13:03:49"
|
||||||
|
REMOVED_USER,,0,0,0,0,0,0,0,"2018-10-27 15:34:36"
|
||||||
|
REMOVED_USER,,0,0,0,0,0,0,0,"2018-08-24 00:17:43"
|
||||||
|
Elham1361,,0,0,0,0,0,0,0,"2018-10-27 12:01:06"
|
||||||
|
dongchen.yue,German,0,0,0,4,0,0,0,"2020-09-12 15:05:59"
|
||||||
|
"Ahnaf Tajwar (atn4404)",,0,0,0,0,0,0,0,"2018-10-16 11:13:30"
|
||||||
|
AsadullahIlyas,,0,0,0,0,0,0,0,"2019-01-04 06:14:15"
|
||||||
|
droidahmed,Arabic,0,0,0,7,0,0,0,"2018-01-31 02:18:49"
|
||||||
|
philfr49,French,0,0,0,2,0,0,0,"2018-09-03 14:20:32"
|
||||||
|
"Ahmed Nazir (ahmednazir333)",,0,0,0,0,0,0,0,"2018-05-06 12:10:27"
|
||||||
|
"Balaji Jayaraman (jkbalaji1103)",,0,0,0,0,0,0,0,"2017-10-30 22:12:27"
|
||||||
|
"Wellington Ribeiro (wellington.rib)",,0,0,0,0,0,0,0,"2017-11-16 07:32:25"
|
||||||
|
"Javid IRAN (twitteriran98)",Persian,0,0,0,1,0,0,0,"2017-11-25 16:47:25"
|
||||||
|
"박인호 (wphestiraid)",Korean,0,0,0,2,0,0,0,"2018-01-05 00:33:14"
|
||||||
|
"Pumpith Ungsupanit (pumpithu)",,0,0,0,0,0,0,0,"2019-01-19 23:47:57"
|
||||||
|
Sandhu564.,,0,0,0,0,0,0,0,"2020-12-14 01:27:45"
|
||||||
|
"Quentin Hibon (hiq)",,0,0,0,0,0,0,0,"2021-02-07 16:39:31"
|
||||||
|
AhmedDz,Arabic,0,0,0,1,0,0,0,"2017-12-31 10:12:31"
|
||||||
|
"Shuvashish Sahoo (shuvashish76)",,0,0,0,0,0,0,0,"2020-09-17 09:10:09"
|
||||||
|
REMOVED_USER,,0,0,0,0,0,0,0,"2018-01-05 16:56:12"
|
||||||
|
NairaDNV,Spanish,0,0,0,9,0,0,0,"2018-01-05 19:10:33"
|
||||||
|
Raulbertassi,,0,0,0,0,0,0,0,"2018-01-07 17:23:18"
|
||||||
|
"Карлен Шаухаев (KarlenShaukhaev)",,0,0,0,0,0,0,0,"2020-04-27 08:53:49"
|
||||||
|
dimateos,,0,0,0,0,0,0,0,"2021-01-10 06:29:52"
|
||||||
|
"Katherine Alexandra Flórez Ramírez (katherine.florez12)",Spanish,0,0,0,46,0,0,0,"2018-01-20 02:18:32"
|
||||||
|
"reza golestanzadeh (reza.golestanzadeh)",Persian,0,0,0,1,0,0,0,"2020-10-21 12:07:20"
|
||||||
|
farbod66,Persian,0,0,0,1,0,0,0,"2018-01-20 11:04:23"
|
||||||
|
"Muhammet Furkan ALMACI (furkan.almaci)",Turkish,0,0,0,1,0,0,0,"2017-10-29 13:44:56"
|
||||||
|
|||||||
|
@@ -20,7 +20,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("com.github.triplet.play") version "3.2.0"
|
id("com.github.triplet.play") version "3.5.0"
|
||||||
id("com.android.application")
|
id("com.android.application")
|
||||||
id("org.jetbrains.kotlin.android")
|
id("org.jetbrains.kotlin.android")
|
||||||
id("org.jetbrains.kotlin.kapt")
|
id("org.jetbrains.kotlin.kapt")
|
||||||
@@ -37,8 +37,8 @@ android {
|
|||||||
compileSdkVersion(30)
|
compileSdkVersion(30)
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
versionCode(20002)
|
versionCode(20003)
|
||||||
versionName("2.0.2")
|
versionName("2.0.3")
|
||||||
minSdkVersion(23)
|
minSdkVersion(23)
|
||||||
targetSdkVersion(30)
|
targetSdkVersion(30)
|
||||||
applicationId("org.isoron.uhabits")
|
applicationId("org.isoron.uhabits")
|
||||||
@@ -88,11 +88,11 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
val daggerVersion = "2.34.1"
|
val daggerVersion = "2.38.1"
|
||||||
val kotlinVersion = "1.4.32"
|
val kotlinVersion = "1.5.21"
|
||||||
val kxCoroutinesVersion = "1.4.2"
|
val kxCoroutinesVersion = "1.5.1"
|
||||||
val ktorVersion = "1.5.3"
|
val ktorVersion = "1.6.2"
|
||||||
val espressoVersion = "3.3.0"
|
val espressoVersion = "3.4.0"
|
||||||
|
|
||||||
androidTestImplementation("androidx.test.espresso:espresso-contrib:$espressoVersion")
|
androidTestImplementation("androidx.test.espresso:espresso-contrib:$espressoVersion")
|
||||||
androidTestImplementation("androidx.test.espresso:espresso-core:$espressoVersion")
|
androidTestImplementation("androidx.test.espresso:espresso-core:$espressoVersion")
|
||||||
@@ -101,13 +101,13 @@ dependencies {
|
|||||||
androidTestImplementation("io.ktor:ktor-client-mock:$ktorVersion")
|
androidTestImplementation("io.ktor:ktor-client-mock:$ktorVersion")
|
||||||
androidTestImplementation("io.ktor:ktor-jackson:$ktorVersion")
|
androidTestImplementation("io.ktor:ktor-jackson:$ktorVersion")
|
||||||
androidTestImplementation("androidx.annotation:annotation:1.2.0")
|
androidTestImplementation("androidx.annotation:annotation:1.2.0")
|
||||||
androidTestImplementation("androidx.test.ext:junit:1.1.2")
|
androidTestImplementation("androidx.test.ext:junit:1.1.3")
|
||||||
androidTestImplementation("androidx.test.uiautomator:uiautomator:2.2.0")
|
androidTestImplementation("androidx.test.uiautomator:uiautomator:2.2.0")
|
||||||
androidTestImplementation("androidx.test:rules:1.3.0")
|
androidTestImplementation("androidx.test:rules:1.4.0")
|
||||||
androidTestImplementation("com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0")
|
androidTestImplementation("com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0")
|
||||||
compileOnly("javax.annotation:jsr250-api:1.0")
|
compileOnly("javax.annotation:jsr250-api:1.0")
|
||||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.5")
|
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.5")
|
||||||
implementation("com.github.paolorotolo:appintro:3.4.0")
|
implementation("com.github.paolorotolo:appintro:4.1.0")
|
||||||
implementation("com.google.code.findbugs:jsr305:3.0.2")
|
implementation("com.google.code.findbugs:jsr305:3.0.2")
|
||||||
implementation("com.google.dagger:dagger:$daggerVersion")
|
implementation("com.google.dagger:dagger:$daggerVersion")
|
||||||
implementation("com.google.guava:guava:30.1.1-android")
|
implementation("com.google.guava:guava:30.1.1-android")
|
||||||
@@ -118,11 +118,12 @@ dependencies {
|
|||||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion")
|
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion")
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$kxCoroutinesVersion")
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$kxCoroutinesVersion")
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kxCoroutinesVersion")
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kxCoroutinesVersion")
|
||||||
implementation("androidx.appcompat:appcompat:1.2.0")
|
implementation("androidx.appcompat:appcompat:1.3.1")
|
||||||
implementation("androidx.legacy:legacy-preference-v14:1.0.0")
|
implementation("androidx.legacy:legacy-preference-v14:1.0.0")
|
||||||
implementation("androidx.legacy:legacy-support-v4:1.0.0")
|
implementation("androidx.legacy:legacy-support-v4:1.0.0")
|
||||||
implementation("com.google.android.material:material:1.3.0")
|
implementation("com.google.android.material:material:1.4.0")
|
||||||
implementation("com.opencsv:opencsv:5.4")
|
implementation("com.google.zxing:core:3.4.1")
|
||||||
|
implementation("com.opencsv:opencsv:5.5.1")
|
||||||
implementation(project(":uhabits-core"))
|
implementation(project(":uhabits-core"))
|
||||||
kapt("com.google.dagger:dagger-compiler:$daggerVersion")
|
kapt("com.google.dagger:dagger-compiler:$daggerVersion")
|
||||||
kaptAndroidTest("com.google.dagger:dagger-compiler:$daggerVersion")
|
kaptAndroidTest("com.google.dagger:dagger-compiler:$daggerVersion")
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 8.9 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
@@ -23,10 +23,10 @@ import org.isoron.uhabits.core.models.Entry.Companion.YES_MANUAL
|
|||||||
import org.isoron.uhabits.core.models.Frequency
|
import org.isoron.uhabits.core.models.Frequency
|
||||||
import org.isoron.uhabits.core.models.Frequency.Companion.DAILY
|
import org.isoron.uhabits.core.models.Frequency.Companion.DAILY
|
||||||
import org.isoron.uhabits.core.models.Habit
|
import org.isoron.uhabits.core.models.Habit
|
||||||
import org.isoron.uhabits.core.models.Habit.Companion.AT_LEAST
|
|
||||||
import org.isoron.uhabits.core.models.Habit.Companion.NUMBER_HABIT
|
|
||||||
import org.isoron.uhabits.core.models.HabitList
|
import org.isoron.uhabits.core.models.HabitList
|
||||||
|
import org.isoron.uhabits.core.models.HabitType
|
||||||
import org.isoron.uhabits.core.models.ModelFactory
|
import org.isoron.uhabits.core.models.ModelFactory
|
||||||
|
import org.isoron.uhabits.core.models.NumericalHabitType
|
||||||
import org.isoron.uhabits.core.models.PaletteColor
|
import org.isoron.uhabits.core.models.PaletteColor
|
||||||
import org.isoron.uhabits.core.models.Timestamp
|
import org.isoron.uhabits.core.models.Timestamp
|
||||||
import org.isoron.uhabits.core.utils.DateUtils.Companion.getToday
|
import org.isoron.uhabits.core.utils.DateUtils.Companion.getToday
|
||||||
@@ -102,8 +102,8 @@ class HabitFixtures(private val modelFactory: ModelFactory, private val habitLis
|
|||||||
val habit = modelFactory.buildHabit().apply {
|
val habit = modelFactory.buildHabit().apply {
|
||||||
name = "Read"
|
name = "Read"
|
||||||
question = "How many pages did you walk today?"
|
question = "How many pages did you walk today?"
|
||||||
type = NUMBER_HABIT
|
type = HabitType.NUMERICAL
|
||||||
targetType = AT_LEAST
|
targetType = NumericalHabitType.AT_LEAST
|
||||||
targetValue = 200.0
|
targetValue = 200.0
|
||||||
unit = "pages"
|
unit = "pages"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import androidx.test.filters.MediumTest
|
|||||||
import org.isoron.uhabits.BaseViewTest
|
import org.isoron.uhabits.BaseViewTest
|
||||||
import org.isoron.uhabits.core.models.Habit
|
import org.isoron.uhabits.core.models.Habit
|
||||||
import org.isoron.uhabits.core.ui.screens.habits.show.views.ScoreCardPresenter.Companion.buildState
|
import org.isoron.uhabits.core.ui.screens.habits.show.views.ScoreCardPresenter.Companion.buildState
|
||||||
|
import org.isoron.uhabits.core.ui.views.LightTheme
|
||||||
import org.isoron.uhabits.utils.toFixedAndroidColor
|
import org.isoron.uhabits.utils.toFixedAndroidColor
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@@ -39,7 +40,12 @@ class ScoreChartTest : BaseViewTest() {
|
|||||||
super.setUp()
|
super.setUp()
|
||||||
fixtures.purgeHabits(habitList)
|
fixtures.purgeHabits(habitList)
|
||||||
habit = fixtures.createLongHabit()
|
habit = fixtures.createLongHabit()
|
||||||
val state = buildState(habit, prefs.firstWeekdayInt, 0)
|
val state = buildState(
|
||||||
|
habit = habit,
|
||||||
|
firstWeekday = prefs.firstWeekdayInt,
|
||||||
|
spinnerPosition = 0,
|
||||||
|
theme = LightTheme(),
|
||||||
|
)
|
||||||
view = ScoreChart(targetContext).apply {
|
view = ScoreChart(targetContext).apply {
|
||||||
setScores(state.scores)
|
setScores(state.scores)
|
||||||
setColor(state.color.toFixedAndroidColor())
|
setColor(state.color.toFixedAndroidColor())
|
||||||
@@ -72,7 +78,7 @@ class ScoreChartTest : BaseViewTest() {
|
|||||||
@Test
|
@Test
|
||||||
@Throws(Throwable::class)
|
@Throws(Throwable::class)
|
||||||
fun testRender_withMonthlyBucket() {
|
fun testRender_withMonthlyBucket() {
|
||||||
val (scores, bucketSize) = buildState(habit, prefs.firstWeekdayInt, 2)
|
val (scores, bucketSize) = buildState(habit, prefs.firstWeekdayInt, 2, LightTheme())
|
||||||
view.setScores(scores)
|
view.setScores(scores)
|
||||||
view.setBucketSize(bucketSize)
|
view.setBucketSize(bucketSize)
|
||||||
view.invalidate()
|
view.invalidate()
|
||||||
@@ -89,7 +95,7 @@ class ScoreChartTest : BaseViewTest() {
|
|||||||
@Test
|
@Test
|
||||||
@Throws(Throwable::class)
|
@Throws(Throwable::class)
|
||||||
fun testRender_withYearlyBucket() {
|
fun testRender_withYearlyBucket() {
|
||||||
val state = buildState(habit, prefs.firstWeekdayInt, 4)
|
val state = buildState(habit, prefs.firstWeekdayInt, 4, LightTheme())
|
||||||
view.setScores(state.scores)
|
view.setScores(state.scores)
|
||||||
view.setBucketSize(state.bucketSize)
|
view.setBucketSize(state.bucketSize)
|
||||||
view.invalidate()
|
view.invalidate()
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import androidx.test.filters.MediumTest
|
|||||||
import org.isoron.uhabits.BaseViewTest
|
import org.isoron.uhabits.BaseViewTest
|
||||||
import org.isoron.uhabits.R
|
import org.isoron.uhabits.R
|
||||||
import org.isoron.uhabits.core.ui.screens.habits.show.views.FrequencyCardPresenter
|
import org.isoron.uhabits.core.ui.screens.habits.show.views.FrequencyCardPresenter
|
||||||
|
import org.isoron.uhabits.core.ui.views.LightTheme
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
@@ -43,7 +44,13 @@ class FrequencyCardViewTest : BaseViewTest() {
|
|||||||
.from(targetContext)
|
.from(targetContext)
|
||||||
.inflate(R.layout.show_habit, null)
|
.inflate(R.layout.show_habit, null)
|
||||||
.findViewById<View>(R.id.frequencyCard) as FrequencyCardView
|
.findViewById<View>(R.id.frequencyCard) as FrequencyCardView
|
||||||
view.setState(FrequencyCardPresenter.buildState(habit = habit, firstWeekday = 0))
|
view.setState(
|
||||||
|
FrequencyCardPresenter.buildState(
|
||||||
|
habit = habit,
|
||||||
|
firstWeekday = 0,
|
||||||
|
theme = LightTheme(),
|
||||||
|
)
|
||||||
|
)
|
||||||
measureView(view, 800f, 600f)
|
measureView(view, 800f, 600f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import org.isoron.uhabits.BaseViewTest
|
|||||||
import org.isoron.uhabits.R
|
import org.isoron.uhabits.R
|
||||||
import org.isoron.uhabits.core.models.PaletteColor
|
import org.isoron.uhabits.core.models.PaletteColor
|
||||||
import org.isoron.uhabits.core.ui.screens.habits.show.views.OverviewCardState
|
import org.isoron.uhabits.core.ui.screens.habits.show.views.OverviewCardState
|
||||||
|
import org.isoron.uhabits.core.ui.views.LightTheme
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
@@ -50,6 +51,7 @@ class OverviewCardViewTest : BaseViewTest() {
|
|||||||
scoreYearDiff = 0.74f,
|
scoreYearDiff = 0.74f,
|
||||||
totalCount = 44,
|
totalCount = 44,
|
||||||
color = PaletteColor(7),
|
color = PaletteColor(7),
|
||||||
|
theme = LightTheme(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
measureView(view, 800f, 300f)
|
measureView(view, 800f, 300f)
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import androidx.test.filters.MediumTest
|
|||||||
import org.isoron.uhabits.BaseViewTest
|
import org.isoron.uhabits.BaseViewTest
|
||||||
import org.isoron.uhabits.R
|
import org.isoron.uhabits.R
|
||||||
import org.isoron.uhabits.core.ui.screens.habits.show.views.ScoreCardPresenter.Companion.buildState
|
import org.isoron.uhabits.core.ui.screens.habits.show.views.ScoreCardPresenter.Companion.buildState
|
||||||
|
import org.isoron.uhabits.core.ui.views.LightTheme
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
@@ -43,7 +44,14 @@ class ScoreCardViewTest : BaseViewTest() {
|
|||||||
.from(targetContext)
|
.from(targetContext)
|
||||||
.inflate(R.layout.show_habit, null)
|
.inflate(R.layout.show_habit, null)
|
||||||
.findViewById<View>(R.id.scoreCard) as ScoreCardView
|
.findViewById<View>(R.id.scoreCard) as ScoreCardView
|
||||||
view.setState(buildState(habit = habit, firstWeekday = 0, spinnerPosition = 0))
|
view.setState(
|
||||||
|
buildState(
|
||||||
|
habit = habit,
|
||||||
|
firstWeekday = 0,
|
||||||
|
spinnerPosition = 0,
|
||||||
|
theme = LightTheme(),
|
||||||
|
)
|
||||||
|
)
|
||||||
measureView(view, 800f, 600f)
|
measureView(view, 800f, 600f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import androidx.test.filters.MediumTest
|
|||||||
import org.isoron.uhabits.BaseViewTest
|
import org.isoron.uhabits.BaseViewTest
|
||||||
import org.isoron.uhabits.R
|
import org.isoron.uhabits.R
|
||||||
import org.isoron.uhabits.core.ui.screens.habits.show.views.StreakCardState
|
import org.isoron.uhabits.core.ui.screens.habits.show.views.StreakCardState
|
||||||
|
import org.isoron.uhabits.core.ui.views.LightTheme
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
@@ -47,6 +48,7 @@ class StreakCardViewTest : BaseViewTest() {
|
|||||||
StreakCardState(
|
StreakCardState(
|
||||||
bestStreaks = habit.streaks.getBest(10),
|
bestStreaks = habit.streaks.getBest(10),
|
||||||
color = habit.color,
|
color = habit.color,
|
||||||
|
theme = LightTheme(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
measureView(view, 800f, 600f)
|
measureView(view, 800f, 600f)
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import org.isoron.uhabits.core.models.PaletteColor
|
|||||||
import org.isoron.uhabits.core.models.Reminder
|
import org.isoron.uhabits.core.models.Reminder
|
||||||
import org.isoron.uhabits.core.models.WeekdayList.Companion.EVERY_DAY
|
import org.isoron.uhabits.core.models.WeekdayList.Companion.EVERY_DAY
|
||||||
import org.isoron.uhabits.core.ui.screens.habits.show.views.SubtitleCardState
|
import org.isoron.uhabits.core.ui.screens.habits.show.views.SubtitleCardState
|
||||||
|
import org.isoron.uhabits.core.ui.views.LightTheme
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
@@ -54,6 +55,7 @@ class SubtitleCardViewTest : BaseViewTest() {
|
|||||||
reminder = Reminder(8, 30, EVERY_DAY),
|
reminder = Reminder(8, 30, EVERY_DAY),
|
||||||
unit = "",
|
unit = "",
|
||||||
targetValue = 0.0,
|
targetValue = 0.0,
|
||||||
|
theme = LightTheme(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
measureView(view, 800f, 200f)
|
measureView(view, 800f, 200f)
|
||||||
|
|||||||
@@ -0,0 +1,154 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016-2021 Álinson Santos Xavier <git@axavier.org>
|
||||||
|
*
|
||||||
|
* This file is part of Loop Habit Tracker.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by the
|
||||||
|
* Free Software Foundation, either version 3 of the License, or (at your
|
||||||
|
* option) any later version.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.isoron.uhabits.sync
|
||||||
|
|
||||||
|
import androidx.test.filters.MediumTest
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import io.ktor.client.HttpClient
|
||||||
|
import io.ktor.client.engine.mock.MockEngine
|
||||||
|
import io.ktor.client.engine.mock.MockRequestHandleScope
|
||||||
|
import io.ktor.client.engine.mock.respond
|
||||||
|
import io.ktor.client.engine.mock.respondError
|
||||||
|
import io.ktor.client.engine.mock.respondOk
|
||||||
|
import io.ktor.client.features.json.JsonFeature
|
||||||
|
import io.ktor.client.request.HttpRequestData
|
||||||
|
import io.ktor.client.request.HttpResponseData
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.http.fullPath
|
||||||
|
import io.ktor.http.headersOf
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.isoron.uhabits.BaseAndroidTest
|
||||||
|
import org.isoron.uhabits.core.sync.AbstractSyncServer
|
||||||
|
import org.isoron.uhabits.core.sync.GetDataVersionResponse
|
||||||
|
import org.isoron.uhabits.core.sync.KeyNotFoundException
|
||||||
|
import org.isoron.uhabits.core.sync.RegisterReponse
|
||||||
|
import org.isoron.uhabits.core.sync.ServiceUnavailable
|
||||||
|
import org.isoron.uhabits.core.sync.SyncData
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
@MediumTest
|
||||||
|
class RemoteSyncServerTest : BaseAndroidTest() {
|
||||||
|
|
||||||
|
private val mapper = ObjectMapper()
|
||||||
|
val data = SyncData(1, "Hello world")
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun when_register_succeeds_should_return_key() = runBlocking {
|
||||||
|
val server = server("/register") {
|
||||||
|
respondWithJson(RegisterReponse("ABCDEF"))
|
||||||
|
}
|
||||||
|
assertEquals("ABCDEF", server.register())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = ServiceUnavailable::class)
|
||||||
|
fun when_register_fails_should_raise_correct_exception() = runBlocking {
|
||||||
|
val server = server("/register") {
|
||||||
|
respondError(HttpStatusCode.ServiceUnavailable)
|
||||||
|
}
|
||||||
|
server.register()
|
||||||
|
return@runBlocking
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun when_get_data_version_succeeds_should_return_version() = runBlocking {
|
||||||
|
server("/db/ABC/version") {
|
||||||
|
respondWithJson(GetDataVersionResponse(5))
|
||||||
|
}.apply {
|
||||||
|
assertEquals(5, getDataVersion("ABC"))
|
||||||
|
}
|
||||||
|
return@runBlocking
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = ServiceUnavailable::class)
|
||||||
|
fun when_get_data_version_with_server_error_should_raise_exception() = runBlocking {
|
||||||
|
server("/db/ABC/version") {
|
||||||
|
respondError(HttpStatusCode.InternalServerError)
|
||||||
|
}.apply {
|
||||||
|
getDataVersion("ABC")
|
||||||
|
}
|
||||||
|
return@runBlocking
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = KeyNotFoundException::class)
|
||||||
|
fun when_get_data_version_with_invalid_key_should_raise_exception() = runBlocking {
|
||||||
|
server("/db/ABC/version") {
|
||||||
|
respondError(HttpStatusCode.NotFound)
|
||||||
|
}.apply {
|
||||||
|
getDataVersion("ABC")
|
||||||
|
}
|
||||||
|
return@runBlocking
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun when_get_data_succeeds_should_return_data() = runBlocking {
|
||||||
|
server("/db/ABC") {
|
||||||
|
respondWithJson(data)
|
||||||
|
}.apply {
|
||||||
|
assertEquals(data, getData("ABC"))
|
||||||
|
}
|
||||||
|
return@runBlocking
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = KeyNotFoundException::class)
|
||||||
|
fun when_get_data_with_invalid_key_should_raise_exception() = runBlocking {
|
||||||
|
server("/db/ABC") {
|
||||||
|
respondError(HttpStatusCode.NotFound)
|
||||||
|
}.apply {
|
||||||
|
getData("ABC")
|
||||||
|
}
|
||||||
|
return@runBlocking
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun when_put_succeeds_should_not_raise_exceptions() = runBlocking {
|
||||||
|
server("/db/ABC") {
|
||||||
|
respondOk()
|
||||||
|
}.apply {
|
||||||
|
put("ABC", data)
|
||||||
|
}
|
||||||
|
return@runBlocking
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun server(
|
||||||
|
expectedPath: String,
|
||||||
|
action: MockRequestHandleScope.(HttpRequestData) -> HttpResponseData
|
||||||
|
): AbstractSyncServer {
|
||||||
|
return RemoteSyncServer(
|
||||||
|
httpClient = HttpClient(MockEngine) {
|
||||||
|
install(JsonFeature)
|
||||||
|
engine {
|
||||||
|
addHandler { request ->
|
||||||
|
when (request.url.fullPath) {
|
||||||
|
expectedPath -> action(request)
|
||||||
|
else -> error("unexpected call: ${request.url.fullPath}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
preferences = prefs
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun MockRequestHandleScope.respondWithJson(content: Any) =
|
||||||
|
respond(
|
||||||
|
mapper.writeValueAsBytes(content),
|
||||||
|
headers = headersOf("Content-Type" to listOf("application/json"))
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -22,6 +22,8 @@
|
|||||||
|
|
||||||
<uses-permission android:name="android.permission.VIBRATE" />
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".HabitsApplication"
|
android:name=".HabitsApplication"
|
||||||
@@ -40,6 +42,14 @@
|
|||||||
android:value=".activities.habits.list.ListHabitsActivity" />
|
android:value=".activities.habits.list.ListHabitsActivity" />
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".activities.sync.SyncActivity"
|
||||||
|
android:exported="true">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
|
android:value=".activities.settings.SettingsActivity" />
|
||||||
|
</activity>
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="com.google.android.backup.api_key"
|
android:name="com.google.android.backup.api_key"
|
||||||
android:value="AEdPqrEAAAAI6aeWncbnMNo8E5GWeZ44dlc5cQ7tCROwFhOtiw" />
|
android:value="AEdPqrEAAAAI6aeWncbnMNo8E5GWeZ44dlc5cQ7tCROwFhOtiw" />
|
||||||
@@ -49,6 +59,16 @@
|
|||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:label="@string/main_activity_title"
|
android:label="@string/main_activity_title"
|
||||||
android:launchMode="singleTop">
|
android:launchMode="singleTop">
|
||||||
|
<tools:validation testUrl="https://loophabits.org/sync/123" />
|
||||||
|
<intent-filter android:autoVerify="true">
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data
|
||||||
|
android:scheme="https"
|
||||||
|
android:host="loophabits.org"
|
||||||
|
android:pathPrefix="/sync" />
|
||||||
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity-alias
|
<activity-alias
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ class AndroidCanvas : Canvas {
|
|||||||
var innerDensity = 1.0
|
var innerDensity = 1.0
|
||||||
var innerWidth = 0
|
var innerWidth = 0
|
||||||
var innerHeight = 0
|
var innerHeight = 0
|
||||||
|
var mHeight = 15
|
||||||
|
|
||||||
var paint = Paint().apply {
|
var paint = Paint().apply {
|
||||||
isAntiAlias = true
|
isAntiAlias = true
|
||||||
@@ -64,11 +65,10 @@ class AndroidCanvas : Canvas {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun drawText(text: String, x: Double, y: Double) {
|
override fun drawText(text: String, x: Double, y: Double) {
|
||||||
textPaint.getTextBounds(text, 0, text.length, textBounds)
|
|
||||||
innerCanvas.drawText(
|
innerCanvas.drawText(
|
||||||
text,
|
text,
|
||||||
x.toDp(),
|
x.toDp(),
|
||||||
y.toDp() - textBounds.exactCenterY(),
|
y.toDp() + 0.6f * mHeight,
|
||||||
textPaint,
|
textPaint,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -126,10 +126,17 @@ class AndroidCanvas : Canvas {
|
|||||||
Font.BOLD -> Typeface.DEFAULT_BOLD
|
Font.BOLD -> Typeface.DEFAULT_BOLD
|
||||||
Font.FONT_AWESOME -> getFontAwesome(context)
|
Font.FONT_AWESOME -> getFontAwesome(context)
|
||||||
}
|
}
|
||||||
|
updateMHeight()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setFontSize(size: Double) {
|
override fun setFontSize(size: Double) {
|
||||||
textPaint.textSize = size.toDp()
|
textPaint.textSize = size.toDp()
|
||||||
|
updateMHeight()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateMHeight() {
|
||||||
|
textPaint.getTextBounds("m", 0, 1, textBounds)
|
||||||
|
mHeight = textBounds.height()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setStrokeWidth(size: Double) {
|
override fun setStrokeWidth(size: Double) {
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import org.isoron.uhabits.core.preferences.Preferences
|
|||||||
import org.isoron.uhabits.core.ui.ThemeSwitcher
|
import org.isoron.uhabits.core.ui.ThemeSwitcher
|
||||||
import org.isoron.uhabits.core.ui.views.DarkTheme
|
import org.isoron.uhabits.core.ui.views.DarkTheme
|
||||||
import org.isoron.uhabits.core.ui.views.LightTheme
|
import org.isoron.uhabits.core.ui.views.LightTheme
|
||||||
|
import org.isoron.uhabits.core.ui.views.PureBlackTheme
|
||||||
import org.isoron.uhabits.core.ui.views.Theme
|
import org.isoron.uhabits.core.ui.views.Theme
|
||||||
import org.isoron.uhabits.inject.ActivityContext
|
import org.isoron.uhabits.inject.ActivityContext
|
||||||
import org.isoron.uhabits.inject.ActivityScope
|
import org.isoron.uhabits.inject.ActivityScope
|
||||||
@@ -66,7 +67,7 @@ constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun applyPureBlackTheme() {
|
override fun applyPureBlackTheme() {
|
||||||
currentTheme = DarkTheme()
|
currentTheme = PureBlackTheme()
|
||||||
context.setTheme(R.style.AppBaseThemeDark_PureBlack)
|
context.setTheme(R.style.AppBaseThemeDark_PureBlack)
|
||||||
(context as Activity).window.navigationBarColor =
|
(context as Activity).window.navigationBarColor =
|
||||||
ContextCompat.getColor(context, R.color.black)
|
ContextCompat.getColor(context, R.color.black)
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import org.isoron.uhabits.BuildConfig
|
|||||||
import org.isoron.uhabits.R
|
import org.isoron.uhabits.R
|
||||||
import org.isoron.uhabits.core.models.PaletteColor
|
import org.isoron.uhabits.core.models.PaletteColor
|
||||||
import org.isoron.uhabits.databinding.AboutBinding
|
import org.isoron.uhabits.databinding.AboutBinding
|
||||||
|
import org.isoron.uhabits.utils.currentTheme
|
||||||
import org.isoron.uhabits.utils.setupToolbar
|
import org.isoron.uhabits.utils.setupToolbar
|
||||||
|
|
||||||
@SuppressLint("ViewConstructor")
|
@SuppressLint("ViewConstructor")
|
||||||
@@ -41,7 +42,8 @@ class AboutView(
|
|||||||
setupToolbar(
|
setupToolbar(
|
||||||
toolbar = binding.toolbar,
|
toolbar = binding.toolbar,
|
||||||
color = PaletteColor(11),
|
color = PaletteColor(11),
|
||||||
title = resources.getString(R.string.about)
|
title = resources.getString(R.string.about),
|
||||||
|
theme = currentTheme(),
|
||||||
)
|
)
|
||||||
val version = resources.getString(R.string.version_n)
|
val version = resources.getString(R.string.version_n)
|
||||||
binding.tvContributors.setOnClickListener { screen.showCodeContributorsWebsite() }
|
binding.tvContributors.setOnClickListener { screen.showCodeContributorsWebsite() }
|
||||||
|
|||||||
@@ -19,20 +19,21 @@
|
|||||||
package org.isoron.uhabits.activities.common.dialogs
|
package org.isoron.uhabits.activities.common.dialogs
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import org.isoron.platform.gui.toInt
|
||||||
import org.isoron.uhabits.R
|
import org.isoron.uhabits.R
|
||||||
import org.isoron.uhabits.core.models.PaletteColor
|
import org.isoron.uhabits.core.models.PaletteColor
|
||||||
|
import org.isoron.uhabits.core.ui.views.Theme
|
||||||
import org.isoron.uhabits.inject.ActivityContext
|
import org.isoron.uhabits.inject.ActivityContext
|
||||||
import org.isoron.uhabits.inject.ActivityScope
|
import org.isoron.uhabits.inject.ActivityScope
|
||||||
import org.isoron.uhabits.utils.StyledResources
|
import org.isoron.uhabits.utils.StyledResources
|
||||||
import org.isoron.uhabits.utils.toThemedAndroidColor
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ActivityScope
|
@ActivityScope
|
||||||
class ColorPickerDialogFactory @Inject constructor(@param:ActivityContext private val context: Context) {
|
class ColorPickerDialogFactory @Inject constructor(@param:ActivityContext private val context: Context) {
|
||||||
fun create(color: PaletteColor): ColorPickerDialog {
|
fun create(color: PaletteColor, theme: Theme): ColorPickerDialog {
|
||||||
val dialog = ColorPickerDialog()
|
val dialog = ColorPickerDialog()
|
||||||
val res = StyledResources(context)
|
val res = StyledResources(context)
|
||||||
val androidColor = color.toThemedAndroidColor(context)
|
val androidColor = theme.color(color).toInt()
|
||||||
dialog.initialize(
|
dialog.initialize(
|
||||||
R.string.color_picker_default_title,
|
R.string.color_picker_default_title,
|
||||||
res.getPalette(),
|
res.getPalette(),
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016-2021 Álinson Santos Xavier <git@axavier.org>
|
||||||
|
*
|
||||||
|
* This file is part of Loop Habit Tracker.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by the
|
||||||
|
* Free Software Foundation, either version 3 of the License, or (at your
|
||||||
|
* option) any later version.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.isoron.uhabits.activities.common.dialogs
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.DialogInterface
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import org.isoron.uhabits.R
|
||||||
|
import org.isoron.uhabits.core.ui.callbacks.OnConfirmedCallback
|
||||||
|
import org.isoron.uhabits.inject.ActivityContext
|
||||||
|
|
||||||
|
class ConfirmSyncKeyDialog(
|
||||||
|
@ActivityContext context: Context,
|
||||||
|
callback: OnConfirmedCallback
|
||||||
|
) : AlertDialog(context) {
|
||||||
|
init {
|
||||||
|
setTitle(R.string.device_sync)
|
||||||
|
val res = context.resources
|
||||||
|
setMessage(res.getString(R.string.sync_confirm))
|
||||||
|
setButton(
|
||||||
|
BUTTON_POSITIVE,
|
||||||
|
res.getString(R.string.yes)
|
||||||
|
) { dialog: DialogInterface?, which: Int -> callback.onConfirmed() }
|
||||||
|
setButton(
|
||||||
|
BUTTON_NEGATIVE,
|
||||||
|
res.getString(R.string.no)
|
||||||
|
) { dialog: DialogInterface?, which: Int -> }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -160,26 +160,28 @@ class FrequencyPickerDialog(
|
|||||||
|
|
||||||
private fun populateViews() {
|
private fun populateViews() {
|
||||||
uncheckAll()
|
uncheckAll()
|
||||||
if (freqNumerator == 1) {
|
if (freqDenominator == 30 || freqDenominator == 31) {
|
||||||
if (freqDenominator == 1) {
|
contentView.xTimesPerMonthRadioButton.isChecked = true
|
||||||
contentView.everyDayRadioButton.isChecked = true
|
contentView.xTimesPerMonthTextView.setText(freqNumerator.toString())
|
||||||
} else {
|
focus(contentView.xTimesPerMonthTextView)
|
||||||
contentView.everyXDaysRadioButton.isChecked = true
|
|
||||||
contentView.everyXDaysTextView.setText(freqDenominator.toString())
|
|
||||||
focus(contentView.everyXDaysTextView)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (freqDenominator == 7) {
|
if (freqNumerator == 1) {
|
||||||
contentView.xTimesPerWeekRadioButton.isChecked = true
|
if (freqDenominator == 1) {
|
||||||
contentView.xTimesPerWeekTextView.setText(freqNumerator.toString())
|
contentView.everyDayRadioButton.isChecked = true
|
||||||
focus(contentView.xTimesPerWeekTextView)
|
} else {
|
||||||
} else if (freqDenominator == 30 || freqDenominator == 31) {
|
contentView.everyXDaysRadioButton.isChecked = true
|
||||||
contentView.xTimesPerMonthRadioButton.isChecked = true
|
contentView.everyXDaysTextView.setText(freqDenominator.toString())
|
||||||
contentView.xTimesPerMonthTextView.setText(freqNumerator.toString())
|
focus(contentView.everyXDaysTextView)
|
||||||
focus(contentView.xTimesPerMonthTextView)
|
}
|
||||||
} else {
|
} else {
|
||||||
Log.w("FrequencyPickerDialog", "Unknown frequency: $freqNumerator/$freqDenominator")
|
if (freqDenominator == 7) {
|
||||||
contentView.everyDayRadioButton.isChecked = true
|
contentView.xTimesPerWeekRadioButton.isChecked = true
|
||||||
|
contentView.xTimesPerWeekTextView.setText(freqNumerator.toString())
|
||||||
|
focus(contentView.xTimesPerWeekTextView)
|
||||||
|
} else {
|
||||||
|
Log.w("FrequencyPickerDialog", "Unknown frequency: $freqNumerator/$freqDenominator")
|
||||||
|
contentView.everyDayRadioButton.isChecked = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,10 +29,10 @@ class TaskProgressBar(
|
|||||||
context: Context,
|
context: Context,
|
||||||
private val runner: TaskRunner
|
private val runner: TaskRunner
|
||||||
) : ProgressBar(
|
) : ProgressBar(
|
||||||
context,
|
context,
|
||||||
null,
|
null,
|
||||||
android.R.attr.progressBarStyleHorizontal
|
android.R.attr.progressBarStyleHorizontal
|
||||||
),
|
),
|
||||||
TaskRunner.Listener {
|
TaskRunner.Listener {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ package org.isoron.uhabits.activities.habits.edit
|
|||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
|
import android.content.res.Resources
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.Html
|
import android.text.Html
|
||||||
@@ -39,6 +40,7 @@ import kotlinx.android.synthetic.main.activity_edit_habit.notesInput
|
|||||||
import kotlinx.android.synthetic.main.activity_edit_habit.questionInput
|
import kotlinx.android.synthetic.main.activity_edit_habit.questionInput
|
||||||
import kotlinx.android.synthetic.main.activity_edit_habit.targetInput
|
import kotlinx.android.synthetic.main.activity_edit_habit.targetInput
|
||||||
import kotlinx.android.synthetic.main.activity_edit_habit.unitInput
|
import kotlinx.android.synthetic.main.activity_edit_habit.unitInput
|
||||||
|
import org.isoron.platform.gui.toInt
|
||||||
import org.isoron.uhabits.HabitsApplication
|
import org.isoron.uhabits.HabitsApplication
|
||||||
import org.isoron.uhabits.R
|
import org.isoron.uhabits.R
|
||||||
import org.isoron.uhabits.activities.AndroidThemeSwitcher
|
import org.isoron.uhabits.activities.AndroidThemeSwitcher
|
||||||
@@ -50,6 +52,8 @@ import org.isoron.uhabits.core.commands.CreateHabitCommand
|
|||||||
import org.isoron.uhabits.core.commands.EditHabitCommand
|
import org.isoron.uhabits.core.commands.EditHabitCommand
|
||||||
import org.isoron.uhabits.core.models.Frequency
|
import org.isoron.uhabits.core.models.Frequency
|
||||||
import org.isoron.uhabits.core.models.Habit
|
import org.isoron.uhabits.core.models.Habit
|
||||||
|
import org.isoron.uhabits.core.models.HabitType
|
||||||
|
import org.isoron.uhabits.core.models.NumericalHabitType
|
||||||
import org.isoron.uhabits.core.models.PaletteColor
|
import org.isoron.uhabits.core.models.PaletteColor
|
||||||
import org.isoron.uhabits.core.models.Reminder
|
import org.isoron.uhabits.core.models.Reminder
|
||||||
import org.isoron.uhabits.core.models.WeekdayList
|
import org.isoron.uhabits.core.models.WeekdayList
|
||||||
@@ -57,7 +61,16 @@ import org.isoron.uhabits.databinding.ActivityEditHabitBinding
|
|||||||
import org.isoron.uhabits.utils.ColorUtils
|
import org.isoron.uhabits.utils.ColorUtils
|
||||||
import org.isoron.uhabits.utils.formatTime
|
import org.isoron.uhabits.utils.formatTime
|
||||||
import org.isoron.uhabits.utils.toFormattedString
|
import org.isoron.uhabits.utils.toFormattedString
|
||||||
import org.isoron.uhabits.utils.toThemedAndroidColor
|
|
||||||
|
fun formatFrequency(freqNum: Int, freqDen: Int, resources: Resources) = when {
|
||||||
|
freqNum == 1 && (freqDen == 30 || freqDen == 31) -> resources.getString(R.string.every_month)
|
||||||
|
freqDen == 30 || freqDen == 31 -> resources.getString(R.string.x_times_per_month, freqNum)
|
||||||
|
freqNum == 1 && freqDen == 1 -> resources.getString(R.string.every_day)
|
||||||
|
freqNum == 1 && freqDen == 7 -> resources.getString(R.string.every_week)
|
||||||
|
freqNum == 1 && freqDen > 1 -> resources.getString(R.string.every_x_days, freqDen)
|
||||||
|
freqDen == 7 -> resources.getString(R.string.x_times_per_week, freqNum)
|
||||||
|
else -> "$freqNum/$freqDen"
|
||||||
|
}
|
||||||
|
|
||||||
class EditHabitActivity : AppCompatActivity() {
|
class EditHabitActivity : AppCompatActivity() {
|
||||||
|
|
||||||
@@ -66,7 +79,7 @@ class EditHabitActivity : AppCompatActivity() {
|
|||||||
private lateinit var commandRunner: CommandRunner
|
private lateinit var commandRunner: CommandRunner
|
||||||
|
|
||||||
var habitId = -1L
|
var habitId = -1L
|
||||||
var habitType = -1
|
lateinit var habitType: HabitType
|
||||||
var unit = ""
|
var unit = ""
|
||||||
var color = PaletteColor(11)
|
var color = PaletteColor(11)
|
||||||
var androidColor = 0
|
var androidColor = 0
|
||||||
@@ -105,12 +118,12 @@ class EditHabitActivity : AppCompatActivity() {
|
|||||||
binding.unitInput.setText(habit.unit)
|
binding.unitInput.setText(habit.unit)
|
||||||
binding.targetInput.setText(habit.targetValue.toString())
|
binding.targetInput.setText(habit.targetValue.toString())
|
||||||
} else {
|
} else {
|
||||||
habitType = intent.getIntExtra("habitType", Habit.YES_NO_HABIT)
|
habitType = HabitType.fromInt(intent.getIntExtra("habitType", HabitType.YES_NO.value))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state != null) {
|
if (state != null) {
|
||||||
habitId = state.getLong("habitId")
|
habitId = state.getLong("habitId")
|
||||||
habitType = state.getInt("habitType")
|
habitType = HabitType.fromInt(state.getInt("habitType"))
|
||||||
color = PaletteColor(state.getInt("paletteColor"))
|
color = PaletteColor(state.getInt("paletteColor"))
|
||||||
freqNum = state.getInt("freqNum")
|
freqNum = state.getInt("freqNum")
|
||||||
freqDen = state.getInt("freqDen")
|
freqDen = state.getInt("freqDen")
|
||||||
@@ -121,13 +134,16 @@ class EditHabitActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
updateColors()
|
updateColors()
|
||||||
|
|
||||||
if (habitType == Habit.YES_NO_HABIT) {
|
when (habitType) {
|
||||||
binding.unitOuterBox.visibility = View.GONE
|
HabitType.YES_NO -> {
|
||||||
binding.targetOuterBox.visibility = View.GONE
|
binding.unitOuterBox.visibility = View.GONE
|
||||||
} else {
|
binding.targetOuterBox.visibility = View.GONE
|
||||||
binding.nameInput.hint = getString(R.string.measurable_short_example)
|
}
|
||||||
binding.questionInput.hint = getString(R.string.measurable_question_example)
|
HabitType.NUMERICAL -> {
|
||||||
binding.frequencyOuterBox.visibility = View.GONE
|
binding.nameInput.hint = getString(R.string.measurable_short_example)
|
||||||
|
binding.questionInput.hint = getString(R.string.measurable_question_example)
|
||||||
|
binding.frequencyOuterBox.visibility = View.GONE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setSupportActionBar(binding.toolbar)
|
setSupportActionBar(binding.toolbar)
|
||||||
@@ -137,7 +153,7 @@ class EditHabitActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
val colorPickerDialogFactory = ColorPickerDialogFactory(this)
|
val colorPickerDialogFactory = ColorPickerDialogFactory(this)
|
||||||
binding.colorButton.setOnClickListener {
|
binding.colorButton.setOnClickListener {
|
||||||
val dialog = colorPickerDialogFactory.create(color)
|
val dialog = colorPickerDialogFactory.create(color, themeSwitcher.currentTheme)
|
||||||
dialog.setListener { paletteColor ->
|
dialog.setListener { paletteColor ->
|
||||||
this.color = paletteColor
|
this.color = paletteColor
|
||||||
updateColors()
|
updateColors()
|
||||||
@@ -244,9 +260,9 @@ class EditHabitActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
habit.frequency = Frequency(freqNum, freqDen)
|
habit.frequency = Frequency(freqNum, freqDen)
|
||||||
if (habitType == Habit.NUMBER_HABIT) {
|
if (habitType == HabitType.NUMERICAL) {
|
||||||
habit.targetValue = targetInput.text.toString().toDouble()
|
habit.targetValue = targetInput.text.toString().toDouble()
|
||||||
habit.targetType = Habit.AT_LEAST
|
habit.targetType = NumericalHabitType.AT_LEAST
|
||||||
habit.unit = unitInput.text.trim().toString()
|
habit.unit = unitInput.text.trim().toString()
|
||||||
}
|
}
|
||||||
habit.type = habitType
|
habit.type = habitType
|
||||||
@@ -274,7 +290,7 @@ class EditHabitActivity : AppCompatActivity() {
|
|||||||
nameInput.error = getFormattedValidationError(R.string.validation_cannot_be_blank)
|
nameInput.error = getFormattedValidationError(R.string.validation_cannot_be_blank)
|
||||||
isValid = false
|
isValid = false
|
||||||
}
|
}
|
||||||
if (habitType == Habit.NUMBER_HABIT) {
|
if (habitType == HabitType.NUMERICAL) {
|
||||||
if (targetInput.text.isEmpty()) {
|
if (targetInput.text.isEmpty()) {
|
||||||
targetInput.error = getString(R.string.validation_cannot_be_blank)
|
targetInput.error = getString(R.string.validation_cannot_be_blank)
|
||||||
isValid = false
|
isValid = false
|
||||||
@@ -299,14 +315,7 @@ class EditHabitActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
@SuppressLint("StringFormatMatches")
|
@SuppressLint("StringFormatMatches")
|
||||||
private fun populateFrequency() {
|
private fun populateFrequency() {
|
||||||
binding.booleanFrequencyPicker.text = when {
|
binding.booleanFrequencyPicker.text = formatFrequency(freqNum, freqDen, resources)
|
||||||
freqNum == 1 && freqDen == 1 -> getString(R.string.every_day)
|
|
||||||
freqNum == 1 && freqDen == 7 -> getString(R.string.every_week)
|
|
||||||
freqNum == 1 && freqDen > 1 -> getString(R.string.every_x_days, freqDen)
|
|
||||||
freqDen == 7 -> getString(R.string.x_times_per_week, freqNum)
|
|
||||||
freqDen == 30 || freqDen == 31 -> getString(R.string.x_times_per_month, freqNum)
|
|
||||||
else -> "$freqNum/$freqDen"
|
|
||||||
}
|
|
||||||
binding.numericalFrequencyPicker.text = when (freqDen) {
|
binding.numericalFrequencyPicker.text = when (freqDen) {
|
||||||
1 -> getString(R.string.every_day)
|
1 -> getString(R.string.every_day)
|
||||||
7 -> getString(R.string.every_week)
|
7 -> getString(R.string.every_week)
|
||||||
@@ -316,7 +325,7 @@ class EditHabitActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun updateColors() {
|
private fun updateColors() {
|
||||||
androidColor = color.toThemedAndroidColor(this)
|
androidColor = themeSwitcher.currentTheme.color(color).toInt()
|
||||||
binding.colorButton.backgroundTintList = ColorStateList.valueOf(androidColor)
|
binding.colorButton.backgroundTintList = ColorStateList.valueOf(androidColor)
|
||||||
if (!themeSwitcher.isNightMode) {
|
if (!themeSwitcher.isNightMode) {
|
||||||
val darkerAndroidColor = ColorUtils.mixColors(Color.BLACK, androidColor, 0.15f)
|
val darkerAndroidColor = ColorUtils.mixColors(Color.BLACK, androidColor, 0.15f)
|
||||||
@@ -334,7 +343,7 @@ class EditHabitActivity : AppCompatActivity() {
|
|||||||
super.onSaveInstanceState(state)
|
super.onSaveInstanceState(state)
|
||||||
with(state) {
|
with(state) {
|
||||||
putLong("habitId", habitId)
|
putLong("habitId", habitId)
|
||||||
putInt("habitType", habitType)
|
putInt("habitType", habitType.value)
|
||||||
putInt("paletteColor", color.paletteIndex)
|
putInt("paletteColor", color.paletteIndex)
|
||||||
putInt("androidColor", androidColor)
|
putInt("androidColor", androidColor)
|
||||||
putInt("freqNum", freqNum)
|
putInt("freqNum", freqNum)
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.appcompat.app.AppCompatDialogFragment
|
import androidx.appcompat.app.AppCompatDialogFragment
|
||||||
import org.isoron.uhabits.R
|
import org.isoron.uhabits.R
|
||||||
import org.isoron.uhabits.core.models.Habit
|
import org.isoron.uhabits.core.models.HabitType
|
||||||
import org.isoron.uhabits.databinding.SelectHabitTypeBinding
|
import org.isoron.uhabits.databinding.SelectHabitTypeBinding
|
||||||
import org.isoron.uhabits.intents.IntentFactory
|
import org.isoron.uhabits.intents.IntentFactory
|
||||||
|
|
||||||
@@ -40,13 +40,13 @@ class HabitTypeDialog : AppCompatDialogFragment() {
|
|||||||
val binding = SelectHabitTypeBinding.inflate(inflater, container, false)
|
val binding = SelectHabitTypeBinding.inflate(inflater, container, false)
|
||||||
|
|
||||||
binding.buttonYesNo.setOnClickListener {
|
binding.buttonYesNo.setOnClickListener {
|
||||||
val intent = IntentFactory().startEditActivity(activity!!, Habit.YES_NO_HABIT)
|
val intent = IntentFactory().startEditActivity(activity!!, HabitType.YES_NO.value)
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.buttonMeasurable.setOnClickListener {
|
binding.buttonMeasurable.setOnClickListener {
|
||||||
val intent = IntentFactory().startEditActivity(activity!!, Habit.NUMBER_HABIT)
|
val intent = IntentFactory().startEditActivity(activity!!, HabitType.NUMERICAL.value)
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,10 +26,12 @@ import android.view.MenuItem
|
|||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.isoron.uhabits.BaseExceptionHandler
|
import org.isoron.uhabits.BaseExceptionHandler
|
||||||
import org.isoron.uhabits.HabitsApplication
|
import org.isoron.uhabits.HabitsApplication
|
||||||
import org.isoron.uhabits.activities.habits.list.views.HabitCardListAdapter
|
import org.isoron.uhabits.activities.habits.list.views.HabitCardListAdapter
|
||||||
import org.isoron.uhabits.core.preferences.Preferences
|
import org.isoron.uhabits.core.preferences.Preferences
|
||||||
|
import org.isoron.uhabits.core.sync.SyncManager
|
||||||
import org.isoron.uhabits.core.tasks.TaskRunner
|
import org.isoron.uhabits.core.tasks.TaskRunner
|
||||||
import org.isoron.uhabits.core.ui.ThemeSwitcher.Companion.THEME_DARK
|
import org.isoron.uhabits.core.ui.ThemeSwitcher.Companion.THEME_DARK
|
||||||
import org.isoron.uhabits.core.utils.MidnightTimer
|
import org.isoron.uhabits.core.utils.MidnightTimer
|
||||||
@@ -38,7 +40,7 @@ import org.isoron.uhabits.inject.ActivityContextModule
|
|||||||
import org.isoron.uhabits.inject.DaggerHabitsActivityComponent
|
import org.isoron.uhabits.inject.DaggerHabitsActivityComponent
|
||||||
import org.isoron.uhabits.utils.restartWithFade
|
import org.isoron.uhabits.utils.restartWithFade
|
||||||
|
|
||||||
class ListHabitsActivity : AppCompatActivity() {
|
class ListHabitsActivity : AppCompatActivity(), Preferences.Listener {
|
||||||
|
|
||||||
var pureBlack: Boolean = false
|
var pureBlack: Boolean = false
|
||||||
lateinit var taskRunner: TaskRunner
|
lateinit var taskRunner: TaskRunner
|
||||||
@@ -47,10 +49,15 @@ class ListHabitsActivity : AppCompatActivity() {
|
|||||||
lateinit var screen: ListHabitsScreen
|
lateinit var screen: ListHabitsScreen
|
||||||
lateinit var prefs: Preferences
|
lateinit var prefs: Preferences
|
||||||
lateinit var midnightTimer: MidnightTimer
|
lateinit var midnightTimer: MidnightTimer
|
||||||
|
lateinit var syncManager: SyncManager
|
||||||
private val scope = CoroutineScope(Dispatchers.Main)
|
private val scope = CoroutineScope(Dispatchers.Main)
|
||||||
|
|
||||||
private lateinit var menu: ListHabitsMenu
|
private lateinit var menu: ListHabitsMenu
|
||||||
|
|
||||||
|
override fun onQuestionMarksChanged() {
|
||||||
|
invalidateOptionsMenu()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
@@ -63,6 +70,8 @@ class ListHabitsActivity : AppCompatActivity() {
|
|||||||
component.themeSwitcher.apply()
|
component.themeSwitcher.apply()
|
||||||
|
|
||||||
prefs = appComponent.preferences
|
prefs = appComponent.preferences
|
||||||
|
prefs.addListener(this)
|
||||||
|
syncManager = appComponent.syncManager
|
||||||
pureBlack = prefs.isPureBlackEnabled
|
pureBlack = prefs.isPureBlackEnabled
|
||||||
midnightTimer = appComponent.midnightTimer
|
midnightTimer = appComponent.midnightTimer
|
||||||
rootView = component.listHabitsRootView
|
rootView = component.listHabitsRootView
|
||||||
@@ -79,6 +88,9 @@ class ListHabitsActivity : AppCompatActivity() {
|
|||||||
midnightTimer.onPause()
|
midnightTimer.onPause()
|
||||||
screen.onDettached()
|
screen.onDettached()
|
||||||
adapter.cancelRefresh()
|
adapter.cancelRefresh()
|
||||||
|
scope.launch {
|
||||||
|
syncManager.onPause()
|
||||||
|
}
|
||||||
super.onPause()
|
super.onPause()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,6 +99,9 @@ class ListHabitsActivity : AppCompatActivity() {
|
|||||||
screen.onAttached()
|
screen.onAttached()
|
||||||
rootView.postInvalidate()
|
rootView.postInvalidate()
|
||||||
midnightTimer.onResume()
|
midnightTimer.onResume()
|
||||||
|
scope.launch {
|
||||||
|
syncManager.onResume()
|
||||||
|
}
|
||||||
taskRunner.run {
|
taskRunner.run {
|
||||||
AutoBackup(this@ListHabitsActivity).run()
|
AutoBackup(this@ListHabitsActivity).run()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,11 @@ class ListHabitsMenu @Inject constructor(
|
|||||||
nightModeItem.isChecked = themeSwitcher.isNightMode
|
nightModeItem.isChecked = themeSwitcher.isNightMode
|
||||||
hideArchivedItem.isChecked = !preferences.showArchived
|
hideArchivedItem.isChecked = !preferences.showArchived
|
||||||
hideCompletedItem.isChecked = !preferences.showCompleted
|
hideCompletedItem.isChecked = !preferences.showCompleted
|
||||||
|
if (preferences.areQuestionMarksEnabled || preferences.isSkipEnabled) {
|
||||||
|
hideCompletedItem.title = activity.resources.getString(R.string.hide_entered)
|
||||||
|
} else {
|
||||||
|
hideCompletedItem.title = activity.resources.getString(R.string.hide_completed)
|
||||||
|
}
|
||||||
updateArrows(menu)
|
updateArrows(menu)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ import org.isoron.uhabits.utils.addAtBottom
|
|||||||
import org.isoron.uhabits.utils.addAtTop
|
import org.isoron.uhabits.utils.addAtTop
|
||||||
import org.isoron.uhabits.utils.addBelow
|
import org.isoron.uhabits.utils.addBelow
|
||||||
import org.isoron.uhabits.utils.buildToolbar
|
import org.isoron.uhabits.utils.buildToolbar
|
||||||
|
import org.isoron.uhabits.utils.currentTheme
|
||||||
import org.isoron.uhabits.utils.dim
|
import org.isoron.uhabits.utils.dim
|
||||||
import org.isoron.uhabits.utils.dp
|
import org.isoron.uhabits.utils.dp
|
||||||
import org.isoron.uhabits.utils.setupToolbar
|
import org.isoron.uhabits.utils.setupToolbar
|
||||||
@@ -93,6 +94,7 @@ class ListHabitsRootView @Inject constructor(
|
|||||||
title = resources.getString(R.string.main_activity_title),
|
title = resources.getString(R.string.main_activity_title),
|
||||||
color = PaletteColor(17),
|
color = PaletteColor(17),
|
||||||
displayHomeAsUpEnabled = false,
|
displayHomeAsUpEnabled = false,
|
||||||
|
theme = currentTheme(),
|
||||||
)
|
)
|
||||||
addView(rootView, MATCH_PARENT, MATCH_PARENT)
|
addView(rootView, MATCH_PARENT, MATCH_PARENT)
|
||||||
listAdapter.setListView(listView)
|
listAdapter.setListView(listView)
|
||||||
|
|||||||
@@ -22,11 +22,13 @@ package org.isoron.uhabits.activities.habits.list
|
|||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.util.Log
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import dagger.Lazy
|
import dagger.Lazy
|
||||||
import org.isoron.uhabits.R
|
import org.isoron.uhabits.R
|
||||||
import org.isoron.uhabits.activities.common.dialogs.ColorPickerDialogFactory
|
import org.isoron.uhabits.activities.common.dialogs.ColorPickerDialogFactory
|
||||||
import org.isoron.uhabits.activities.common.dialogs.ConfirmDeleteDialog
|
import org.isoron.uhabits.activities.common.dialogs.ConfirmDeleteDialog
|
||||||
|
import org.isoron.uhabits.activities.common.dialogs.ConfirmSyncKeyDialog
|
||||||
import org.isoron.uhabits.activities.common.dialogs.NumberPickerFactory
|
import org.isoron.uhabits.activities.common.dialogs.NumberPickerFactory
|
||||||
import org.isoron.uhabits.activities.habits.edit.HabitTypeDialog
|
import org.isoron.uhabits.activities.habits.edit.HabitTypeDialog
|
||||||
import org.isoron.uhabits.activities.habits.list.views.HabitCardListAdapter
|
import org.isoron.uhabits.activities.habits.list.views.HabitCardListAdapter
|
||||||
@@ -51,6 +53,8 @@ import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior.Message
|
|||||||
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior.Message.FILE_NOT_RECOGNIZED
|
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior.Message.FILE_NOT_RECOGNIZED
|
||||||
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior.Message.IMPORT_FAILED
|
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior.Message.IMPORT_FAILED
|
||||||
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior.Message.IMPORT_SUCCESSFUL
|
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior.Message.IMPORT_SUCCESSFUL
|
||||||
|
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior.Message.SYNC_ENABLED
|
||||||
|
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior.Message.SYNC_KEY_ALREADY_INSTALLED
|
||||||
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsMenuBehavior
|
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsMenuBehavior
|
||||||
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsSelectionMenuBehavior
|
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsSelectionMenuBehavior
|
||||||
import org.isoron.uhabits.inject.ActivityContext
|
import org.isoron.uhabits.inject.ActivityContext
|
||||||
@@ -99,6 +103,14 @@ class ListHabitsScreen
|
|||||||
|
|
||||||
fun onAttached() {
|
fun onAttached() {
|
||||||
commandRunner.addListener(this)
|
commandRunner.addListener(this)
|
||||||
|
if (activity.intent.action == "android.intent.action.VIEW") {
|
||||||
|
val uri = activity.intent.data!!.toString()
|
||||||
|
val parts = uri.replace(Regex("^.*sync/"), "").split("#")
|
||||||
|
val syncKey = parts[0]
|
||||||
|
val encKey = parts[1]
|
||||||
|
Log.i("ListHabitsScreen", "sync: $syncKey enc: $encKey")
|
||||||
|
behavior.get().onSyncKeyOffer(syncKey, encKey)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onDettached() {
|
fun onDettached() {
|
||||||
@@ -196,6 +208,8 @@ class ListHabitsScreen
|
|||||||
DATABASE_REPAIRED -> R.string.database_repaired
|
DATABASE_REPAIRED -> R.string.database_repaired
|
||||||
COULD_NOT_GENERATE_BUG_REPORT -> R.string.bug_report_failed
|
COULD_NOT_GENERATE_BUG_REPORT -> R.string.bug_report_failed
|
||||||
FILE_NOT_RECOGNIZED -> R.string.file_not_recognized
|
FILE_NOT_RECOGNIZED -> R.string.file_not_recognized
|
||||||
|
SYNC_ENABLED -> R.string.sync_enabled
|
||||||
|
SYNC_KEY_ALREADY_INSTALLED -> R.string.sync_key_already_installed
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -217,7 +231,7 @@ class ListHabitsScreen
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun showColorPicker(defaultColor: PaletteColor, callback: OnColorPickedCallback) {
|
override fun showColorPicker(defaultColor: PaletteColor, callback: OnColorPickedCallback) {
|
||||||
val picker = colorPickerFactory.create(defaultColor)
|
val picker = colorPickerFactory.create(defaultColor, themeSwitcher.currentTheme!!)
|
||||||
picker.setListener(callback)
|
picker.setListener(callback)
|
||||||
picker.show(activity.supportFragmentManager, "picker")
|
picker.show(activity.supportFragmentManager, "picker")
|
||||||
}
|
}
|
||||||
@@ -230,6 +244,10 @@ class ListHabitsScreen
|
|||||||
numberPickerFactory.create(value, unit, callback).show()
|
numberPickerFactory.create(value, unit, callback).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun showConfirmInstallSyncKey(callback: OnConfirmedCallback) {
|
||||||
|
ConfirmSyncKeyDialog(activity, callback).show()
|
||||||
|
}
|
||||||
|
|
||||||
private fun getExecuteString(command: Command): String? {
|
private fun getExecuteString(command: Command): String? {
|
||||||
when (command) {
|
when (command) {
|
||||||
is ArchiveHabitsCommand -> {
|
is ArchiveHabitsCommand -> {
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ class CheckmarkButtonView(
|
|||||||
paint.color = when (value) {
|
paint.color = when (value) {
|
||||||
YES_MANUAL, YES_AUTO, SKIP -> color
|
YES_MANUAL, YES_AUTO, SKIP -> color
|
||||||
NO -> {
|
NO -> {
|
||||||
if (preferences.areQuestionMarksEnabled()) mediumContrastColor
|
if (preferences.areQuestionMarksEnabled) mediumContrastColor
|
||||||
else lowContrastColor
|
else lowContrastColor
|
||||||
}
|
}
|
||||||
else -> lowContrastColor
|
else -> lowContrastColor
|
||||||
@@ -140,7 +140,7 @@ class CheckmarkButtonView(
|
|||||||
SKIP -> R.string.fa_skipped
|
SKIP -> R.string.fa_skipped
|
||||||
NO -> R.string.fa_times
|
NO -> R.string.fa_times
|
||||||
UNKNOWN -> {
|
UNKNOWN -> {
|
||||||
if (preferences.areQuestionMarksEnabled()) R.string.fa_question
|
if (preferences.areQuestionMarksEnabled) R.string.fa_question
|
||||||
else R.string.fa_times
|
else R.string.fa_times
|
||||||
}
|
}
|
||||||
else -> R.string.fa_check
|
else -> R.string.fa_check
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
|
|||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import org.isoron.platform.gui.toInt
|
||||||
import org.isoron.uhabits.R
|
import org.isoron.uhabits.R
|
||||||
import org.isoron.uhabits.activities.common.views.RingView
|
import org.isoron.uhabits.activities.common.views.RingView
|
||||||
import org.isoron.uhabits.core.models.Habit
|
import org.isoron.uhabits.core.models.Habit
|
||||||
@@ -41,9 +42,9 @@ import org.isoron.uhabits.core.models.Timestamp
|
|||||||
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior
|
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior
|
||||||
import org.isoron.uhabits.core.utils.DateUtils
|
import org.isoron.uhabits.core.utils.DateUtils
|
||||||
import org.isoron.uhabits.inject.ActivityContext
|
import org.isoron.uhabits.inject.ActivityContext
|
||||||
|
import org.isoron.uhabits.utils.currentTheme
|
||||||
import org.isoron.uhabits.utils.dp
|
import org.isoron.uhabits.utils.dp
|
||||||
import org.isoron.uhabits.utils.sres
|
import org.isoron.uhabits.utils.sres
|
||||||
import org.isoron.uhabits.utils.toThemedAndroidColor
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class HabitCardViewFactory
|
class HabitCardViewFactory
|
||||||
@@ -213,7 +214,7 @@ class HabitCardView(
|
|||||||
fun getActiveColor(habit: Habit): Int {
|
fun getActiveColor(habit: Habit): Int {
|
||||||
return when (habit.isArchived) {
|
return when (habit.isArchived) {
|
||||||
true -> sres.getColor(R.attr.contrast60)
|
true -> sres.getColor(R.attr.contrast60)
|
||||||
false -> habit.color.toThemedAndroidColor(context)
|
false -> currentTheme().color(habit.color).toInt()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ class NumberButtonView(
|
|||||||
typeface = BOLD_TYPEFACE
|
typeface = BOLD_TYPEFACE
|
||||||
textSize = dim(R.dimen.smallTextSize)
|
textSize = dim(R.dimen.smallTextSize)
|
||||||
}
|
}
|
||||||
preferences.areQuestionMarksEnabled() -> {
|
preferences.areQuestionMarksEnabled -> {
|
||||||
label = resources.getString(R.string.fa_question)
|
label = resources.getString(R.string.fa_question)
|
||||||
typeface = getFontAwesome()
|
typeface = getFontAwesome()
|
||||||
textSize = dim(R.dimen.smallerTextSize)
|
textSize = dim(R.dimen.smallerTextSize)
|
||||||
|
|||||||
@@ -35,7 +35,12 @@ class ShowHabitView(context: Context) : FrameLayout(context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun setState(data: ShowHabitState) {
|
fun setState(data: ShowHabitState) {
|
||||||
setupToolbar(binding.toolbar, title = data.title, color = data.color)
|
setupToolbar(
|
||||||
|
binding.toolbar,
|
||||||
|
title = data.title,
|
||||||
|
color = data.color,
|
||||||
|
theme = data.theme,
|
||||||
|
)
|
||||||
binding.subtitleCard.setState(data.subtitle)
|
binding.subtitleCard.setState(data.subtitle)
|
||||||
binding.overviewCard.setState(data.overview)
|
binding.overviewCard.setState(data.overview)
|
||||||
binding.notesCard.setState(data.notes)
|
binding.notesCard.setState(data.notes)
|
||||||
|
|||||||
@@ -24,12 +24,12 @@ import android.view.LayoutInflater
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.AdapterView
|
import android.widget.AdapterView
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
|
import org.isoron.platform.gui.toInt
|
||||||
import org.isoron.platform.time.JavaLocalDateFormatter
|
import org.isoron.platform.time.JavaLocalDateFormatter
|
||||||
import org.isoron.uhabits.core.ui.screens.habits.show.views.BarCardPresenter
|
import org.isoron.uhabits.core.ui.screens.habits.show.views.BarCardPresenter
|
||||||
import org.isoron.uhabits.core.ui.screens.habits.show.views.BarCardState
|
import org.isoron.uhabits.core.ui.screens.habits.show.views.BarCardState
|
||||||
import org.isoron.uhabits.core.ui.views.BarChart
|
import org.isoron.uhabits.core.ui.views.BarChart
|
||||||
import org.isoron.uhabits.databinding.ShowHabitBarBinding
|
import org.isoron.uhabits.databinding.ShowHabitBarBinding
|
||||||
import org.isoron.uhabits.utils.toThemedAndroidColor
|
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
class BarCardView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) {
|
class BarCardView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) {
|
||||||
@@ -37,7 +37,7 @@ class BarCardView(context: Context, attrs: AttributeSet) : LinearLayout(context,
|
|||||||
private var binding = ShowHabitBarBinding.inflate(LayoutInflater.from(context), this)
|
private var binding = ShowHabitBarBinding.inflate(LayoutInflater.from(context), this)
|
||||||
|
|
||||||
fun setState(state: BarCardState) {
|
fun setState(state: BarCardState) {
|
||||||
val androidColor = state.color.toThemedAndroidColor(context)
|
val androidColor = state.theme.color(state.color).toInt()
|
||||||
binding.chart.view = BarChart(state.theme, JavaLocalDateFormatter(Locale.US)).apply {
|
binding.chart.view = BarChart(state.theme, JavaLocalDateFormatter(Locale.US)).apply {
|
||||||
series = mutableListOf(state.entries.map { it.value / 1000.0 })
|
series = mutableListOf(state.entries.map { it.value / 1000.0 })
|
||||||
colors = mutableListOf(theme.color(state.color.paletteIndex))
|
colors = mutableListOf(theme.color(state.color.paletteIndex))
|
||||||
|
|||||||
@@ -22,16 +22,16 @@ import android.content.Context
|
|||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
|
import org.isoron.platform.gui.toInt
|
||||||
import org.isoron.uhabits.core.ui.screens.habits.show.views.FrequencyCardState
|
import org.isoron.uhabits.core.ui.screens.habits.show.views.FrequencyCardState
|
||||||
import org.isoron.uhabits.databinding.ShowHabitFrequencyBinding
|
import org.isoron.uhabits.databinding.ShowHabitFrequencyBinding
|
||||||
import org.isoron.uhabits.utils.toThemedAndroidColor
|
|
||||||
|
|
||||||
class FrequencyCardView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) {
|
class FrequencyCardView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) {
|
||||||
|
|
||||||
private var binding = ShowHabitFrequencyBinding.inflate(LayoutInflater.from(context), this)
|
private var binding = ShowHabitFrequencyBinding.inflate(LayoutInflater.from(context), this)
|
||||||
|
|
||||||
fun setState(state: FrequencyCardState) {
|
fun setState(state: FrequencyCardState) {
|
||||||
val androidColor = state.color.toThemedAndroidColor(context)
|
val androidColor = state.theme.color(state.color).toInt()
|
||||||
binding.frequencyChart.setFrequency(state.frequency)
|
binding.frequencyChart.setFrequency(state.frequency)
|
||||||
binding.frequencyChart.setFirstWeekday(state.firstWeekday)
|
binding.frequencyChart.setFirstWeekday(state.firstWeekday)
|
||||||
binding.title.setTextColor(androidColor)
|
binding.title.setTextColor(androidColor)
|
||||||
|
|||||||
@@ -22,12 +22,12 @@ import android.content.Context
|
|||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
|
import org.isoron.platform.gui.toInt
|
||||||
import org.isoron.platform.time.JavaLocalDateFormatter
|
import org.isoron.platform.time.JavaLocalDateFormatter
|
||||||
import org.isoron.uhabits.core.ui.screens.habits.show.views.HistoryCardPresenter
|
import org.isoron.uhabits.core.ui.screens.habits.show.views.HistoryCardPresenter
|
||||||
import org.isoron.uhabits.core.ui.screens.habits.show.views.HistoryCardState
|
import org.isoron.uhabits.core.ui.screens.habits.show.views.HistoryCardState
|
||||||
import org.isoron.uhabits.core.ui.views.HistoryChart
|
import org.isoron.uhabits.core.ui.views.HistoryChart
|
||||||
import org.isoron.uhabits.databinding.ShowHabitHistoryBinding
|
import org.isoron.uhabits.databinding.ShowHabitHistoryBinding
|
||||||
import org.isoron.uhabits.utils.toThemedAndroidColor
|
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
class HistoryCardView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) {
|
class HistoryCardView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) {
|
||||||
@@ -35,7 +35,7 @@ class HistoryCardView(context: Context, attrs: AttributeSet) : LinearLayout(cont
|
|||||||
private var binding = ShowHabitHistoryBinding.inflate(LayoutInflater.from(context), this)
|
private var binding = ShowHabitHistoryBinding.inflate(LayoutInflater.from(context), this)
|
||||||
|
|
||||||
fun setState(state: HistoryCardState) {
|
fun setState(state: HistoryCardState) {
|
||||||
val androidColor = state.color.toThemedAndroidColor(context)
|
val androidColor = state.theme.color(state.color).toInt()
|
||||||
binding.title.setTextColor(androidColor)
|
binding.title.setTextColor(androidColor)
|
||||||
binding.chart.view = HistoryChart(
|
binding.chart.view = HistoryChart(
|
||||||
today = state.today,
|
today = state.today,
|
||||||
|
|||||||
@@ -22,11 +22,11 @@ import android.content.Context
|
|||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
|
import org.isoron.platform.gui.toInt
|
||||||
import org.isoron.uhabits.R
|
import org.isoron.uhabits.R
|
||||||
import org.isoron.uhabits.core.ui.screens.habits.show.views.OverviewCardState
|
import org.isoron.uhabits.core.ui.screens.habits.show.views.OverviewCardState
|
||||||
import org.isoron.uhabits.databinding.ShowHabitOverviewBinding
|
import org.isoron.uhabits.databinding.ShowHabitOverviewBinding
|
||||||
import org.isoron.uhabits.utils.StyledResources
|
import org.isoron.uhabits.utils.StyledResources
|
||||||
import org.isoron.uhabits.utils.toThemedAndroidColor
|
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
class OverviewCardView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) {
|
class OverviewCardView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) {
|
||||||
@@ -42,7 +42,7 @@ class OverviewCardView(context: Context, attrs: AttributeSet) : LinearLayout(con
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun setState(state: OverviewCardState) {
|
fun setState(state: OverviewCardState) {
|
||||||
val androidColor = state.color.toThemedAndroidColor(context)
|
val androidColor = state.theme.color(state.color).toInt()
|
||||||
val res = StyledResources(context)
|
val res = StyledResources(context)
|
||||||
val inactiveColor = res.getColor(R.attr.contrast60)
|
val inactiveColor = res.getColor(R.attr.contrast60)
|
||||||
binding.monthDiffLabel.setTextColor(if (state.scoreMonthDiff >= 0) androidColor else inactiveColor)
|
binding.monthDiffLabel.setTextColor(if (state.scoreMonthDiff >= 0) androidColor else inactiveColor)
|
||||||
|
|||||||
@@ -24,16 +24,16 @@ import android.view.LayoutInflater
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.AdapterView
|
import android.widget.AdapterView
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
|
import org.isoron.platform.gui.toInt
|
||||||
import org.isoron.uhabits.core.ui.screens.habits.show.views.ScoreCardPresenter
|
import org.isoron.uhabits.core.ui.screens.habits.show.views.ScoreCardPresenter
|
||||||
import org.isoron.uhabits.core.ui.screens.habits.show.views.ScoreCardState
|
import org.isoron.uhabits.core.ui.screens.habits.show.views.ScoreCardState
|
||||||
import org.isoron.uhabits.databinding.ShowHabitScoreBinding
|
import org.isoron.uhabits.databinding.ShowHabitScoreBinding
|
||||||
import org.isoron.uhabits.utils.toThemedAndroidColor
|
|
||||||
|
|
||||||
class ScoreCardView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) {
|
class ScoreCardView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) {
|
||||||
private var binding = ShowHabitScoreBinding.inflate(LayoutInflater.from(context), this)
|
private var binding = ShowHabitScoreBinding.inflate(LayoutInflater.from(context), this)
|
||||||
|
|
||||||
fun setState(state: ScoreCardState) {
|
fun setState(state: ScoreCardState) {
|
||||||
val androidColor = state.color.toThemedAndroidColor(context)
|
val androidColor = state.theme.color(state.color).toInt()
|
||||||
binding.title.setTextColor(androidColor)
|
binding.title.setTextColor(androidColor)
|
||||||
binding.spinner.setSelection(state.spinnerPosition)
|
binding.spinner.setSelection(state.spinnerPosition)
|
||||||
binding.scoreView.setScores(state.scores)
|
binding.scoreView.setScores(state.scores)
|
||||||
|
|||||||
@@ -22,16 +22,16 @@ import android.content.Context
|
|||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
|
import org.isoron.platform.gui.toInt
|
||||||
import org.isoron.uhabits.core.ui.screens.habits.show.views.StreakCardState
|
import org.isoron.uhabits.core.ui.screens.habits.show.views.StreakCardState
|
||||||
import org.isoron.uhabits.databinding.ShowHabitStreakBinding
|
import org.isoron.uhabits.databinding.ShowHabitStreakBinding
|
||||||
import org.isoron.uhabits.utils.toThemedAndroidColor
|
|
||||||
|
|
||||||
class StreakCardView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) {
|
class StreakCardView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) {
|
||||||
private val binding = ShowHabitStreakBinding.inflate(LayoutInflater.from(context), this)
|
private val binding = ShowHabitStreakBinding.inflate(LayoutInflater.from(context), this)
|
||||||
fun setState(state: StreakCardState) {
|
fun setState(state: StreakCardState) {
|
||||||
val color = state.color.toThemedAndroidColor(context)
|
val androidColor = state.theme.color(state.color).toInt()
|
||||||
binding.title.setTextColor(color)
|
binding.title.setTextColor(androidColor)
|
||||||
binding.streakChart.setColor(color)
|
binding.streakChart.setColor(androidColor)
|
||||||
binding.streakChart.setStreaks(state.bestStreaks)
|
binding.streakChart.setStreaks(state.bestStreaks)
|
||||||
postInvalidate()
|
postInvalidate()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,19 +20,18 @@ package org.isoron.uhabits.activities.habits.show.views
|
|||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.Resources
|
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
|
import org.isoron.platform.gui.toInt
|
||||||
import org.isoron.uhabits.R
|
import org.isoron.uhabits.R
|
||||||
|
import org.isoron.uhabits.activities.habits.edit.formatFrequency
|
||||||
import org.isoron.uhabits.activities.habits.list.views.toShortString
|
import org.isoron.uhabits.activities.habits.list.views.toShortString
|
||||||
import org.isoron.uhabits.core.models.Frequency
|
|
||||||
import org.isoron.uhabits.core.ui.screens.habits.show.views.SubtitleCardState
|
import org.isoron.uhabits.core.ui.screens.habits.show.views.SubtitleCardState
|
||||||
import org.isoron.uhabits.databinding.ShowHabitSubtitleBinding
|
import org.isoron.uhabits.databinding.ShowHabitSubtitleBinding
|
||||||
import org.isoron.uhabits.utils.InterfaceUtils
|
import org.isoron.uhabits.utils.InterfaceUtils
|
||||||
import org.isoron.uhabits.utils.formatTime
|
import org.isoron.uhabits.utils.formatTime
|
||||||
import org.isoron.uhabits.utils.toThemedAndroidColor
|
|
||||||
|
|
||||||
class SubtitleCardView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) {
|
class SubtitleCardView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) {
|
||||||
|
|
||||||
@@ -47,9 +46,13 @@ class SubtitleCardView(context: Context, attrs: AttributeSet) : LinearLayout(con
|
|||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
fun setState(state: SubtitleCardState) {
|
fun setState(state: SubtitleCardState) {
|
||||||
val color = state.color.toThemedAndroidColor(context)
|
val color = state.theme.color(state.color).toInt()
|
||||||
val reminder = state.reminder
|
val reminder = state.reminder
|
||||||
binding.frequencyLabel.text = state.frequency.format(resources)
|
binding.frequencyLabel.text = formatFrequency(
|
||||||
|
state.frequency.numerator,
|
||||||
|
state.frequency.denominator,
|
||||||
|
resources,
|
||||||
|
)
|
||||||
binding.questionLabel.setTextColor(color)
|
binding.questionLabel.setTextColor(color)
|
||||||
binding.questionLabel.text = state.question
|
binding.questionLabel.text = state.question
|
||||||
binding.reminderLabel.text = if (reminder != null) {
|
binding.reminderLabel.text = if (reminder != null) {
|
||||||
@@ -72,32 +75,4 @@ class SubtitleCardView(context: Context, attrs: AttributeSet) : LinearLayout(con
|
|||||||
|
|
||||||
postInvalidate()
|
postInvalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("StringFormatMatches")
|
|
||||||
private fun Frequency.format(resources: Resources): String {
|
|
||||||
val num = this.numerator
|
|
||||||
val den = this.denominator
|
|
||||||
if (num == den) {
|
|
||||||
return resources.getString(R.string.every_day)
|
|
||||||
}
|
|
||||||
if (den == 7) {
|
|
||||||
return resources.getString(R.string.x_times_per_week, num)
|
|
||||||
}
|
|
||||||
if (den == 30 || den == 31) {
|
|
||||||
return resources.getString(R.string.x_times_per_month, num)
|
|
||||||
}
|
|
||||||
if (num == 1) {
|
|
||||||
if (den == 7) {
|
|
||||||
return resources.getString(R.string.every_week)
|
|
||||||
}
|
|
||||||
if (den % 7 == 0) {
|
|
||||||
return resources.getString(R.string.every_x_weeks, den / 7)
|
|
||||||
}
|
|
||||||
if (den == 30 || den == 31) {
|
|
||||||
return resources.getString(R.string.every_month)
|
|
||||||
}
|
|
||||||
return resources.getString(R.string.every_x_days, den)
|
|
||||||
}
|
|
||||||
return "$num/$den"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,15 +23,15 @@ import android.content.res.Resources
|
|||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
|
import org.isoron.platform.gui.toInt
|
||||||
import org.isoron.uhabits.R
|
import org.isoron.uhabits.R
|
||||||
import org.isoron.uhabits.core.ui.screens.habits.show.views.TargetCardState
|
import org.isoron.uhabits.core.ui.screens.habits.show.views.TargetCardState
|
||||||
import org.isoron.uhabits.databinding.ShowHabitTargetBinding
|
import org.isoron.uhabits.databinding.ShowHabitTargetBinding
|
||||||
import org.isoron.uhabits.utils.toThemedAndroidColor
|
|
||||||
|
|
||||||
class TargetCardView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) {
|
class TargetCardView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) {
|
||||||
private val binding = ShowHabitTargetBinding.inflate(LayoutInflater.from(context), this)
|
private val binding = ShowHabitTargetBinding.inflate(LayoutInflater.from(context), this)
|
||||||
fun setState(state: TargetCardState) {
|
fun setState(state: TargetCardState) {
|
||||||
val androidColor = state.color.toThemedAndroidColor(context)
|
val androidColor = state.theme.color(state.color).toInt()
|
||||||
binding.targetChart.setValues(state.values)
|
binding.targetChart.setValues(state.values)
|
||||||
binding.targetChart.setTargets(state.targets)
|
binding.targetChart.setTargets(state.targets)
|
||||||
binding.targetChart.setLabels(state.intervals.map { intervalToLabel(resources, it) })
|
binding.targetChart.setLabels(state.intervals.map { intervalToLabel(resources, it) })
|
||||||
|
|||||||
@@ -32,13 +32,15 @@ class SettingsActivity : AppCompatActivity() {
|
|||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
val component = (application as HabitsApplication).component
|
val component = (application as HabitsApplication).component
|
||||||
AndroidThemeSwitcher(this, component.preferences).apply()
|
val themeSwitcher = AndroidThemeSwitcher(this, component.preferences)
|
||||||
|
themeSwitcher.apply()
|
||||||
|
|
||||||
val binding = SettingsActivityBinding.inflate(LayoutInflater.from(this))
|
val binding = SettingsActivityBinding.inflate(LayoutInflater.from(this))
|
||||||
binding.root.setupToolbar(
|
binding.root.setupToolbar(
|
||||||
toolbar = binding.toolbar,
|
toolbar = binding.toolbar,
|
||||||
title = resources.getString(R.string.settings),
|
title = resources.getString(R.string.settings),
|
||||||
color = PaletteColor(11),
|
color = PaletteColor(11),
|
||||||
|
theme = themeSwitcher.currentTheme,
|
||||||
)
|
)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
package org.isoron.uhabits.activities.settings
|
package org.isoron.uhabits.activities.settings
|
||||||
|
|
||||||
import android.app.backup.BackupManager
|
import android.app.backup.BackupManager
|
||||||
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
|
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
|
||||||
@@ -27,6 +28,7 @@ import android.os.Build.VERSION
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import androidx.preference.CheckBoxPreference
|
||||||
import androidx.preference.ListPreference
|
import androidx.preference.ListPreference
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import androidx.preference.PreferenceCategory
|
import androidx.preference.PreferenceCategory
|
||||||
@@ -41,6 +43,7 @@ import org.isoron.uhabits.activities.habits.list.RESULT_REPAIR_DB
|
|||||||
import org.isoron.uhabits.core.preferences.Preferences
|
import org.isoron.uhabits.core.preferences.Preferences
|
||||||
import org.isoron.uhabits.core.ui.NotificationTray
|
import org.isoron.uhabits.core.ui.NotificationTray
|
||||||
import org.isoron.uhabits.core.utils.DateUtils.Companion.getLongWeekdayNames
|
import org.isoron.uhabits.core.utils.DateUtils.Companion.getLongWeekdayNames
|
||||||
|
import org.isoron.uhabits.intents.IntentFactory
|
||||||
import org.isoron.uhabits.notifications.AndroidNotificationTray.Companion.createAndroidNotificationChannel
|
import org.isoron.uhabits.notifications.AndroidNotificationTray.Companion.createAndroidNotificationChannel
|
||||||
import org.isoron.uhabits.notifications.RingtoneManager
|
import org.isoron.uhabits.notifications.RingtoneManager
|
||||||
import org.isoron.uhabits.widgets.WidgetUpdater
|
import org.isoron.uhabits.widgets.WidgetUpdater
|
||||||
@@ -97,6 +100,13 @@ class SettingsFragment : PreferenceFragmentCompat(), OnSharedPreferenceChangeLis
|
|||||||
intent.putExtra(Settings.EXTRA_CHANNEL_ID, NotificationTray.REMINDERS_CHANNEL_ID)
|
intent.putExtra(Settings.EXTRA_CHANNEL_ID, NotificationTray.REMINDERS_CHANNEL_ID)
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
return true
|
return true
|
||||||
|
} else if (key == "pref_sync_enabled_dummy") {
|
||||||
|
if (prefs.isSyncEnabled) {
|
||||||
|
prefs.disableSync()
|
||||||
|
} else {
|
||||||
|
val context: Context? = activity
|
||||||
|
context!!.startActivity(IntentFactory().startSyncActivity(context))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return super.onPreferenceTreeClick(preference)
|
return super.onPreferenceTreeClick(preference)
|
||||||
}
|
}
|
||||||
@@ -111,6 +121,7 @@ class SettingsFragment : PreferenceFragmentCompat(), OnSharedPreferenceChangeLis
|
|||||||
devCategory.isVisible = false
|
devCategory.isVisible = false
|
||||||
}
|
}
|
||||||
updateWeekdayPreference()
|
updateWeekdayPreference()
|
||||||
|
updateSyncPreferences()
|
||||||
|
|
||||||
if (VERSION.SDK_INT < Build.VERSION_CODES.O)
|
if (VERSION.SDK_INT < Build.VERSION_CODES.O)
|
||||||
findPreference("reminderCustomize").isVisible = false
|
findPreference("reminderCustomize").isVisible = false
|
||||||
@@ -119,6 +130,12 @@ class SettingsFragment : PreferenceFragmentCompat(), OnSharedPreferenceChangeLis
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun updateSyncPreferences() {
|
||||||
|
findPreference("pref_sync_display").isVisible = prefs.isSyncEnabled
|
||||||
|
(findPreference("pref_sync_enabled_dummy") as CheckBoxPreference).isChecked =
|
||||||
|
prefs.isSyncEnabled
|
||||||
|
}
|
||||||
|
|
||||||
private fun updateWeekdayPreference() {
|
private fun updateWeekdayPreference() {
|
||||||
val weekdayPref = findPreference("pref_first_weekday") as ListPreference
|
val weekdayPref = findPreference("pref_first_weekday") as ListPreference
|
||||||
val currentFirstWeekday = prefs.firstWeekday.daysSinceSunday + 1
|
val currentFirstWeekday = prefs.firstWeekday.daysSinceSunday + 1
|
||||||
@@ -140,6 +157,7 @@ class SettingsFragment : PreferenceFragmentCompat(), OnSharedPreferenceChangeLis
|
|||||||
}
|
}
|
||||||
BackupManager.dataChanged("org.isoron.uhabits")
|
BackupManager.dataChanged("org.isoron.uhabits")
|
||||||
updateWeekdayPreference()
|
updateWeekdayPreference()
|
||||||
|
updateSyncPreferences()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setResultOnPreferenceClick(key: String, result: Int) {
|
private fun setResultOnPreferenceClick(key: String, result: Int) {
|
||||||
|
|||||||
@@ -0,0 +1,132 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016-2021 Álinson Santos Xavier <git@axavier.org>
|
||||||
|
*
|
||||||
|
* This file is part of Loop Habit Tracker.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by the
|
||||||
|
* Free Software Foundation, either version 3 of the License, or (at your
|
||||||
|
* option) any later version.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.isoron.uhabits.activities.sync
|
||||||
|
|
||||||
|
import android.content.ClipData
|
||||||
|
import android.content.ClipboardManager
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.text.Html
|
||||||
|
import android.view.View
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import com.google.zxing.BarcodeFormat
|
||||||
|
import com.google.zxing.qrcode.QRCodeWriter
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.invoke
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.isoron.uhabits.HabitsApplication
|
||||||
|
import org.isoron.uhabits.R
|
||||||
|
import org.isoron.uhabits.activities.AndroidThemeSwitcher
|
||||||
|
import org.isoron.uhabits.core.models.PaletteColor
|
||||||
|
import org.isoron.uhabits.core.ui.screens.sync.SyncBehavior
|
||||||
|
import org.isoron.uhabits.databinding.ActivitySyncBinding
|
||||||
|
import org.isoron.uhabits.sync.RemoteSyncServer
|
||||||
|
import org.isoron.uhabits.utils.InterfaceUtils.getFontAwesome
|
||||||
|
import org.isoron.uhabits.utils.setupToolbar
|
||||||
|
import org.isoron.uhabits.utils.showMessage
|
||||||
|
|
||||||
|
class SyncActivity : AppCompatActivity(), SyncBehavior.Screen {
|
||||||
|
|
||||||
|
private lateinit var binding: ActivitySyncBinding
|
||||||
|
private lateinit var behavior: SyncBehavior
|
||||||
|
|
||||||
|
private val scope = CoroutineScope(Dispatchers.Main)
|
||||||
|
|
||||||
|
override fun onCreate(savedInstance: Bundle?) {
|
||||||
|
super.onCreate(savedInstance)
|
||||||
|
val component = (application as HabitsApplication).component
|
||||||
|
val preferences = component.preferences
|
||||||
|
val server = RemoteSyncServer(preferences = preferences)
|
||||||
|
val themeSwitcher = AndroidThemeSwitcher(this, component.preferences)
|
||||||
|
themeSwitcher.apply()
|
||||||
|
|
||||||
|
behavior = SyncBehavior(this, preferences, server, component.logging)
|
||||||
|
binding = ActivitySyncBinding.inflate(layoutInflater)
|
||||||
|
binding.errorIcon.typeface = getFontAwesome(this)
|
||||||
|
binding.root.setupToolbar(
|
||||||
|
toolbar = binding.toolbar,
|
||||||
|
color = PaletteColor(11),
|
||||||
|
title = resources.getString(R.string.device_sync),
|
||||||
|
theme = themeSwitcher.currentTheme,
|
||||||
|
)
|
||||||
|
binding.syncLink.setOnClickListener { copyToClipboard() }
|
||||||
|
binding.instructions.text = Html.fromHtml(resources.getString(R.string.sync_instructions))
|
||||||
|
setContentView(binding.root)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
scope.launch {
|
||||||
|
behavior.onResume()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun copyToClipboard() {
|
||||||
|
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||||
|
clipboard.setPrimaryClip(ClipData.newPlainText("Loop Sync Link", binding.syncLink.text))
|
||||||
|
showMessage(resources.getString(R.string.copied_to_the_clipboard))
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun generateQR(msg: String): Bitmap = Dispatchers.IO {
|
||||||
|
val writer = QRCodeWriter()
|
||||||
|
val matrix = writer.encode(msg, BarcodeFormat.QR_CODE, 1024, 1024)
|
||||||
|
val height = matrix.height
|
||||||
|
val width = matrix.width
|
||||||
|
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565)
|
||||||
|
val bgColor = Color.WHITE
|
||||||
|
val fgColor = Color.BLACK
|
||||||
|
for (x in 0 until width) {
|
||||||
|
for (y in 0 until height) {
|
||||||
|
val color = if (matrix.get(x, y)) fgColor else bgColor
|
||||||
|
bitmap.setPixel(x, y, color)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return@IO bitmap
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun showQR(msg: String) {
|
||||||
|
binding.progress.visibility = View.GONE
|
||||||
|
binding.qrCode.visibility = View.VISIBLE
|
||||||
|
binding.qrCode.setImageBitmap(generateQR(msg))
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun showLoadingScreen() {
|
||||||
|
binding.qrCode.visibility = View.GONE
|
||||||
|
binding.progress.visibility = View.VISIBLE
|
||||||
|
binding.errorPanel.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun showErrorScreen() {
|
||||||
|
binding.qrCode.visibility = View.GONE
|
||||||
|
binding.progress.visibility = View.GONE
|
||||||
|
binding.errorPanel.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun showLink(link: String) {
|
||||||
|
binding.qrCode.visibility = View.GONE
|
||||||
|
binding.progress.visibility = View.VISIBLE
|
||||||
|
binding.errorPanel.visibility = View.GONE
|
||||||
|
binding.syncLink.text = link
|
||||||
|
showQR(link)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,6 +33,7 @@ import org.isoron.uhabits.core.models.Habit
|
|||||||
import org.isoron.uhabits.core.models.HabitList
|
import org.isoron.uhabits.core.models.HabitList
|
||||||
import org.isoron.uhabits.core.models.PaletteColor
|
import org.isoron.uhabits.core.models.PaletteColor
|
||||||
import org.isoron.uhabits.databinding.AutomationBinding
|
import org.isoron.uhabits.databinding.AutomationBinding
|
||||||
|
import org.isoron.uhabits.utils.currentTheme
|
||||||
import org.isoron.uhabits.utils.setupToolbar
|
import org.isoron.uhabits.utils.setupToolbar
|
||||||
import java.util.LinkedList
|
import java.util.LinkedList
|
||||||
|
|
||||||
@@ -53,6 +54,7 @@ class EditSettingRootView(
|
|||||||
title = resources.getString(R.string.app_name),
|
title = resources.getString(R.string.app_name),
|
||||||
color = PaletteColor(11),
|
color = PaletteColor(11),
|
||||||
displayHomeAsUpEnabled = false,
|
displayHomeAsUpEnabled = false,
|
||||||
|
theme = currentTheme(),
|
||||||
)
|
)
|
||||||
populateHabitSpinner()
|
populateHabitSpinner()
|
||||||
binding.habitSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
binding.habitSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import org.isoron.uhabits.core.models.ModelFactory
|
|||||||
import org.isoron.uhabits.core.preferences.Preferences
|
import org.isoron.uhabits.core.preferences.Preferences
|
||||||
import org.isoron.uhabits.core.preferences.WidgetPreferences
|
import org.isoron.uhabits.core.preferences.WidgetPreferences
|
||||||
import org.isoron.uhabits.core.reminders.ReminderScheduler
|
import org.isoron.uhabits.core.reminders.ReminderScheduler
|
||||||
|
import org.isoron.uhabits.core.sync.SyncManager
|
||||||
import org.isoron.uhabits.core.tasks.TaskRunner
|
import org.isoron.uhabits.core.tasks.TaskRunner
|
||||||
import org.isoron.uhabits.core.ui.NotificationTray
|
import org.isoron.uhabits.core.ui.NotificationTray
|
||||||
import org.isoron.uhabits.core.ui.screens.habits.list.HabitCardListCache
|
import org.isoron.uhabits.core.ui.screens.habits.list.HabitCardListCache
|
||||||
@@ -63,4 +64,5 @@ interface HabitsApplicationComponent {
|
|||||||
val taskRunner: TaskRunner
|
val taskRunner: TaskRunner
|
||||||
val widgetPreferences: WidgetPreferences
|
val widgetPreferences: WidgetPreferences
|
||||||
val widgetUpdater: WidgetUpdater
|
val widgetUpdater: WidgetUpdater
|
||||||
|
val syncManager: SyncManager
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
package org.isoron.uhabits.inject
|
package org.isoron.uhabits.inject
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import org.isoron.uhabits.core.AppScope
|
import org.isoron.uhabits.core.AppScope
|
||||||
@@ -33,6 +34,8 @@ import org.isoron.uhabits.core.models.sqlite.SQLiteHabitList
|
|||||||
import org.isoron.uhabits.core.preferences.Preferences
|
import org.isoron.uhabits.core.preferences.Preferences
|
||||||
import org.isoron.uhabits.core.preferences.WidgetPreferences
|
import org.isoron.uhabits.core.preferences.WidgetPreferences
|
||||||
import org.isoron.uhabits.core.reminders.ReminderScheduler
|
import org.isoron.uhabits.core.reminders.ReminderScheduler
|
||||||
|
import org.isoron.uhabits.core.sync.AbstractSyncServer
|
||||||
|
import org.isoron.uhabits.core.sync.NetworkManager
|
||||||
import org.isoron.uhabits.core.tasks.TaskRunner
|
import org.isoron.uhabits.core.tasks.TaskRunner
|
||||||
import org.isoron.uhabits.core.ui.NotificationTray
|
import org.isoron.uhabits.core.ui.NotificationTray
|
||||||
import org.isoron.uhabits.database.AndroidDatabase
|
import org.isoron.uhabits.database.AndroidDatabase
|
||||||
@@ -41,6 +44,8 @@ import org.isoron.uhabits.intents.IntentScheduler
|
|||||||
import org.isoron.uhabits.io.AndroidLogging
|
import org.isoron.uhabits.io.AndroidLogging
|
||||||
import org.isoron.uhabits.notifications.AndroidNotificationTray
|
import org.isoron.uhabits.notifications.AndroidNotificationTray
|
||||||
import org.isoron.uhabits.preferences.SharedPreferencesStorage
|
import org.isoron.uhabits.preferences.SharedPreferencesStorage
|
||||||
|
import org.isoron.uhabits.sync.AndroidNetworkManager
|
||||||
|
import org.isoron.uhabits.sync.RemoteSyncServer
|
||||||
import org.isoron.uhabits.utils.DatabaseUtils
|
import org.isoron.uhabits.utils.DatabaseUtils
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
@@ -109,6 +114,18 @@ class HabitsModule(dbFile: File) {
|
|||||||
return AndroidLogging()
|
return AndroidLogging()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@AppScope
|
||||||
|
fun getNetworkManager(@AppContext context: Context): NetworkManager {
|
||||||
|
return AndroidNetworkManager(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@AppScope
|
||||||
|
fun getSyncServer(preferences: Preferences): AbstractSyncServer {
|
||||||
|
return RemoteSyncServer(preferences)
|
||||||
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@AppScope
|
@AppScope
|
||||||
fun getDatabase(): Database {
|
fun getDatabase(): Database {
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import org.isoron.uhabits.activities.habits.edit.EditHabitActivity
|
|||||||
import org.isoron.uhabits.activities.habits.show.ShowHabitActivity
|
import org.isoron.uhabits.activities.habits.show.ShowHabitActivity
|
||||||
import org.isoron.uhabits.activities.intro.IntroActivity
|
import org.isoron.uhabits.activities.intro.IntroActivity
|
||||||
import org.isoron.uhabits.activities.settings.SettingsActivity
|
import org.isoron.uhabits.activities.settings.SettingsActivity
|
||||||
|
import org.isoron.uhabits.activities.sync.SyncActivity
|
||||||
import org.isoron.uhabits.core.models.Habit
|
import org.isoron.uhabits.core.models.Habit
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@@ -100,4 +101,8 @@ class IntentFactory
|
|||||||
intent.putExtra("habitType", habitType)
|
intent.putExtra("habitType", habitType)
|
||||||
return intent
|
return intent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun startSyncActivity(context: Context): Intent {
|
||||||
|
return Intent(context, SyncActivity::class.java)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,13 +63,14 @@ class PendingIntentFactory
|
|||||||
FLAG_UPDATE_CURRENT
|
FLAG_UPDATE_CURRENT
|
||||||
)
|
)
|
||||||
|
|
||||||
fun removeRepetition(habit: Habit): PendingIntent =
|
fun removeRepetition(habit: Habit, timestamp: Timestamp?): PendingIntent =
|
||||||
getBroadcast(
|
getBroadcast(
|
||||||
context,
|
context,
|
||||||
3,
|
3,
|
||||||
Intent(context, WidgetReceiver::class.java).apply {
|
Intent(context, WidgetReceiver::class.java).apply {
|
||||||
action = WidgetReceiver.ACTION_REMOVE_REPETITION
|
action = WidgetReceiver.ACTION_REMOVE_REPETITION
|
||||||
data = Uri.parse(habit.uriString)
|
data = Uri.parse(habit.uriString)
|
||||||
|
if (timestamp != null) putExtra("timestamp", timestamp.unixTime)
|
||||||
},
|
},
|
||||||
FLAG_UPDATE_CURRENT
|
FLAG_UPDATE_CURRENT
|
||||||
)
|
)
|
||||||
@@ -132,17 +133,17 @@ class PendingIntentFactory
|
|||||||
timestamp: Long?
|
timestamp: Long?
|
||||||
):
|
):
|
||||||
PendingIntent =
|
PendingIntent =
|
||||||
getBroadcast(
|
getBroadcast(
|
||||||
widgetContext,
|
widgetContext,
|
||||||
2,
|
2,
|
||||||
Intent(widgetContext, WidgetReceiver::class.java).apply {
|
Intent(widgetContext, WidgetReceiver::class.java).apply {
|
||||||
data = Uri.parse(habit.uriString)
|
data = Uri.parse(habit.uriString)
|
||||||
action = WidgetReceiver.ACTION_SET_NUMERICAL_VALUE
|
action = WidgetReceiver.ACTION_SET_NUMERICAL_VALUE
|
||||||
putExtra("numericalValue", numericalValue)
|
putExtra("numericalValue", numericalValue)
|
||||||
if (timestamp != null) putExtra("timestamp", timestamp)
|
if (timestamp != null) putExtra("timestamp", timestamp)
|
||||||
},
|
},
|
||||||
FLAG_UPDATE_CURRENT
|
FLAG_UPDATE_CURRENT
|
||||||
)
|
)
|
||||||
|
|
||||||
fun updateWidgets(): PendingIntent =
|
fun updateWidgets(): PendingIntent =
|
||||||
getBroadcast(
|
getBroadcast(
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ class AndroidNotificationTray
|
|||||||
val removeRepetitionAction = Action(
|
val removeRepetitionAction = Action(
|
||||||
R.drawable.ic_action_cancel,
|
R.drawable.ic_action_cancel,
|
||||||
context.getString(R.string.no),
|
context.getString(R.string.no),
|
||||||
pendingIntents.removeRepetition(habit)
|
pendingIntents.removeRepetition(habit, timestamp)
|
||||||
)
|
)
|
||||||
|
|
||||||
val enterAction = Action(
|
val enterAction = Action(
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import android.widget.AdapterView.OnItemClickListener
|
|||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
import com.android.datetimepicker.time.RadialPickerLayout
|
import com.android.datetimepicker.time.RadialPickerLayout
|
||||||
import com.android.datetimepicker.time.TimePickerDialog
|
import com.android.datetimepicker.time.TimePickerDialog
|
||||||
|
import org.isoron.platform.gui.toInt
|
||||||
import org.isoron.uhabits.HabitsApplication
|
import org.isoron.uhabits.HabitsApplication
|
||||||
import org.isoron.uhabits.R
|
import org.isoron.uhabits.R
|
||||||
import org.isoron.uhabits.activities.AndroidThemeSwitcher
|
import org.isoron.uhabits.activities.AndroidThemeSwitcher
|
||||||
@@ -35,14 +36,13 @@ import org.isoron.uhabits.core.models.Habit
|
|||||||
import org.isoron.uhabits.core.ui.ThemeSwitcher.Companion.THEME_LIGHT
|
import org.isoron.uhabits.core.ui.ThemeSwitcher.Companion.THEME_LIGHT
|
||||||
import org.isoron.uhabits.receivers.ReminderController
|
import org.isoron.uhabits.receivers.ReminderController
|
||||||
import org.isoron.uhabits.utils.SystemUtils
|
import org.isoron.uhabits.utils.SystemUtils
|
||||||
import org.isoron.uhabits.utils.toThemedAndroidColor
|
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
|
|
||||||
class SnoozeDelayPickerActivity : FragmentActivity(), OnItemClickListener {
|
class SnoozeDelayPickerActivity : FragmentActivity(), OnItemClickListener {
|
||||||
private var habit: Habit? = null
|
private var habit: Habit? = null
|
||||||
private var reminderController: ReminderController? = null
|
private var reminderController: ReminderController? = null
|
||||||
private var dialog: AlertDialog? = null
|
private var dialog: AlertDialog? = null
|
||||||
private var color: Int = 0
|
private var androidColor: Int = 0
|
||||||
|
|
||||||
override fun onCreate(bundle: Bundle?) {
|
override fun onCreate(bundle: Bundle?) {
|
||||||
super.onCreate(bundle)
|
super.onCreate(bundle)
|
||||||
@@ -63,7 +63,7 @@ class SnoozeDelayPickerActivity : FragmentActivity(), OnItemClickListener {
|
|||||||
habit = appComponent.habitList.getById(ContentUris.parseId(data))
|
habit = appComponent.habitList.getById(ContentUris.parseId(data))
|
||||||
}
|
}
|
||||||
if (habit == null) finish()
|
if (habit == null) finish()
|
||||||
color = habit!!.color.toThemedAndroidColor(this)
|
androidColor = themeSwitcher.currentTheme.color(habit!!.color).toInt()
|
||||||
reminderController = appComponent.reminderController
|
reminderController = appComponent.reminderController
|
||||||
dialog = AlertDialog.Builder(this)
|
dialog = AlertDialog.Builder(this)
|
||||||
.setTitle(R.string.select_snooze_delay)
|
.setTitle(R.string.select_snooze_delay)
|
||||||
@@ -85,7 +85,7 @@ class SnoozeDelayPickerActivity : FragmentActivity(), OnItemClickListener {
|
|||||||
calendar[Calendar.HOUR_OF_DAY],
|
calendar[Calendar.HOUR_OF_DAY],
|
||||||
calendar[Calendar.MINUTE],
|
calendar[Calendar.MINUTE],
|
||||||
DateFormat.is24HourFormat(this),
|
DateFormat.is24HourFormat(this),
|
||||||
color
|
androidColor
|
||||||
)
|
)
|
||||||
dialog.show(supportFragmentManager, "timePicker")
|
dialog.show(supportFragmentManager, "timePicker")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,6 +92,9 @@ class SharedPreferencesStorage
|
|||||||
preferences.setNotificationsSticky(getBoolean(key, false))
|
preferences.setNotificationsSticky(getBoolean(key, false))
|
||||||
"pref_led_notifications" ->
|
"pref_led_notifications" ->
|
||||||
preferences.setNotificationsLed(getBoolean(key, false))
|
preferences.setNotificationsLed(getBoolean(key, false))
|
||||||
|
"pref_unknown_enabled" -> {
|
||||||
|
preferences.areQuestionMarksEnabled = getBoolean(key, false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
sharedPreferences.registerOnSharedPreferenceChangeListener(this)
|
sharedPreferences.registerOnSharedPreferenceChangeListener(this)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016-2021 Álinson Santos Xavier <git@axavier.org>
|
||||||
|
*
|
||||||
|
* This file is part of Loop Habit Tracker.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by the
|
||||||
|
* Free Software Foundation, either version 3 of the License, or (at your
|
||||||
|
* option) any later version.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.isoron.uhabits.sync
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.ConnectivityManager
|
||||||
|
import android.net.Network
|
||||||
|
import android.net.NetworkRequest
|
||||||
|
import org.isoron.uhabits.core.sync.NetworkManager
|
||||||
|
|
||||||
|
class AndroidNetworkManager(
|
||||||
|
val context: Context,
|
||||||
|
) : NetworkManager, ConnectivityManager.NetworkCallback() {
|
||||||
|
|
||||||
|
val listeners = mutableListOf<NetworkManager.Listener>()
|
||||||
|
var connected = false
|
||||||
|
|
||||||
|
init {
|
||||||
|
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||||
|
cm.registerNetworkCallback(NetworkRequest.Builder().build(), this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addListener(listener: NetworkManager.Listener) {
|
||||||
|
if (connected) listener.onNetworkAvailable()
|
||||||
|
else listener.onNetworkLost()
|
||||||
|
listeners.add(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun remoteListener(listener: NetworkManager.Listener) {
|
||||||
|
listeners.remove(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAvailable(network: Network) {
|
||||||
|
connected = true
|
||||||
|
for (l in listeners) l.onNetworkAvailable()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLost(network: Network) {
|
||||||
|
connected = false
|
||||||
|
for (l in listeners) l.onNetworkLost()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016-2021 Álinson Santos Xavier <git@axavier.org>
|
||||||
|
*
|
||||||
|
* This file is part of Loop Habit Tracker.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by the
|
||||||
|
* Free Software Foundation, either version 3 of the License, or (at your
|
||||||
|
* option) any later version.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.isoron.uhabits.sync
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import io.ktor.client.HttpClient
|
||||||
|
import io.ktor.client.engine.android.Android
|
||||||
|
import io.ktor.client.features.ClientRequestException
|
||||||
|
import io.ktor.client.features.ServerResponseException
|
||||||
|
import io.ktor.client.features.json.JsonFeature
|
||||||
|
import io.ktor.client.request.get
|
||||||
|
import io.ktor.client.request.header
|
||||||
|
import io.ktor.client.request.post
|
||||||
|
import io.ktor.client.request.put
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.invoke
|
||||||
|
import org.isoron.uhabits.core.preferences.Preferences
|
||||||
|
import org.isoron.uhabits.core.sync.AbstractSyncServer
|
||||||
|
import org.isoron.uhabits.core.sync.EditConflictException
|
||||||
|
import org.isoron.uhabits.core.sync.GetDataVersionResponse
|
||||||
|
import org.isoron.uhabits.core.sync.KeyNotFoundException
|
||||||
|
import org.isoron.uhabits.core.sync.RegisterReponse
|
||||||
|
import org.isoron.uhabits.core.sync.ServiceUnavailable
|
||||||
|
import org.isoron.uhabits.core.sync.SyncData
|
||||||
|
|
||||||
|
class RemoteSyncServer(
|
||||||
|
private val preferences: Preferences,
|
||||||
|
private val httpClient: HttpClient = HttpClient(Android) {
|
||||||
|
install(JsonFeature)
|
||||||
|
}
|
||||||
|
) : AbstractSyncServer {
|
||||||
|
|
||||||
|
override suspend fun register(): String = Dispatchers.IO {
|
||||||
|
try {
|
||||||
|
val url = "${preferences.syncBaseURL}/register"
|
||||||
|
Log.i("RemoteSyncServer", "POST $url")
|
||||||
|
val response: RegisterReponse = httpClient.post(url)
|
||||||
|
return@IO response.key
|
||||||
|
} catch (e: ServerResponseException) {
|
||||||
|
throw ServiceUnavailable()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun put(key: String, newData: SyncData) = Dispatchers.IO {
|
||||||
|
try {
|
||||||
|
val url = "${preferences.syncBaseURL}/db/$key"
|
||||||
|
Log.i("RemoteSyncServer", "PUT $url")
|
||||||
|
val response: String = httpClient.put(url) {
|
||||||
|
header("Content-Type", "application/json")
|
||||||
|
body = newData
|
||||||
|
}
|
||||||
|
} catch (e: ServerResponseException) {
|
||||||
|
throw ServiceUnavailable()
|
||||||
|
} catch (e: ClientRequestException) {
|
||||||
|
Log.w("RemoteSyncServer", "ClientRequestException", e)
|
||||||
|
if (e.message!!.contains("409")) throw EditConflictException()
|
||||||
|
if (e.message!!.contains("404")) throw KeyNotFoundException()
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getData(key: String): SyncData = Dispatchers.IO {
|
||||||
|
try {
|
||||||
|
val url = "${preferences.syncBaseURL}/db/$key"
|
||||||
|
Log.i("RemoteSyncServer", "GET $url")
|
||||||
|
return@IO httpClient.get<SyncData>(url)
|
||||||
|
} catch (e: ServerResponseException) {
|
||||||
|
throw ServiceUnavailable()
|
||||||
|
} catch (e: ClientRequestException) {
|
||||||
|
Log.w("RemoteSyncServer", "ClientRequestException", e)
|
||||||
|
throw KeyNotFoundException()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getDataVersion(key: String): Long = Dispatchers.IO {
|
||||||
|
try {
|
||||||
|
val url = "${preferences.syncBaseURL}/db/$key/version"
|
||||||
|
Log.i("RemoteSyncServer", "GET $url")
|
||||||
|
val response: GetDataVersionResponse = httpClient.get(url)
|
||||||
|
return@IO response.version
|
||||||
|
} catch (e: ServerResponseException) {
|
||||||
|
throw ServiceUnavailable()
|
||||||
|
} catch (e: ClientRequestException) {
|
||||||
|
Log.w("RemoteSyncServer", "ClientRequestException", e)
|
||||||
|
throw KeyNotFoundException()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,7 +21,6 @@ package org.isoron.uhabits.utils
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.util.Log
|
|
||||||
import org.isoron.uhabits.core.models.PaletteColor
|
import org.isoron.uhabits.core.models.PaletteColor
|
||||||
|
|
||||||
object PaletteUtils {
|
object PaletteUtils {
|
||||||
@@ -29,16 +28,6 @@ object PaletteUtils {
|
|||||||
fun getAndroidTestColor(index: Int) = PaletteColor(index).toFixedAndroidColor()
|
fun getAndroidTestColor(index: Int) = PaletteColor(index).toFixedAndroidColor()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun PaletteColor.toThemedAndroidColor(context: Context): Int {
|
|
||||||
val palette = StyledResources(context).getPalette()
|
|
||||||
return if (paletteIndex in palette.indices) {
|
|
||||||
palette[paletteIndex]
|
|
||||||
} else {
|
|
||||||
Log.w("ColorHelper", "Invalid color: $paletteIndex. Returning default.")
|
|
||||||
palette[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun PaletteColor.toFixedAndroidColor(): Int {
|
fun PaletteColor.toFixedAndroidColor(): Int {
|
||||||
return intArrayOf(
|
return intArrayOf(
|
||||||
Color.parseColor("#D32F2F"), // 0 red
|
Color.parseColor("#D32F2F"), // 0 red
|
||||||
|
|||||||
@@ -40,8 +40,12 @@ import androidx.appcompat.app.AppCompatActivity
|
|||||||
import androidx.appcompat.widget.Toolbar
|
import androidx.appcompat.widget.Toolbar
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
import org.isoron.platform.gui.toInt
|
||||||
|
import org.isoron.uhabits.HabitsApplication
|
||||||
import org.isoron.uhabits.R
|
import org.isoron.uhabits.R
|
||||||
|
import org.isoron.uhabits.activities.AndroidThemeSwitcher
|
||||||
import org.isoron.uhabits.core.models.PaletteColor
|
import org.isoron.uhabits.core.models.PaletteColor
|
||||||
|
import org.isoron.uhabits.core.ui.views.Theme
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
fun RelativeLayout.addBelow(
|
fun RelativeLayout.addBelow(
|
||||||
@@ -157,6 +161,7 @@ fun View.setupToolbar(
|
|||||||
toolbar: Toolbar,
|
toolbar: Toolbar,
|
||||||
title: String,
|
title: String,
|
||||||
color: PaletteColor,
|
color: PaletteColor,
|
||||||
|
theme: Theme,
|
||||||
displayHomeAsUpEnabled: Boolean = true,
|
displayHomeAsUpEnabled: Boolean = true,
|
||||||
) {
|
) {
|
||||||
toolbar.elevation = InterfaceUtils.dpToPixels(context, 2f)
|
toolbar.elevation = InterfaceUtils.dpToPixels(context, 2f)
|
||||||
@@ -165,7 +170,7 @@ fun View.setupToolbar(
|
|||||||
val toolbarColor = if (!res.getBoolean(R.attr.useHabitColorAsPrimary)) {
|
val toolbarColor = if (!res.getBoolean(R.attr.useHabitColorAsPrimary)) {
|
||||||
StyledResources(context).getColor(R.attr.colorPrimary)
|
StyledResources(context).getColor(R.attr.colorPrimary)
|
||||||
} else {
|
} else {
|
||||||
color.toThemedAndroidColor(context)
|
theme.color(color).toInt()
|
||||||
}
|
}
|
||||||
val darkerColor = ColorUtils.mixColors(toolbarColor, Color.BLACK, 0.75f)
|
val darkerColor = ColorUtils.mixColors(toolbarColor, Color.BLACK, 0.75f)
|
||||||
toolbar.background = ColorDrawable(toolbarColor)
|
toolbar.background = ColorDrawable(toolbarColor)
|
||||||
@@ -175,6 +180,13 @@ fun View.setupToolbar(
|
|||||||
activity.supportActionBar?.setDisplayHomeAsUpEnabled(displayHomeAsUpEnabled)
|
activity.supportActionBar?.setDisplayHomeAsUpEnabled(displayHomeAsUpEnabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun View.currentTheme(): Theme {
|
||||||
|
val component = (context.applicationContext as HabitsApplication).component
|
||||||
|
val themeSwitcher = AndroidThemeSwitcher(context, component.preferences)
|
||||||
|
themeSwitcher.apply()
|
||||||
|
return themeSwitcher.currentTheme
|
||||||
|
}
|
||||||
|
|
||||||
fun Int.toMeasureSpec(mode: Int) =
|
fun Int.toMeasureSpec(mode: Int) =
|
||||||
View.MeasureSpec.makeMeasureSpec(this, mode)
|
View.MeasureSpec.makeMeasureSpec(this, mode)
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import org.isoron.uhabits.core.commands.CommandRunner
|
|||||||
import org.isoron.uhabits.core.preferences.Preferences
|
import org.isoron.uhabits.core.preferences.Preferences
|
||||||
import org.isoron.uhabits.core.preferences.WidgetPreferences
|
import org.isoron.uhabits.core.preferences.WidgetPreferences
|
||||||
import org.isoron.uhabits.intents.PendingIntentFactory
|
import org.isoron.uhabits.intents.PendingIntentFactory
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
abstract class BaseWidget(val context: Context, val id: Int, val stacked: Boolean) {
|
abstract class BaseWidget(val context: Context, val id: Int, val stacked: Boolean) {
|
||||||
private val widgetPrefs: WidgetPreferences
|
private val widgetPrefs: WidgetPreferences
|
||||||
@@ -103,8 +104,8 @@ abstract class BaseWidget(val context: Context, val id: Int, val stacked: Boolea
|
|||||||
|
|
||||||
private fun getBitmapFromView(view: View): Bitmap {
|
private fun getBitmapFromView(view: View): Bitmap {
|
||||||
view.invalidate()
|
view.invalidate()
|
||||||
val width = view.measuredWidth
|
val width = max(1, view.measuredWidth)
|
||||||
val height = view.measuredHeight
|
val height = max(1, view.measuredHeight)
|
||||||
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
||||||
val canvas = Canvas(bitmap)
|
val canvas = Canvas(bitmap)
|
||||||
view.draw(canvas)
|
view.draw(canvas)
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ abstract class BaseWidgetProvider : AppWidgetProvider() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected fun getHabitsFromWidgetId(widgetId: Int): List<Habit> {
|
protected fun getHabitsFromWidgetId(widgetId: Int): List<Habit> {
|
||||||
val selectedIds = widgetPrefs.getHabitIdsFromWidgetId(widgetId)!!
|
val selectedIds = widgetPrefs.getHabitIdsFromWidgetId(widgetId)
|
||||||
val selectedHabits = ArrayList<Habit>(selectedIds.size)
|
val selectedHabits = ArrayList<Habit>(selectedIds.size)
|
||||||
for (id in selectedIds) {
|
for (id in selectedIds) {
|
||||||
val h = habits.getById(id) ?: throw HabitNotFoundException()
|
val h = habits.getById(id) ?: throw HabitNotFoundException()
|
||||||
|
|||||||
@@ -24,10 +24,11 @@ import android.content.Context
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
|
import org.isoron.platform.gui.toInt
|
||||||
import org.isoron.uhabits.core.models.Entry
|
import org.isoron.uhabits.core.models.Entry
|
||||||
import org.isoron.uhabits.core.models.Habit
|
import org.isoron.uhabits.core.models.Habit
|
||||||
|
import org.isoron.uhabits.core.ui.views.WidgetTheme
|
||||||
import org.isoron.uhabits.core.utils.DateUtils
|
import org.isoron.uhabits.core.utils.DateUtils
|
||||||
import org.isoron.uhabits.utils.toThemedAndroidColor
|
|
||||||
import org.isoron.uhabits.widgets.views.CheckmarkWidgetView
|
import org.isoron.uhabits.widgets.views.CheckmarkWidgetView
|
||||||
|
|
||||||
open class CheckmarkWidget(
|
open class CheckmarkWidget(
|
||||||
@@ -53,7 +54,7 @@ open class CheckmarkWidget(
|
|||||||
(widgetView as CheckmarkWidgetView).apply {
|
(widgetView as CheckmarkWidgetView).apply {
|
||||||
val today = DateUtils.getTodayWithOffset()
|
val today = DateUtils.getTodayWithOffset()
|
||||||
setBackgroundAlpha(preferedBackgroundAlpha)
|
setBackgroundAlpha(preferedBackgroundAlpha)
|
||||||
activeColor = habit.color.toThemedAndroidColor(context)
|
activeColor = WidgetTheme().color(habit.color).toInt()
|
||||||
name = habit.name
|
name = habit.name
|
||||||
entryValue = habit.computedEntries.get(today).value
|
entryValue = habit.computedEntries.get(today).value
|
||||||
if (habit.isNumerical) {
|
if (habit.isNumerical) {
|
||||||
|
|||||||
@@ -22,9 +22,10 @@ package org.isoron.uhabits.widgets
|
|||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import org.isoron.platform.gui.toInt
|
||||||
import org.isoron.uhabits.activities.common.views.FrequencyChart
|
import org.isoron.uhabits.activities.common.views.FrequencyChart
|
||||||
import org.isoron.uhabits.core.models.Habit
|
import org.isoron.uhabits.core.models.Habit
|
||||||
import org.isoron.uhabits.utils.toThemedAndroidColor
|
import org.isoron.uhabits.core.ui.views.WidgetTheme
|
||||||
import org.isoron.uhabits.widgets.views.GraphWidgetView
|
import org.isoron.uhabits.widgets.views.GraphWidgetView
|
||||||
|
|
||||||
class FrequencyWidget(
|
class FrequencyWidget(
|
||||||
@@ -47,7 +48,7 @@ class FrequencyWidget(
|
|||||||
if (preferedBackgroundAlpha >= 255) widgetView.setShadowAlpha(0x4f)
|
if (preferedBackgroundAlpha >= 255) widgetView.setShadowAlpha(0x4f)
|
||||||
(widgetView.dataView as FrequencyChart).apply {
|
(widgetView.dataView as FrequencyChart).apply {
|
||||||
setFirstWeekday(firstWeekday)
|
setFirstWeekday(firstWeekday)
|
||||||
setColor(habit.color.toThemedAndroidColor(context))
|
setColor(WidgetTheme().color(habit.color).toInt())
|
||||||
setFrequency(habit.originalEntries.computeWeekdayFrequency(habit.isNumerical))
|
setFrequency(habit.originalEntries.computeWeekdayFrequency(habit.isNumerical))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,10 +22,11 @@ package org.isoron.uhabits.widgets
|
|||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import org.isoron.platform.gui.toInt
|
||||||
import org.isoron.uhabits.activities.common.views.ScoreChart
|
import org.isoron.uhabits.activities.common.views.ScoreChart
|
||||||
import org.isoron.uhabits.core.models.Habit
|
import org.isoron.uhabits.core.models.Habit
|
||||||
import org.isoron.uhabits.core.ui.screens.habits.show.views.ScoreCardPresenter
|
import org.isoron.uhabits.core.ui.screens.habits.show.views.ScoreCardPresenter
|
||||||
import org.isoron.uhabits.utils.toThemedAndroidColor
|
import org.isoron.uhabits.core.ui.views.WidgetTheme
|
||||||
import org.isoron.uhabits.widgets.views.GraphWidgetView
|
import org.isoron.uhabits.widgets.views.GraphWidgetView
|
||||||
|
|
||||||
class ScoreWidget(
|
class ScoreWidget(
|
||||||
@@ -44,7 +45,8 @@ class ScoreWidget(
|
|||||||
val viewModel = ScoreCardPresenter.buildState(
|
val viewModel = ScoreCardPresenter.buildState(
|
||||||
habit = habit,
|
habit = habit,
|
||||||
firstWeekday = prefs.firstWeekdayInt,
|
firstWeekday = prefs.firstWeekdayInt,
|
||||||
spinnerPosition = prefs.scoreCardSpinnerPosition
|
spinnerPosition = prefs.scoreCardSpinnerPosition,
|
||||||
|
theme = WidgetTheme(),
|
||||||
)
|
)
|
||||||
val widgetView = view as GraphWidgetView
|
val widgetView = view as GraphWidgetView
|
||||||
widgetView.setBackgroundAlpha(preferedBackgroundAlpha)
|
widgetView.setBackgroundAlpha(preferedBackgroundAlpha)
|
||||||
@@ -52,7 +54,7 @@ class ScoreWidget(
|
|||||||
(widgetView.dataView as ScoreChart).apply {
|
(widgetView.dataView as ScoreChart).apply {
|
||||||
setIsTransparencyEnabled(true)
|
setIsTransparencyEnabled(true)
|
||||||
setBucketSize(viewModel.bucketSize)
|
setBucketSize(viewModel.bucketSize)
|
||||||
setColor(habit.color.toThemedAndroidColor(context))
|
setColor(WidgetTheme().color(habit.color).toInt())
|
||||||
setScores(viewModel.scores)
|
setScores(viewModel.scores)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ internal class StackRemoteViewsFactory(private val context: Context, intent: Int
|
|||||||
AppWidgetManager.INVALID_APPWIDGET_ID
|
AppWidgetManager.INVALID_APPWIDGET_ID
|
||||||
)
|
)
|
||||||
private val habitIds: LongArray
|
private val habitIds: LongArray
|
||||||
private val widgetType: StackWidgetType?
|
private val widgetType: StackWidgetType
|
||||||
private var remoteViews = ArrayList<RemoteViews>()
|
private var remoteViews = ArrayList<RemoteViews>()
|
||||||
override fun onCreate() {}
|
override fun onCreate() {}
|
||||||
override fun onDestroy() {}
|
override fun onDestroy() {}
|
||||||
@@ -86,27 +86,27 @@ internal class StackRemoteViewsFactory(private val context: Context, intent: Int
|
|||||||
|
|
||||||
override fun getViewAt(position: Int): RemoteViews? {
|
override fun getViewAt(position: Int): RemoteViews? {
|
||||||
Log.i("StackRemoteViewsFactory", "getViewAt $position")
|
Log.i("StackRemoteViewsFactory", "getViewAt $position")
|
||||||
return if (position < 0 || position > remoteViews.size) null else remoteViews[position]
|
return if (0 <= position && position < remoteViews.size) remoteViews[position] else null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun constructWidget(
|
private fun constructWidget(
|
||||||
habit: Habit,
|
habit: Habit,
|
||||||
prefs: Preferences
|
prefs: Preferences
|
||||||
): BaseWidget {
|
): BaseWidget {
|
||||||
when (widgetType) {
|
return when (widgetType) {
|
||||||
StackWidgetType.CHECKMARK -> return CheckmarkWidget(context, widgetId, habit, true)
|
StackWidgetType.CHECKMARK -> CheckmarkWidget(context, widgetId, habit, true)
|
||||||
StackWidgetType.FREQUENCY -> return FrequencyWidget(
|
StackWidgetType.FREQUENCY -> FrequencyWidget(
|
||||||
context,
|
context,
|
||||||
widgetId,
|
widgetId,
|
||||||
habit,
|
habit,
|
||||||
prefs.firstWeekdayInt,
|
prefs.firstWeekdayInt,
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
StackWidgetType.SCORE -> return ScoreWidget(context, widgetId, habit, true)
|
StackWidgetType.SCORE -> ScoreWidget(context, widgetId, habit, true)
|
||||||
StackWidgetType.HISTORY -> return HistoryWidget(context, widgetId, habit, true)
|
StackWidgetType.HISTORY -> HistoryWidget(context, widgetId, habit, true)
|
||||||
StackWidgetType.STREAKS -> return StreakWidget(context, widgetId, habit, true)
|
StackWidgetType.STREAKS -> StreakWidget(context, widgetId, habit, true)
|
||||||
|
StackWidgetType.TARGET -> TargetWidget(context, widgetId, habit, true)
|
||||||
}
|
}
|
||||||
throw IllegalStateException()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getLoadingView(): RemoteViews {
|
override fun getLoadingView(): RemoteViews {
|
||||||
@@ -157,6 +157,7 @@ internal class StackRemoteViewsFactory(private val context: Context, intent: Int
|
|||||||
if (widgetTypeValue < 0) throw RuntimeException("invalid widget type")
|
if (widgetTypeValue < 0) throw RuntimeException("invalid widget type")
|
||||||
if (habitIdsStr == null) throw RuntimeException("habitIdsStr is null")
|
if (habitIdsStr == null) throw RuntimeException("habitIdsStr is null")
|
||||||
widgetType = StackWidgetType.getWidgetTypeFromValue(widgetTypeValue)
|
widgetType = StackWidgetType.getWidgetTypeFromValue(widgetTypeValue)
|
||||||
|
?: throw RuntimeException("unknown widget type value: $widgetTypeValue")
|
||||||
habitIds = splitLongs(habitIdsStr)
|
habitIds = splitLongs(habitIdsStr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,61 +19,59 @@
|
|||||||
package org.isoron.uhabits.widgets
|
package org.isoron.uhabits.widgets
|
||||||
|
|
||||||
import org.isoron.uhabits.R
|
import org.isoron.uhabits.R
|
||||||
|
import java.lang.IllegalStateException
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by victoryu on 11/3/17.
|
|
||||||
*/
|
|
||||||
enum class StackWidgetType(val value: Int) {
|
enum class StackWidgetType(val value: Int) {
|
||||||
CHECKMARK(0), FREQUENCY(1), SCORE(2), // habit strength widget
|
CHECKMARK(0), FREQUENCY(1), SCORE(2), // habit strength widget
|
||||||
HISTORY(3), STREAKS(4), TARGET(5);
|
HISTORY(3), STREAKS(4), TARGET(5);
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun getWidgetTypeFromValue(value: Int): StackWidgetType? {
|
fun getWidgetTypeFromValue(value: Int): StackWidgetType? {
|
||||||
return when {
|
return when (value) {
|
||||||
CHECKMARK.value == value -> CHECKMARK
|
CHECKMARK.value -> CHECKMARK
|
||||||
FREQUENCY.value == value -> FREQUENCY
|
FREQUENCY.value -> FREQUENCY
|
||||||
SCORE.value == value -> SCORE
|
SCORE.value -> SCORE
|
||||||
HISTORY.value == value -> HISTORY
|
HISTORY.value -> HISTORY
|
||||||
STREAKS.value == value -> STREAKS
|
STREAKS.value -> STREAKS
|
||||||
TARGET.value == value -> TARGET
|
TARGET.value -> TARGET
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getStackWidgetLayoutId(type: StackWidgetType?): Int {
|
fun getStackWidgetLayoutId(type: StackWidgetType?): Int {
|
||||||
when (type) {
|
return when (type) {
|
||||||
CHECKMARK -> return R.layout.checkmark_stackview_widget
|
CHECKMARK -> R.layout.checkmark_stackview_widget
|
||||||
FREQUENCY -> return R.layout.frequency_stackview_widget
|
FREQUENCY -> R.layout.frequency_stackview_widget
|
||||||
SCORE -> return R.layout.score_stackview_widget
|
SCORE -> R.layout.score_stackview_widget
|
||||||
HISTORY -> return R.layout.history_stackview_widget
|
HISTORY -> R.layout.history_stackview_widget
|
||||||
STREAKS -> return R.layout.streak_stackview_widget
|
STREAKS -> R.layout.streak_stackview_widget
|
||||||
TARGET -> return R.layout.target_stackview_widget
|
TARGET -> R.layout.target_stackview_widget
|
||||||
|
else -> throw IllegalStateException()
|
||||||
}
|
}
|
||||||
return 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getStackWidgetAdapterViewId(type: StackWidgetType?): Int {
|
fun getStackWidgetAdapterViewId(type: StackWidgetType?): Int {
|
||||||
when (type) {
|
return when (type) {
|
||||||
CHECKMARK -> return R.id.checkmarkStackWidgetView
|
CHECKMARK -> R.id.checkmarkStackWidgetView
|
||||||
FREQUENCY -> return R.id.frequencyStackWidgetView
|
FREQUENCY -> R.id.frequencyStackWidgetView
|
||||||
SCORE -> return R.id.scoreStackWidgetView
|
SCORE -> R.id.scoreStackWidgetView
|
||||||
HISTORY -> return R.id.historyStackWidgetView
|
HISTORY -> R.id.historyStackWidgetView
|
||||||
STREAKS -> return R.id.streakStackWidgetView
|
STREAKS -> R.id.streakStackWidgetView
|
||||||
TARGET -> return R.id.targetStackWidgetView
|
TARGET -> R.id.targetStackWidgetView
|
||||||
|
else -> throw IllegalStateException()
|
||||||
}
|
}
|
||||||
return 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getStackWidgetEmptyViewId(type: StackWidgetType?): Int {
|
fun getStackWidgetEmptyViewId(type: StackWidgetType?): Int {
|
||||||
when (type) {
|
return when (type) {
|
||||||
CHECKMARK -> return R.id.checkmarkStackWidgetEmptyView
|
CHECKMARK -> R.id.checkmarkStackWidgetEmptyView
|
||||||
FREQUENCY -> return R.id.frequencyStackWidgetEmptyView
|
FREQUENCY -> R.id.frequencyStackWidgetEmptyView
|
||||||
SCORE -> return R.id.scoreStackWidgetEmptyView
|
SCORE -> R.id.scoreStackWidgetEmptyView
|
||||||
HISTORY -> return R.id.historyStackWidgetEmptyView
|
HISTORY -> R.id.historyStackWidgetEmptyView
|
||||||
STREAKS -> return R.id.streakStackWidgetEmptyView
|
STREAKS -> R.id.streakStackWidgetEmptyView
|
||||||
TARGET -> return R.id.targetStackWidgetEmptyView
|
TARGET -> R.id.targetStackWidgetEmptyView
|
||||||
|
else -> throw IllegalStateException()
|
||||||
}
|
}
|
||||||
return 0
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,9 +24,10 @@ import android.content.Context
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup.LayoutParams
|
import android.view.ViewGroup.LayoutParams
|
||||||
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
|
import org.isoron.platform.gui.toInt
|
||||||
import org.isoron.uhabits.activities.common.views.StreakChart
|
import org.isoron.uhabits.activities.common.views.StreakChart
|
||||||
import org.isoron.uhabits.core.models.Habit
|
import org.isoron.uhabits.core.models.Habit
|
||||||
import org.isoron.uhabits.utils.toThemedAndroidColor
|
import org.isoron.uhabits.core.ui.views.WidgetTheme
|
||||||
import org.isoron.uhabits.widgets.views.GraphWidgetView
|
import org.isoron.uhabits.widgets.views.GraphWidgetView
|
||||||
|
|
||||||
class StreakWidget(
|
class StreakWidget(
|
||||||
@@ -46,7 +47,7 @@ class StreakWidget(
|
|||||||
widgetView.setBackgroundAlpha(preferedBackgroundAlpha)
|
widgetView.setBackgroundAlpha(preferedBackgroundAlpha)
|
||||||
if (preferedBackgroundAlpha >= 255) widgetView.setShadowAlpha(0x4f)
|
if (preferedBackgroundAlpha >= 255) widgetView.setShadowAlpha(0x4f)
|
||||||
(widgetView.dataView as StreakChart).apply {
|
(widgetView.dataView as StreakChart).apply {
|
||||||
setColor(habit.color.toThemedAndroidColor(context))
|
setColor(WidgetTheme().color(habit.color).toInt())
|
||||||
setStreaks(habit.streaks.getBest(maxStreakCount))
|
setStreaks(habit.streaks.getBest(maxStreakCount))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,11 +25,12 @@ import android.view.View
|
|||||||
import android.view.ViewGroup.LayoutParams
|
import android.view.ViewGroup.LayoutParams
|
||||||
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.isoron.platform.gui.toInt
|
||||||
import org.isoron.uhabits.activities.common.views.TargetChart
|
import org.isoron.uhabits.activities.common.views.TargetChart
|
||||||
import org.isoron.uhabits.activities.habits.show.views.TargetCardView.Companion.intervalToLabel
|
import org.isoron.uhabits.activities.habits.show.views.TargetCardView.Companion.intervalToLabel
|
||||||
import org.isoron.uhabits.core.models.Habit
|
import org.isoron.uhabits.core.models.Habit
|
||||||
import org.isoron.uhabits.core.ui.screens.habits.show.views.TargetCardPresenter
|
import org.isoron.uhabits.core.ui.screens.habits.show.views.TargetCardPresenter
|
||||||
import org.isoron.uhabits.utils.toThemedAndroidColor
|
import org.isoron.uhabits.core.ui.views.WidgetTheme
|
||||||
import org.isoron.uhabits.widgets.views.GraphWidgetView
|
import org.isoron.uhabits.widgets.views.GraphWidgetView
|
||||||
|
|
||||||
class TargetWidget(
|
class TargetWidget(
|
||||||
@@ -49,8 +50,12 @@ class TargetWidget(
|
|||||||
widgetView.setBackgroundAlpha(preferedBackgroundAlpha)
|
widgetView.setBackgroundAlpha(preferedBackgroundAlpha)
|
||||||
if (preferedBackgroundAlpha >= 255) widgetView.setShadowAlpha(0x4f)
|
if (preferedBackgroundAlpha >= 255) widgetView.setShadowAlpha(0x4f)
|
||||||
val chart = (widgetView.dataView as TargetChart)
|
val chart = (widgetView.dataView as TargetChart)
|
||||||
val data = TargetCardPresenter.buildState(habit, prefs.firstWeekdayInt)
|
val data = TargetCardPresenter.buildState(
|
||||||
chart.setColor(data.color.toThemedAndroidColor(context))
|
habit = habit,
|
||||||
|
firstWeekday = prefs.firstWeekdayInt,
|
||||||
|
theme = WidgetTheme(),
|
||||||
|
)
|
||||||
|
chart.setColor(WidgetTheme().color(habit.color).toInt())
|
||||||
chart.setTargets(data.targets)
|
chart.setTargets(data.targets)
|
||||||
chart.setLabels(data.intervals.map { intervalToLabel(context.resources, it) })
|
chart.setLabels(data.intervals.map { intervalToLabel(context.resources, it) })
|
||||||
chart.setValues(data.values)
|
chart.setValues(data.values)
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ class WidgetUpdater
|
|||||||
val modifiedWidgetIds = when (modifiedHabitId) {
|
val modifiedWidgetIds = when (modifiedHabitId) {
|
||||||
null -> widgetIds.toList()
|
null -> widgetIds.toList()
|
||||||
else -> widgetIds.filter { w ->
|
else -> widgetIds.filter { w ->
|
||||||
widgetPrefs.getHabitIdsFromWidgetId(w)!!.contains(modifiedHabitId)
|
widgetPrefs.getHabitIdsFromWidgetId(w).contains(modifiedHabitId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import android.widget.ListView
|
|||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import org.isoron.uhabits.HabitsApplication
|
import org.isoron.uhabits.HabitsApplication
|
||||||
import org.isoron.uhabits.R
|
import org.isoron.uhabits.R
|
||||||
|
import org.isoron.uhabits.activities.AndroidThemeSwitcher
|
||||||
import org.isoron.uhabits.core.preferences.WidgetPreferences
|
import org.isoron.uhabits.core.preferences.WidgetPreferences
|
||||||
import org.isoron.uhabits.widgets.WidgetUpdater
|
import org.isoron.uhabits.widgets.WidgetUpdater
|
||||||
import java.util.ArrayList
|
import java.util.ArrayList
|
||||||
@@ -58,6 +59,7 @@ open class HabitPickerDialog : Activity() {
|
|||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
val component = (applicationContext as HabitsApplication).component
|
val component = (applicationContext as HabitsApplication).component
|
||||||
|
AndroidThemeSwitcher(this, component.preferences).apply()
|
||||||
val habitList = component.habitList
|
val habitList = component.habitList
|
||||||
widgetPreferences = component.widgetPreferences
|
widgetPreferences = component.widgetPreferences
|
||||||
widgetUpdater = component.widgetUpdater
|
widgetUpdater = component.widgetUpdater
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import org.isoron.uhabits.utils.PaletteUtils.getAndroidTestColor
|
|||||||
import org.isoron.uhabits.utils.StyledResources
|
import org.isoron.uhabits.utils.StyledResources
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
class CheckmarkWidgetView : HabitWidgetView {
|
class CheckmarkWidgetView : HabitWidgetView {
|
||||||
var activeColor: Int = 0
|
var activeColor: Int = 0
|
||||||
@@ -102,7 +103,7 @@ class CheckmarkWidgetView : HabitWidgetView {
|
|||||||
SKIP -> resources.getString(R.string.fa_skipped)
|
SKIP -> resources.getString(R.string.fa_skipped)
|
||||||
UNKNOWN -> {
|
UNKNOWN -> {
|
||||||
run {
|
run {
|
||||||
if (preferences!!.areQuestionMarksEnabled()) {
|
if (preferences!!.areQuestionMarksEnabled) {
|
||||||
return resources.getString(R.string.fa_question)
|
return resources.getString(R.string.fa_question)
|
||||||
} else {
|
} else {
|
||||||
resources.getString(R.string.fa_times)
|
resources.getString(R.string.fa_times)
|
||||||
@@ -118,19 +119,25 @@ class CheckmarkWidgetView : HabitWidgetView {
|
|||||||
get() = R.layout.widget_checkmark
|
get() = R.layout.widget_checkmark
|
||||||
|
|
||||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||||
val width = MeasureSpec.getSize(widthMeasureSpec)
|
var width = MeasureSpec.getSize(widthMeasureSpec)
|
||||||
val height = MeasureSpec.getSize(heightMeasureSpec)
|
var height = MeasureSpec.getSize(heightMeasureSpec)
|
||||||
var textSize = 0.15f * height
|
if (height >= width) {
|
||||||
val maxTextSize = getDimension(context, R.dimen.smallerTextSize)
|
height = min(height, (width * 1.5).roundToInt())
|
||||||
textSize = min(textSize, maxTextSize)
|
} else {
|
||||||
|
width = min(width, height)
|
||||||
|
}
|
||||||
|
val textSize = min(0.2f * width, getDimension(context, R.dimen.smallerTextSize))
|
||||||
label.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
|
label.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
|
||||||
if (isNumerical) {
|
if (isNumerical) {
|
||||||
ring.setTextSize(textSize * 0.75f)
|
ring.setTextSize(textSize * 0.9f)
|
||||||
} else {
|
} else {
|
||||||
ring.setTextSize(textSize)
|
ring.setTextSize(textSize)
|
||||||
}
|
}
|
||||||
ring.setThickness(0.15f * textSize)
|
ring.setThickness(0.03f * width)
|
||||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
super.onMeasure(
|
||||||
|
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
|
||||||
|
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun init() {
|
private fun init() {
|
||||||
|
|||||||
1
uhabits-android/src/main/play/listings/af-rZA/title.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Loop - Gewoonte Boekhouer
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
لب يساعدك على خلق والحفاظ على العادات الجيدة، مما يسمح لك لتحقيق أهدافكة. الرسوم البيانية والإحصاءات التفصيلية تبين لكم كيف تحسن عاداتك مع مرور الوقت. هو تماما خالية من الاعلانات ومفتوحة المصدر.
|
|
||||||
|
|
||||||
<b>واجهة بسيطة، جميلة وحديثة </b>
|
|
||||||
لوب يحتوي على واجهة بسيطة وهي سهلة الاستخدام و تتابع نظام تصميم الماتريل دسيجن.
|
|
||||||
|
|
||||||
<b>نتيجة العادات</b>
|
|
||||||
بالإضافة إلى عرض التقدم الحالي، لوب ديه خوارزمية متقدمة لحساب قوة عاداتك. كل التكرار يجعل هذه العادة أقوى، وفي كل يوم غاب يجعلها أضعف. مع ذلك غيب أيام قليلة بعد تقدم طويلة ، لن تدمر تماما تقدمك .
|
|
||||||
|
|
||||||
<b>الرسوم البيانية والإحصاءات المفصلة</b>
|
|
||||||
نرى بوضوح كيف كنت قد تحسنت عاداتك بمرور الوقت مع الرسوم البيانية الجميله ومفصلة. انتقل إلى الوراء لنرى التاريخ الكامل لعاداتك.
|
|
||||||
|
|
||||||
<b>جداول مرنة</b>
|
|
||||||
تؤيد كل من العادات اليومية والعادات مع جداول أكثر تعقيدا، مثل 3 مرات كل أسبوع، مرة واحدة كل أسبوعين، أو مرة كل يومين.
|
|
||||||
|
|
||||||
<b>تذكير</b>
|
|
||||||
إنشاء تذكير لكل فرد من عاداتك، في ساعة اختيار من اليوم. تحقق بسهولة، رفض أو غفوة عادتك مباشرة من الإخطار، دون الحاجة إلى فتح التطبيق.
|
|
||||||
|
|
||||||
<b>خالية تماما من الإعلانات و المصدر المفتوح</b>
|
|
||||||
لا توجد على الاطلاق الإعلانات والشعارات المزعجة أو أذونات إضافية في هذا التطبيق، و سوف يكون هناك أبدا.
|
|
||||||
|
|
||||||
<b>الأمثل للساعات الذكية</b>
|
|
||||||
يمكن التحقق من رسائل التذكير، رفض أو غفوة عادتك مباشرة من ساعتك الاندرويد وير.
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
خلق عادات جيدة وتتبع تقدمك على مر الزمن
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
لوب ملاحق العادة
|
|
||||||
1
uhabits-android/src/main/play/listings/bg-rBG/title.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Loop Следене на навици
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
Loop Seguiment d'hàbits t'ajuda a crear i mantenir en la teva vida hàbits positius de llarg termini. Estadístiques i gràfiques detallades et donen una imatge clara de com els teus hàbits han millorat en el temps. L'app és completament sense anuncis, de codi obert i respecta la teva privadesa.
|
Loop Seguiment d'hàbits t'ajuda a crear i mantenir en la teva vida hàbits positius de llarg termini. Gràfics i estadístiques detallades us proporcionen una imatge clara de com els vostres hàbits han millorat amb el pas del temps. L'aplicació Blokada és totalment lliure de publicitat, de codi obert i respecta la vostra privadesa.
|
||||||
|
|
||||||
<b>Interfície minimalista, lleugera i bonica</b>
|
<b>Interfície minimalista, lleugera i bonica</b>
|
||||||
Loop té una interfície elegant i minimalista que es molt fàcil d'usar, inclús per als usuaris primerencs. Altament optimitzada per a que sigui ràpida, l'app funciona bé inclús en telèfons antics.
|
Loop té una interfície elegant i minimalista que es molt fàcil d'usar, inclús per als usuaris primerencs. Altament optimitzada per a que sigui ràpida, l'app funciona bé inclús en telèfons antics.
|
||||||
|
|||||||
1
uhabits-android/src/main/play/listings/ca-rES/title.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Loop - Hàbit Rastrejador
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
Loop t'ajuda a crear i mantenir bons hàbits, permetent-te aconseguir els teus objectius a llarg termini. Els gràfics i estadístiques detallades et mostren com han millorat els teus hàbits al llarg del temps. És completament de codi obert i lliure de publicitat.
|
|
||||||
|
|
||||||
<b>Senzilla, bonica i moderna interfície</b>
|
|
||||||
Loop té una interfície minimalista que és fàcil d'utilitzar i segueix les guies de "material design".
|
|
||||||
|
|
||||||
<b>Puntuació d'hàbit</b>
|
|
||||||
A més de mostrar la teva ratxa actual, Loop té un algoritme avançat per a calcular la fortalesa dels teus hàbits. Cada repetició fa el teu hàbit més fort, i cada dia que fallis el farà més dèbil. Tot i això uns quants dies que fallis després d'una llarga ratxa no malmetrà completament el teu progrès.
|
|
||||||
|
|
||||||
<b>Gràfics i estadístiques detallades</b>
|
|
||||||
Permet veure de forma clara com han millorat els teus hàbits al llarg del temps amb gràfics bonics i detallats. Pots anar enrera i veure també l'històric complet dels teus hàbits.
|
|
||||||
|
|
||||||
<b>Planificació flexible</b>
|
|
||||||
Suporta tant hàbits diaris com hàbits amb planificacions més complexes, com per exemple 3 vegades per setmana; un cop setmana si setmana no; o un dia si i altre no.
|
|
||||||
|
|
||||||
<b>Recordatoris</b>
|
|
||||||
Crea un recordatori individual per a cada hàbit, a l'hora escollida del dia. Revisa fàcilment, endarrereix o anul·la el teu hàbit directament des de la notificació, sense obrir l'app.
|
|
||||||
|
|
||||||
<b>Completament de codi obert i lliure de publicitat</b>
|
|
||||||
No hi ha cap tipus de publicitat, notificacions molestes o permisos intrusius en aquesta app, i mai n'hi haurà. El codi font complet està disponible sota la llicència GPLv3.
|
|
||||||
|
|
||||||
<b>Optimitzat per a rellotges intel·ligents</b>
|
|
||||||
Els recordatoris poden ser revisats, endarrerits o anul·lats directament des del teu rellotge Android Wear.
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
Crea bons hàbits i segueix el seu progrés al llarg del temps (sense publicitat)
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
Aplikace ti pomůže vytvořit a udržovat si prospěšné návyky a dosáhnout tak tvých dlouhodobých cílů. Detailní grafy a statistiky ti ukáží, jak se tvoje zvyky postupem času zlepšují. Vše je kompletně bez reklam a open source.
|
|
||||||
|
|
||||||
<b>Jednoduché, krásné a moderní prostředí</b>
|
|
||||||
Aplikace má minimalistický design s jednoduchým použitím. Dodržuje pravidla material designu.
|
|
||||||
|
|
||||||
<b>Síla zvyku</b>
|
|
||||||
Pro zobrazení aktuální úspěšné serie aplikace využívá pokročilý algoritmus, aby vypočítala sílu tvých zvyků. Každé opakování dělá tvůj zvyk silnějším a každé nedodržení ho oslabuje. Ale pár vynechaných dní po dlouhé řadě kompletně nezničí celý tvůj postup.
|
|
||||||
|
|
||||||
<b>Detailní grafy a statistika</b>
|
|
||||||
Přehledně vidíš na krásných grafech, jak moc se tvoje zvyky zlepšují v průběhu času. Jeď zpět, abys viděl kompletní historii.
|
|
||||||
|
|
||||||
<b>Flexibilní časový plán</b>
|
|
||||||
Podpora jak denních návyků, tak návyků s komplexnějším rozvrhem. Jako jsou 3 krát týdně, jednou za dva týdny, nebo obden.
|
|
||||||
|
|
||||||
<b>Upomínky</b>
|
|
||||||
Vytvoř si individuální upomínku pro každý zvyk ve zvolený čas. Jednoduše potvrď, přeskoč nebo odlož notifikace bez nutnosti otevření aplikace.
|
|
||||||
|
|
||||||
<b>Kompletně bez reklam a open source</b>
|
|
||||||
Nejsou tu naprosto žádné reklamy, otravné notifikace nebo dotěrná povolení aplikace. A nikdy nebudou. Kompletní zdrojový kód je dostupný pod GPLv3.
|
|
||||||
|
|
||||||
<b>Optimalizované pro chytré hodinky</b>
|
|
||||||
Upomínky mohou být potvrzen, odloženy nebo smazány přímo z tvého zařízení s Android Wear.
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
Vytvoř si prospěšné návyky a sleduj jejich vývoj v průběhu času (bez reklam)
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
Loop - Habit Tracker
|
|
||||||
1
uhabits-android/src/main/play/listings/da-rDK/title.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Loop Vane Tracker
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
Loop hilft dir gute Gewohnheiten anzunehmen und deine langfristigen Ziele zu erreichen. Detailierte Statistiken zeigen dir, wie du dich entwickelt hast. Es ist ohne Werbung und Open Source.
|
|
||||||
|
|
||||||
<b>Einfaches, schönes und modernes Oberfläche</b>
|
|
||||||
Loop hat eine minimale Oberfläche und ist deshalb einfach zu nutzen. Es folgt dem material Design.
|
|
||||||
|
|
||||||
<b>Habit Punkte</b>
|
|
||||||
Um dir deine Schwächen zu zeigen, hat Loop einen Algorithmus, um deine starken Angewohnheiten zu erkennen. Jede Wiederholung verstärkt diese und jedes Aussetzen schwächt sie. Aber ein paar Verfehlungen nach langem Durchhalten machen natürlich nicht gleich alles zu nichte.
|
|
||||||
|
|
||||||
<b>Statistiken</b>
|
|
||||||
Schau dir an, wie sich deine Angewohnheiten im Laufe der Zeit gemacht haben. Schau auf die schönen Diagramme und gehe zurück im gesamten Verlauf.
|
|
||||||
|
|
||||||
<b>Flexible Zeiten</b>
|
|
||||||
Unterstützt sowohl tägliche Vorgaben, als auch komplexere Pläne, woe etwa 3 mal pro Woche; ein mal in jeder anderen Woche; oder jeden anderen Tag.
|
|
||||||
|
|
||||||
<b>Erinnerungen</b>
|
|
||||||
Erstelle individuelle Erinnerungen und wann diese dich benachrichtigen sollen. Kontrolliere deine Vorhaben ganz einfach und lehne sie bei Bedarf direkt ab, ohne in die App zu wechseln.
|
|
||||||
|
|
||||||
<b>Komplett werbefrei und Open Source</b>
|
|
||||||
Es gibt absolut keine Werbung, nervende Einblendungen oder merkwürdige Berechtigungen in dieser App und das wird auch so bleiben. Der komplette Quellcode steht unter der GPLv3.
|
|
||||||
|
|
||||||
<b>Optimiert für Smartwatches</b>
|
|
||||||
Erinnerungen können direkt von der Android Wear watch kontrolliert, pausiert, oder verschoben werden.
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
Nimm gute Gewohnheiten an und verfolge deinen Fortschritt (ohne Werbung)
|
|
||||||
1
uhabits-android/src/main/play/listings/de-rDE/title.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Loop - Gewohnheiten Tracking
|
||||||
1
uhabits-android/src/main/play/listings/el-rGR/title.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Loop - Καταγραφή Συνηθειών
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
Loop te ayuda a crear y mantener buenos hábitos, permitiéndote alcanzar tus metas a largo plazo. Detallados gráficos y estadísticas muestran como tus hábitos mejoran con el tiempo. No existe ningún anuncio y es de código abierto.
|
|
||||||
|
|
||||||
<b>Una interfaz simple, bella y moderna</b>
|
|
||||||
Loop tiene una interfaz minimalista que es fácil de usar y sigue los principios del material design.
|
|
||||||
|
|
||||||
<b>Puntuación del hábito</b>
|
|
||||||
Además de mostrar tu racha actual, Loop tiene un algoritmo avanzado para calcular la fuerza de tus hábitos. Cada repetición hace tu hábito más fuerte y cada día fallido lo hace más débil. Sin embargo, unos pocos días después de una larga racha no destruirán completamente todo tu progreso.
|
|
||||||
|
|
||||||
<b>Detallados gráficos y estadísticas</b>
|
|
||||||
Observa claramente como tus hábitos han mejorado con el tiempo con bellos y detallados gráficos. Ve hacia atrás para ver el historial completo del hábito.
|
|
||||||
|
|
||||||
<b>Horarios flexibles</b>
|
|
||||||
Soporta hábitos diarios y hábitos con repeticiones más complejas, como 3 veces por semana; una vez cada varias semanas; o cada día.
|
|
||||||
|
|
||||||
<b>Recordatorios</b>
|
|
||||||
Crea recordatorios individuales para cada hábito a una hora determinada del día. Fácilmente marcables, descartables o posponibles directamente desde la notificación, sin abrir la app.
|
|
||||||
|
|
||||||
<b>Completamente sin anuncios y de código abierto</b>
|
|
||||||
No existe ningún tipo de publicidad, notificaciones molestas o permisos intrusivos, y nunca los habrá. Todo el código está disponible bajo GPLv3.
|
|
||||||
|
|
||||||
<b>Optimizado para smartwatches</b>
|
|
||||||
Los recordatorios se pueden marcar, posponer o descartar directamente desde tu reloj Android Wear.
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
Crea buenos hábitos y haz un seguimiento de su progreso a lo largo del tiempo
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
Loop - Analizador de Hábitos
|
|
||||||