Compare commits
396 Commits
v2.0.1
...
feature/sy
| Author | SHA1 | Date | |
|---|---|---|---|
|
b2cb54e32b
|
|||
|
e42d41ef30
|
|||
|
5498ff8a87
|
|||
|
20dcc7929b
|
|||
|
1283cf979d
|
|||
|
0a493ff065
|
|||
|
16cd249bae
|
|||
|
d136572960
|
|||
|
318aa3c821
|
|||
|
73712e0d10
|
|||
| bf504641c6 | |||
|
ec1f0c5356
|
|||
|
2154d8c192
|
|||
|
961fb7618f
|
|||
|
11f726064a
|
|||
|
4c5a722dc5
|
|||
| ee39ff0eda | |||
| abced92a07 | |||
|
|
eeacc5eef8 | ||
|
|
16c65f19fd | ||
|
|
6a9c3a36eb | ||
|
|
99ccb44ad3 | ||
| fc402fd81b | |||
| f7c6bc716c | |||
| 2535347d5a | |||
|
|
8c0655c352 | ||
|
|
335f8c32fd | ||
|
|
f77c064722 | ||
|
|
8b0b757f04 | ||
| 13af054214 | |||
| fcbb586e80 | |||
| 458c9f3b15 | |||
| dfa74960b3 | |||
| f082842fbe | |||
| d4d818a085 | |||
|
|
f074d0331d | ||
|
|
f94bc62a94 | ||
|
|
428bf42e79 | ||
| b0097fa45e | |||
|
|
15fa1fea8c | ||
| 31368cff45 | |||
| b44dd96dd3 | |||
| 838e13f30c | |||
|
|
08fab0cd8d | ||
|
|
a142685d2e | ||
| a7a95f2030 | |||
|
|
d3c90481be | ||
| f9bb0d7d7b | |||
| f23a1bedee | |||
| dcf31ba115 | |||
| 0eae43fe55 | |||
| c0fcd4e763 | |||
| 79e2402c9d | |||
|
|
5409a324e8 | ||
|
|
504362e680 | ||
|
|
0ce2f8fae2 | ||
| 2fc6c67432 | |||
| 459cf02642 | |||
| 44cb64601f | |||
| 53c270ee12 | |||
| e4b16f6d59 | |||
| 3021e408a7 | |||
| 7649119db7 | |||
| b0a4f26e7a | |||
|
|
dd47d4cf08 | ||
|
|
8912a9d73c | ||
|
|
9bd1c6f685 | ||
|
|
e15e06034c | ||
|
|
a6180a5049 | ||
|
|
7c69b17e77 | ||
|
|
602a40a532 | ||
|
|
e00998f913 | ||
|
|
af5914c2db | ||
|
|
6c5f70638a | ||
|
|
ba88552919 | ||
|
|
8c90c4f68d | ||
|
|
08eb1a600d | ||
|
|
8ea0480d4a | ||
|
|
36ee39589e | ||
|
|
ac7a721940 | ||
|
|
b36ca8673a | ||
|
|
7cfac486f9 | ||
|
|
21dd413ab5 | ||
|
|
39e10638b5 | ||
| 0a95b6d2a0 | |||
| e6bcbb39ff | |||
| 90de1f3723 | |||
| 68740b4043 | |||
| b66a6ff717 | |||
| 7c1a91e35a | |||
| 366e9af167 | |||
| 03b02aaa06 | |||
|
|
180cf25ad3 | ||
| 3490cd183a | |||
|
|
0c292d1eaa | ||
| 0b256cb2c0 | |||
| cc03c48648 | |||
| 53e7ef2918 | |||
| 9609bee0f7 | |||
| 5b23a3f960 | |||
| d2d45991b0 | |||
| 25aeafb759 | |||
| 9045ae5c24 | |||
| 555873354c | |||
| 2a012619a7 | |||
| d1de3a852b | |||
| f04e37e905 | |||
| 825a5f2cb9 | |||
| 0de6896691 | |||
| 7187214282 | |||
|
|
9d4161a255 | ||
|
|
d82a3c145d | ||
|
|
a6cbd44e42 | ||
|
|
45c62b4ab2 | ||
|
|
d53312d261 | ||
|
|
c453810785 | ||
|
|
999057300b | ||
|
|
2245347e28 | ||
|
|
b79f7850ed | ||
|
|
4469f86e46 | ||
|
|
bdeddb149e | ||
|
|
3b12ec4bfe | ||
| ca4618579e | |||
| 2f13aba22c | |||
| dba5912ecd | |||
| 88b8663484 | |||
| 609886fd09 | |||
| a4db997e06 | |||
|
|
090216ccb7 | ||
|
|
472092d1bf | ||
|
|
6d3791de31 | ||
|
|
24a99d5791 | ||
|
|
6c631b1cf6 | ||
|
|
5d7677b354 | ||
|
|
22820f4f24 | ||
| 1d3bd48535 | |||
|
|
694446b7e3 | ||
|
|
20ae9d247e | ||
|
|
1943fac610 | ||
|
|
a07a50e635 | ||
| b7232b12cd | |||
| 8cd5b93b47 | |||
| 1b07efe291 | |||
| 232b25bed4 | |||
| 2ea6fe0570 | |||
| bb8b742dc4 | |||
| 85b52a9840 | |||
|
|
8ccb9bbab1 | ||
|
|
743b8d26ad | ||
|
|
c47bd4c328 | ||
|
|
d84abc3a6a | ||
|
|
4ae85f1ec0 | ||
|
|
79d40d1d79 | ||
|
|
1902b8821e | ||
|
|
075542d605 | ||
|
|
0b5894ee6d | ||
|
|
92fb9dbdb6 | ||
|
|
1860abf532 | ||
|
|
3f4780c9fb | ||
|
|
d5d6e4616e | ||
|
|
18e267053d | ||
| 7ba9a7e4e7 | |||
| 5b8a7c39e2 | |||
| d40a5a89cd | |||
| 308d558347 | |||
| 9770ce187a | |||
| 535bc03b70 | |||
| 642e45af89 | |||
| 56d2307b75 | |||
| d875af8a8e | |||
| feeb4f057d | |||
| 1ad5c6b896 | |||
| 774412492f | |||
| 370ddfb8db | |||
| 1567e2c0ad | |||
| baee3b9f86 | |||
| e6167baab1 | |||
|
|
1c15e7742e | ||
|
|
072ba63789 | ||
|
|
0fa0daa058 | ||
|
|
5a5ed3d631 | ||
|
|
3dfa376f59 | ||
|
|
7bf74634bb | ||
|
|
fc645a81bc | ||
|
|
35365bbdf3 | ||
|
|
779ef5dbee | ||
|
|
e82994c76b | ||
|
|
4e1d01d8d1 | ||
|
|
5de0fc86e5 | ||
| e26b643423 | |||
| 621534d610 | |||
| a01300e9c6 | |||
| ecb8ce105a | |||
| 32ef3c14f7 | |||
| 4972257635 | |||
| c98cb50baa | |||
| c331f34fa9 | |||
| a1aea532b5 | |||
| 43489aeb4c | |||
| 990c85aedd | |||
|
|
64337b9bee | ||
|
|
8bdfaa2434 | ||
|
|
5f6060858d | ||
| b62e436054 | |||
| bf63b4dbcf | |||
| a82d940bcc | |||
| ba59dc7ca9 | |||
| 181290a0f3 | |||
|
|
d553c2f3f2 | ||
|
|
7776093217 | ||
|
|
b27f3f8540 | ||
| eb041bf6b2 | |||
|
|
e3c53bf07f | ||
|
|
9ddab6ee59 | ||
|
|
2615795402 | ||
|
|
6531445d7f | ||
|
|
4fbf8a8ca2 | ||
|
|
707b2b4380 | ||
|
|
aae85c1170 | ||
|
|
c12a6c6a4d | ||
|
|
b15c02adbf | ||
|
|
9e24128675 | ||
|
|
66c61e2e6c | ||
|
|
7bddfbe5a7 | ||
|
|
8036b10ee6 | ||
|
|
71f400f587 | ||
|
|
79e302f922 | ||
|
|
af7f60fc4d | ||
|
|
7cc4b66dfd | ||
|
|
a9fddf9963 | ||
|
|
36c1504c6a | ||
|
|
d644170141 | ||
|
|
d38f83e961 | ||
|
|
c50c5af497 | ||
|
|
fa3774a32b | ||
|
|
fd124f2a6c | ||
|
|
265b65eb8a | ||
|
|
4c269c55d2 | ||
|
|
c03305120e | ||
|
|
29615b670b | ||
|
|
6ab4a696b6 | ||
|
|
23479c7765 | ||
|
|
6d98f7aafa | ||
| 75078ed52b | |||
| 2a0afedb1d | |||
| 66a2b41250 | |||
|
|
d6a7fa3d7a | ||
|
|
07e55f1c76 | ||
| 4ee5dd910b | |||
| 87f071b5b4 | |||
| bb0b5e8adf | |||
|
|
c79d1e82a5 | ||
|
|
4aebeedec6 | ||
|
|
7de94f2caf | ||
|
|
17ed85fc1b | ||
|
|
4355fb4d68 | ||
|
|
508200abeb | ||
|
|
a29943e783 | ||
|
|
3e6a9181d6 | ||
| 1fe3a3d1ca | |||
|
|
b2951a3475 | ||
|
|
9d3c63cf62 | ||
|
|
65d237254c | ||
|
|
fe1d5c66cb | ||
|
|
113a5028af | ||
|
|
1a56260757 | ||
|
|
697fffbc99 | ||
|
|
804edfa64e | ||
| 2ab6c396d0 | |||
| a55f467339 | |||
|
|
cf682f68c9 | ||
| 0e988e746c | |||
|
|
f119cbf8e7 | ||
|
|
056f5f6fce | ||
|
|
42f6125d5e | ||
|
|
3e20fc4d1d | ||
|
|
1f763feb69 | ||
|
|
6e7ad329fe | ||
|
|
5cb241475d | ||
|
|
27e76c7243 | ||
|
|
576ad04064 | ||
|
|
5f8187ef6d | ||
|
|
f16f919e27 | ||
|
|
736bb8a75e | ||
|
|
de9ad6d4a4 | ||
|
|
1d37ce54ea | ||
|
|
f88f1cfb54 | ||
|
|
fc1478645b | ||
|
|
ffab001b09 | ||
|
|
a58a8005e1 | ||
|
|
c884ada187 | ||
|
|
e3d46ad5a0 | ||
|
|
f4a2c03216 | ||
|
|
f2b8f2f98d | ||
|
|
2c5fd87a2a | ||
|
|
39768f7f04 | ||
|
|
cc3e1ced15 | ||
|
|
2e26cc104e | ||
|
|
42fd0926ef | ||
| ec202aa9a7 | |||
|
|
1fb56c8777 | ||
|
|
a5d4a37da8 | ||
|
|
4804a48549 | ||
|
|
c892a845b4 | ||
|
|
e98064b6a5 | ||
| 57f5f6ed5b | |||
| 79f5b8b7e8 | |||
| 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 | |||
| 2163a2b93b | |||
| afad56ab91 | |||
| ece1b93f8d | |||
| 10416e40fa | |||
| 88f8581acc | |||
| b33dd2a994 | |||
| d87961d800 | |||
| 38d2606d6d | |||
| 0a91c097e8 | |||
|
|
b1c53bd820 | ||
| c973f93424 | |||
| fcadbe7c38 | |||
| 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"
|
||||||
|
|||||||
54
.github/workflows/main.yml
vendored
@@ -7,54 +7,24 @@ on:
|
|||||||
paths-ignore:
|
paths-ignore:
|
||||||
- '**.md'
|
- '**.md'
|
||||||
jobs:
|
jobs:
|
||||||
Build:
|
Test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: self-hosted
|
||||||
steps:
|
steps:
|
||||||
- name: Check out source code
|
- name: Check out source code
|
||||||
uses: actions/checkout@v1
|
uses: actions/checkout@v1
|
||||||
|
|
||||||
- name: Install Java Development Kit 1.8
|
- name: Build project
|
||||||
uses: actions/setup-java@v1
|
|
||||||
with:
|
|
||||||
java-version: 1.8
|
|
||||||
|
|
||||||
- name: Build Project
|
|
||||||
run: ./build.sh build
|
run: ./build.sh build
|
||||||
|
|
||||||
- name: Upload Build Artifacts
|
- name: Run Android tests
|
||||||
|
run: ./build.sh android-tests-parallel 28 29 30 31 32 33
|
||||||
|
|
||||||
|
- name: Upload artifacts
|
||||||
|
if: always()
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: uhabits-android
|
name: build
|
||||||
path: uhabits-android/build/outputs/
|
path: |
|
||||||
|
build/*log
|
||||||
AndroidTest:
|
uhabits-android/build/outputs
|
||||||
needs: Build
|
|
||||||
runs-on: macOS-10.15
|
|
||||||
timeout-minutes: 45
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
api: [
|
|
||||||
23,
|
|
||||||
24,
|
|
||||||
25,
|
|
||||||
26,
|
|
||||||
27,
|
|
||||||
28,
|
|
||||||
# 29, # Crashes constantly, see: https://issuetracker.google.com/issues/159732638
|
|
||||||
# 30, # Not available yet
|
|
||||||
# 31, # Not available yet
|
|
||||||
]
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Check out source code
|
|
||||||
uses: actions/checkout@v1
|
|
||||||
|
|
||||||
- name: Download Previously Built APK
|
|
||||||
uses: actions/download-artifact@v2
|
|
||||||
with:
|
|
||||||
name: uhabits-android
|
|
||||||
path: uhabits-android/build/outputs/
|
|
||||||
|
|
||||||
- name: Run Android Tests
|
|
||||||
run: ./build.sh android-tests ${{ matrix.api }}
|
|
||||||
|
|
||||||
|
|||||||
5
.gitignore
vendored
@@ -12,13 +12,8 @@
|
|||||||
.idea
|
.idea
|
||||||
.secret
|
.secret
|
||||||
build
|
build
|
||||||
build/
|
|
||||||
captures
|
|
||||||
local.properties
|
local.properties
|
||||||
node_modules
|
node_modules
|
||||||
*xcuserdata*
|
*xcuserdata*
|
||||||
*.sketch
|
*.sketch
|
||||||
/design
|
|
||||||
/releases
|
|
||||||
/screenshots
|
|
||||||
crowdin.yml
|
crowdin.yml
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
cd "$(dirname "$0")"
|
|
||||||
if [ -z "$GPG_PASSWORD" ]; then
|
|
||||||
echo Env variable GPG_PASSWORD must be defined
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
gpg \
|
|
||||||
--quiet \
|
|
||||||
--batch \
|
|
||||||
--yes \
|
|
||||||
--decrypt \
|
|
||||||
--passphrase="$GPG_PASSWORD" \
|
|
||||||
--output secret.tar.gz \
|
|
||||||
secret
|
|
||||||
tar -xzf secret.tar.gz
|
|
||||||
rm secret.tar.gz
|
|
||||||
BIN
.secret/secret
60
CHANGELOG.md
@@ -1,5 +1,65 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [2.1.0] -- 2022-09-10
|
||||||
|
### Added
|
||||||
|
- Allow user to add notes to specific dates (@vbh, #1103)
|
||||||
|
- Allow user to track "at most" numerical habits (@KristianTashkov, #1101)
|
||||||
|
- Allow user to add skips to measurable habits (@kalina559, #1319)
|
||||||
|
- Bring back custom frequencies (x times in y days) (@hiqua, #1079)
|
||||||
|
- Improve number picker (@hiqua, @iSoron, #1082, #1370)
|
||||||
|
- Add new checkmark and number picker (@iSoron, #1370)
|
||||||
|
- Allow user to import numerical habits from HabitBull (@hiqua, #1278)
|
||||||
|
- Add support for Android 13 themed icons (@cheeeeer, #1497)
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- Hide snooze button Android 12 notifications (@hiqua, #1226)
|
||||||
|
- Remove preference to set LED lights (@iSoron)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Hide failed habits along with completed ones (@hiqua, #1052)
|
||||||
|
- Cycle through all checkmark states when toggling (@iSoron)
|
||||||
|
- Add delay after toggling a habit (@hiqua, @kalina559, #1147)
|
||||||
|
- Small theme improvements (@KristianTashkov, #1113)
|
||||||
|
- Left-align habit notes (@iSoron)
|
||||||
|
- Increase target SDK to 31 (@hiqua)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fix small dialog buttons (@kalina559, #1096)
|
||||||
|
- Fix invalid CSV files (@hiqua, #1177)
|
||||||
|
- Fix small issues in calendar chart (@kalina559, #1314)
|
||||||
|
- Resort habit list after edit (@hiqua, #1350)
|
||||||
|
- Fix marker scaling in frequency display (@eduebernal, #1425)
|
||||||
|
- Fix widgets not working correctly on API 33 (@iSoron, #1488)
|
||||||
|
|
||||||
|
### Refactoring & Testing
|
||||||
|
- Replace raster icons by vector assets (@kalina559)
|
||||||
|
- Remove JVM dependencies from uhabits-core module (@sgallese)
|
||||||
|
- Add various missing tests (@sgallese)
|
||||||
|
- Upgrade project dependencies (@hiqua, @sgallese)
|
||||||
|
|
||||||
|
## [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
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Make checkmark widget resizable
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fix crash caused by numerical habits with zero target (@iSoron, #903)
|
||||||
|
- Fix small issues with font size (@iSoron)
|
||||||
|
- Allow fractional target values (@sumanabhi, #911)
|
||||||
|
- Fix IllegalStateException in androidx.customview.view (@iSoron, #906)
|
||||||
|
- Fix crash when selecting habit frequency in some languages (@iSoron, #926)
|
||||||
|
- Fix IllegalArgumentException in RingView (@iSoron, #904)
|
||||||
|
|
||||||
## [2.0.1] - 2021-05-09
|
## [2.0.1] - 2021-05-09
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
10
README.md
@@ -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
|
||||||
@@ -90,7 +90,7 @@ contribute, even if you are not a software developer.
|
|||||||
is already completed, you are also very welcome to join and proofread it.
|
is already completed, you are also very welcome to join and proofread it.
|
||||||
|
|
||||||
* **Write some code.** If you are an Android developer, you are very welcome to
|
* **Write some code.** If you are an Android developer, you are very welcome to
|
||||||
contribute with code. Please see `docs/GUIDELINES.md`.
|
contribute with code. Please see the [guidelines](https://github.com/iSoron/uhabits/blob/dev/docs/GUIDELINES.md).
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
@@ -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.7.10"
|
||||||
id("com.android.application") version ("4.1.0") apply (false)
|
id("com.android.application") version ("7.3.0-rc01") 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 "11.0.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
apply {
|
apply {
|
||||||
@@ -15,11 +15,9 @@ apply {
|
|||||||
allprojects {
|
allprojects {
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven(url = "https://plugins.gradle.org/m2/")
|
maven(url = "https://plugins.gradle.org/m2/")
|
||||||
maven(url = "https://oss.sonatype.org/content/repositories/snapshots/")
|
maven(url = "https://oss.sonatype.org/content/repositories/snapshots/")
|
||||||
maven(url = "https://kotlin.bintray.com/ktor")
|
maven(url = "https://jitpack.io")
|
||||||
maven(url = "https://kotlin.bintray.com/kotlin-js-wrappers")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
143
build.sh
@@ -26,6 +26,7 @@ GRADLE="./gradlew --stacktrace --quiet"
|
|||||||
PACKAGE_NAME=org.isoron.uhabits
|
PACKAGE_NAME=org.isoron.uhabits
|
||||||
SDKMANAGER="${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager"
|
SDKMANAGER="${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager"
|
||||||
VERSION=$(grep versionName uhabits-android/build.gradle.kts | sed -e 's/.*"\([^"]*\)".*/\1/g')
|
VERSION=$(grep versionName uhabits-android/build.gradle.kts | sed -e 's/.*"\([^"]*\)".*/\1/g')
|
||||||
|
BOOT_TIMEOUT=360
|
||||||
|
|
||||||
if [ -z $VERSION ]; then
|
if [ -z $VERSION ]; then
|
||||||
echo "Could not parse app version from: uhabits-android/build.gradle.kts"
|
echo "Could not parse app version from: uhabits-android/build.gradle.kts"
|
||||||
@@ -69,8 +70,7 @@ core_build() {
|
|||||||
# Android
|
# Android
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
# shellcheck disable=SC2016
|
android_setup() {
|
||||||
android_test() {
|
|
||||||
API=$1
|
API=$1
|
||||||
AVDNAME=${AVD_PREFIX}${API}
|
AVDNAME=${AVD_PREFIX}${API}
|
||||||
|
|
||||||
@@ -85,25 +85,63 @@ android_test() {
|
|||||||
$AVDMANAGER delete avd --name $AVDNAME
|
$AVDMANAGER delete avd --name $AVDNAME
|
||||||
|
|
||||||
log_info "Creating new Android virtual device (API $API)..."
|
log_info "Creating new Android virtual device (API $API)..."
|
||||||
(echo "y" | $SDKMANAGER --install "system-images;android-$API;default;x86_64") || return 1
|
(echo "y" | $SDKMANAGER --install "system-images;android-$API;google_apis;x86_64") || return 1
|
||||||
$AVDMANAGER create avd \
|
$AVDMANAGER create avd \
|
||||||
--name $AVDNAME \
|
--name $AVDNAME \
|
||||||
--package "system-images;android-$API;default;x86_64" \
|
--package "system-images;android-$API;google_apis;x86_64" \
|
||||||
--device "Nexus 4" || return 1
|
--device "Nexus 4" || return 1
|
||||||
|
|
||||||
flock -u 10
|
flock -u 10
|
||||||
) 10>/tmp/uhabitsTest.lock
|
) 10>/tmp/uhabitsTest.lock
|
||||||
|
|
||||||
log_info "Launching emulator..."
|
log_info "Launching emulator..."
|
||||||
$EMULATOR -avd $AVDNAME -port 6${API}0 1>/dev/null 2>&1 &
|
$EMULATOR \
|
||||||
|
-avd $AVDNAME \
|
||||||
|
-port 6${API}0 \
|
||||||
|
1>/dev/null 2>&1 &
|
||||||
|
|
||||||
|
log_info "Waiting for emulator to boot..."
|
||||||
|
export ADB="$ADB -s emulator-6${API}0"
|
||||||
|
timeout $BOOT_TIMEOUT $ADB wait-for-device shell 'while [[ -z "$(getprop sys.boot_completed)" ]]; do echo Waiting...; sleep 1; done; input keyevent 82'
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
log_error "Emulator failed to boot after $BOOT_TIMEOUT seconds."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "Saving snapshot..."
|
||||||
|
$ADB emu avd snapshot save fresh-install
|
||||||
|
}
|
||||||
|
|
||||||
|
android_boot_attempt() {
|
||||||
|
API=$1
|
||||||
|
AVDNAME=${AVD_PREFIX}${API}
|
||||||
|
|
||||||
|
log_info "Stopping Android emulator..."
|
||||||
|
while [[ -n $(pgrep -f ${AVDNAME}) ]]; do
|
||||||
|
pkill -9 -f ${AVDNAME}
|
||||||
|
done
|
||||||
|
|
||||||
|
log_info "Launching emulator..."
|
||||||
|
$EMULATOR \
|
||||||
|
-avd $AVDNAME \
|
||||||
|
-port 6${API}0 \
|
||||||
|
-snapshot fresh-install \
|
||||||
|
-no-snapshot-save \
|
||||||
|
-wipe-data \
|
||||||
|
1>/dev/null 2>&1 &
|
||||||
|
|
||||||
log_info "Waiting for emulator to boot..."
|
log_info "Waiting for emulator to boot..."
|
||||||
export ADB="$ADB -s emulator-6${API}0"
|
export ADB="$ADB -s emulator-6${API}0"
|
||||||
$ADB wait-for-device shell 'while [[ -z "$(getprop sys.boot_completed)" ]]; do echo Waiting...; sleep 1; done; input keyevent 82' || return 1
|
|
||||||
$ADB root || return 1
|
|
||||||
sleep 5
|
sleep 5
|
||||||
|
timeout $BOOT_TIMEOUT $ADB wait-for-device shell 'while [[ -z "$(getprop sys.boot_completed)" ]]; do echo Waiting...; sleep 1; done; input keyevent 82'
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
log_error "Emulator failed to boot after $BOOT_TIMEOUT seconds."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
log_info "Disabling animations..."
|
log_info "Disabling animations..."
|
||||||
|
$ADB root || return 1
|
||||||
|
sleep 5
|
||||||
$ADB shell settings put global window_animation_scale 0 || return 1
|
$ADB shell settings put global window_animation_scale 0 || return 1
|
||||||
$ADB shell settings put global transition_animation_scale 0 || return 1
|
$ADB shell settings put global transition_animation_scale 0 || return 1
|
||||||
$ADB shell settings put global animator_duration_scale 0 || return 1
|
$ADB shell settings put global animator_duration_scale 0 || return 1
|
||||||
@@ -111,6 +149,24 @@ android_test() {
|
|||||||
log_info "Acquiring wake lock..."
|
log_info "Acquiring wake lock..."
|
||||||
$ADB shell 'echo android-test > /sys/power/wake_lock' || return 1
|
$ADB shell 'echo android-test > /sys/power/wake_lock' || return 1
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
android_boot() {
|
||||||
|
for attempt in {1..5}; do
|
||||||
|
android_boot_attempt $1 && return 0
|
||||||
|
sleep 5
|
||||||
|
done
|
||||||
|
log_error "Too many failed attempts. Aborting."
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# shellcheck disable=SC2016
|
||||||
|
android_test() {
|
||||||
|
API=$1
|
||||||
|
AVDNAME=${AVD_PREFIX}${API}
|
||||||
|
|
||||||
|
android_boot $API || return 1
|
||||||
|
|
||||||
if [ -n "$RELEASE" ]; then
|
if [ -n "$RELEASE" ]; then
|
||||||
log_info "Installing release APK..."
|
log_info "Installing release APK..."
|
||||||
$ADB install -r ${ANDROID_OUTPUTS_DIR}/apk/release/uhabits-android-release.apk || return 1
|
$ADB install -r ${ANDROID_OUTPUTS_DIR}/apk/release/uhabits-android-release.apk || return 1
|
||||||
@@ -122,26 +178,41 @@ android_test() {
|
|||||||
$ADB install -r ${ANDROID_OUTPUTS_DIR}/apk/androidTest/debug/uhabits-android-debug-androidTest.apk || return 1
|
$ADB install -r ${ANDROID_OUTPUTS_DIR}/apk/androidTest/debug/uhabits-android-debug-androidTest.apk || return 1
|
||||||
|
|
||||||
for size in medium large; do
|
for size in medium large; do
|
||||||
log_info "Running $size instrumented tests..."
|
|
||||||
OUT_INSTRUMENT=${ANDROID_OUTPUTS_DIR}/instrument-${API}.txt
|
OUT_INSTRUMENT=${ANDROID_OUTPUTS_DIR}/instrument-${API}.txt
|
||||||
OUT_LOGCAT=${ANDROID_OUTPUTS_DIR}/logcat-${API}.txt
|
OUT_LOGCAT=${ANDROID_OUTPUTS_DIR}/logcat-${API}.txt
|
||||||
|
FAILED_TESTS=""
|
||||||
|
for i in {1..5}; do
|
||||||
|
log_info "Running $size instrumented tests (attempt $i)..."
|
||||||
$ADB shell am instrument \
|
$ADB shell am instrument \
|
||||||
-r -e coverage true -e size $size \
|
-r -e coverage true -e size "$size" $FAILED_TESTS \
|
||||||
-w ${PACKAGE_NAME}.test/androidx.test.runner.AndroidJUnitRunner \
|
-w ${PACKAGE_NAME}.test/androidx.test.runner.AndroidJUnitRunner \
|
||||||
| tee $OUT_INSTRUMENT
|
| ts "%.s" | tee "$OUT_INSTRUMENT"
|
||||||
if grep "\(INSTRUMENTATION_STATUS_CODE.*-1\|FAILURES\|ABORTED\|onError\|Error type\|crashed\)" $OUT_INSTRUMENT; then
|
|
||||||
|
FAILED_TESTS=$(tools/parseInstrument.py "$OUT_INSTRUMENT")
|
||||||
|
SUCCESS=$?
|
||||||
|
if [ $SUCCESS -eq 0 ]; then
|
||||||
|
log_info "$size tests passed."
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ $SUCCESS -ne 0 ]; then
|
||||||
log_error "Some $size instrumented tests failed."
|
log_error "Some $size instrumented tests failed."
|
||||||
log_error "Saving logcat: $OUT_LOGCAT..."
|
log_error "Saving logcat: $OUT_LOGCAT..."
|
||||||
$ADB logcat -d > $OUT_LOGCAT
|
$ADB logcat -d > $OUT_LOGCAT
|
||||||
|
log_error "Fetching test screenshots..."
|
||||||
|
$ADB pull /sdcard/Android/data/${PACKAGE_NAME}/files/test-screenshots ${ANDROID_OUTPUTS_DIR}/
|
||||||
|
$ADB shell rm -r /sdcard/Android/data/${PACKAGE_NAME}/files/test-screenshots/
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
log_info "$size tests passed."
|
|
||||||
done
|
done
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
android_test_parallel() {
|
android_test_parallel() {
|
||||||
|
# Launch background processes
|
||||||
|
PIDS=""
|
||||||
for API in $*; do
|
for API in $*; do
|
||||||
(
|
(
|
||||||
LOG=build/android-test-$API.log
|
LOG=build/android-test-$API.log
|
||||||
@@ -149,12 +220,27 @@ android_test_parallel() {
|
|||||||
if android_test $API 1>$LOG 2>&1; then
|
if android_test $API 1>$LOG 2>&1; then
|
||||||
log_info "API $API: Passed"
|
log_info "API $API: Passed"
|
||||||
else
|
else
|
||||||
log_error "API $API: Failed. See $LOG for more details."
|
log_error "API $API: Failed"
|
||||||
fi
|
fi
|
||||||
pkill -9 -f ${AVD_PREFIX}${API}
|
pkill -9 -f ${AVD_PREFIX}${API}
|
||||||
)&
|
)&
|
||||||
|
PIDS+=" $!"
|
||||||
done
|
done
|
||||||
wait
|
|
||||||
|
# Check exit codes
|
||||||
|
RET_CODE=0
|
||||||
|
for pid in $PIDS; do
|
||||||
|
wait $pid || RET_CODE=1
|
||||||
|
done
|
||||||
|
|
||||||
|
# Print all logs
|
||||||
|
for API in $*; do
|
||||||
|
echo "::group::Android Tests (API $API)"
|
||||||
|
cat build/android-test-$API.log
|
||||||
|
echo "::endgroup::"
|
||||||
|
done
|
||||||
|
|
||||||
|
return $RET_CODE
|
||||||
}
|
}
|
||||||
|
|
||||||
android_build() {
|
android_build() {
|
||||||
@@ -198,13 +284,6 @@ android_build() {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
android_fetch_images() {
|
|
||||||
log_info "Fetching images"
|
|
||||||
rm -rf ${ANDROID_OUTPUTS_DIR}/test-screenshots
|
|
||||||
$ADB pull /sdcard/Android/data/${PACKAGE_NAME}/files/test-screenshots ${ANDROID_OUTPUTS_DIR}/
|
|
||||||
$ADB shell rm -r /sdcard/Android/data/${PACKAGE_NAME}/files/test-screenshots/
|
|
||||||
}
|
|
||||||
|
|
||||||
android_accept_images() {
|
android_accept_images() {
|
||||||
find ${ANDROID_OUTPUTS_DIR}/test-screenshots -name '*.expected*' -delete
|
find ${ANDROID_OUTPUTS_DIR}/test-screenshots -name '*.expected*' -delete
|
||||||
rsync -av ${ANDROID_OUTPUTS_DIR}/test-screenshots/ uhabits-android/src/androidTest/assets/
|
rsync -av ${ANDROID_OUTPUTS_DIR}/test-screenshots/ uhabits-android/src/androidTest/assets/
|
||||||
@@ -233,18 +312,16 @@ CI/CD script for Loop Habit Tracker.
|
|||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
build.sh build [options]
|
build.sh build [options]
|
||||||
build.sh clean [options]
|
build.sh android-setup <API>
|
||||||
build.sh android-tests <API> [options]
|
build.sh android-tests <API> [options]
|
||||||
build.sh android-tests-parallel <API> <API>... [options]
|
build.sh android-tests-parallel <API> <API>... [options]
|
||||||
build.sh android-fetch-images [options]
|
|
||||||
build.sh android-accept-images [options]
|
build.sh android-accept-images [options]
|
||||||
|
|
||||||
Commands:
|
Commands:
|
||||||
build Build the app and run small tests
|
build Build the app and run small tests
|
||||||
clean Remove all build directories
|
android-setup Create Android virtual machine
|
||||||
android-tests Run medium and large Android tests on an emulator
|
android-tests Run medium and large Android tests on an emulator
|
||||||
android-tests-parallel Tests multiple API levels simultaneously
|
android-tests-parallel Tests multiple API levels simultaneously
|
||||||
android-fetch-images Fetch failed view test images from device
|
|
||||||
android-accept-images Copy fetched images to corresponding assets folder
|
android-accept-images Copy fetched images to corresponding assets folder
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
@@ -274,11 +351,13 @@ main() {
|
|||||||
case "$1" in
|
case "$1" in
|
||||||
build)
|
build)
|
||||||
shift; _parse_opts "$@"
|
shift; _parse_opts "$@"
|
||||||
|
clean
|
||||||
core_build
|
core_build
|
||||||
android_build
|
android_build
|
||||||
;;
|
;;
|
||||||
clean)
|
android-setup)
|
||||||
clean
|
shift; _parse_opts "$@"
|
||||||
|
android_setup $1
|
||||||
;;
|
;;
|
||||||
android-tests)
|
android-tests)
|
||||||
shift; _parse_opts "$@"
|
shift; _parse_opts "$@"
|
||||||
@@ -286,20 +365,12 @@ main() {
|
|||||||
_print_usage
|
_print_usage
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
for attempt in {1..3}; do
|
android_test $1
|
||||||
log_info "Running Android tests (attempt $attempt)..."
|
|
||||||
android_test $1 && return 0
|
|
||||||
done
|
|
||||||
log_error "Maximum number of attempts reached. Failing."
|
|
||||||
return 1
|
|
||||||
;;
|
;;
|
||||||
android-tests-parallel)
|
android-tests-parallel)
|
||||||
shift; _parse_opts "$@"
|
shift; _parse_opts "$@"
|
||||||
android_test_parallel $*
|
android_test_parallel $*
|
||||||
;;
|
;;
|
||||||
android-fetch-images)
|
|
||||||
android_fetch_images
|
|
||||||
;;
|
|
||||||
android-accept-images)
|
android-accept-images)
|
||||||
android_accept_images
|
android_accept_images
|
||||||
;;
|
;;
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ The repository will be downloaded to the directory `uhabits`.
|
|||||||
2. When the IDE asks you for the project location, select `uhabits` and click "Ok".
|
2. When the IDE asks you for the project location, select `uhabits` and click "Ok".
|
||||||
3. Android Studio will spend some time indexing the project. When this is complete, click the toolbar icon "Sync Project with Gradle File", located near the right corner of the top toolbar.
|
3. Android Studio will spend some time indexing the project. When this is complete, click the toolbar icon "Sync Project with Gradle File", located near the right corner of the top toolbar.
|
||||||
4. The operation will likely fail several times due to missing Android SDK components. Each time it fails, click the link "Install missing platforms", "Install build tools", etc, and try again.
|
4. The operation will likely fail several times due to missing Android SDK components. Each time it fails, click the link "Install missing platforms", "Install build tools", etc, and try again.
|
||||||
5. To test the application, create a virtual Android device using the menu "Tools" and "AVD Manager". The default options should work fine, but free to customize the device.
|
5. To test the application, create a virtual Android device using the menu "Tools" and "AVD Manager". The default options should work fine, but feel free to customize the device.
|
||||||
6. Click the menu "Run" and "uhabits-android". The application should launch.
|
6. Click the menu "Run" and "uhabits-android". The application should launch.
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
org.gradle.parallel=false
|
org.gradle.parallel=false
|
||||||
org.gradle.daemon=true
|
org.gradle.daemon=true
|
||||||
org.gradle.jvmargs=-Xms2048m -Xmx2048m -XX:MaxPermSize=2048m
|
org.gradle.jvmargs=-Xms2048m -Xmx2048m
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
|
|||||||
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.5-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
|
||||||
|
|||||||
2
landing/.gitignore
vendored
@@ -1,2 +0,0 @@
|
|||||||
out/
|
|
||||||
.sass-cache
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
haml := src/*.haml
|
|
||||||
sass := src/*.sass
|
|
||||||
|
|
||||||
html := $(patsubst src/%, out/%, $(patsubst %.haml,%.html,$(wildcard $(haml))))
|
|
||||||
css := $(patsubst src/%, out/%, $(patsubst %.sass,%.css,$(wildcard $(sass))))
|
|
||||||
src := $(wildcard src/**)
|
|
||||||
|
|
||||||
compile: $(html) $(css)
|
|
||||||
@rsync -rupE assets/ out/
|
|
||||||
|
|
||||||
out/%.css: src/%.sass $(src)
|
|
||||||
@echo ' sass $<'
|
|
||||||
@mkdir -p `dirname $@`
|
|
||||||
@sass $< $@
|
|
||||||
|
|
||||||
out/%.html: src/%.haml $(src)
|
|
||||||
@echo ' haml $<'
|
|
||||||
@mkdir -p `dirname $@`
|
|
||||||
@haml -E UTF-8 $< $@
|
|
||||||
|
|
||||||
push:
|
|
||||||
rsync -avP out/ axavier.org:/www/loophabits.org/
|
|
||||||
|
|
||||||
clean:
|
|
||||||
@rm -rfv out
|
|
||||||
@rm -rfv tmp
|
|
||||||
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
Loop Habit Tracker Landing Page
|
|
||||||
===============================
|
|
||||||
|
|
||||||
This folder contains the source code that generates the project landing page, currently hosted at https://loophabits.org/
|
|
||||||
|
|
||||||
Pull requests with ideas for improving it are very welcome.
|
|
||||||
|
|
||||||
Build instructions
|
|
||||||
------------------
|
|
||||||
|
|
||||||
1. Install `haml`:
|
|
||||||
```bash
|
|
||||||
sudo apt install ruby-haml
|
|
||||||
```
|
|
||||||
2. Install `pandoc-ruby`:
|
|
||||||
```bash
|
|
||||||
gem install pandoc-ruby
|
|
||||||
```
|
|
||||||
3. Run `Makefile`
|
|
||||||
```bash
|
|
||||||
make
|
|
||||||
```
|
|
||||||
4. View the results (using, for example, [npm serve](https://www.npmjs.com/package/serve))
|
|
||||||
```bash
|
|
||||||
npm serve out/
|
|
||||||
```
|
|
||||||
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
<meta http-equiv="Refresh" content="0; url='https://github.com/iSoron/uhabits/discussions/689'" />
|
|
||||||
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 458 KiB |
7
landing/assets/lib/css/bootstrap.min.css
vendored
2
landing/assets/lib/js/jquery.min.js
vendored
|
Before Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 112 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 118 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 2.0 MiB |
|
Before Width: | Height: | Size: 142 KiB |
|
Before Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 116 KiB |
|
Before Width: | Height: | Size: 28 KiB |
@@ -1,106 +0,0 @@
|
|||||||
!!! 5
|
|
||||||
%html
|
|
||||||
%head
|
|
||||||
%meta(charset="UTF-8")
|
|
||||||
%link(href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet" type="text/css")
|
|
||||||
%meta(name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no")
|
|
||||||
%title Loop Habit Tracker
|
|
||||||
%link(rel="stylesheet" type="text/css" href="lib/css/bootstrap.min.css")
|
|
||||||
%link(rel="stylesheet" type="text/css" href="index.css")
|
|
||||||
|
|
||||||
%body
|
|
||||||
.navbar.navbar-expand-md.navbar-light.bg-light
|
|
||||||
%a.navbar-brand(href="/")
|
|
||||||
%b Loop
|
|
||||||
Habit Tracker
|
|
||||||
%button.navbar-toggler(type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation")
|
|
||||||
%span.navbar-toggler-icon
|
|
||||||
#navbar.collapse.navbar-collapse
|
|
||||||
%ul.navbar-nav.mr-auto.mt-2.mt-lg-0
|
|
||||||
%li.nav-item
|
|
||||||
%a.nav-link(href="faq.html") FAQ
|
|
||||||
%li.nav-item
|
|
||||||
%a.nav-link(href="privacy.html") Privacy
|
|
||||||
%li.nav-item
|
|
||||||
%a.nav-link(href="https://source.loophabits.org") Source Code
|
|
||||||
%li.nav-item
|
|
||||||
%a.nav-link(href="https://translate.loophabits.org") Translate
|
|
||||||
|
|
||||||
.jumbotron.jumbotron-fluid
|
|
||||||
.site-wrapper
|
|
||||||
.container
|
|
||||||
.row.vertical-align
|
|
||||||
.col-md
|
|
||||||
%h1.display-4
|
|
||||||
Get your life on track
|
|
||||||
%p.lead
|
|
||||||
With daily reminders, beautiful charts and insightful statistics,
|
|
||||||
Loop Habit Tracker™ helps you create and maintain great habits. Completely free and open-source.
|
|
||||||
|
|
||||||
.store-badges
|
|
||||||
%a(href="https://play.google.com/store/apps/details?id=org.isoron.uhabits")
|
|
||||||
%img(src="images/google-play.png")
|
|
||||||
%a(href="https://f-droid.org/en/packages/org.isoron.uhabits/")
|
|
||||||
%img(src="images/f-droid.png")
|
|
||||||
.col-md
|
|
||||||
.s2
|
|
||||||
%img.screenshot(src="screenshots/uhabits1.png")
|
|
||||||
.s1
|
|
||||||
%img.screenshot(src="screenshots/uhabits4.png")
|
|
||||||
|
|
||||||
.section.screenshots
|
|
||||||
%span
|
|
||||||
%a(href="screenshots/uhabits1.png")
|
|
||||||
%img(src="screenshots/uhabits1_th.png")
|
|
||||||
%a(href="screenshots/uhabits2.png")
|
|
||||||
%img(src="screenshots/uhabits2_th.png")
|
|
||||||
%a(href="screenshots/uhabits3.png")
|
|
||||||
%img(src="screenshots/uhabits3_th.png")
|
|
||||||
%span
|
|
||||||
%a(href="screenshots/uhabits4.png")
|
|
||||||
%img(src="screenshots/uhabits4_th.png")
|
|
||||||
%a(href="screenshots/uhabits5.png")
|
|
||||||
%img(src="screenshots/uhabits5_th.png")
|
|
||||||
|
|
||||||
.section
|
|
||||||
.feature-header
|
|
||||||
%h1
|
|
||||||
Features
|
|
||||||
.container
|
|
||||||
.row
|
|
||||||
.col-md
|
|
||||||
%ul
|
|
||||||
%li
|
|
||||||
%h3 Habit score
|
|
||||||
Loop has an advanced formula for calculating the strength of your habits. Every repetition makes your habit stronger and every missed day makes it weaker. A few missed days after a long streak, however, will not completely destroy your progress, unlike many other don't-break-the-chain apps.
|
|
||||||
|
|
||||||
%li
|
|
||||||
%h3 Flexible schedules
|
|
||||||
In addition to daily habits, Loop supports habits with more complex schedules, such as 3 times per week or every other day.
|
|
||||||
%li
|
|
||||||
%h3 Reminders
|
|
||||||
Schedule notifications to remind you of your habits. Each habit can have its own reminder, at a chosen time of the day. Easily check or dismiss your habit directly from the notification.
|
|
||||||
%li
|
|
||||||
%h3 Widgets
|
|
||||||
Be reminded of your habits whenever you unlock your phone. Colorful widgets allow you to track your habits directly from your home screen, without even opening the app.
|
|
||||||
.col-md
|
|
||||||
%ul
|
|
||||||
%li
|
|
||||||
%h3 Take control of your data
|
|
||||||
If you want to further analyze your data, or move it to another service, Loop allows you to export it to spreadsheets (CSV) or to a database file (SQLite). For power users, check marks can be added through task automation apps such as Tasker.
|
|
||||||
%li
|
|
||||||
%h3 No limitations
|
|
||||||
Track as many habits as you wish. Loop imposes no artificial limits on how many habits you can have. All features are available to all users, and there are no in-app purchases.
|
|
||||||
%li
|
|
||||||
%h3 Completely ad-free and open source
|
|
||||||
There are no advertisements, annoying notifications or intrusive permissions in this app, and there will never be. The app is completely open-source (GPLv3).
|
|
||||||
%li
|
|
||||||
%h3 Works offline and respects your privacy
|
|
||||||
Loop doesn't require an Internet connection or online account registration. Your confidential data is never sent to anyone. Neither the developers nor any third-parties have access to it.
|
|
||||||
|
|
||||||
.section.footer
|
|
||||||
Copyright © 2016–2020, Alinson Santos Xavier. All Rights Reserved.
|
|
||||||
|
|
||||||
|
|
||||||
%script(type="text/javascript" src="lib/js/jquery.min.js")
|
|
||||||
%script(type="text/javascript" src="lib/js/bootstrap.bundle.min.js")
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
html, body
|
|
||||||
max-width: 100%
|
|
||||||
overflow-x: hidden
|
|
||||||
|
|
||||||
body
|
|
||||||
font-family: 'Open Sans', sans-serif
|
|
||||||
padding-bottom: 0
|
|
||||||
|
|
||||||
a, a:hover
|
|
||||||
text-decoration: none
|
|
||||||
|
|
||||||
.navbar
|
|
||||||
box-shadow: rgba(0,0,0,0.4) 0px 0px 20px
|
|
||||||
background-color: white !important
|
|
||||||
|
|
||||||
.nav-link
|
|
||||||
margin: 0px 18px
|
|
||||||
|
|
||||||
.section
|
|
||||||
background-color: transparent
|
|
||||||
padding: 18px 0px
|
|
||||||
|
|
||||||
.container
|
|
||||||
ul
|
|
||||||
list-style-type: none
|
|
||||||
h3
|
|
||||||
font-size: 16px
|
|
||||||
font-weight: bold
|
|
||||||
margin: 18px 0px 0px 0px
|
|
||||||
|
|
||||||
.screenshots
|
|
||||||
text-align: center
|
|
||||||
background-color: #222
|
|
||||||
img
|
|
||||||
margin: 0.5%
|
|
||||||
border-radius: 10px
|
|
||||||
border: 3px solid #fff2
|
|
||||||
box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.5)
|
|
||||||
max-width: 17%
|
|
||||||
|
|
||||||
.footer
|
|
||||||
color: #888
|
|
||||||
background-color: #222
|
|
||||||
text-align: center
|
|
||||||
font-size: 12px
|
|
||||||
|
|
||||||
.jumbotron
|
|
||||||
background: linear-gradient(rgba(0,30,200,0.8),rgba(90,30,150,0.5)), url("images/hero-background-filter.jpg")
|
|
||||||
box-shadow: rgba(0,0,0,0.5) 0px 0px 20px
|
|
||||||
margin: 0
|
|
||||||
h1
|
|
||||||
max-width: 25rem
|
|
||||||
font-weight: bold
|
|
||||||
color: white
|
|
||||||
p
|
|
||||||
max-width: 40rem
|
|
||||||
color: white
|
|
||||||
.screenshot
|
|
||||||
box-shadow: rgba(0, 0, 0, 0.5) 5px 5px 20px
|
|
||||||
padding: 0px 0px 0px 0px
|
|
||||||
border-radius: 10px
|
|
||||||
border: 2px solid rgba(255, 255, 255, 0.2)
|
|
||||||
background-color: transparent
|
|
||||||
max-width: 300px
|
|
||||||
.store-badges
|
|
||||||
margin: 2rem 1rem
|
|
||||||
img
|
|
||||||
opacity: 0.8
|
|
||||||
height: 75px
|
|
||||||
img:hover
|
|
||||||
opacity: 1.0
|
|
||||||
.s1
|
|
||||||
padding-bottom: 50px
|
|
||||||
padding-left: 50px
|
|
||||||
.s2
|
|
||||||
position: absolute
|
|
||||||
top: 50px
|
|
||||||
left: 175px
|
|
||||||
|
|
||||||
.feature-header
|
|
||||||
text-align: center
|
|
||||||
font-weight: bold
|
|
||||||
padding: 18px
|
|
||||||
|
|
||||||
.align-right
|
|
||||||
text-align: right
|
|
||||||
|
|
||||||
.vertical-align
|
|
||||||
display: flex
|
|
||||||
align-items: center
|
|
||||||
|
|
||||||
.content
|
|
||||||
max-width: 800px
|
|
||||||
margin: 18px auto
|
|
||||||
padding: 0px 18px
|
|
||||||
//padding-left: 120px
|
|
||||||
h2, h3, h4
|
|
||||||
margin: 27px 0px 9px 0px
|
|
||||||
h2, h3
|
|
||||||
//margin-left: -120px
|
|
||||||
h4
|
|
||||||
//margin-left: -60px
|
|
||||||
font-size: 16px
|
|
||||||
font-weight: bold
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
!!! 5
|
|
||||||
%html
|
|
||||||
%head
|
|
||||||
%meta(charset="UTF-8")
|
|
||||||
%link(href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet" type="text/css")
|
|
||||||
%meta(name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no")
|
|
||||||
%title Privacy | Loop Habit Tracker
|
|
||||||
%link(rel="stylesheet" type="text/css" href="lib/css/bootstrap.min.css")
|
|
||||||
%link(rel="stylesheet" type="text/css" href="index.css")
|
|
||||||
|
|
||||||
%body
|
|
||||||
.navbar.navbar-expand-md.navbar-light.bg-light
|
|
||||||
%a.navbar-brand(href="/")
|
|
||||||
%b Loop
|
|
||||||
Habit Tracker
|
|
||||||
%button.navbar-toggler(type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation")
|
|
||||||
%span.navbar-toggler-icon
|
|
||||||
#navbar.collapse.navbar-collapse
|
|
||||||
%ul.navbar-nav.mr-auto.mt-2.mt-lg-0
|
|
||||||
%li.nav-item
|
|
||||||
%a.nav-link(href="faq.html") FAQ
|
|
||||||
%li.nav-item
|
|
||||||
%a.nav-link(href="privacy.html") Privacy
|
|
||||||
%li.nav-item
|
|
||||||
%a.nav-link(href="https://source.loophabits.org") Source Code
|
|
||||||
%li.nav-item
|
|
||||||
%a.nav-link(href="https://translate.loophabits.org") Translate
|
|
||||||
|
|
||||||
%body
|
|
||||||
.content
|
|
||||||
:markdown
|
|
||||||
#{File.open("src/privacy.md").read}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
## Privacy Policy
|
|
||||||
|
|
||||||
- All data provided to Loop Habit Tracker is only stored locally in your
|
|
||||||
device. Loop Habit Tracker does not upload your data anywhere. The
|
|
||||||
developers of Loop Habit Tracker do not have access to your data.
|
|
||||||
|
|
||||||
- Your data is not shared with any 3rd parties. Loop Habit Tracker does not
|
|
||||||
include any advertisement libraries or any 3rd party tracking (analytics)
|
|
||||||
code, such as Google Analytics or Facebook SDK.
|
|
||||||
|
|
||||||
- If you have activated "backup & reset" in your phone settings (Settings /
|
|
||||||
Backup & Reset / Back up my data), you should be aware that Android itself
|
|
||||||
will periodically save a copy of your phone's data in Google's servers. The
|
|
||||||
developers of Loop Habit Tracker do not have access to this data.
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
pluginManagement {
|
pluginManagement {
|
||||||
repositories {
|
repositories {
|
||||||
gradlePluginPortal()
|
gradlePluginPortal()
|
||||||
jcenter()
|
|
||||||
google()
|
google()
|
||||||
}
|
}
|
||||||
resolutionStrategy.eachPlugin {
|
resolutionStrategy.eachPlugin {
|
||||||
|
|||||||
67
tools/parseInstrument.py
Executable file
@@ -0,0 +1,67 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Android Instrumentation Test Parser
|
||||||
|
|
||||||
|
Given a raw Android Instrumentation log (produced by "adb shell am instrument -r ...") this script
|
||||||
|
return zero if all tests pass and non-zero if some tests fail. In case of failure, this script
|
||||||
|
also prints arguments that, if passed to "am instrument", will cause it to re-run just the tests
|
||||||
|
that failed. This script additionally prints warnings about the tests on the STDERR; e.g. slow tests.
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
import re
|
||||||
|
|
||||||
|
STATUS_START = 1
|
||||||
|
STATUS_DISABLED = -3
|
||||||
|
SLOW_TEST_THRESHOLD = 5.0
|
||||||
|
|
||||||
|
COLOR_RED = '\033[91m'
|
||||||
|
COLOR_YELLOW = '\033[93m'
|
||||||
|
COLOR_END = '\033[0m'
|
||||||
|
|
||||||
|
def error(msg):
|
||||||
|
sys.stderr.write("%s%s%s\n" % (COLOR_RED, msg, COLOR_END))
|
||||||
|
|
||||||
|
def warning(msg):
|
||||||
|
sys.stderr.write("%s%s%s\n" % (COLOR_YELLOW, msg, COLOR_END))
|
||||||
|
|
||||||
|
log_filename = sys.argv[1]
|
||||||
|
current_class, current_method = None, None
|
||||||
|
failed_tests = []
|
||||||
|
am_args = "-e class "
|
||||||
|
exit_code = 1
|
||||||
|
|
||||||
|
for line in open(log_filename).readlines():
|
||||||
|
matches = re.findall('^([0-9.]*)', line)
|
||||||
|
current_time = float(matches[0])
|
||||||
|
|
||||||
|
matches = re.findall('INSTRUMENTATION_STATUS: class=(.*)', line)
|
||||||
|
if len(matches) > 0:
|
||||||
|
current_class = matches[0]
|
||||||
|
|
||||||
|
matches = re.findall('INSTRUMENTATION_STATUS: test=(.*)', line)
|
||||||
|
if len(matches) > 0:
|
||||||
|
current_method = matches[0]
|
||||||
|
|
||||||
|
matches = re.findall('OK \([0-9]* tests?\)', line)
|
||||||
|
if len(matches) > 0:
|
||||||
|
exit_code = 0
|
||||||
|
|
||||||
|
matches = re.findall('INSTRUMENTATION_STATUS_CODE: ([-0-9]*)', line)
|
||||||
|
if len(matches) > 0:
|
||||||
|
status_code = int(matches[0])
|
||||||
|
if (status_code < 0) and (status_code != STATUS_DISABLED):
|
||||||
|
am_args += f"{current_class}#{current_method},"
|
||||||
|
failed_tests.append(f"{current_class}#{current_method}")
|
||||||
|
if status_code == STATUS_START:
|
||||||
|
initial_time = current_time
|
||||||
|
else:
|
||||||
|
elapsed_time = current_time - initial_time
|
||||||
|
if(elapsed_time > SLOW_TEST_THRESHOLD):
|
||||||
|
warning("SLOW %s#%s (%.2f seconds)" % (current_class, current_method, elapsed_time))
|
||||||
|
|
||||||
|
if len(failed_tests) > 0:
|
||||||
|
for test in failed_tests:
|
||||||
|
error("FAIL %s" % test)
|
||||||
|
print(am_args[:-1])
|
||||||
|
|
||||||
|
sys.exit(exit_code)
|
||||||
@@ -1,350 +1,415 @@
|
|||||||
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; Chinese Simplified; Italian; Spanish; Portuguese; French; Hungarian; Chinese Traditional; Turkish; Russian; Polish; Arabic; German; Korean; Greek; Catalan; Bulgarian; Hindi; Slovenian; Ukrainian; Serbian (Cyrillic); Czech; Indonesian; Croatian; Danish; Dutch; Romanian; Swedish; Basque; Persian; Finnish; Vietnamese; Tamil; Telugu; Hebrew; Esperanto; Norwegian; Afrikaans; Slovak; Armenian; Serbian (Latin); Uyghur",15497,18825,1308,0,1896,84,4315,"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
|
dukelc,Slovak,1046,993,0,0,0,0,0,"2020-08-27 14:02:41"
|
||||||
dukelc,Slovak,919,880,0,0,0,0,0
|
"David (Cliff122)",Swedish,1040,1019,725,6,0,0,700,"2020-01-21 13:56:55"
|
||||||
KMakoto,"Chinese Traditional",745,1146,949,0,0,0,745
|
"Omer I.S. (omeritzics)",Hebrew,1040,927,1122,14,1,0,975,"2020-10-11 20:10:51"
|
||||||
"Evren (evrenkiymaz)",Turkish,688,604,0,71,4,0,0
|
"Intan Ayunda (Intan_Ayunda)",Indonesian,818,811,985,0,0,0,729,"2020-10-14 07:51:58"
|
||||||
"David Nos (david.nos)","Catalan; Spanish",667,731,0,0,0,0,0
|
"Mihail Stefanov (MStefanov)",Bulgarian,755,794,3,0,2,0,2,"2017-03-31 16:09:02"
|
||||||
androide74,Italian,644,659,0,2,0,0,0
|
KMakoto,"Chinese Traditional",745,1146,949,0,0,0,745,"2019-10-22 04:19:52"
|
||||||
Tomairuka,Japanese,633,1636,909,43,0,0,564
|
"Evren (evrenkiymaz)",Turkish,688,604,0,71,28,22,0,"2020-10-04 03:39:16"
|
||||||
"Dmitriy Bogdanov (di72nn)",Russian,625,572,1175,0,34,0,519
|
andaryon,Czech,681,606,0,108,0,0,0,"2021-11-25 10:20:45"
|
||||||
reyhoon,Persian,624,759,0,1,1,0,0
|
"Antti Kallio (antti.kallio)",Finnish,668,539,0,5,0,0,0,"2021-07-03 05:54:44"
|
||||||
Osoitz,Basque,610,545,0,9,0,0,3
|
"David Nos (david.nos)","Catalan; Spanish",667,731,0,0,1,0,0,"2020-01-04 10:15:36"
|
||||||
fabian.bouchal,German,548,527,0,6,0,3,72
|
androide74,Italian,662,681,0,2,0,0,0,"2020-02-06 15:46:28"
|
||||||
boban77,Czech,509,461,0,2,0,0,0
|
Osoitz,Basque,655,595,0,9,0,0,3,"2018-01-23 14:07:47"
|
||||||
"Yoav Argov (YoavArgov)",Hebrew,501,461,0,0,1,8,3
|
"Dmitriy Bogdanov (di72nn)",Russian,643,589,1197,0,36,0,515,"2017-03-31 10:00:48"
|
||||||
REMOVED_USER,Norwegian,501,498,501,0,148,0,501
|
Tomairuka,Japanese,633,1636,909,43,0,0,564,"2020-12-12 12:14:22"
|
||||||
"Martim Parente (Sharlimar)",Portuguese,497,534,0,38,0,0,0
|
reyhoon,Persian,624,759,0,1,3,1,0,"2020-10-01 18:17:23"
|
||||||
"chrrris1987 (Chrrris1987)",Dutch,467,478,0,23,0,0,0
|
"Saeed Esmaili (saaeed.es20)",Persian,586,795,0,5,4,0,0,"2020-11-26 15:41:15"
|
||||||
"Huy Ngo (huyngo)",Vietnamese,461,695,0,1,0,0,0
|
fabian.bouchal,German,548,527,0,6,0,3,72,"2020-01-07 06:43:37"
|
||||||
"黄克 (hk13127)","Chinese Simplified",461,765,0,1,0,0,0
|
"Isti (eisti)",Hungarian,528,476,0,0,0,0,0,"2020-12-03 12:02:51"
|
||||||
"Arkadiusz Bubak (epitek)",Polish,458,416,29,24,0,3,0
|
boban77,Czech,509,461,0,2,29,0,0,"2020-04-30 13:18:24"
|
||||||
marco.baturan,Esperanto,452,452,0,0,0,0,0
|
"Martim Parente (martimparente)",Portuguese,505,542,0,38,0,0,0,"2020-08-26 10:22:11"
|
||||||
"Sief Tarek (sieftarek135)",Arabic,447,455,0,0,0,0,0
|
"Yoav Argov (YoavArgov)",Hebrew,501,461,0,0,1,8,91,"2017-04-28 07:23:01"
|
||||||
"Omer I.S. (omeritzics)",Hebrew,428,366,0,14,1,0,0
|
REMOVED_USER,Norwegian,501,498,501,0,148,0,501,"2017-07-05 19:02:25"
|
||||||
"Samuel Guay (SamGuay)",French,426,486,0,6,0,0,0
|
"chrrris1987 (Chrrris1987)",Dutch,467,478,0,23,0,0,0,"2020-02-03 05:26:04"
|
||||||
"Diana Karaseva (Sun_Dianka)",Russian,399,373,0,10,1,0,221
|
"黄克 (hk13127)","Chinese Simplified",461,765,0,1,0,0,24,"2020-01-17 23:16:03"
|
||||||
"Alexander Jansson (dalecarlian)",Swedish,396,406,507,0,0,3,399
|
"Huy Ngo (huyngo)",Vietnamese,461,695,0,1,0,0,0,"2020-01-26 11:58:36"
|
||||||
luiandresgonzalez,Spanish,383,403,0,1,0,0,0
|
"Arkadiusz Bubak (epitek)",Polish,458,416,52,24,9,4,0,"2020-11-05 05:11:58"
|
||||||
"Thamara Andrade (tkcandrade)","Portuguese, Brazilian",380,387,0,0,1,0,252
|
marco.baturan,Esperanto,452,452,0,0,0,0,0,"2020-06-23 02:49:46"
|
||||||
"Sølv Ræven (soelvraeven)",Danish,370,370,0,0,0,0,0
|
"Sief Tarek (sieftarek135)",Arabic,447,455,0,0,0,0,0,"2021-02-07 14:35:21"
|
||||||
"Isti (eisti)",Hungarian,367,329,0,0,0,0,0
|
"Alparslan Şakçi (sakci)",Turkish,436,372,0,118,1,0,0,"2022-01-14 12:03:11"
|
||||||
"Anh Quân (dangquanuet)",Vietnamese,362,530,0,42,1,0,0
|
JY3,"Chinese Simplified",427,727,295,0,1,0,222,"2021-03-08 08:53:35"
|
||||||
gapszi,Hungarian,348,301,0,86,0,0,0
|
"Samuel Guay (SamGuay)",French,426,486,0,6,0,0,0,"2020-06-25 07:14:38"
|
||||||
"Mahdi Nasiri (mahdi.nasiri)",Persian,343,465,0,39,3,0,0
|
"Diana Karaseva (Sun_Dianka)",Russian,399,373,0,10,1,0,209,"2020-01-30 06:40:02"
|
||||||
Seoyul,Korean,339,825,0,0,27,0,0
|
"Alexander Jansson (dalecarlian)",Swedish,396,406,507,0,0,3,399,"2017-06-21 01:37:32"
|
||||||
"Magimai Prakasam (magimai)",Tamil,336,831,0,12,0,0,0
|
luiandresgonzalez,Spanish,383,403,0,1,28,0,0,"2020-07-11 14:20:44"
|
||||||
"Michael Malak (MichaelKMalak)",Arabic,304,271,0,0,0,0,0
|
"Thamara Andrade (tkcandrade)","Portuguese, Brazilian",380,387,0,0,1,0,239,"2020-01-09 19:35:48"
|
||||||
"Elina Salminen (salminen.elina.m)",Finnish,297,227,0,0,0,0,0
|
"Sølv Ræven (soelvraeven)",Danish,370,370,0,0,0,0,0,"2020-11-28 16:46:18"
|
||||||
ayane.m,Japanese,292,863,0,1,2,0,22
|
"Anh Quân (dangquanuet)",Vietnamese,362,530,0,42,2,0,0,"2017-10-29 12:27:44"
|
||||||
"Marius Teufelweich (teufelweich)",German,249,256,606,4,0,0,146
|
gapszi,Hungarian,348,301,0,86,0,0,0,"2019-04-08 01:35:54"
|
||||||
cobalt59,German,237,234,0,1,23,1,132
|
"Mahdi Nasiri (mahdi.nasiri)",Persian,343,465,0,39,3,1,0,"2017-07-14 09:17:25"
|
||||||
"QWERT (lurenjia01)","Chinese Simplified",236,407,0,0,0,0,0
|
Seoyul,Korean,339,825,0,0,27,0,0,"2017-06-21 08:11:39"
|
||||||
beriain,Basque,234,235,0,0,2,0,0
|
"Magimai Prakasam (magimai)",Tamil,336,831,0,12,0,0,0,"2018-04-15 21:16:08"
|
||||||
"Intan Ayunda (Intan_Ayunda)",Indonesian,228,239,336,0,0,0,135
|
"Michael Malak (MichaelKMalak)",Arabic,304,271,0,0,1,0,0,"2020-05-26 19:47:58"
|
||||||
pnhpnh,Vietnamese,225,343,0,1,3,0,0
|
Blinkin,Dutch,297,334,0,5,0,0,0,"2021-06-14 10:30:05"
|
||||||
"Dika Fitrian Dwi Putra (OsamuDazai)",Indonesian,221,215,0,0,0,0,33
|
"Elina Salminen (salminen.elina.m)",Finnish,297,227,0,0,0,0,0,"2021-01-06 01:28:57"
|
||||||
easyrepro,Telugu,214,297,0,0,0,0,0
|
ayane.m,Japanese,292,863,0,1,5,0,22,"2019-11-20 03:28:26"
|
||||||
taras-ko,Ukrainian,211,183,0,1,4,0,19
|
"Marius Teufelweich (teufelweich)",German,267,272,611,4,13,1,146,"2021-03-12 04:11:38"
|
||||||
sojusnik,German,207,200,1,0,30,0,66
|
hypnotichemionus,"Chinese Simplified",249,430,0,0,8,0,19,"2020-03-08 01:46:25"
|
||||||
axmed99,Ukrainian,203,177,0,40,0,0,53
|
cobalt59,German,237,234,0,1,24,1,132,"2017-06-05 05:18:33"
|
||||||
"Heru Yen (heruyen)",Indonesian,201,201,0,0,0,0,2
|
beriain,Basque,234,235,0,0,2,0,0,"2017-03-31 15:42:28"
|
||||||
Ishmaeel,Turkish,193,174,0,129,6,0,0
|
pnhpnh,Vietnamese,225,343,0,1,3,0,0,"2017-11-27 12:06:07"
|
||||||
fbruna17,Danish,181,179,0,1,0,0,0
|
"Dika Fitrian Dwi Putra (OsamuDazai)",Indonesian,221,215,0,0,0,0,48,"2020-07-13 04:40:27"
|
||||||
"Omry Cohen (omrycohen)",Hebrew,175,156,0,1,0,0,0
|
easyrepro,Telugu,214,297,0,0,4,0,0,"2020-06-12 12:52:10"
|
||||||
Bryanx,Dutch,174,165,0,5,0,0,0
|
taras-ko,Ukrainian,211,183,0,1,4,0,19,"2017-10-26 16:52:22"
|
||||||
"Pierre GALIEGUE (pierre.galiegue)",French,171,194,0,24,2,0,0
|
sojusnik,German,207,200,1,0,30,0,66,"2017-04-03 17:11:56"
|
||||||
DionysosDV,Greek,165,153,0,0,0,0,0
|
"Andrij Mizyk (andmizyk)",Ukrainian,204,178,0,40,0,0,53,"2021-04-01 03:56:20"
|
||||||
"Gustavo Lima (GustavoLima)",Portuguese,158,177,0,1,4,10,0
|
"Heru Yen (heruyen)",Indonesian,201,201,0,0,0,0,25,"2020-06-29 18:39:15"
|
||||||
"Aputsiak Niels Janussen (aputtu)",Danish,153,171,0,0,0,0,0
|
"Vijaykumar Borkar (vjkumar)",Hindi,200,364,0,11,0,0,0,"2021-08-06 16:12:15"
|
||||||
plitwin,Polish,145,128,0,1,16,0,26
|
_translator,French,199,227,0,11,0,0,0,"2021-07-06 07:54:12"
|
||||||
"Lương Vĩnh Khang (LuongVinhKhang)",Vietnamese,144,256,0,0,46,1,0
|
Ishmaeel,Turkish,193,174,0,129,17,6,0,"2017-10-04 03:54:00"
|
||||||
azzamsa,Indonesian,142,136,0,48,0,1,50
|
oscfd,Spanish,192,201,0,2,4,0,0,"2021-05-21 17:58:22"
|
||||||
"Neysa Nasywa (neysanasywa)",Indonesian,140,141,0,0,0,0,35
|
bruhwut,Vietnamese,189,292,0,1,0,0,0,"2021-05-21 07:16:30"
|
||||||
JY3,"Chinese Simplified",136,217,80,0,0,0,80
|
"Aputsiak Niels Janussen (aputtu)",Danish,187,200,0,0,0,0,0,"2019-08-28 05:47:42"
|
||||||
"Mohammed Imthath (mimthath4)",Tamil,136,274,0,0,11,0,0
|
fbruna17,Danish,181,179,0,1,0,0,0,"2021-01-28 15:48:47"
|
||||||
roptat,French,132,154,0,112,88,5,0
|
Bryanx,Dutch,179,168,0,5,2,0,0,"2019-11-21 17:08:12"
|
||||||
"Trần Thái (tranhoangthai2001)",Vietnamese,127,186,0,8,1,0,0
|
"Omry Cohen (omrycohen)",Hebrew,175,156,0,1,0,0,33,"2021-01-18 07:33:23"
|
||||||
"OP Smosher (teenwolffan44)","Serbian (Cyrillic)",124,122,0,0,0,0,18
|
"Pierre GALIEGUE (pierre.galiegue)",French,171,194,0,24,4,0,0,"2020-08-16 11:41:35"
|
||||||
4001982248998,Esperanto,122,119,0,0,0,0,0
|
plitwin,Polish,168,151,0,2,31,0,49,"2021-01-20 06:18:37"
|
||||||
"StoP4Me (Lcqp)",Romanian,121,119,0,0,3,0,0
|
DionysosDV,Greek,165,153,0,0,0,0,0,"2021-02-27 19:05:25"
|
||||||
Sebastian05067,Spanish,114,133,0,55,18,0,0
|
"Gustavo Lima (GustavoLima)",Portuguese,158,177,0,1,4,10,0,"2020-08-26 10:35:05"
|
||||||
"Tanya (MagicUnderHood)",Russian,114,98,0,19,0,0,32
|
"Ravi Rami (ramiravi)",Hindi,151,248,0,0,0,0,0,"2021-10-10 09:19:40"
|
||||||
REMOVED_USER,Arabic,111,106,0,22,21,2,0
|
"Lương Vĩnh Khang (LuongVinhKhang)",Vietnamese,144,256,0,0,46,1,0,"2017-08-10 10:05:58"
|
||||||
mohmans,Arabic,109,103,0,2,0,0,0
|
azzamsa,Indonesian,142,136,0,48,0,1,26,"2017-06-16 18:29:45"
|
||||||
"Ivan Krušlin (krux3r)",Croatian,108,122,503,0,0,0,108
|
"yoding (yodingc)","Chinese Traditional; Chinese Simplified",141,271,0,10,0,0,0,"2021-07-07 01:45:45"
|
||||||
"Iabin Arteaga (iabin)",Spanish,108,111,0,4,20,0,0
|
"Neysa Nasywa (neysanasywa)",Indonesian,140,141,0,0,0,0,60,"2020-11-18 10:32:10"
|
||||||
2kaafone,Finnish,105,90,0,0,0,0,0
|
mohmans,Arabic,139,141,0,12,1,0,0,"2020-11-23 02:48:00"
|
||||||
"Adam Jurkiewicz (hasztagg)",Polish,104,105,529,0,0,0,104
|
"Eilif Adelvice (adelvice)",Spanish,139,154,0,96,1,0,0,"2021-08-05 07:20:21"
|
||||||
"Saeed Esmaili (saaeed.es20)",Persian,104,141,0,0,0,0,0
|
"Mohammed Imthath (mimthath4)",Tamil,136,274,0,0,11,0,0,"2018-02-15 22:41:15"
|
||||||
"just a name bro (justanamebr0)",Danish,98,109,0,0,1,0,0
|
carllacan,Catalan,134,155,0,2,0,0,0,"2021-11-13 13:12:07"
|
||||||
"Nam Nguyen (namnl2706)",Vietnamese,95,137,0,0,0,0,0
|
roptat,French,132,154,0,112,89,5,0,"2017-04-19 16:54:47"
|
||||||
"손유정 (yuwon1213)",Korean,95,57,0,0,0,0,0
|
"Trần Thái (tranhoangthai2001)",Vietnamese,127,186,0,8,1,0,0,"2018-03-01 10:51:39"
|
||||||
ranmagen,Hebrew,91,78,0,0,0,0,0
|
"OP Smosher (teenwolffan44)","Serbian (Cyrillic)",124,122,0,0,0,0,18,"2020-11-05 09:41:35"
|
||||||
LoneWanderer,"Chinese Traditional",90,137,0,4,0,0,0
|
4001982248998,Esperanto,122,119,0,0,0,0,0,"2017-10-08 04:13:02"
|
||||||
"Vo - (voyl)","Chinese Traditional",89,126,0,0,5,0,0
|
"StoP4Me (Lcqp)",Romanian,121,119,0,0,3,0,0,"2018-05-06 18:51:59"
|
||||||
ikkaz,Indonesian,89,84,0,5,0,0,4
|
alalloush,Arabic,118,129,0,2,14,3,0,"2017-03-31 12:37:17"
|
||||||
"Irene K (Heaun)",Korean,88,75,0,25,0,0,0
|
"Tanya (MagicUnderHood)",Russian,114,98,0,19,0,0,54,"2019-04-21 10:44:03"
|
||||||
"Kumar Anand (kumar0500)",Hindi,87,125,0,0,0,0,0
|
Sebastian05067,Spanish,114,133,0,55,28,0,0,"2017-05-14 00:48:16"
|
||||||
Prosta4ok_ua,Ukrainian,87,84,0,1,0,0,17
|
REMOVED_USER,Arabic,111,106,0,22,22,2,0,"2018-01-05 07:01:45"
|
||||||
"Ohad Edri (ohadalte)",Hebrew,85,79,0,0,1,3,0
|
"Iabin Arteaga (iabin)",Spanish,108,111,0,4,21,0,0,"2017-08-26 21:08:54"
|
||||||
helectron,Persian,84,102,0,1,0,0,0
|
"Ivan Krušlin (krux3r)",Croatian,108,122,503,0,0,0,108,"2017-03-31 09:15:24"
|
||||||
"Radu Cebotari (wildProgrammer)",Romanian,84,92,0,1,0,0,0
|
2kaafone,Finnish,105,90,0,0,0,0,0,"2019-08-12 06:58:48"
|
||||||
"Israa Z (sosozozo)",Arabic,79,87,0,43,11,0,3
|
"Adam Jurkiewicz (hasztagg)",Polish,104,105,529,0,0,0,104,"2017-03-31 09:50:51"
|
||||||
"Sofia Neves (sofiasonev)","Portuguese, Brazilian",79,84,0,1,0,0,46
|
"just a name bro (justanamebr0)",Danish,98,109,0,0,1,0,0,"2019-06-19 11:57:55"
|
||||||
Tiralka,French,79,91,0,92,1,0,0
|
"Nam Nguyen (namnl2706)",Vietnamese,95,137,0,0,0,0,0,"2020-08-18 23:02:33"
|
||||||
"Jacob Roller (jdr28070)",Korean,79,61,0,0,0,0,0
|
"손유정 (yuwon1213)",Korean,95,57,0,0,1,0,0,"2021-03-30 05:25:33"
|
||||||
"Toni Mustonen (toni.mustonen)",Finnish,78,72,0,0,0,0,0
|
ranmagen,Hebrew,91,78,0,0,0,0,0,"2021-02-16 05:44:31"
|
||||||
"Michael (quelbs)",German,76,75,0,1,0,0,39
|
LoneWanderer,"Chinese Traditional",90,137,0,4,0,0,0,"2020-09-29 05:24:48"
|
||||||
"Fauz Aladeem (topfauz)",Arabic,76,77,0,0,0,1,0
|
ikkaz,Indonesian,89,84,0,5,0,0,4,"2019-09-02 19:58:54"
|
||||||
RealDonald,Dutch,67,69,0,121,10,0,0
|
"Vo - (voyl)","Chinese Traditional",89,126,0,0,5,0,0,"2020-09-02 23:34:42"
|
||||||
sirekanyan,"Armenian; Russian",66,65,0,0,0,0,0
|
"Irene K (Heaun)",Korean,88,75,0,25,0,0,0,"2020-03-16 11:31:12"
|
||||||
"Константин К. (kocyak1991)",Russian,64,60,0,0,1,2,0
|
Prosta4ok_ua,Ukrainian,87,84,0,1,0,0,17,"2020-01-23 19:43:41"
|
||||||
"Laura Sophie (laurasophie20)",German,62,67,0,4,0,0,0
|
"Kumar Anand (kumar0500)",Hindi,87,125,0,0,0,0,0,"2020-11-07 02:46:09"
|
||||||
raden20,Indonesian,61,62,177,0,1,0,64
|
"Ohad Edri (ohadalte)",Hebrew,85,79,0,0,1,3,13,"2020-07-04 03:42:09"
|
||||||
"Jan Wojtecki (j4nw)",Polish,58,46,0,0,0,0,26
|
helectron,Persian,84,102,0,1,0,0,0,"2021-03-02 04:10:51"
|
||||||
"Deepak Bharathi (deepakbharathi1994)",Tamil,56,107,0,0,11,4,0
|
"Radu Cebotari (wildProgrammer)",Romanian,84,92,0,1,0,0,0,"2020-02-05 01:20:00"
|
||||||
"Андрій Козицький (andriikozytskyi1108)",Ukrainian,52,52,0,0,1,0,0
|
"Bruces Lee (aplusbdesign)",Korean,82,66,0,0,0,0,0,"2021-08-23 11:27:18"
|
||||||
REMOVED_USER,Italian,51,52,0,2,0,0,0
|
"Israa Z (sosozozo)",Arabic,79,87,0,43,14,0,3,"2017-11-27 14:10:50"
|
||||||
govindap,"Japanese; Hindi",51,114,0,6,1,0,0
|
"Sofia Neves (sofiasonev)","Portuguese, Brazilian",79,84,0,1,0,0,46,"2020-03-12 18:19:46"
|
||||||
"Mare Geldenhuys (mare.geldenhuys)",Afrikaans,50,57,0,0,0,0,0
|
"Jacob Roller (jdr28070)",Korean,79,61,0,0,1,0,0,"2020-01-03 11:36:40"
|
||||||
"Peter Williams (williamspete001)",Japanese,49,133,0,1,0,0,3
|
Tiralka,French,79,91,0,92,1,0,0,"2018-02-09 18:39:01"
|
||||||
"Behnood HRazy (behnoodhr)",Persian,49,70,0,0,0,0,0
|
"Toni Mustonen (toni.mustonen)",Finnish,78,72,0,0,5,0,0,"2017-09-02 05:34:12"
|
||||||
J3ll3nl,Dutch,48,48,0,0,17,1,3
|
"Michael (quelbs)",German,76,75,0,1,0,0,39,"2020-08-18 07:39:26"
|
||||||
"tat bz (Tat_i)",German,48,56,0,55,0,0,27
|
"Fauz Aladeem (topfauz)",Arabic,76,77,0,0,0,1,0,"2020-02-21 22:46:12"
|
||||||
vach,Armenian,47,36,0,0,0,0,0
|
"Radoslaw Biernacki (radoslaw.biernacki)",Polish,70,74,0,56,1,0,1,"2020-12-15 17:55:31"
|
||||||
andowero,Czech,47,38,0,0,0,0,0
|
"Oliver Gronowski (OliverGronowski)",German,70,69,0,5,2,0,0,"2021-05-14 16:37:10"
|
||||||
"Andrew Firnes (Anechan)",Russian,47,47,0,3,0,0,29
|
RealDonald,Dutch,67,69,0,121,10,0,0,"2017-06-23 20:10:12"
|
||||||
"Coni Ragni (coni2ragnii)",Spanish,46,46,0,0,0,0,0
|
sirekanyan,"Armenian; Russian",66,65,0,0,0,0,0,"2020-04-18 11:32:52"
|
||||||
"Rahul Shishodia (rahul.shishodia.10)",Hindi,46,85,0,6,0,0,0
|
"Константин К. (kocyak1991)",Russian,64,60,0,0,1,2,0,"2018-06-10 13:39:37"
|
||||||
Cp0204,"Chinese Simplified",45,72,0,0,0,0,0
|
"Laura Sophie (laurasophie20)",German,62,67,0,4,0,0,0,"2018-01-06 14:21:24"
|
||||||
"cc (cavaz)",Italian,44,41,0,0,0,0,0
|
raden20,Indonesian,61,62,177,0,1,0,64,"2017-04-09 22:04:23"
|
||||||
"Kamil Dziadek (prso94)",Polish,43,39,0,0,2,0,0
|
"Peter Williams (williamspete001)",Japanese,60,173,0,2,0,0,3,"2020-01-01 13:17:44"
|
||||||
"Boban Jagertraum (boban40)",Czech,43,38,0,2,1,1,0
|
"Jan Wojtecki (j4nw)",Polish,58,46,0,0,0,0,26,"2017-11-02 05:42:14"
|
||||||
andreea.muscalagiu,Romanian,42,52,0,1,0,0,0
|
"Deepak Bharathi (deepakbharathi1994)",Tamil,56,107,0,0,11,4,0,"2017-09-17 08:00:31"
|
||||||
"Me Me (gentelwom)",Arabic,42,40,0,0,0,0,0
|
"Андрій Козицький (andriikozytskyi1108)",Ukrainian,52,52,0,0,1,0,0,"2018-10-22 01:45:08"
|
||||||
"Balázs Keresztury (belidzs)",Hungarian,42,41,501,0,7,0,38
|
"Nil riera (nilriera2000)",Catalan,52,61,0,1,2,0,0,"2021-06-22 16:37:44"
|
||||||
MStefanov,Bulgarian,41,55,2,0,2,0,2
|
"Neoone (Neooneqq)",Romanian,51,54,0,0,0,0,0,"2022-05-05 20:42:11"
|
||||||
"Ali Zali (stm19951995)",Persian,40,60,0,0,0,0,0
|
REMOVED_USER,Italian,51,52,0,2,0,0,0,"2017-08-21 05:15:31"
|
||||||
"Sofia Veijonen (Suklaa) (sofia.veijonen)",Finnish,40,33,0,0,0,0,0
|
govindap,"Japanese; Hindi",51,114,0,6,1,0,0,"2020-06-02 20:15:52"
|
||||||
dusanstrgar,Slovenian,39,41,0,0,0,0,0
|
"Mare Geldenhuys (mare.geldenhuys)",Afrikaans,50,57,0,0,0,0,0,"2017-10-20 18:00:14"
|
||||||
"Limin Lu (liminlu)","Chinese Simplified",39,79,503,0,0,0,39
|
"Mahmoud Magdy (M7moudManson)",Arabic,49,60,0,6,8,1,0,"2021-08-21 09:01:38"
|
||||||
Anshoe,Tamil,38,65,0,14,0,0,0
|
"Behnood HRazy (behnoodhr)",Persian,49,70,0,0,0,0,0,"2017-11-25 10:57:21"
|
||||||
anasshm,Arabic,37,36,0,9,0,0,0
|
J3ll3nl,Dutch,48,48,0,0,17,1,3,"2017-03-31 11:56:09"
|
||||||
hrexen,Armenian,37,37,0,0,0,0,0
|
"tat bz (Tat_i)",German,48,56,0,55,0,1,27,"2021-03-26 05:12:54"
|
||||||
REMOVED_USER,Swedish,36,33,0,5,1,0,0
|
vach,Armenian,47,36,0,0,0,0,0,"2020-04-18 16:53:12"
|
||||||
"Abdulrahman (D7M)",Arabic,36,39,0,0,0,0,0
|
"Andrew Firnes (Anechan)",Russian,47,47,0,3,0,0,29,"2019-09-18 09:51:59"
|
||||||
"長谷川知里 (chase0213)",Japanese,34,138,0,13,0,0,24
|
andowero,Czech,47,38,0,0,3,0,0,"2020-01-20 02:29:01"
|
||||||
"Piotr Łuczyński (peterluczynski)",Polish,33,30,0,6,5,0,2
|
"Rahul Shishodia (rahul.shishodia.10)",Hindi,46,85,0,6,5,1,0,"2018-12-24 22:18:19"
|
||||||
"milad farahani (miladfarmahini90)",Persian,33,44,0,18,0,0,3
|
"Coni Ragni (coni2ragnii)",Spanish,46,46,0,0,0,0,0,"2021-02-28 20:18:37"
|
||||||
"Luis E. Perichon (luisperichon)",Spanish,33,40,0,104,0,0,0
|
Cp0204,"Chinese Simplified",45,72,0,0,0,0,0,"2019-08-20 11:04:27"
|
||||||
andriikozytskyi2625,Ukrainian,31,23,0,0,0,0,0
|
"cc (cavaz)",Italian,44,41,0,0,0,0,0,"2017-04-01 04:21:08"
|
||||||
JoeLi,"Chinese Traditional",31,70,0,12,0,0,24
|
"Boban Jagertraum (boban40)",Czech,43,38,0,2,18,1,0,"2017-03-31 09:39:16"
|
||||||
Moastafa,Arabic,31,25,0,0,0,0,0
|
"Kamil Dziadek (prso94)",Polish,43,39,0,0,6,0,0,"2020-04-06 17:12:06"
|
||||||
REMOVED_USER,Russian,31,30,0,2,4,0,3
|
andreea.muscalagiu,Romanian,42,52,0,1,0,0,0,"2017-10-22 07:19:49"
|
||||||
"hamza gamal (hamzagamal4444)",Arabic,31,28,0,0,0,0,0
|
"Me Me (gentelwom)",Arabic,42,40,0,0,0,0,0,"2020-11-08 20:44:01"
|
||||||
yancyn,"Chinese Simplified",30,40,0,0,0,0,0
|
"Balázs Keresztury (belidzs)",Hungarian,42,41,501,0,7,0,38,"2017-04-06 02:40:24"
|
||||||
"비니몬youtube (khj01025276475)",Korean,29,25,0,0,0,0,0
|
"Mateusz Duda (MateuszDuda)",Polish,42,42,0,0,6,0,0,"2021-08-17 11:27:11"
|
||||||
"Ruud Schouten (ruudschouten)",Dutch,29,32,0,41,3,0,0
|
"Ali Elsheikh (aelsheikh1987)",Arabic,42,41,0,0,0,0,0,"2021-06-16 10:17:26"
|
||||||
"Guillaume Collic (gcollic)",French,26,28,0,126,11,0,0
|
"Ali Zali (stm19951995)",Persian,40,60,0,0,0,0,0,"2020-03-23 19:57:26"
|
||||||
"Niraj Yadav (neverforgetniraj)",Hindi,26,48,0,0,0,0,0
|
"Sofia Veijonen (Suklaa) (sofia.veijonen)",Finnish,40,33,0,0,0,0,0,"2018-03-07 09:24:22"
|
||||||
"Radoslaw Biernacki (radoslaw.biernacki)",Polish,26,24,0,8,0,0,1
|
dusanstrgar,Slovenian,39,41,0,0,0,0,0,"2017-03-31 10:30:28"
|
||||||
"Jonny I (jonny99dj)",Italian,26,26,0,5,0,0,0
|
"Limin Lu (liminlu)","Chinese Simplified",39,79,503,0,0,0,39,"2017-03-31 09:49:35"
|
||||||
"Aaron Dalton (Perlkonig)",French,26,25,0,141,1,0,0
|
Anshoe,Tamil,38,65,0,14,0,0,0,"2018-01-02 11:06:52"
|
||||||
"Eddie (eddieattaboy)","Chinese Traditional",25,34,0,1,0,0,0
|
anasshm,Arabic,37,36,0,9,0,0,0,"2019-01-27 04:07:22"
|
||||||
Pan_Filuta,Czech,25,21,0,5,4,0,3
|
hrexen,Armenian,37,37,0,0,0,0,0,"2020-12-09 02:30:34"
|
||||||
"eduard83 (barbany.eduard)",Catalan,24,25,0,2,0,0,0
|
"Abdulrahman (D7M)",Arabic,36,39,0,0,0,0,0,"2020-01-29 18:55:30"
|
||||||
"Ľuboš Čaky (lubos.caky)",Slovak,23,22,0,0,0,0,0
|
REMOVED_USER,Swedish,36,33,0,5,1,0,0,"2018-09-29 17:47:33"
|
||||||
"Caner Başaran (basarancaner)",Turkish,23,21,0,0,20,0,0
|
xphsis,Basque,36,31,0,0,0,0,0,"2022-01-02 08:16:19"
|
||||||
hodanli,Turkish,22,26,0,0,1,0,0
|
"Maria Chushnyakova (maria.ch)",Russian,36,31,0,3,0,0,0,"2021-08-17 03:23:58"
|
||||||
"Neeraj Verma (verma.neeraj.in)",Hindi,22,37,0,0,1,0,0
|
"長谷川知里 (chase0213)",Japanese,34,138,0,13,0,0,24,"2018-12-14 10:52:44"
|
||||||
"Alcarkse (alexis.brusle)",French,21,25,0,7,11,0,0
|
"Piotr Łuczyński (peterluczynski)",Polish,33,30,0,6,10,0,2,"2020-01-29 07:27:40"
|
||||||
"Shashwat (goforgold)",Hindi,20,33,0,0,0,0,0
|
"Luis E. Perichon (luisperichon)",Spanish,33,40,0,104,0,0,0,"2017-09-04 13:46:06"
|
||||||
"사자솥 (toke1597)",Korean,19,19,0,0,0,0,0
|
"milad farahani (miladfarmahini90)",Persian,33,44,0,18,1,0,3,"2017-08-31 16:09:00"
|
||||||
"İsa Eş (IsaEs)",Turkish,19,17,0,0,6,1,0
|
JoeLi,"Chinese Traditional",31,70,0,12,0,0,24,"2017-06-25 05:32:48"
|
||||||
sheeCesu,French,19,18,0,48,2,0,0
|
andriikozytskyi2625,Ukrainian,31,23,0,0,0,0,0,"2019-07-08 00:16:41"
|
||||||
can13,Turkish,19,14,0,8,0,0,0
|
REMOVED_USER,Russian,31,30,0,2,4,0,3,"2018-12-03 23:55:47"
|
||||||
"Magdalena Urbańczyk (madziia139)",Polish,19,19,0,0,0,0,0
|
Moastafa,Arabic,31,25,0,0,0,0,0,"2020-07-06 11:37:53"
|
||||||
axikman11111,Uyghur,18,19,0,0,0,0,0
|
"hamza gamal (hamzagamal4444)",Arabic,31,28,0,0,0,0,0,"2020-08-03 15:23:34"
|
||||||
Adeline31,French,17,20,0,3,0,0,0
|
yancyn,"Chinese Simplified",30,40,0,0,0,0,1,"2020-05-18 20:06:03"
|
||||||
"Hoon Jung (hooni100)",Korean,17,10,0,0,0,0,0
|
"Ruud Schouten (ruudschouten)",Dutch,29,32,0,41,3,0,0,"2017-07-22 17:49:17"
|
||||||
"Ceara Lopez (cealopez)",Spanish,17,18,0,0,5,1,0
|
"비니몬youtube (khj01025276475)",Korean,29,25,0,0,0,0,0,"2020-02-09 20:44:35"
|
||||||
takoyakibento,Korean,17,13,0,3,0,0,0
|
avelneve,Indonesian,29,28,0,0,0,0,0,"2022-04-13 13:26:10"
|
||||||
bretzel15,German,16,20,0,0,0,0,0
|
"Niraj Yadav (neverforgetniraj)",Hindi,26,48,0,0,0,0,0,"2017-04-11 02:26:50"
|
||||||
engineeringforgood,Russian,16,15,0,0,0,0,0
|
"Aaron Dalton (Perlkonig)",French,26,25,0,141,1,0,0,"2018-01-14 12:58:19"
|
||||||
"Şamil Ateşoğlu (m.samilatesoglu)",Turkish,16,22,0,11,6,3,0
|
"Jonny I (jonny99dj)",Italian,26,26,0,5,0,0,0,"2017-10-07 07:35:34"
|
||||||
DebatablySane,Bulgarian,16,15,0,48,0,0,0
|
"Guillaume Collic (gcollic)",French,26,28,0,126,11,0,0,"2017-05-05 16:13:00"
|
||||||
"Bhava Tharini (bhavidanush)",Tamil,15,37,0,0,0,0,0
|
Pan_Filuta,Czech,25,21,0,5,8,0,3,"2017-04-29 12:55:14"
|
||||||
"Anastasia Borchuk (al2.borchuk)",Russian,14,14,0,0,0,0,0
|
"Eddie (eddieattaboy)","Chinese Traditional",25,34,0,1,0,0,0,"2020-11-04 21:48:05"
|
||||||
"EuiHo Hwang (euiho.hwang)",Korean,14,16,0,0,0,0,0
|
"eduard83 (barbany.eduard)",Catalan,24,25,0,2,0,0,0,"2019-06-26 14:59:47"
|
||||||
iamsurajbobade,Hindi,14,30,0,0,0,0,0
|
"A Aa (ylayzlmimashisafyoutub)",Arabic,23,33,0,34,1,1,0,"2021-09-27 15:34:26"
|
||||||
"Zeynep Esen (nezihaesen50)",Turkish,14,13,0,0,0,0,0
|
"Caner Başaran (basarancaner)",Turkish,23,21,0,0,26,1,0,"2017-04-09 06:34:59"
|
||||||
"Fikret Bilici (fikretbilici)",Turkish,14,13,0,0,0,0,0
|
"Ľuboš Čaky (lubos.caky)",Slovak,23,22,0,0,0,0,0,"2019-07-02 16:51:44"
|
||||||
"Zeeshan Rabbani (Zeera)",Hindi,14,25,0,0,0,0,0
|
"Neeraj Verma (verma.neeraj.in)",Hindi,22,37,0,0,1,0,0,"2018-07-23 07:16:41"
|
||||||
"Sanji Vinsmock (mukanzhanbolat4)",Russian,14,14,0,0,0,0,0
|
gnu-ewm,Polish,22,23,0,6,2,0,0,"2021-02-24 03:42:01"
|
||||||
"Nenad Vukotic (vukotic.nenad)","Serbian (Cyrillic)",13,13,0,1,2,6,0
|
hodanli,Turkish,22,26,0,0,1,0,0,"2017-11-03 14:33:41"
|
||||||
"Uwe Mönks (schirinowski)",German,13,12,0,0,0,0,0
|
"Alcarkse (alexis.brusle)",French,21,25,0,7,11,0,0,"2017-08-06 09:32:29"
|
||||||
"Dave (xdave)",Hungarian,13,11,0,0,0,0,0
|
"Shashwat (goforgold)",Hindi,20,33,0,0,0,0,0,"2020-05-17 10:34:42"
|
||||||
soura2,Arabic,12,13,0,0,0,0,0
|
olbotta,Italian,20,25,0,2,0,0,0,"2021-06-06 04:22:55"
|
||||||
"shreyas (techiespace)",Hindi,12,20,0,0,0,0,0
|
can13,Turkish,19,14,0,8,0,0,0,"2021-01-03 10:39:03"
|
||||||
"Jo Chuang (josephch405)","Chinese Traditional",11,24,0,0,0,0,11
|
"사자솥 (toke1597)",Korean,19,19,0,0,0,0,0,"2020-02-04 13:36:11"
|
||||||
Vmrc,French,11,12,0,2,0,0,0
|
KenKailer,Arabic,19,25,0,0,0,0,0,"2022-05-10 06:16:54"
|
||||||
"sathvic k (sathvictripleseven)",Telugu,10,17,0,0,0,0,0
|
"İsa Eş (IsaEs)",Turkish,19,17,0,0,6,2,0,"2017-06-20 07:30:22"
|
||||||
"Edwin van Rooij (edwinvrooij)",Dutch,10,13,0,17,0,0,0
|
"Magdalena Urbańczyk (madziia139)",Polish,19,19,0,0,0,0,0,"2017-10-21 03:01:04"
|
||||||
"Brian Camacho (bmcamacho)",Polish,10,11,0,0,1,0,0
|
sheeCesu,French,19,18,0,48,4,0,0,"2017-12-21 17:01:39"
|
||||||
"Ahmed Mosaad (ahmed.mosaad2018)",Arabic,10,12,0,6,0,0,0
|
axikman11111,Uyghur,18,19,0,0,0,0,0,"2018-10-13 12:25:31"
|
||||||
"Mihael Wagner (miha.wagner)",Slovenian,10,9,0,7,0,0,0
|
Adeline31,French,17,20,0,3,0,0,0,"2019-12-06 00:00:11"
|
||||||
"Anonymous edgy nerd (yamentaad)",Arabic,10,13,0,1,0,0,0
|
"Hoon Jung (hooni100)",Korean,17,10,0,0,0,0,0,"2021-01-03 02:26:54"
|
||||||
"Martin Vostatek (martinvostatek)",Czech,9,8,0,32,2,0,0
|
takoyakibento,Korean,17,13,0,3,0,0,0,"2020-08-01 08:44:15"
|
||||||
"Sourire Lucide (sourire_lucide)",Russian,9,10,0,0,1,0,0
|
"Ceara Lopez (cealopez)",Spanish,17,18,0,0,5,1,0,"2017-08-22 22:56:13"
|
||||||
"Suhaili Hassan (kucingsyg96)",Indonesian,9,10,0,0,0,0,0
|
bretzel15,German,16,20,0,0,0,0,0,"2020-04-06 02:49:14"
|
||||||
"Seweryn Piotrowski (Draxxsx)",Polish,9,10,0,0,19,0,0
|
DebatablySane,Bulgarian,16,15,0,48,0,0,0,"2017-07-10 15:13:18"
|
||||||
Rex123,Persian,8,8,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"
|
||||||
"Konstantin (KZhidovinov)",Russian,7,7,0,0,0,0,0
|
engineeringforgood,Russian,16,15,0,0,0,0,16,"2021-01-22 03:32:35"
|
||||||
pkorove,Greek,7,7,0,0,0,0,0
|
"Bhava Tharini (bhavidanush)",Tamil,15,37,0,0,0,0,0,"2019-10-09 05:43:11"
|
||||||
ftfoi,Norwegian,7,6,0,0,0,0,0
|
"Maro Chr (caprisunglasses)",Greek,14,17,0,0,0,0,0,"2021-08-17 06:53:33"
|
||||||
"Андрій Козицький (andriikozytskyi3807)",Ukrainian,7,12,0,2,0,0,0
|
"Zeynep Esen (nezihaesen50)",Turkish,14,13,0,0,0,0,0,"2020-01-28 07:05:15"
|
||||||
"Vladimir Pavlychev (KeyJoo)",Russian,7,9,0,0,0,0,0
|
iamsurajbobade,Hindi,14,30,0,0,0,0,0,"2018-05-21 11:23:27"
|
||||||
"Andrey ZaXeLoN (waragaa)",Russian,7,7,0,8,1,0,0
|
"Faiz Ahamed (faiznewton)",Tamil,14,31,0,0,0,0,0,"2021-05-06 23:06:46"
|
||||||
erfan2927,Persian,6,6,0,0,0,0,0
|
"Sanji Vinsmock (mukanzhanbolat4)",Russian,14,14,0,0,0,0,0,"2020-02-18 12:38:54"
|
||||||
dragnus,Arabic,6,6,0,1,1,0,0
|
"Zeeshan Rabbani (Zeera)",Hindi,14,25,0,0,0,0,0,"2020-09-15 11:32:01"
|
||||||
"Sam (SorodonSorodon)",German,6,6,0,13,0,0,0
|
"pi hobbes (uwe_silv)",Japanese,14,46,0,0,0,0,0,"2022-01-15 02:57:14"
|
||||||
ChloeLiang,Japanese,6,22,0,0,1,0,3
|
"Anastasia Borchuk (al2.borchuk)",Russian,14,14,0,0,0,0,0,"2020-04-14 13:22:49"
|
||||||
"Burak Ceylan (7burakceylan)",Turkish,6,6,0,0,0,0,0
|
"Fikret Bilici (fikretbilici)",Turkish,14,13,0,0,0,0,0,"2020-06-21 17:16:11"
|
||||||
"닉닉 (seohu9466)",Korean,6,14,0,13,0,0,0
|
"EuiHo Hwang (euiho.hwang)",Korean,14,16,0,0,0,0,0,"2020-06-23 02:40:01"
|
||||||
"Matthias Joly (joly.matt12)",French,5,8,0,27,1,0,0
|
"Uwe Mönks (schirinowski)",German,13,12,0,0,0,0,0,"2021-02-18 04:00:41"
|
||||||
"Guerra Ivaneth (rossanaiva-04)",Spanish,5,7,0,0,0,0,0
|
"Dave (xdave)",Hungarian,13,11,0,0,0,0,0,"2020-03-02 20:56:50"
|
||||||
"Manuel Tassi (Mannivu)",Italian,5,6,0,0,0,0,0
|
"Ana Kelly Vale (anakvale)","Portuguese, Brazilian",13,21,0,4,0,0,2,"2022-03-30 00:15:37"
|
||||||
"Micaela Pighin (micaelapiighin)",Spanish,5,6,0,1,0,0,0
|
GiorgioHerbie,Italian,13,15,0,0,0,0,0,"2022-01-17 17:35:40"
|
||||||
"Дмитрий Хапенков (d.khapenkov)",Russian,5,5,0,6,4,0,2
|
"Nenad Vukotic (vukotic.nenad)","Serbian (Cyrillic)",13,13,0,1,2,6,0,"2019-01-31 14:29:15"
|
||||||
andriikozytskyi2018,Ukrainian,5,5,0,0,0,0,0
|
soura2,Arabic,12,13,0,0,0,0,0,"2020-01-13 19:23:47"
|
||||||
"Neko123 (emandic11)","Serbian (Cyrillic)",4,4,0,57,0,0,0
|
"shreyas (techiespace)",Hindi,12,20,0,0,0,0,0,"2018-06-10 01:14:26"
|
||||||
marmo,German,4,4,0,0,0,0,0
|
"Jo Chuang (josephch405)","Chinese Traditional",11,24,0,0,0,0,11,"2017-06-16 20:21:06"
|
||||||
"Eli Besirov (elibesirov07)",Turkish,4,4,0,0,0,0,0
|
Vmrc,French,11,12,0,2,0,0,0,"2020-11-02 05:35:06"
|
||||||
"Lopo Isaac Fernández (rocapata)",Spanish,4,3,0,0,0,0,0
|
"Ammar Naif (Ammar_Naif)",Arabic,11,11,0,4,0,0,0,"2022-01-15 05:16:41"
|
||||||
bziuum,Polish,4,4,0,0,0,0,0
|
"Sonu Sharma (riteetude)",Hindi,11,23,0,0,0,0,0,"2021-05-30 19:38:00"
|
||||||
"Thoum Ptrgnt (thomas.petrignet)",French,3,3,0,2,0,3,0
|
"Edwin van Rooij (edwinvrooij)",Dutch,10,13,0,17,0,0,0,"2018-11-05 03:59:10"
|
||||||
"Vagner Roberto (vagner.trompete)","Portuguese, Brazilian",3,3,0,0,0,0,0
|
"Brian Camacho (bmcamacho)",Polish,10,11,0,0,1,1,0,"2020-08-03 02:27:28"
|
||||||
"Unnie Here (Carb)",Hindi,3,8,0,0,0,0,0
|
"Mihael Wagner (miha.wagner)",Slovenian,10,9,0,7,0,0,0,"2017-10-18 18:26:29"
|
||||||
carsten_kafke,German,3,3,0,43,0,0,3
|
"Hrant Hakobian (hrastgh1)",Armenian,10,9,0,0,0,0,0,"2021-08-29 15:22:10"
|
||||||
Magidxz,Arabic,3,3,0,0,0,0,0
|
"sathvic k (sathvictripleseven)",Telugu,10,17,0,0,0,0,0,"2020-09-11 08:11:32"
|
||||||
atomjani,Hungarian,3,3,0,0,0,0,0
|
"Ahmed Mosaad (ahmed.mosaad2018)",Arabic,10,12,0,6,0,0,0,"2021-02-03 18:45:43"
|
||||||
"Péter Bernát (bernatp)",Hungarian,3,2,0,0,0,0,0
|
"Anonymous edgy nerd (yamentaad)",Arabic,10,13,0,1,0,0,0,"2018-05-06 09:23:57"
|
||||||
"Igor Piskun (i_piskun)",Ukrainian,3,3,0,0,0,0,0
|
"Zesar Cebrián (Txorrota)",Spanish,10,44,0,0,0,0,0,"2022-02-09 01:34:32"
|
||||||
"joabe gabriel (joabegabrielcma1)","Portuguese, Brazilian",3,4,0,0,0,0,0
|
"Milan Siebenbürger (lennyd)",Czech,10,7,0,1,0,0,0,"2022-01-30 07:09:42"
|
||||||
"Oleg Kogut (kogut_oleg)",Ukrainian,3,3,0,0,0,0,0
|
"Suhaili Hassan (kucingsyg96)",Indonesian,9,10,0,0,0,0,0,"2018-06-10 11:55:09"
|
||||||
"Andrea Bianchi (andreawhite1597)",Italian,3,1,0,1,0,0,0
|
"Sourire Lucide (sourire_lucide)",Russian,9,10,0,0,1,0,0,"2018-03-22 01:37:55"
|
||||||
"Gabriel Cavalcante (gabrielc.alves14)","Portuguese, Brazilian",3,4,0,0,0,0,0
|
"Martin Vostatek (martinvostatek)",Czech,9,8,0,32,2,0,0,"2019-01-21 13:52:36"
|
||||||
REMOVED_USER,"Portuguese, Brazilian",3,4,0,0,0,0,0
|
"Seweryn Piotrowski (Draxxsx)",Polish,9,10,0,0,19,0,0,"2020-01-02 09:55:48"
|
||||||
"Martin Zimdahl (zimdahlmartin)",Swedish,3,2,0,0,1,0,3
|
"Jakob Weickmann (jweickm)",Japanese,8,21,0,0,0,0,0,"2021-10-05 11:10:25"
|
||||||
"Hiohana Rilary (hiohanarilary)","Portuguese, Brazilian",3,4,0,0,0,0,0
|
Rex123,Persian,8,8,0,0,0,0,0,"2017-07-01 00:47:42"
|
||||||
"Sarath S (CyberShark)",Tamil,3,7,0,0,0,0,0
|
"Andrey ZaXeLoN (waragaa)",Russian,7,7,0,8,1,0,0,"2017-09-18 21:37:42"
|
||||||
"Cláudio Bernardo (claudiobernardo.ti)","Portuguese, Brazilian",3,4,0,1,0,0,0
|
"Konstantin (KZhidovinov)",Russian,7,7,0,0,0,0,0,"2020-01-29 13:35:12"
|
||||||
"hesamiranii (esam.matouri)",Persian,2,2,0,0,0,0,0
|
ftfoi,Norwegian,7,6,0,0,0,0,0,"2020-04-11 20:42:35"
|
||||||
"조화정 (yunjoo337)",Korean,2,2,0,0,0,0,0
|
"Vladimir Pavlychev (vovs03)",Russian,7,9,0,0,0,0,0,"2017-12-18 02:46:56"
|
||||||
"Walid Baazia (walidbaazia2005)",Arabic,2,1,0,0,0,0,0
|
"Felipe Chagas (chagretes)","Portuguese, Brazilian",7,8,0,0,3,0,5,"2022-01-10 12:20:25"
|
||||||
LNDDYL,"Chinese Traditional",2,4,0,0,0,0,2
|
"Андрій Козицький (andriikozytskyi3807)",Ukrainian,7,12,0,2,0,0,0,"2020-09-26 20:31:56"
|
||||||
REMOVED_USER,Ukrainian,2,2,0,0,0,0,0
|
pkorove,Greek,7,7,0,0,0,0,0,"2020-03-07 11:36:12"
|
||||||
"fatemeh s (fargolseifoori3)",Persian,2,2,0,0,0,0,0
|
ChloeLiang,Japanese,6,22,0,0,1,0,3,"2017-08-08 05:02:59"
|
||||||
"أم محمد تقي (souadboudia19)",Arabic,2,2,0,0,0,0,0
|
"Sam (SorodonSorodon)",German,6,6,0,13,0,0,0,"2017-04-14 11:09:27"
|
||||||
"Alex Stein (diefaust1993)",Russian,2,2,0,4,4,0,2
|
"닉닉 (seohu9466)",Korean,6,14,0,13,0,0,0,"2017-10-09 23:08:15"
|
||||||
"Danial Agh (danialagh)",Persian,2,3,0,0,0,0,0
|
"Sarita Cajas (sarayanacajas)",Spanish,6,4,0,0,1,0,0,"2021-05-14 14:27:59"
|
||||||
omerfarukbas,Turkish,2,3,0,19,2,0,0
|
erfan2927,Persian,6,6,0,0,0,0,0,"2018-04-09 02:12:44"
|
||||||
"Valerij D (vala.dobler)",German,2,2,0,0,0,0,0
|
"Burak Ceylan (7burakceylan)",Turkish,6,6,0,0,0,0,0,"2018-05-20 17:24:19"
|
||||||
"Naveen jai krishna (njsbpolymer1)",Tamil,2,5,0,0,0,0,0
|
andriikozytskyi2018,Ukrainian,5,5,0,0,0,0,0,"2017-09-03 05:24:43"
|
||||||
"Balthazar Aubard (Balatzar)",French,2,5,0,0,1,0,0
|
"Vitor Henrique (vitorhcl)","Portuguese, Brazilian",5,8,0,1,0,0,0,"2022-03-08 20:00:59"
|
||||||
"FAy FAy (fayfayfay52)","Chinese Traditional",2,5,0,0,0,0,0
|
"Matthias Joly (joly.matt12)",French,5,8,0,27,1,0,0,"2017-08-28 09:53:59"
|
||||||
Soroor_SI,Persian,2,2,0,0,0,0,0
|
"Tomáš Hrabáček (Hrabyyy)",Czech,5,3,0,0,1,0,0,"2021-05-27 11:58:11"
|
||||||
chavs1997,Russian,2,2,0,9,0,0,0
|
"Guerra Ivaneth (rossanaiva-04)",Spanish,5,7,0,0,0,0,0,"2019-02-03 16:48:59"
|
||||||
amei,"Portuguese, Brazilian",2,2,0,0,0,0,0
|
"Дмитрий Хапенков (d.khapenkov)",Russian,5,5,0,6,4,0,2,"2018-01-06 23:00:43"
|
||||||
"Ilyas Fekhar (il47yas)",Arabic,2,2,0,0,0,0,0
|
"Micaela Pighin (micaelapiighin)",Spanish,5,6,0,1,0,0,0,"2019-10-09 23:32:42"
|
||||||
"Ali Zaida (alizaeda92)",Arabic,2,2,0,0,0,0,0
|
"Manuel Tassi (Mannivu)",Italian,5,6,0,0,0,0,0,"2021-01-03 11:00:33"
|
||||||
gnu-ewm,Polish,1,1,0,6,0,0,0
|
"Neko123 (emandic11)","Serbian (Cyrillic)",4,4,0,57,0,0,0,"2021-04-21 15:33:29"
|
||||||
"Luca Gori (grolcu)",Italian,1,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"
|
||||||
"Alan Jeon (skyisle)",Korean,1,2,0,8,0,0,0
|
"Eli Besirov (elibesirov07)",Turkish,4,4,0,0,0,0,0,"2019-03-25 07:12:34"
|
||||||
iSoron2,"Portuguese, Brazilian",1,1,0,0,0,0,0
|
marmo,German,4,4,0,0,0,0,0,"2021-01-13 01:16:35"
|
||||||
"Anastasiia Bondarenko (nastasya.bondarenko.97)",Russian,1,1,0,0,0,0,0
|
bziuum,Polish,4,4,0,0,3,0,0,"2020-09-01 09:08:01"
|
||||||
"Patrick Pimenta (trickap1)","Portuguese, Brazilian",1,1,0,0,0,0,0
|
"Craig Foobar (craig.foobar)",German,3,3,0,25,0,0,0,"2022-02-20 16:55:47"
|
||||||
axd,Spanish,1,1,0,15,0,0,0
|
Katarin,Ukrainian,3,3,0,0,0,0,0,"2022-03-17 14:44:59"
|
||||||
jonesses,German,1,1,0,1,0,0,1
|
"Sarath S (CyberShark)",Tamil,3,7,0,0,0,0,0,"2020-08-27 22:43:16"
|
||||||
"박찌 (perpact20)",Korean,1,1,0,0,0,0,0
|
"Vagner Roberto (vagner.trompete)","Portuguese, Brazilian",3,3,0,0,0,0,0,"2017-12-30 17:54:26"
|
||||||
"Kan Black (kanblack.va)",Vietnamese,1,2,0,0,0,1,0
|
"Igor Piskun (i_piskun)",Ukrainian,3,3,0,0,0,0,0,"2018-01-19 15:20:27"
|
||||||
REMOVED_USER,Russian,1,2,0,6,1,0,1
|
"Cláudio Bernardo (claudiobernardo.ti)","Portuguese, Brazilian",3,4,0,1,0,0,0,"2019-01-08 14:41:10"
|
||||||
"Wibi Cahyo (wbcahyoh)",Indonesian,1,3,0,0,0,0,0
|
"Unnie Here (Carb)",Hindi,3,8,0,0,0,0,0,"2020-03-18 23:34:35"
|
||||||
"Anton (tT0NG)","Chinese Traditional",1,2,0,0,0,0,1
|
REMOVED_USER,"Portuguese, Brazilian",3,4,0,0,0,0,0,"2018-11-18 09:02:37"
|
||||||
"Maria Fefelova (mashafefel)",Russian,1,1,0,0,0,0,0
|
"Thoum Ptrgnt (thomas.petrignet)",French,3,3,0,2,0,3,0,"2017-09-23 19:25:52"
|
||||||
Itch,,0,0,0,0,0,0,0
|
"Oleg Kogut (kogut_oleg)",Ukrainian,3,3,0,0,0,0,0,"2018-12-28 14:31:02"
|
||||||
"Muhammet Furkan ALMACI (furkan.almaci)",Turkish,0,0,0,1,0,0,0
|
carsten_kafke,German,3,3,0,43,0,0,3,"2017-10-27 13:27:47"
|
||||||
Sandhu564.,,0,0,0,0,0,0,0
|
Magidxz,Arabic,3,3,0,0,0,0,0,"2021-01-05 05:02:54"
|
||||||
"Quentin Hibon (hiq)",,0,0,0,0,0,0,0
|
"Péter Bernát (bernatp)",Hungarian,3,2,0,0,0,0,0,"2019-11-30 15:50:33"
|
||||||
mushin,,0,0,0,0,0,0,0
|
"joabe gabriel (joabegabrielcma1)","Portuguese, Brazilian",3,4,0,0,0,0,0,"2018-08-21 09:08:59"
|
||||||
"Balaji Jayaraman (jkbalaji1103)",,0,0,0,0,0,0,0
|
"Gabriel Cavalcante (gabrielc.alves14)","Portuguese, Brazilian",3,4,0,0,0,0,0,"2018-08-06 22:24:54"
|
||||||
"Wellington Ribeiro (wellington.rib)",,0,0,0,0,0,0,0
|
"Martin Zimdahl (zimdahlmartin)",Swedish,3,2,0,0,1,0,3,"2018-09-15 04:39:22"
|
||||||
"Javid IRAN (twitteriran98)",Persian,0,0,0,1,0,0,0
|
atomjani,Hungarian,3,3,0,0,0,0,0,"2019-01-19 00:49:25"
|
||||||
"박인호 (wphestiraid)",Korean,0,0,0,2,0,0,0
|
"mohammadali barati (mabaraty)",Persian,3,3,0,0,0,0,0,"2021-07-10 05:54:44"
|
||||||
"Ahmed Nazir (ahmednazir333)",,0,0,0,0,0,0,0
|
"Hiohana Rilary (hiohanarilary)","Portuguese, Brazilian",3,4,0,0,0,0,0,"2019-07-31 20:42:20"
|
||||||
dimateos,,0,0,0,0,0,0,0
|
"Tejaswini Boppana (Tejaswini)",Telugu,3,1,0,0,0,0,0,"2021-08-27 23:48:55"
|
||||||
AhmedDz,Arabic,0,0,0,1,0,0,0
|
"Andrea Bianchi (andreawhite1597)",Italian,3,1,0,1,0,0,0,"2018-01-21 17:45:48"
|
||||||
"reza golestanzadeh (reza.golestanzadeh)",Persian,0,0,0,1,0,0,0
|
"Ño Bí Tã (pt614553)",Arabic,2,8,0,1,0,2,0,"2021-05-22 20:41:01"
|
||||||
"Petros Bleyan (coolbleyan)",Russian,0,0,0,14,0,0,0
|
"Judith Ayala (Azul1612)",Spanish,2,1,0,0,0,1,0,"2021-05-18 17:07:19"
|
||||||
Kamalakannan,,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"
|
||||||
"Dagna Q (dagnaq)",,0,0,0,0,0,0,0
|
"Balthazar Aubard (Balatzar)",French,2,5,0,0,1,0,0,"2017-09-23 01:42:57"
|
||||||
"Thomas Orlita (Thomas995)",Czech,0,0,0,1,0,0,0
|
"Ahmed Bazazo (ahmedbazazo)",Arabic,2,2,0,0,0,0,0,"2022-02-19 20:11:09"
|
||||||
Fazy1380,,0,0,0,0,0,0,0
|
"Ali Zaida (alizaeda92)",Arabic,2,2,0,0,0,0,0,"2019-12-01 11:47:00"
|
||||||
"Rivo Zängov (Eraser)",,0,0,0,0,0,0,0
|
"FAy FAy (fayfayfay52)","Chinese Traditional",2,5,0,0,0,0,0,"2017-10-06 08:53:21"
|
||||||
T-v-Gerwen,Dutch,0,0,0,47,0,0,0
|
Soroor_SI,Persian,2,2,0,0,0,0,0,"2018-06-10 06:28:27"
|
||||||
"عبد الناصر سعيد الثبيتي (asaeed)",,0,0,0,0,0,0,0
|
chavs1997,Russian,2,2,0,9,0,0,0,"2018-05-18 16:58:19"
|
||||||
"Edmunds Edmundam (edmundam)",,0,0,0,0,0,0,0
|
"Naveen jai krishna (njsbpolymer1)",Tamil,2,5,0,0,0,0,0,"2020-01-10 14:19:41"
|
||||||
"vi ve (VimalV)",,0,0,0,0,0,0,0
|
omerfarukbas,Turkish,2,3,0,19,2,0,0,"2017-08-14 16:10:35"
|
||||||
"LeMeD (LeMeS)",French,0,0,0,2,0,0,0
|
"Ilyas Fekhar (il47yas)",Arabic,2,2,0,0,0,0,0,"2018-04-17 22:00:41"
|
||||||
"Éjbãss Übbeî (littlebittlebottle)",Norwegian,0,0,0,152,0,0,0
|
"Héctor Mañas García (hectodium)",Catalan,2,3,0,0,0,0,0,"2021-10-02 20:32:09"
|
||||||
EmanAmini,,0,0,0,0,0,0,0
|
"Walid Baazia (walidbaazia2005)",Arabic,2,1,0,0,0,0,0,"2021-01-27 12:47:34"
|
||||||
AnggaRifandi,,0,0,0,0,0,0,0
|
"fatemeh s (fargolseifoori3)",Persian,2,2,0,0,0,0,0,"2019-01-31 12:06:57"
|
||||||
"Mateusz Teteruk (mttet)",Polish,0,0,0,1,0,0,0
|
"hesamiranii (esam.matouri)",Persian,2,2,0,0,0,0,0,"2018-09-22 16:33:36"
|
||||||
"Lori Amico (lavodkaclyde2323)",Italian,0,0,0,1,0,0,0
|
REMOVED_USER,Ukrainian,2,2,0,0,0,0,0,"2017-06-15 12:24:44"
|
||||||
"Florian Stuhlmann (stuhlmann)",German,0,0,0,10,0,0,0
|
"Alex Stein (diefaust1993)",Russian,2,2,0,4,4,0,2,"2017-07-13 06:56:17"
|
||||||
"Yasin Okumus (lacivert)",Turkish,0,0,0,1,0,0,0
|
amei,"Portuguese, Brazilian",2,2,0,0,0,0,0,"2018-04-19 19:42:28"
|
||||||
NairaDNV,Spanish,0,0,0,9,0,0,0
|
"أم محمد تقي (souadboudia19)",Arabic,2,2,0,0,0,0,0,"2020-06-13 15:24:17"
|
||||||
REMOVED_USER,,0,0,0,0,0,0,0
|
LNDDYL,"Chinese Traditional",2,4,0,0,0,0,2,"2018-04-22 04:00:19"
|
||||||
farbod66,Persian,0,0,0,1,0,0,0
|
"조화정 (yunjoo337)",Korean,2,2,0,0,0,0,0,"2019-06-16 22:25:31"
|
||||||
Raulbertassi,,0,0,0,0,0,0,0
|
"Sidali Aymen (sidaliaymen950)",Arabic,2,2,0,0,0,0,0,"2022-01-31 18:50:59"
|
||||||
"Равиль Мифтахов (ravilmif47)",Russian,0,0,0,1,0,0,0
|
"Danial Agh (danialagh)",Persian,2,3,0,0,0,0,0,"2019-03-30 13:24:16"
|
||||||
"Pumpith Ungsupanit (pumpithu)",,0,0,0,0,0,0,0
|
iSoron2,"Portuguese, Brazilian",1,1,0,0,0,0,0,"2017-03-18 17:56:29"
|
||||||
"Nat Fomicheva (natac)",Russian,0,0,0,3,0,0,0
|
"Anton (tT0NG)","Chinese Traditional",1,2,0,0,0,0,1,"2017-07-06 14:18:39"
|
||||||
EwanB,,0,0,0,0,0,0,0
|
"Luca Gori (grolcu)",Italian,1,2,0,0,0,0,0,"2020-09-26 23:26:15"
|
||||||
ava_rfie,Persian,0,0,0,1,0,0,0
|
axd,Spanish,1,1,0,15,0,0,0,"2017-09-12 05:48:51"
|
||||||
"George Merkulov (george142.emarket)",Russian,0,0,0,11,0,0,0
|
REMOVED_USER,Russian,1,2,0,6,1,0,1,"2019-12-26 05:37:01"
|
||||||
REMOVED_USER,,0,0,0,0,0,0,0
|
jonesses,German,1,1,0,1,0,0,1,"2021-01-01 08:03:18"
|
||||||
sanyoniket,,0,0,0,0,0,0,0
|
"Alan Jeon (skyisle)",Korean,1,2,0,8,0,0,0,"2018-01-09 10:46:00"
|
||||||
"Shuvashish Sahoo (shuvashish76)",,0,0,0,0,0,0,0
|
"Maria Fefelova (mashafefel)",Russian,1,1,0,0,0,0,0,"2019-05-18 02:03:56"
|
||||||
martyaberger,,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"
|
||||||
"Карлен Шаухаев (KarlenShaukhaev)",,0,0,0,0,0,0,0
|
"박찌 (perpact20)",Korean,1,1,0,0,0,0,0,"2018-02-10 10:11:44"
|
||||||
"Elmo (oberknecht)",,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"
|
||||||
Irsgram,Russian,0,0,0,1,0,0,0
|
"Anastasiia Bondarenko (nastasya.bondarenko.97)",Russian,1,1,0,0,0,0,0,"2019-06-07 17:43:08"
|
||||||
"Matus Zdansky (matuszdansky)",,0,0,0,0,0,0,0
|
"Wibi Cahyo (wbcahyoh)",Indonesian,1,3,0,0,0,0,0,"2017-12-14 06:35:58"
|
||||||
shuvo786,,0,0,0,0,0,0,0
|
sanyoniket,,0,0,0,0,0,0,0,"2019-07-23 12:58:40"
|
||||||
"Eduard Boboc (edi.boboc33)",Romanian,0,0,0,4,0,0,0
|
"Sri Harsha Bhogi (sriharshabhogi)",,0,0,0,0,0,0,0,"2018-09-02 05:31:53"
|
||||||
Hayder21,,0,0,0,0,0,0,0
|
Irsgram,Russian,0,0,0,1,0,0,0,"2019-09-30 16:42:20"
|
||||||
AsadullahIlyas,,0,0,0,0,0,0,0
|
"Baran Özavcı (n2141n)",Turkish,0,0,0,1,0,0,0,"2022-02-26 04:32:51"
|
||||||
"Никита Карамов (nikita.karamoff)",Russian,0,0,0,10,0,0,0
|
"Masataka Yakura (myakura)",Japanese,0,0,0,1,0,0,0,"2021-09-03 22:10:36"
|
||||||
"Katherine Alexandra Flórez Ramírez (katherine.florez12)",Spanish,0,0,0,46,0,0,0
|
ava_rfie,Persian,0,0,0,1,0,0,0,"2019-06-09 16:19:24"
|
||||||
mdrobulis,,0,0,0,0,0,0,0
|
T-v-Gerwen,Dutch,0,0,0,47,0,0,0,"2018-03-02 10:26:33"
|
||||||
"Eliška Roubalová (roubaeli)",Czech,0,0,0,6,0,0,0
|
"George Merkulov (george142.emarket)",Russian,0,0,0,11,0,0,0,"2019-06-09 19:47:02"
|
||||||
droidahmed,Arabic,0,0,0,7,0,0,0
|
philfr49,French,0,0,0,2,0,0,0,"2018-09-03 14:20:32"
|
||||||
"Arjun K. (arjunkdot)",,0,0,0,0,0,0,0
|
"عبد الناصر سعيد الثبيتي (asaeed)",,0,0,0,0,0,0,0,"2018-03-13 02:09:35"
|
||||||
"Sarah BCNN (fsarahboucenna)",French,0,0,0,16,0,0,0
|
"Thomas Orlita (Thomas995)",Czech,0,0,0,1,0,0,0,"2017-12-24 04:08:27"
|
||||||
REMOVED_USER,Czech,0,0,0,18,0,0,0
|
"Edmunds Edmundam (edmundam)",,0,0,0,0,0,0,0,"2020-06-01 14:18:18"
|
||||||
BongTran,Vietnamese,0,0,0,2,0,0,0
|
"Elmo (oberknecht)",,0,0,0,0,0,0,0,"2020-04-16 08:45:50"
|
||||||
"Arttu Ylhävuori (arttu.ylhavuori)",,0,0,0,0,0,0,0
|
"Равиль Мифтахов (ravilmif47)",Russian,0,0,0,1,0,0,0,"2019-08-12 21:58:30"
|
||||||
valney.faria,"Portuguese, Brazilian",0,0,0,1,0,0,0
|
"Manny Farsangy (manifarsangi)",Persian,0,0,0,12,0,0,0,"2021-08-10 05:32:28"
|
||||||
REMOVED_USER,,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"
|
||||||
rooban23,,0,0,0,0,0,0,0
|
"Saiprasath B (Saiprasath)",,0,0,0,0,0,0,0,"2021-07-11 11:10:41"
|
||||||
REMOVED_USER,,0,0,0,0,0,0,0
|
REMOVED_USER,,0,0,0,0,0,0,0,"2018-08-24 00:17:43"
|
||||||
"Алтынбек Наурызғали (altinbeknaurizgali)",Russian,0,0,0,1,0,0,0
|
REMOVED_USER,,0,0,0,0,0,0,0,"2020-02-01 03:47:48"
|
||||||
"Sri Harsha Bhogi (sriharshabhogi)",,0,0,0,0,0,0,0
|
"Arjun K. (arjunkdot)",,0,0,0,0,0,0,0,"2020-09-20 11:16:18"
|
||||||
"Ahnaf Tajwar (atn4404)",,0,0,0,0,0,0,0
|
EwanB,,0,0,0,0,0,0,0,"2019-11-19 10:04:38"
|
||||||
Elham1361,,0,0,0,0,0,0,0
|
shuvo786,,0,0,0,0,0,0,0,"2019-11-13 00:18:12"
|
||||||
dongchen.yue,German,0,0,0,4,0,0,0
|
"Pro AAA (pro1010)",Arabic,0,0,0,1,0,0,0,"2022-02-14 03:32:44"
|
||||||
philfr49,French,0,0,0,2,0,0,0
|
"manu (manuL96)",,0,0,0,0,0,0,0,"2022-05-06 23:34:55"
|
||||||
|
"Rivo Zängov (Eraser)",,0,0,0,0,0,0,0,"2020-10-13 04:38:26"
|
||||||
|
ashik8113,,0,0,0,0,0,0,0,"2022-04-13 11:58:26"
|
||||||
|
deepbird,,0,0,0,0,0,0,0,"2022-04-11 03:21:05"
|
||||||
|
REMOVED_USER,,0,0,0,0,0,0,0,"2018-10-27 15:34:36"
|
||||||
|
Elham1361,,0,0,0,0,0,0,0,"2018-10-27 12:01:06"
|
||||||
|
"Ahnaf Tajwar (atn4404)",,0,0,0,0,0,0,0,"2018-10-16 11:13:30"
|
||||||
|
martyaberger,,0,0,0,0,0,0,0,"2019-01-01 18:48:08"
|
||||||
|
AsadullahIlyas,,0,0,0,0,0,0,0,"2019-01-04 06:14:15"
|
||||||
|
"akmal shafiq (mohdakmalshafiq)",,0,0,0,0,0,0,0,"2021-11-01 01:04:50"
|
||||||
|
"Sylwuskak (sylwuskak)",Polish,0,0,0,1,0,0,0,"2022-01-25 04:19:53"
|
||||||
|
"Yunsu Kim (yunsukim86)",Korean,0,0,0,2,0,0,0,"2022-01-14 06:33:43"
|
||||||
|
"Pumpith Ungsupanit (pumpithu)",,0,0,0,0,0,0,0,"2019-01-19 23:47:57"
|
||||||
|
"Nat Fomicheva (natac)",Russian,0,0,0,3,0,0,0,"2019-01-25 14:35:02"
|
||||||
|
HemanthMeda,Telugu,0,0,0,4,0,0,0,"2021-12-01 14:02:14"
|
||||||
|
"darkkingredian (rediancool)",,0,0,0,0,0,0,0,"2021-07-27 16:04:32"
|
||||||
|
catemlitten,Japanese,0,0,0,1,0,0,0,"2021-11-17 15:06:02"
|
||||||
|
"Said Tahsin Dane (tasomaniac)",,0,0,0,0,0,0,0,"2021-09-25 05:31:01"
|
||||||
|
"Matus Zdansky (matuszdansky)",,0,0,0,0,0,0,0,"2019-10-20 13:52:24"
|
||||||
|
mdrobulis,,0,0,0,0,0,0,0,"2018-05-24 01:40:42"
|
||||||
|
valney.faria,"Portuguese, Brazilian",0,0,0,1,0,0,0,"2020-02-02 14:45:02"
|
||||||
|
"Petros Bleyan (coolbleyan)",Russian,0,0,0,14,0,0,0,"2017-08-18 18:37:18"
|
||||||
|
"Карлен Шаухаев (KarlenShaukhaev)",,0,0,0,0,0,0,0,"2020-04-27 08:53:49"
|
||||||
|
"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"
|
||||||
|
"Dagna Q (dagnaq)",,0,0,0,0,0,0,0,"2017-08-06 01:42:52"
|
||||||
|
Sandhu564.,,0,0,0,0,0,0,0,"2020-12-14 01:27:45"
|
||||||
|
AhmedDz,Arabic,0,0,0,1,0,0,0,"2017-12-31 10:12:31"
|
||||||
|
"Quentin Hibon (hiq)",,0,0,0,0,0,0,0,"2021-02-07 16:39:31"
|
||||||
|
"Ahmed Nazir (ahmednazir333)",,0,0,0,0,0,0,0,"2018-05-06 12:10:27"
|
||||||
|
"박인호 (wphestiraid)",Korean,0,0,0,2,0,0,0,"2018-01-05 00:33:14"
|
||||||
|
Raulbertassi,,0,0,0,0,0,0,0,"2018-01-07 17:23:18"
|
||||||
|
"Javid IRAN (twitteriran98)",Persian,0,0,0,1,0,0,0,"2017-11-25 16:47:25"
|
||||||
|
"Wellington Ribeiro (wellington.rib)",,0,0,0,0,0,0,0,"2017-11-16 07:32:25"
|
||||||
|
dimateos,,0,0,0,0,0,0,0,"2021-01-10 06:29:52"
|
||||||
|
"Balaji Jayaraman (jkbalaji1103)",,0,0,0,0,0,0,0,"2017-10-30 22:12:27"
|
||||||
|
"reza golestanzadeh (reza.golestanzadeh)",Persian,0,0,0,1,0,0,0,"2020-10-21 12:07:20"
|
||||||
|
"Muhammet Furkan ALMACI (furkan.almaci)",Turkish,0,0,0,1,0,0,0,"2017-10-29 13:44:56"
|
||||||
|
dongchen.yue,German,0,0,0,4,0,0,0,"2020-09-12 15:05:59"
|
||||||
|
"Алтынбек Наурызғали (altinbeknaurizgali)",Russian,0,0,0,1,0,0,0,"2020-08-12 13:03:49"
|
||||||
|
rooban23,,0,0,0,0,0,0,0,"2020-09-15 11:49:14"
|
||||||
|
NairaDNV,Spanish,0,0,0,9,0,0,0,"2018-01-05 19:10:33"
|
||||||
|
"Katherine Alexandra Flórez Ramírez (katherine.florez12)",Spanish,0,0,0,46,0,0,0,"2018-01-20 02:18:32"
|
||||||
|
Itch,,0,0,0,0,0,0,0,"2017-10-16 09:18:42"
|
||||||
|
"Yasin Okumus (lacivert)",Turkish,0,0,0,1,0,0,0,"2018-02-07 04:13:51"
|
||||||
|
"Eduard Boboc (edi.boboc33)",Romanian,0,0,0,4,0,0,0,"2019-12-16 09:08:39"
|
||||||
|
Hayder21,,0,0,0,0,0,0,0,"2019-12-31 10:56:24"
|
||||||
|
"Eliška Roubalová (roubaeli)",Czech,0,0,0,6,0,0,0,"2019-12-31 12:47:29"
|
||||||
|
Fazy1380,,0,0,0,0,0,0,0,"2021-04-10 11:02:53"
|
||||||
|
"Arttu Ylhävuori (arttu.ylhavuori)",,0,0,0,0,0,0,0,"2019-07-24 15:03:42"
|
||||||
|
EmanAmini,,0,0,0,0,0,0,0,"2017-03-31 13:27:43"
|
||||||
|
AnggaRifandi,,0,0,0,0,0,0,0,"2017-03-31 19:28:35"
|
||||||
|
"Lori Amico (lavodkaclyde2323)",Italian,0,0,0,1,0,0,0,"2017-04-09 10:08:13"
|
||||||
|
"Florian Stuhlmann (stuhlmann)",German,0,0,0,10,0,0,0,"2017-04-15 04:04:00"
|
||||||
|
Kamalakannan,,0,0,0,0,0,0,0,"2017-05-14 11:40:23"
|
||||||
|
farbod66,Persian,0,0,0,1,0,0,0,"2018-01-20 11:04:23"
|
||||||
|
"vi ve (VimalV)",,0,0,0,0,0,0,0,"2021-02-08 02:35:45"
|
||||||
|
"Éjbãss Übbeî (littlebittlebottle)",Norwegian,0,0,0,152,0,0,0,"2017-07-05 21:12:02"
|
||||||
|
"LeMeD (LeMeS)",French,0,0,0,2,0,0,0,"2021-02-06 15:35:00"
|
||||||
|
BongTran,Vietnamese,0,0,0,2,0,0,0,"2018-04-24 05:16:07"
|
||||||
|
REMOVED_USER,Czech,0,0,0,18,0,0,0,"2018-03-27 06:19:52"
|
||||||
|
mushin,,0,0,0,0,0,0,0,"2020-02-02 04:08:05"
|
||||||
|
"Mateusz Teteruk (mttet)",Polish,0,0,0,1,0,0,0,"2021-01-23 13:09:59"
|
||||||
|
"Sarah BCNN (fsarahboucenna)",French,0,0,0,16,0,0,0,"2018-02-11 11:07:36"
|
||||||
|
droidahmed,Arabic,0,0,0,7,0,0,0,"2018-01-31 02:18:49"
|
||||||
|
"Никита Карамов (nikita.karamoff)",Russian,0,0,0,10,0,0,0,"2018-10-29 03:57:21"
|
||||||
|
|||||||
|
@@ -1,5 +1,3 @@
|
|||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2016-2021 Álinson Santos Xavier <git@axavier.org>
|
* Copyright (C) 2016-2021 Álinson Santos Xavier <git@axavier.org>
|
||||||
*
|
*
|
||||||
@@ -20,7 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("com.github.triplet.play") version "3.2.0"
|
id("com.github.triplet.play") version "3.7.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")
|
||||||
@@ -34,15 +32,15 @@ tasks.compileLint {
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
|
|
||||||
compileSdkVersion(30)
|
compileSdk = 32
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
versionCode(20001)
|
versionCode = 20100
|
||||||
versionName("2.0.1")
|
versionName = "2.1.0"
|
||||||
minSdkVersion(23)
|
minSdk = 28
|
||||||
targetSdkVersion(30)
|
targetSdk = 31
|
||||||
applicationId("org.isoron.uhabits")
|
applicationId = "org.isoron.uhabits"
|
||||||
testInstrumentationRunner("androidx.test.runner.AndroidJUnitRunner")
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
@@ -58,7 +56,7 @@ android {
|
|||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
getByName("release") {
|
getByName("release") {
|
||||||
minifyEnabled(true)
|
isMinifyEnabled = true
|
||||||
proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.txt")
|
proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.txt")
|
||||||
if (signingConfigs.findByName("release") != null) {
|
if (signingConfigs.findByName("release") != null) {
|
||||||
signingConfig = signingConfigs.getByName("release")
|
signingConfig = signingConfigs.getByName("release")
|
||||||
@@ -70,12 +68,6 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lintOptions {
|
|
||||||
isCheckReleaseBuilds = false
|
|
||||||
isAbortOnError = false
|
|
||||||
disable("GoogleAppIndexingWarning")
|
|
||||||
}
|
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
isCoreLibraryDesugaringEnabled = true
|
isCoreLibraryDesugaringEnabled = true
|
||||||
targetCompatibility(JavaVersion.VERSION_1_8)
|
targetCompatibility(JavaVersion.VERSION_1_8)
|
||||||
@@ -88,29 +80,29 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
val daggerVersion = "2.34.1"
|
val daggerVersion = "2.43.2"
|
||||||
val kotlinVersion = "1.4.32"
|
val kotlinVersion = "1.7.10"
|
||||||
val kxCoroutinesVersion = "1.4.2"
|
val kxCoroutinesVersion = "1.6.4"
|
||||||
val ktorVersion = "1.5.3"
|
val ktorVersion = "1.6.8"
|
||||||
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")
|
||||||
androidTestImplementation("com.google.dagger:dagger:$daggerVersion")
|
androidTestImplementation("com.google.dagger:dagger:$daggerVersion")
|
||||||
androidTestImplementation("com.linkedin.dexmaker:dexmaker-mockito:2.28.1")
|
androidTestImplementation("com.linkedin.dexmaker:dexmaker-mockito:2.28.3")
|
||||||
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.4.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.2.2")
|
||||||
implementation("com.github.paolorotolo:appintro:3.4.0")
|
implementation("com.github.AppIntro:AppIntro:6.2.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:31.1-android")
|
||||||
implementation("io.ktor:ktor-client-android:$ktorVersion")
|
implementation("io.ktor:ktor-client-android:$ktorVersion")
|
||||||
implementation("io.ktor:ktor-client-core:$ktorVersion")
|
implementation("io.ktor:ktor-client-core:$ktorVersion")
|
||||||
implementation("io.ktor:ktor-client-jackson:$ktorVersion")
|
implementation("io.ktor:ktor-client-jackson:$ktorVersion")
|
||||||
@@ -118,11 +110,11 @@ 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.5.0")
|
||||||
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.6.1")
|
||||||
implementation("com.opencsv:opencsv:5.4")
|
implementation("com.opencsv:opencsv:5.6")
|
||||||
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: 9.0 KiB After Width: | Height: | Size: 8.6 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 8.6 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 8.9 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 22 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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ import org.junit.runner.RunWith
|
|||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
@LargeTest
|
@LargeTest
|
||||||
class HabitsTest : BaseUserInterfaceTest() {
|
class HabitsTest : BaseUserInterfaceTest() {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
fun shouldCreateHabit() {
|
fun shouldCreateHabit() {
|
||||||
@@ -180,6 +181,8 @@ class HabitsTest : BaseUserInterfaceTest() {
|
|||||||
longPressCheckmarks("Wake up early", count = 2)
|
longPressCheckmarks("Wake up early", count = 2)
|
||||||
clickText("Wake up early")
|
clickText("Wake up early")
|
||||||
verifyShowsScreen(SHOW_HABIT)
|
verifyShowsScreen(SHOW_HABIT)
|
||||||
|
// TODO: find a better way than sleeping in tests
|
||||||
|
Thread.sleep(2001L)
|
||||||
verifyDisplaysText("10%")
|
verifyDisplaysText("10%")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,6 +197,8 @@ class HabitsTest : BaseUserInterfaceTest() {
|
|||||||
verifyDoesNotDisplayText("Track time")
|
verifyDoesNotDisplayText("Track time")
|
||||||
verifyDisplaysText("Wake up early")
|
verifyDisplaysText("Wake up early")
|
||||||
longPressCheckmarks("Wake up early", count = 1)
|
longPressCheckmarks("Wake up early", count = 1)
|
||||||
|
// TODO: find a better way than sleeping in tests
|
||||||
|
Thread.sleep(2001L)
|
||||||
verifyDoesNotDisplayText("Wake up early")
|
verifyDoesNotDisplayText("Wake up early")
|
||||||
clickMenu(TOGGLE_COMPLETED)
|
clickMenu(TOGGLE_COMPLETED)
|
||||||
verifyDisplaysText("Track time")
|
verifyDisplaysText("Track time")
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
package org.isoron.uhabits.acceptance.steps
|
package org.isoron.uhabits.acceptance.steps
|
||||||
|
|
||||||
import android.os.Build.VERSION.SDK_INT
|
import android.os.Build.VERSION.SDK_INT
|
||||||
|
import android.os.SystemClock.sleep
|
||||||
import androidx.test.uiautomator.By
|
import androidx.test.uiautomator.By
|
||||||
import androidx.test.uiautomator.UiSelector
|
import androidx.test.uiautomator.UiSelector
|
||||||
import org.isoron.uhabits.BaseUserInterfaceTest.Companion.device
|
import org.isoron.uhabits.BaseUserInterfaceTest.Companion.device
|
||||||
@@ -39,7 +40,7 @@ fun exportFullBackup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun clearDownloadFolder() {
|
fun clearDownloadFolder() {
|
||||||
device.executeShellCommand("rm -rf /sdcard/Download/")
|
device.executeShellCommand("rm -rf /sdcard/Download")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clearBackupFolder() {
|
fun clearBackupFolder() {
|
||||||
@@ -86,6 +87,7 @@ fun importBackupFromDownloadFolder() {
|
|||||||
device.findObject(UiSelector().textContains("Loop")).click()
|
device.findObject(UiSelector().textContains("Loop")).click()
|
||||||
} else {
|
} else {
|
||||||
device.click(50, 90) // Click menu button
|
device.click(50, 90) // Click menu button
|
||||||
|
Thread.sleep(1000)
|
||||||
device.findObject(UiSelector().textContains("Download")).click()
|
device.findObject(UiSelector().textContains("Download")).click()
|
||||||
device.findObject(UiSelector().textContains("Loop")).click()
|
device.findObject(UiSelector().textContains("Loop")).click()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,9 @@
|
|||||||
*/
|
*/
|
||||||
package org.isoron.uhabits.acceptance.steps
|
package org.isoron.uhabits.acceptance.steps
|
||||||
|
|
||||||
import android.os.Build.VERSION
|
import android.os.Build
|
||||||
|
import android.os.Build.VERSION.SDK_INT
|
||||||
|
import android.os.Build.VERSION_CODES
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.test.espresso.Espresso
|
import androidx.test.espresso.Espresso
|
||||||
@@ -72,7 +74,7 @@ object CommonSteps : BaseUserInterfaceTest() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun offsetHeaders() {
|
fun offsetHeaders() {
|
||||||
device.swipe(750, 160, 600, 160, 20)
|
device.swipe(500, 160, 350, 160, 20)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun scrollToText(text: String?) {
|
fun scrollToText(text: String?) {
|
||||||
@@ -133,7 +135,7 @@ object CommonSteps : BaseUserInterfaceTest() {
|
|||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
fun verifyOpensWebsite(url: String?) {
|
fun verifyOpensWebsite(url: String?) {
|
||||||
var browserPkg = "org.chromium.webview_shell"
|
var browserPkg = "org.chromium.webview_shell"
|
||||||
if (VERSION.SDK_INT <= 23) {
|
if (SDK_INT <= Build.VERSION_CODES.M) {
|
||||||
browserPkg = "com.android.browser"
|
browserPkg = "com.android.browser"
|
||||||
}
|
}
|
||||||
assertTrue(device.wait(Until.hasObject(By.pkg(browserPkg)), 5000))
|
assertTrue(device.wait(Until.hasObject(By.pkg(browserPkg)), 5000))
|
||||||
@@ -178,6 +180,22 @@ object CommonSteps : BaseUserInterfaceTest() {
|
|||||||
EditHabitSteps.clickSave()
|
EditHabitSteps.clickSave()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun changeFrequencyToDaily(habitName: String) {
|
||||||
|
clickText(habitName)
|
||||||
|
Espresso.onView(ViewMatchers.withId(R.id.action_edit_habit)).perform(ViewActions.click())
|
||||||
|
EditHabitSteps.pickDailyFrequency()
|
||||||
|
EditHabitSteps.clickSave()
|
||||||
|
pressBack()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun changeFrequencyToMonthly(habitName: String) {
|
||||||
|
clickText(habitName)
|
||||||
|
Espresso.onView(ViewMatchers.withId(R.id.action_edit_habit)).perform(ViewActions.click())
|
||||||
|
EditHabitSteps.pickMonthFrequency()
|
||||||
|
EditHabitSteps.clickSave()
|
||||||
|
pressBack()
|
||||||
|
}
|
||||||
|
|
||||||
enum class Screen {
|
enum class Screen {
|
||||||
LIST_HABITS, SHOW_HABIT, EDIT_HABIT, SELECT_HABIT_TYPE
|
LIST_HABITS, SHOW_HABIT, EDIT_HABIT, SELECT_HABIT_TYPE
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,24 @@ object EditHabitSteps {
|
|||||||
Espresso.onView(ViewMatchers.withText("SAVE")).perform(ViewActions.click())
|
Espresso.onView(ViewMatchers.withText("SAVE")).perform(ViewActions.click())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun pickMonthFrequency() {
|
||||||
|
Espresso.onView(ViewMatchers.withId(R.id.boolean_frequency_picker))
|
||||||
|
.perform(ViewActions.click())
|
||||||
|
Espresso.onView(ViewMatchers.withId(R.id.xTimesPerMonthRadioButton))
|
||||||
|
.perform(ViewActions.click())
|
||||||
|
Espresso.onView(ViewMatchers.withId(R.id.xTimesPerMonthTextView))
|
||||||
|
.perform(ViewActions.replaceText("1"))
|
||||||
|
Espresso.onView(ViewMatchers.withText("SAVE")).perform(ViewActions.click())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun pickDailyFrequency() {
|
||||||
|
Espresso.onView(ViewMatchers.withId(R.id.boolean_frequency_picker))
|
||||||
|
.perform(ViewActions.click())
|
||||||
|
Espresso.onView(ViewMatchers.withId(R.id.everyDayRadioButton))
|
||||||
|
.perform(ViewActions.click())
|
||||||
|
Espresso.onView(ViewMatchers.withText("SAVE")).perform(ViewActions.click())
|
||||||
|
}
|
||||||
|
|
||||||
fun pickColor(color: Int) {
|
fun pickColor(color: Int) {
|
||||||
Espresso.onView(ViewMatchers.withId(R.id.colorButton)).perform(ViewActions.click())
|
Espresso.onView(ViewMatchers.withId(R.id.colorButton)).perform(ViewActions.click())
|
||||||
BaseUserInterfaceTest.device.findObject(By.descStartsWith(String.format("Color %d", color)))
|
BaseUserInterfaceTest.device.findObject(By.descStartsWith(String.format("Color %d", color)))
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ object ListHabitsSteps {
|
|||||||
clickViewWithId(R.id.action_filter)
|
clickViewWithId(R.id.action_filter)
|
||||||
CommonSteps.clickText(R.string.hide_completed)
|
CommonSteps.clickText(R.string.hide_completed)
|
||||||
}
|
}
|
||||||
|
else -> throw RuntimeException()
|
||||||
}
|
}
|
||||||
device.waitForIdle()
|
device.waitForIdle()
|
||||||
}
|
}
|
||||||
@@ -120,6 +121,12 @@ object ListHabitsSteps {
|
|||||||
BaseUserInterfaceTest.device.waitForIdle()
|
BaseUserInterfaceTest.device.waitForIdle()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun changeSort(sortText: String) {
|
||||||
|
clickViewWithId(R.id.action_filter)
|
||||||
|
Espresso.onView(ViewMatchers.withText("Sort")).perform(ViewActions.click())
|
||||||
|
Espresso.onView(ViewMatchers.withText(sortText)).perform(ViewActions.click())
|
||||||
|
}
|
||||||
|
|
||||||
enum class MenuItem {
|
enum class MenuItem {
|
||||||
ABOUT, HELP, SETTINGS, EDIT, DELETE, ARCHIVE, TOGGLE_ARCHIVED, UNARCHIVE, TOGGLE_COMPLETED, ADD
|
ABOUT, HELP, SETTINGS, EDIT, DELETE, ARCHIVE, TOGGLE_ARCHIVED, UNARCHIVE, TOGGLE_COMPLETED, ADD
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.isoron.uhabits.acceptance.steps
|
package org.isoron.uhabits.acceptance.steps
|
||||||
|
|
||||||
import android.os.Build.VERSION
|
import android.os.Build.VERSION.SDK_INT
|
||||||
import androidx.test.uiautomator.UiScrollable
|
import androidx.test.uiautomator.UiScrollable
|
||||||
import androidx.test.uiautomator.UiSelector
|
import androidx.test.uiautomator.UiSelector
|
||||||
import junit.framework.Assert.assertFalse
|
import junit.framework.Assert.assertFalse
|
||||||
@@ -50,13 +50,6 @@ object WidgetSteps {
|
|||||||
private fun openWidgetScreen() {
|
private fun openWidgetScreen() {
|
||||||
val h = BaseUserInterfaceTest.device.displayHeight
|
val h = BaseUserInterfaceTest.device.displayHeight
|
||||||
val w = BaseUserInterfaceTest.device.displayWidth
|
val w = BaseUserInterfaceTest.device.displayWidth
|
||||||
if (VERSION.SDK_INT <= 21) {
|
|
||||||
BaseUserInterfaceTest.device.pressHome()
|
|
||||||
BaseUserInterfaceTest.device.waitForIdle()
|
|
||||||
BaseUserInterfaceTest.device.findObject(UiSelector().description("Apps")).click()
|
|
||||||
BaseUserInterfaceTest.device.findObject(UiSelector().description("Apps")).click()
|
|
||||||
BaseUserInterfaceTest.device.findObject(UiSelector().description("Widgets")).click()
|
|
||||||
} else {
|
|
||||||
val listId = "com.android.launcher3:id/widgets_list_view"
|
val listId = "com.android.launcher3:id/widgets_list_view"
|
||||||
BaseUserInterfaceTest.device.pressHome()
|
BaseUserInterfaceTest.device.pressHome()
|
||||||
BaseUserInterfaceTest.device.waitForIdle()
|
BaseUserInterfaceTest.device.waitForIdle()
|
||||||
@@ -66,14 +59,13 @@ object WidgetSteps {
|
|||||||
button = BaseUserInterfaceTest.device.findObject(UiSelector().text("Widgets"))
|
button = BaseUserInterfaceTest.device.findObject(UiSelector().text("Widgets"))
|
||||||
}
|
}
|
||||||
button.click()
|
button.click()
|
||||||
if (VERSION.SDK_INT >= 28) {
|
if (SDK_INT >= 28) {
|
||||||
UiScrollable(UiSelector().resourceId(listId))
|
UiScrollable(UiSelector().resourceId(listId))
|
||||||
.scrollForward()
|
.scrollForward()
|
||||||
}
|
}
|
||||||
UiScrollable(UiSelector().resourceId(listId))
|
UiScrollable(UiSelector().resourceId(listId))
|
||||||
.scrollIntoView(UiSelector().text("Checkmark"))
|
.scrollIntoView(UiSelector().text("Checkmark"))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
fun verifyCheckmarkWidgetIsShown() {
|
fun verifyCheckmarkWidgetIsShown() {
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -29,6 +29,10 @@ import org.junit.runner.RunWith
|
|||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
@MediumTest
|
@MediumTest
|
||||||
class EmptyListViewTest : BaseViewTest() {
|
class EmptyListViewTest : BaseViewTest() {
|
||||||
|
init {
|
||||||
|
// TODO: fix rendering differences across APIs
|
||||||
|
similarityCutoff = 0.00035
|
||||||
|
}
|
||||||
|
|
||||||
private val path = "habits/list/EmptyListView"
|
private val path = "habits/list/EmptyListView"
|
||||||
private val view: EmptyListView = EmptyListView(targetContext)
|
private val view: EmptyListView = EmptyListView(targetContext)
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ class EntryButtonViewTest : BaseViewTest() {
|
|||||||
lateinit var view: CheckmarkButtonView
|
lateinit var view: CheckmarkButtonView
|
||||||
|
|
||||||
var toggled = false
|
var toggled = false
|
||||||
|
var edited = false
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
override fun setUp() {
|
override fun setUp() {
|
||||||
@@ -43,7 +44,8 @@ class EntryButtonViewTest : BaseViewTest() {
|
|||||||
view = component.getEntryButtonViewFactory().create().apply {
|
view = component.getEntryButtonViewFactory().create().apply {
|
||||||
value = Entry.NO
|
value = Entry.NO
|
||||||
color = PaletteUtils.getAndroidTestColor(5)
|
color = PaletteUtils.getAndroidTestColor(5)
|
||||||
onToggle = { toggled = true }
|
onToggle = { _, _, _ -> toggled = true }
|
||||||
|
onEdit = { edited = true }
|
||||||
}
|
}
|
||||||
measureView(view, dpToPixels(48), dpToPixels(48))
|
measureView(view, dpToPixels(48), dpToPixels(48))
|
||||||
}
|
}
|
||||||
@@ -70,20 +72,28 @@ class EntryButtonViewTest : BaseViewTest() {
|
|||||||
fun testClick_withShortToggleDisabled() {
|
fun testClick_withShortToggleDisabled() {
|
||||||
prefs.isShortToggleEnabled = false
|
prefs.isShortToggleEnabled = false
|
||||||
view.performClick()
|
view.performClick()
|
||||||
assertFalse(toggled)
|
assertTrue(!toggled and edited)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testClick_withShortToggleEnabled() {
|
fun testClick_withShortToggleEnabled() {
|
||||||
prefs.isShortToggleEnabled = true
|
prefs.isShortToggleEnabled = true
|
||||||
view.performClick()
|
view.performClick()
|
||||||
assertTrue(toggled)
|
assertTrue(toggled and !edited)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testLongClick() {
|
fun testLongClick_withShortToggleDisabled() {
|
||||||
|
prefs.isShortToggleEnabled = false
|
||||||
view.performLongClick()
|
view.performLongClick()
|
||||||
assertTrue(toggled)
|
assertTrue(toggled and !edited)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testLongClick_withShortToggleEnabled() {
|
||||||
|
prefs.isShortToggleEnabled = true
|
||||||
|
view.performLongClick()
|
||||||
|
assertTrue(!toggled and edited)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun assertRendersCheckedExplicitly() {
|
private fun assertRendersCheckedExplicitly() {
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ class EntryPanelViewTest : BaseViewTest() {
|
|||||||
@Test
|
@Test
|
||||||
fun testToggle() {
|
fun testToggle() {
|
||||||
val timestamps = mutableListOf<Timestamp>()
|
val timestamps = mutableListOf<Timestamp>()
|
||||||
view.onToggle = { t, _ -> timestamps.add(t) }
|
view.onToggle = { t, _, _, _ -> timestamps.add(t) }
|
||||||
view.buttons[0].performLongClick()
|
view.buttons[0].performLongClick()
|
||||||
view.buttons[2].performLongClick()
|
view.buttons[2].performLongClick()
|
||||||
view.buttons[3].performLongClick()
|
view.buttons[3].performLongClick()
|
||||||
@@ -88,7 +88,7 @@ class EntryPanelViewTest : BaseViewTest() {
|
|||||||
fun testToggle_withOffset() {
|
fun testToggle_withOffset() {
|
||||||
val timestamps = mutableListOf<Timestamp>()
|
val timestamps = mutableListOf<Timestamp>()
|
||||||
view.dataOffset = 3
|
view.dataOffset = 3
|
||||||
view.onToggle = { t, _ -> timestamps += t }
|
view.onToggle = { t, _, _, _ -> timestamps += t }
|
||||||
view.buttons[0].performLongClick()
|
view.buttons[0].performLongClick()
|
||||||
view.buttons[2].performLongClick()
|
view.buttons[2].performLongClick()
|
||||||
view.buttons[3].performLongClick()
|
view.buttons[3].performLongClick()
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import androidx.test.filters.MediumTest
|
|||||||
import org.hamcrest.CoreMatchers.equalTo
|
import org.hamcrest.CoreMatchers.equalTo
|
||||||
import org.hamcrest.MatcherAssert.assertThat
|
import org.hamcrest.MatcherAssert.assertThat
|
||||||
import org.isoron.uhabits.BaseViewTest
|
import org.isoron.uhabits.BaseViewTest
|
||||||
|
import org.isoron.uhabits.core.models.NumericalHabitType
|
||||||
import org.isoron.uhabits.utils.PaletteUtils
|
import org.isoron.uhabits.utils.PaletteUtils
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@@ -42,6 +43,7 @@ class NumberButtonViewTest : BaseViewTest() {
|
|||||||
super.setUp()
|
super.setUp()
|
||||||
view = component.getNumberButtonViewFactory().create().apply {
|
view = component.getNumberButtonViewFactory().create().apply {
|
||||||
units = "steps"
|
units = "steps"
|
||||||
|
targetType = NumericalHabitType.AT_LEAST
|
||||||
threshold = 100.0
|
threshold = 100.0
|
||||||
color = PaletteUtils.getAndroidTestColor(8)
|
color = PaletteUtils.getAndroidTestColor(8)
|
||||||
onEdit = { edited = true }
|
onEdit = { edited = true }
|
||||||
@@ -74,10 +76,10 @@ class NumberButtonViewTest : BaseViewTest() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testRender_emptyUnits() {
|
fun testRender_atMostAboveThreshold() {
|
||||||
view.value = 500.0
|
view.value = 500.0
|
||||||
view.units = ""
|
view.targetType = NumericalHabitType.AT_MOST
|
||||||
assertRenders(view, "$PATH/render_unitless.png")
|
assertRenders(view, "$PATH/render_at_most_above.png")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -86,6 +88,13 @@ class NumberButtonViewTest : BaseViewTest() {
|
|||||||
assertRenders(view, "$PATH/render_below.png")
|
assertRenders(view, "$PATH/render_below.png")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testRender_atMostBetweenThresholds() {
|
||||||
|
view.value = 110.0
|
||||||
|
view.targetType = NumericalHabitType.AT_MOST
|
||||||
|
assertRenders(view, "$PATH/render_at_most_between.png")
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testRender_zero() {
|
fun testRender_zero() {
|
||||||
view.value = 0.0
|
view.value = 0.0
|
||||||
@@ -93,15 +102,21 @@ class NumberButtonViewTest : BaseViewTest() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testClick_shortToggleDisabled() {
|
fun testRender_atMostBelowThreshold() {
|
||||||
prefs.isShortToggleEnabled = false
|
view.value = 0.0
|
||||||
view.performClick()
|
view.targetType = NumericalHabitType.AT_MOST
|
||||||
assertFalse(edited)
|
assertRenders(view, "$PATH/render_at_most_below.png")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testClick_shortToggleEnabled() {
|
fun testRender_emptyUnits() {
|
||||||
prefs.isShortToggleEnabled = true
|
view.value = 500.0
|
||||||
|
view.units = ""
|
||||||
|
assertRenders(view, "$PATH/render_unitless.png")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testClick() {
|
||||||
view.performClick()
|
view.performClick()
|
||||||
assertTrue(edited)
|
assertTrue(edited)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import androidx.test.filters.MediumTest
|
|||||||
import org.hamcrest.CoreMatchers.equalTo
|
import org.hamcrest.CoreMatchers.equalTo
|
||||||
import org.hamcrest.MatcherAssert.assertThat
|
import org.hamcrest.MatcherAssert.assertThat
|
||||||
import org.isoron.uhabits.BaseViewTest
|
import org.isoron.uhabits.BaseViewTest
|
||||||
|
import org.isoron.uhabits.core.models.NumericalHabitType
|
||||||
import org.isoron.uhabits.core.models.Timestamp
|
import org.isoron.uhabits.core.models.Timestamp
|
||||||
import org.isoron.uhabits.utils.PaletteUtils
|
import org.isoron.uhabits.utils.PaletteUtils
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
@@ -55,6 +56,7 @@ class NumberPanelViewTest : BaseViewTest() {
|
|||||||
buttonCount = 4
|
buttonCount = 4
|
||||||
color = PaletteUtils.getAndroidTestColor(7)
|
color = PaletteUtils.getAndroidTestColor(7)
|
||||||
units = "steps"
|
units = "steps"
|
||||||
|
targetType = NumericalHabitType.AT_LEAST
|
||||||
threshold = 5000.0
|
threshold = 5000.0
|
||||||
}
|
}
|
||||||
view.onAttachedToWindow()
|
view.onAttachedToWindow()
|
||||||
@@ -74,7 +76,7 @@ class NumberPanelViewTest : BaseViewTest() {
|
|||||||
@Test
|
@Test
|
||||||
fun testEdit() {
|
fun testEdit() {
|
||||||
val timestamps = mutableListOf<Timestamp>()
|
val timestamps = mutableListOf<Timestamp>()
|
||||||
view.onEdit = { timestamps.plusAssign(it) }
|
view.onEdit = { t -> timestamps.plusAssign(t) }
|
||||||
view.buttons[0].performLongClick()
|
view.buttons[0].performLongClick()
|
||||||
view.buttons[2].performLongClick()
|
view.buttons[2].performLongClick()
|
||||||
view.buttons[3].performLongClick()
|
view.buttons[3].performLongClick()
|
||||||
@@ -85,7 +87,7 @@ class NumberPanelViewTest : BaseViewTest() {
|
|||||||
fun testEdit_withOffset() {
|
fun testEdit_withOffset() {
|
||||||
val timestamps = mutableListOf<Timestamp>()
|
val timestamps = mutableListOf<Timestamp>()
|
||||||
view.dataOffset = 3
|
view.dataOffset = 3
|
||||||
view.onEdit = { timestamps += it }
|
view.onEdit = { t -> timestamps += t }
|
||||||
view.buttons[0].performLongClick()
|
view.buttons[0].performLongClick()
|
||||||
view.buttons[2].performLongClick()
|
view.buttons[2].performLongClick()
|
||||||
view.buttons[3].performLongClick()
|
view.buttons[3].performLongClick()
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -52,8 +53,7 @@ class SubtitleCardViewTest : BaseViewTest() {
|
|||||||
isNumerical = false,
|
isNumerical = false,
|
||||||
question = "Did you meditate this morning?",
|
question = "Did you meditate this morning?",
|
||||||
reminder = Reminder(8, 30, EVERY_DAY),
|
reminder = Reminder(8, 30, EVERY_DAY),
|
||||||
unit = "",
|
theme = LightTheme(),
|
||||||
targetValue = 0.0,
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
measureView(view, 800f, 200f)
|
measureView(view, 800f, 200f)
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ class PerformanceTest : BaseAndroidTest() {
|
|||||||
val habit = fixtures.createEmptyHabit()
|
val habit = fixtures.createEmptyHabit()
|
||||||
for (i in 0..4999) {
|
for (i in 0..4999) {
|
||||||
val timestamp: Timestamp = Timestamp(i * DAY_LENGTH)
|
val timestamp: Timestamp = Timestamp(i * DAY_LENGTH)
|
||||||
CreateRepetitionCommand(habitList, habit, timestamp, 1).run()
|
CreateRepetitionCommand(habitList, habit, timestamp, 1, "").run()
|
||||||
}
|
}
|
||||||
db.setTransactionSuccessful()
|
db.setTransactionSuccessful()
|
||||||
db.endTransaction()
|
db.endTransaction()
|
||||||
|
|||||||
@@ -21,9 +21,12 @@ package org.isoron.uhabits.regression
|
|||||||
|
|
||||||
import androidx.test.filters.LargeTest
|
import androidx.test.filters.LargeTest
|
||||||
import org.isoron.uhabits.BaseUserInterfaceTest
|
import org.isoron.uhabits.BaseUserInterfaceTest
|
||||||
|
import org.isoron.uhabits.acceptance.steps.CommonSteps
|
||||||
import org.isoron.uhabits.acceptance.steps.CommonSteps.Screen.EDIT_HABIT
|
import org.isoron.uhabits.acceptance.steps.CommonSteps.Screen.EDIT_HABIT
|
||||||
import org.isoron.uhabits.acceptance.steps.CommonSteps.Screen.LIST_HABITS
|
import org.isoron.uhabits.acceptance.steps.CommonSteps.Screen.LIST_HABITS
|
||||||
import org.isoron.uhabits.acceptance.steps.CommonSteps.Screen.SELECT_HABIT_TYPE
|
import org.isoron.uhabits.acceptance.steps.CommonSteps.Screen.SELECT_HABIT_TYPE
|
||||||
|
import org.isoron.uhabits.acceptance.steps.CommonSteps.changeFrequencyToDaily
|
||||||
|
import org.isoron.uhabits.acceptance.steps.CommonSteps.changeFrequencyToMonthly
|
||||||
import org.isoron.uhabits.acceptance.steps.CommonSteps.clickText
|
import org.isoron.uhabits.acceptance.steps.CommonSteps.clickText
|
||||||
import org.isoron.uhabits.acceptance.steps.CommonSteps.createHabit
|
import org.isoron.uhabits.acceptance.steps.CommonSteps.createHabit
|
||||||
import org.isoron.uhabits.acceptance.steps.CommonSteps.launchApp
|
import org.isoron.uhabits.acceptance.steps.CommonSteps.launchApp
|
||||||
@@ -37,9 +40,12 @@ import org.isoron.uhabits.acceptance.steps.EditHabitSteps.clickSave
|
|||||||
import org.isoron.uhabits.acceptance.steps.EditHabitSteps.typeName
|
import org.isoron.uhabits.acceptance.steps.EditHabitSteps.typeName
|
||||||
import org.isoron.uhabits.acceptance.steps.ListHabitsSteps.MenuItem.ADD
|
import org.isoron.uhabits.acceptance.steps.ListHabitsSteps.MenuItem.ADD
|
||||||
import org.isoron.uhabits.acceptance.steps.ListHabitsSteps.MenuItem.DELETE
|
import org.isoron.uhabits.acceptance.steps.ListHabitsSteps.MenuItem.DELETE
|
||||||
|
import org.isoron.uhabits.acceptance.steps.ListHabitsSteps.changeSort
|
||||||
import org.isoron.uhabits.acceptance.steps.ListHabitsSteps.clickMenu
|
import org.isoron.uhabits.acceptance.steps.ListHabitsSteps.clickMenu
|
||||||
import org.isoron.uhabits.acceptance.steps.ListHabitsSteps.longPressCheckmarks
|
import org.isoron.uhabits.acceptance.steps.ListHabitsSteps.longPressCheckmarks
|
||||||
|
import org.isoron.uhabits.core.models.Entry.Companion.NO
|
||||||
import org.isoron.uhabits.core.models.Entry.Companion.UNKNOWN
|
import org.isoron.uhabits.core.models.Entry.Companion.UNKNOWN
|
||||||
|
import org.isoron.uhabits.core.models.Entry.Companion.YES_AUTO
|
||||||
import org.isoron.uhabits.core.models.Entry.Companion.YES_MANUAL
|
import org.isoron.uhabits.core.models.Entry.Companion.YES_MANUAL
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
@@ -83,4 +89,37 @@ class ListHabitsRegressionTest : BaseUserInterfaceTest() {
|
|||||||
offsetHeaders()
|
offsetHeaders()
|
||||||
verifyDisplaysCheckmarks("Wake up early", listOf(UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN))
|
verifyDisplaysCheckmarks("Wake up early", listOf(UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://github.com/iSoron/uhabits/issues/1131
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
@Throws(Exception::class)
|
||||||
|
fun should_refresh_sort_after_habit_edit() {
|
||||||
|
launchApp()
|
||||||
|
verifyShowsScreen(LIST_HABITS)
|
||||||
|
changeSort("By score")
|
||||||
|
changeSort("By status")
|
||||||
|
longPressCheckmarks("Meditate", count = 1)
|
||||||
|
changeFrequencyToMonthly("Read books")
|
||||||
|
longPressCheckmarks("Read books", count = 2)
|
||||||
|
longPressCheckmarks("Read books", count = 1)
|
||||||
|
verifyDisplaysCheckmarks("Meditate", listOf(YES_AUTO, YES_MANUAL, YES_AUTO, YES_MANUAL))
|
||||||
|
CommonSteps.verifyDisplaysTextInSequence(
|
||||||
|
"Wake up early",
|
||||||
|
"Read books",
|
||||||
|
"Meditate",
|
||||||
|
"Track time"
|
||||||
|
)
|
||||||
|
|
||||||
|
changeFrequencyToDaily("Meditate")
|
||||||
|
|
||||||
|
verifyDisplaysCheckmarks("Meditate", listOf(NO, YES_MANUAL, UNKNOWN, YES_MANUAL))
|
||||||
|
CommonSteps.verifyDisplaysTextInSequence(
|
||||||
|
"Wake up early",
|
||||||
|
"Meditate",
|
||||||
|
"Read books",
|
||||||
|
"Track time",
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,14 +24,15 @@ import android.widget.FrameLayout
|
|||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import androidx.test.filters.MediumTest
|
import androidx.test.filters.MediumTest
|
||||||
import org.hamcrest.CoreMatchers
|
import org.hamcrest.CoreMatchers
|
||||||
import org.hamcrest.CoreMatchers.`is`
|
|
||||||
import org.hamcrest.CoreMatchers.equalTo
|
import org.hamcrest.CoreMatchers.equalTo
|
||||||
|
import org.hamcrest.CoreMatchers.`is`
|
||||||
import org.hamcrest.MatcherAssert.assertThat
|
import org.hamcrest.MatcherAssert.assertThat
|
||||||
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.models.Entry
|
import org.isoron.uhabits.core.models.Entry
|
||||||
import org.isoron.uhabits.core.models.EntryList
|
import org.isoron.uhabits.core.models.EntryList
|
||||||
import org.isoron.uhabits.core.models.Habit
|
import org.isoron.uhabits.core.models.Habit
|
||||||
|
import org.isoron.uhabits.core.models.Timestamp
|
||||||
import org.isoron.uhabits.core.utils.DateUtils.Companion.getTodayWithOffset
|
import org.isoron.uhabits.core.utils.DateUtils.Companion.getTodayWithOffset
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
@@ -42,10 +43,12 @@ class CheckmarkWidgetTest : BaseViewTest() {
|
|||||||
private lateinit var habit: Habit
|
private lateinit var habit: Habit
|
||||||
private lateinit var entries: EntryList
|
private lateinit var entries: EntryList
|
||||||
private lateinit var view: FrameLayout
|
private lateinit var view: FrameLayout
|
||||||
private val today = getTodayWithOffset()
|
private lateinit var today: Timestamp
|
||||||
|
|
||||||
override fun setUp() {
|
override fun setUp() {
|
||||||
super.setUp()
|
super.setUp()
|
||||||
setTheme(R.style.WidgetTheme)
|
setTheme(R.style.WidgetTheme)
|
||||||
|
today = getTodayWithOffset()
|
||||||
prefs.widgetOpacity = 255
|
prefs.widgetOpacity = 255
|
||||||
prefs.isSkipEnabled = true
|
prefs.isSkipEnabled = true
|
||||||
habit = fixtures.createVeryLongHabit()
|
habit = fixtures.createVeryLongHabit()
|
||||||
|
|||||||
@@ -32,6 +32,10 @@ import org.junit.runner.RunWith
|
|||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
@MediumTest
|
@MediumTest
|
||||||
class TargetWidgetTest : BaseViewTest() {
|
class TargetWidgetTest : BaseViewTest() {
|
||||||
|
init {
|
||||||
|
// TODO: fix rendering differences across APIs
|
||||||
|
similarityCutoff = 0.00025
|
||||||
|
}
|
||||||
private lateinit var habit: Habit
|
private lateinit var habit: Habit
|
||||||
private lateinit var view: FrameLayout
|
private lateinit var view: FrameLayout
|
||||||
override fun setUp() {
|
override fun setUp() {
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ class CheckmarkWidgetViewTest : BaseViewTest() {
|
|||||||
name = habit.name
|
name = habit.name
|
||||||
}
|
}
|
||||||
view.refresh()
|
view.refresh()
|
||||||
measureView(view, dpToPixels(100), dpToPixels(200))
|
measureView(view, dpToPixels(100), dpToPixels(125))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -17,9 +17,10 @@
|
|||||||
~ with this program. If not, see <http://www.gnu.org/licenses/>.
|
~ with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
-->
|
-->
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
package="org.isoron.uhabits">
|
package="org.isoron.uhabits">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS" />
|
||||||
|
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
||||||
<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" />
|
||||||
|
|
||||||
@@ -48,11 +49,11 @@
|
|||||||
android:name=".activities.habits.list.ListHabitsActivity"
|
android:name=".activities.habits.list.ListHabitsActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:label="@string/main_activity_title"
|
android:label="@string/main_activity_title"
|
||||||
android:launchMode="singleTop">
|
android:launchMode="singleTop" />
|
||||||
</activity>
|
|
||||||
|
|
||||||
<activity-alias
|
<activity-alias
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
|
android:exported="true"
|
||||||
android:label="@string/main_activity_title"
|
android:label="@string/main_activity_title"
|
||||||
android:launchMode="singleTop"
|
android:launchMode="singleTop"
|
||||||
android:targetActivity=".activities.habits.list.ListHabitsActivity">
|
android:targetActivity=".activities.habits.list.ListHabitsActivity">
|
||||||
@@ -78,6 +79,14 @@
|
|||||||
android:value=".activities.habits.list.ListHabitsActivity" />
|
android:value=".activities.habits.list.ListHabitsActivity" />
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".activities.sync.SyncActivity"
|
||||||
|
android:label="@string/device_sync">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
|
android:value=".activities.sync.SyncActivity" />
|
||||||
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".activities.intro.IntroActivity"
|
android:name=".activities.intro.IntroActivity"
|
||||||
android:label=""
|
android:label=""
|
||||||
@@ -85,6 +94,7 @@
|
|||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".widgets.activities.HabitPickerDialog"
|
android:name=".widgets.activities.HabitPickerDialog"
|
||||||
|
android:exported="true"
|
||||||
android:theme="@style/Theme.AppCompat.Light.Dialog">
|
android:theme="@style/Theme.AppCompat.Light.Dialog">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
|
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
|
||||||
@@ -93,6 +103,7 @@
|
|||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".widgets.activities.BooleanHabitPickerDialog"
|
android:name=".widgets.activities.BooleanHabitPickerDialog"
|
||||||
|
android:exported="true"
|
||||||
android:theme="@style/Theme.AppCompat.Light.Dialog">
|
android:theme="@style/Theme.AppCompat.Light.Dialog">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
|
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
|
||||||
@@ -101,6 +112,7 @@
|
|||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".widgets.activities.NumericalHabitPickerDialog"
|
android:name=".widgets.activities.NumericalHabitPickerDialog"
|
||||||
|
android:exported="true"
|
||||||
android:theme="@style/Theme.AppCompat.Light.Dialog">
|
android:theme="@style/Theme.AppCompat.Light.Dialog">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
|
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
|
||||||
@@ -115,26 +127,16 @@
|
|||||||
android:value=".activities.habits.list.ListHabitsActivity" />
|
android:value=".activities.habits.list.ListHabitsActivity" />
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
|
||||||
android:name=".widgets.activities.NumericalCheckmarkWidgetActivity"
|
|
||||||
android:label="NumericalCheckmarkWidget"
|
|
||||||
android:noHistory="true"
|
|
||||||
android:excludeFromRecents="true"
|
|
||||||
android:theme="@style/Theme.AppCompat.Light.Dialog">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="org.isoron.uhabits.ACTION_SHOW_NUMERICAL_VALUE_ACTIVITY" />
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".notifications.SnoozeDelayPickerActivity"
|
android:name=".notifications.SnoozeDelayPickerActivity"
|
||||||
android:taskAffinity=""
|
|
||||||
android:excludeFromRecents="true"
|
android:excludeFromRecents="true"
|
||||||
android:launchMode="singleInstance"
|
android:launchMode="singleInstance"
|
||||||
|
android:taskAffinity=""
|
||||||
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
|
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
|
||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".widgets.CheckmarkWidgetProvider"
|
android:name=".widgets.CheckmarkWidgetProvider"
|
||||||
|
android:exported="true"
|
||||||
android:label="@string/checkmark">
|
android:label="@string/checkmark">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||||
@@ -152,6 +154,7 @@
|
|||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".widgets.HistoryWidgetProvider"
|
android:name=".widgets.HistoryWidgetProvider"
|
||||||
|
android:exported="true"
|
||||||
android:label="@string/history">
|
android:label="@string/history">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||||
@@ -164,6 +167,7 @@
|
|||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".widgets.ScoreWidgetProvider"
|
android:name=".widgets.ScoreWidgetProvider"
|
||||||
|
android:exported="true"
|
||||||
android:label="@string/score">
|
android:label="@string/score">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||||
@@ -176,6 +180,7 @@
|
|||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".widgets.StreakWidgetProvider"
|
android:name=".widgets.StreakWidgetProvider"
|
||||||
|
android:exported="true"
|
||||||
android:label="@string/streaks">
|
android:label="@string/streaks">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||||
@@ -188,6 +193,7 @@
|
|||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".widgets.FrequencyWidgetProvider"
|
android:name=".widgets.FrequencyWidgetProvider"
|
||||||
|
android:exported="true"
|
||||||
android:label="@string/frequency">
|
android:label="@string/frequency">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||||
@@ -200,6 +206,7 @@
|
|||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".widgets.TargetWidgetProvider"
|
android:name=".widgets.TargetWidgetProvider"
|
||||||
|
android:exported="true"
|
||||||
android:label="@string/target">
|
android:label="@string/target">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||||
@@ -210,13 +217,17 @@
|
|||||||
android:resource="@xml/widget_target_info" />
|
android:resource="@xml/widget_target_info" />
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
<receiver android:name=".receivers.ReminderReceiver">
|
<receiver
|
||||||
|
android:name=".receivers.ReminderReceiver"
|
||||||
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
<receiver android:name=".receivers.WidgetReceiver">
|
<receiver android:name=".receivers.WidgetReceiver"
|
||||||
|
android:exported="true"
|
||||||
|
android:permission="false">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<action android:name="org.isoron.uhabits.ACTION_SET_NUMERICAL_VALUE" />
|
<action android:name="org.isoron.uhabits.ACTION_SET_NUMERICAL_VALUE" />
|
||||||
@@ -267,7 +278,7 @@
|
|||||||
<!-- Locale/Tasker -->
|
<!-- Locale/Tasker -->
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".automation.FireSettingReceiver"
|
android:name=".automation.FireSettingReceiver"
|
||||||
android:exported="true">
|
android:exported="false">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="com.twofortyfouram.locale.intent.action.FIRE_SETTING" />
|
<action android:name="com.twofortyfouram.locale.intent.action.FIRE_SETTING" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import android.animation.Keyframe;
|
|||||||
import android.animation.ObjectAnimator;
|
import android.animation.ObjectAnimator;
|
||||||
import android.animation.PropertyValuesHolder;
|
import android.animation.PropertyValuesHolder;
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.os.Build;
|
|
||||||
import android.text.format.Time;
|
import android.text.format.Time;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
@@ -43,17 +42,13 @@ public class Utils {
|
|||||||
|
|
||||||
static final String SHARED_PREFS_NAME = "com.android.calendar_preferences";
|
static final String SHARED_PREFS_NAME = "com.android.calendar_preferences";
|
||||||
|
|
||||||
public static boolean isJellybeanOrLater() {
|
|
||||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Try to speak the specified text, for accessibility. Only available on JB or later.
|
* Try to speak the specified text, for accessibility. Only available on JB or later.
|
||||||
* @param text Text to announce.
|
* @param text Text to announce.
|
||||||
*/
|
*/
|
||||||
@SuppressLint("NewApi")
|
@SuppressLint("NewApi")
|
||||||
public static void tryAccessibilityAnnounce(View view, CharSequence text) {
|
public static void tryAccessibilityAnnounce(View view, CharSequence text) {
|
||||||
if (isJellybeanOrLater() && view != null && text != null) {
|
if (view != null && text != null) {
|
||||||
view.announceForAccessibility(text);
|
view.announceForAccessibility(text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -383,10 +383,6 @@ public abstract class DayPickerView extends ListView implements OnScrollListener
|
|||||||
if (child instanceof MonthView) {
|
if (child instanceof MonthView) {
|
||||||
final CalendarDay focus = ((MonthView) child).getAccessibilityFocus();
|
final CalendarDay focus = ((MonthView) child).getAccessibilityFocus();
|
||||||
if (focus != null) {
|
if (focus != null) {
|
||||||
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
|
||||||
// Clear focus to avoid ListView bug in Jelly Bean MR1.
|
|
||||||
((MonthView) child).clearAccessibilityFocus();
|
|
||||||
}
|
|
||||||
return focus;
|
return focus;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -49,23 +49,12 @@ class AndroidDataView(
|
|||||||
override fun onShowPress(e: MotionEvent?) = Unit
|
override fun onShowPress(e: MotionEvent?) = Unit
|
||||||
|
|
||||||
override fun onSingleTapUp(e: MotionEvent?): Boolean {
|
override fun onSingleTapUp(e: MotionEvent?): Boolean {
|
||||||
val x: Float
|
return handleClick(e, true)
|
||||||
val y: Float
|
|
||||||
try {
|
|
||||||
val pointerId = e!!.getPointerId(0)
|
|
||||||
x = e.getX(pointerId)
|
|
||||||
y = e.getY(pointerId)
|
|
||||||
} catch (ex: RuntimeException) {
|
|
||||||
// Android often throws IllegalArgumentException here. Apparently,
|
|
||||||
// the pointer id may become invalid shortly after calling
|
|
||||||
// e.getPointerId.
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
view?.onClick(x / canvas.innerDensity, y / canvas.innerDensity)
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLongPress(e: MotionEvent?) = Unit
|
override fun onLongPress(e: MotionEvent?) {
|
||||||
|
handleClick(e)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onScroll(
|
override fun onScroll(
|
||||||
e1: MotionEvent?,
|
e1: MotionEvent?,
|
||||||
@@ -137,4 +126,22 @@ class AndroidDataView(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleClick(e: MotionEvent?, isSingleTap: Boolean = false): Boolean {
|
||||||
|
val x: Float
|
||||||
|
val y: Float
|
||||||
|
try {
|
||||||
|
val pointerId = e!!.getPointerId(0)
|
||||||
|
x = e.getX(pointerId)
|
||||||
|
y = e.getY(pointerId)
|
||||||
|
} catch (ex: RuntimeException) {
|
||||||
|
// Android often throws IllegalArgumentException here. Apparently,
|
||||||
|
// the pointer id may become invalid shortly after calling
|
||||||
|
// e.getPointerId.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (isSingleTap) view?.onClick(x / canvas.innerDensity, y / canvas.innerDensity)
|
||||||
|
else view?.onLongClick(x / canvas.innerDensity, y / canvas.innerDensity)
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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() }
|
||||||
|
|||||||
@@ -0,0 +1,128 @@
|
|||||||
|
/*
|
||||||
|
* 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.app.Dialog
|
||||||
|
import android.content.Context
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.View.GONE
|
||||||
|
import android.view.View.VISIBLE
|
||||||
|
import org.isoron.uhabits.R
|
||||||
|
import org.isoron.uhabits.core.models.Entry.Companion.NO
|
||||||
|
import org.isoron.uhabits.core.models.Entry.Companion.SKIP
|
||||||
|
import org.isoron.uhabits.core.models.Entry.Companion.UNKNOWN
|
||||||
|
import org.isoron.uhabits.core.models.Entry.Companion.YES_AUTO
|
||||||
|
import org.isoron.uhabits.core.models.Entry.Companion.YES_MANUAL
|
||||||
|
import org.isoron.uhabits.core.preferences.Preferences
|
||||||
|
import org.isoron.uhabits.databinding.CheckmarkPopupBinding
|
||||||
|
import org.isoron.uhabits.utils.InterfaceUtils.getFontAwesome
|
||||||
|
import org.isoron.uhabits.utils.dimBehind
|
||||||
|
import org.isoron.uhabits.utils.dismissCurrentAndShow
|
||||||
|
import org.isoron.uhabits.utils.dp
|
||||||
|
import org.isoron.uhabits.utils.sres
|
||||||
|
|
||||||
|
const val POPUP_WIDTH = 4 * 48f + 16f
|
||||||
|
const val POPUP_HEIGHT = 48f * 2.5f + 8f
|
||||||
|
|
||||||
|
class CheckmarkPopup(
|
||||||
|
private val context: Context,
|
||||||
|
private val color: Int,
|
||||||
|
private var notes: String,
|
||||||
|
private var value: Int,
|
||||||
|
private val prefs: Preferences,
|
||||||
|
private val anchor: View,
|
||||||
|
) {
|
||||||
|
var onToggle: (Int, String) -> Unit = { _, _ -> }
|
||||||
|
private lateinit var dialog: Dialog
|
||||||
|
|
||||||
|
private val view = CheckmarkPopupBinding.inflate(LayoutInflater.from(context)).apply {
|
||||||
|
// Required for round corners
|
||||||
|
container.clipToOutline = true
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
view.booleanButtons.visibility = VISIBLE
|
||||||
|
initColors()
|
||||||
|
initTypefaces()
|
||||||
|
hideDisabledButtons()
|
||||||
|
populate()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initColors() {
|
||||||
|
arrayOf(view.yesBtn, view.skipBtn).forEach {
|
||||||
|
it.setTextColor(color)
|
||||||
|
}
|
||||||
|
arrayOf(view.noBtn, view.unknownBtn).forEach {
|
||||||
|
it.setTextColor(view.root.sres.getColor(R.attr.contrast60))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initTypefaces() {
|
||||||
|
arrayOf(view.yesBtn, view.noBtn, view.skipBtn, view.unknownBtn).forEach {
|
||||||
|
it.typeface = getFontAwesome(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun hideDisabledButtons() {
|
||||||
|
if (!prefs.isSkipEnabled) view.skipBtn.visibility = GONE
|
||||||
|
if (!prefs.areQuestionMarksEnabled) view.unknownBtn.visibility = GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun populate() {
|
||||||
|
val selectedBtn = when (value) {
|
||||||
|
YES_MANUAL -> view.yesBtn
|
||||||
|
YES_AUTO -> view.noBtn
|
||||||
|
NO -> view.noBtn
|
||||||
|
UNKNOWN -> if (prefs.areQuestionMarksEnabled) view.unknownBtn else view.noBtn
|
||||||
|
SKIP -> if (prefs.isSkipEnabled) view.skipBtn else view.noBtn
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
view.notes.setText(notes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun show() {
|
||||||
|
dialog = Dialog(context, android.R.style.Theme_NoTitleBar)
|
||||||
|
dialog.setContentView(view.root)
|
||||||
|
dialog.window?.apply {
|
||||||
|
setLayout(
|
||||||
|
view.root.dp(POPUP_WIDTH).toInt(),
|
||||||
|
view.root.dp(POPUP_HEIGHT).toInt()
|
||||||
|
)
|
||||||
|
setBackgroundDrawableResource(android.R.color.transparent)
|
||||||
|
}
|
||||||
|
fun onClick(v: Int) {
|
||||||
|
this.value = v
|
||||||
|
save()
|
||||||
|
}
|
||||||
|
view.yesBtn.setOnClickListener { onClick(YES_MANUAL) }
|
||||||
|
view.noBtn.setOnClickListener { onClick(NO) }
|
||||||
|
view.skipBtn.setOnClickListener { onClick(SKIP) }
|
||||||
|
view.unknownBtn.setOnClickListener { onClick(UNKNOWN) }
|
||||||
|
dialog.setCanceledOnTouchOutside(true)
|
||||||
|
dialog.dimBehind()
|
||||||
|
dialog.dismissCurrentAndShow()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun save() {
|
||||||
|
onToggle(value, view.notes.text.toString().trim())
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,7 +28,7 @@ import org.isoron.uhabits.utils.toPaletteColor
|
|||||||
class ColorPickerDialog : ColorPickerDialog() {
|
class ColorPickerDialog : ColorPickerDialog() {
|
||||||
fun setListener(callback: OnColorPickedCallback) {
|
fun setListener(callback: OnColorPickedCallback) {
|
||||||
super.setOnColorSelectedListener { c: Int ->
|
super.setOnColorSelectedListener { c: Int ->
|
||||||
val pc = c.toPaletteColor(context!!)
|
val pc = c.toPaletteColor(requireContext())
|
||||||
callback.onColorPicked(pc)
|
callback.onColorPicked(pc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ package org.isoron.uhabits.activities.common.dialogs
|
|||||||
|
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
@@ -44,7 +43,7 @@ class FrequencyPickerDialog(
|
|||||||
constructor() : this(1, 1)
|
constructor() : this(1, 1)
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
val inflater = LayoutInflater.from(activity!!)
|
val inflater = LayoutInflater.from(requireActivity())
|
||||||
contentView = inflater.inflate(R.layout.frequency_picker_dialog, null)
|
contentView = inflater.inflate(R.layout.frequency_picker_dialog, null)
|
||||||
|
|
||||||
addBeforeAfterText(
|
addBeforeAfterText(
|
||||||
@@ -62,15 +61,19 @@ class FrequencyPickerDialog(
|
|||||||
contentView.xTimesPerMonthContainer,
|
contentView.xTimesPerMonthContainer,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
addBeforeAfterText(
|
||||||
|
this.getString(R.string.x_times_per_y_days),
|
||||||
|
contentView.xTimesPerYDaysContainer,
|
||||||
|
)
|
||||||
|
|
||||||
contentView.everyDayRadioButton.setOnClickListener {
|
contentView.everyDayRadioButton.setOnClickListener {
|
||||||
check(contentView.everyDayRadioButton)
|
check(contentView.everyDayRadioButton)
|
||||||
unfocusAll()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
contentView.everyXDaysRadioButton.setOnClickListener {
|
contentView.everyXDaysRadioButton.setOnClickListener {
|
||||||
check(contentView.everyXDaysRadioButton)
|
check(contentView.everyXDaysRadioButton)
|
||||||
val everyXDaysTextView = contentView.everyXDaysTextView
|
val everyXDaysTextView = contentView.everyXDaysTextView
|
||||||
focus(everyXDaysTextView)
|
selectInputField(everyXDaysTextView)
|
||||||
}
|
}
|
||||||
|
|
||||||
contentView.everyXDaysTextView.setOnFocusChangeListener { v, hasFocus ->
|
contentView.everyXDaysTextView.setOnFocusChangeListener { v, hasFocus ->
|
||||||
@@ -79,7 +82,7 @@ class FrequencyPickerDialog(
|
|||||||
|
|
||||||
contentView.xTimesPerWeekRadioButton.setOnClickListener {
|
contentView.xTimesPerWeekRadioButton.setOnClickListener {
|
||||||
check(contentView.xTimesPerWeekRadioButton)
|
check(contentView.xTimesPerWeekRadioButton)
|
||||||
focus(contentView.xTimesPerWeekTextView)
|
selectInputField(contentView.xTimesPerWeekTextView)
|
||||||
}
|
}
|
||||||
|
|
||||||
contentView.xTimesPerWeekTextView.setOnFocusChangeListener { v, hasFocus ->
|
contentView.xTimesPerWeekTextView.setOnFocusChangeListener { v, hasFocus ->
|
||||||
@@ -88,14 +91,27 @@ class FrequencyPickerDialog(
|
|||||||
|
|
||||||
contentView.xTimesPerMonthRadioButton.setOnClickListener {
|
contentView.xTimesPerMonthRadioButton.setOnClickListener {
|
||||||
check(contentView.xTimesPerMonthRadioButton)
|
check(contentView.xTimesPerMonthRadioButton)
|
||||||
focus(contentView.xTimesPerMonthTextView)
|
selectInputField(contentView.xTimesPerMonthTextView)
|
||||||
}
|
}
|
||||||
|
|
||||||
contentView.xTimesPerMonthTextView.setOnFocusChangeListener { v, hasFocus ->
|
contentView.xTimesPerMonthTextView.setOnFocusChangeListener { v, hasFocus ->
|
||||||
if (hasFocus) check(contentView.xTimesPerMonthRadioButton)
|
if (hasFocus) check(contentView.xTimesPerMonthRadioButton)
|
||||||
}
|
}
|
||||||
|
|
||||||
return AlertDialog.Builder(activity!!)
|
contentView.xTimesPerYDaysRadioButton.setOnClickListener {
|
||||||
|
check(contentView.xTimesPerYDaysRadioButton)
|
||||||
|
selectInputField(contentView.xTimesPerYDaysXTextView)
|
||||||
|
}
|
||||||
|
|
||||||
|
contentView.xTimesPerYDaysXTextView.setOnFocusChangeListener { v, hasFocus ->
|
||||||
|
if (hasFocus) check(contentView.xTimesPerYDaysRadioButton)
|
||||||
|
}
|
||||||
|
|
||||||
|
contentView.xTimesPerYDaysYTextView.setOnFocusChangeListener { v, hasFocus ->
|
||||||
|
if (hasFocus) check(contentView.xTimesPerYDaysRadioButton)
|
||||||
|
}
|
||||||
|
|
||||||
|
return AlertDialog.Builder(requireActivity())
|
||||||
.setView(contentView)
|
.setView(contentView)
|
||||||
.setPositiveButton(R.string.save) { _, _ -> onSaveClicked() }
|
.setPositiveButton(R.string.save) { _, _ -> onSaveClicked() }
|
||||||
.create()
|
.create()
|
||||||
@@ -106,13 +122,12 @@ class FrequencyPickerDialog(
|
|||||||
container: LinearLayout
|
container: LinearLayout
|
||||||
) {
|
) {
|
||||||
val parts = str.split("%d")
|
val parts = str.split("%d")
|
||||||
|
for (i in parts.indices) {
|
||||||
container.addView(
|
container.addView(
|
||||||
TextView(activity).apply { text = parts[0].trim() }, 1,
|
TextView(activity).apply { text = parts[i].trim() }, 2 * i + 1,
|
||||||
)
|
|
||||||
container.addView(
|
|
||||||
TextView(activity).apply { text = parts[1].trim() }, 3,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun onSaveClicked() {
|
private fun onSaveClicked() {
|
||||||
var numerator = 1
|
var numerator = 1
|
||||||
@@ -132,6 +147,14 @@ class FrequencyPickerDialog(
|
|||||||
denominator = 7
|
denominator = 7
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
contentView.xTimesPerYDaysRadioButton.isChecked -> {
|
||||||
|
if (contentView.xTimesPerYDaysXTextView.text.isNotEmpty() && contentView.xTimesPerYDaysYTextView.text.isNotEmpty()) {
|
||||||
|
numerator =
|
||||||
|
Integer.parseInt(contentView.xTimesPerYDaysXTextView.text.toString())
|
||||||
|
denominator =
|
||||||
|
Integer.parseInt(contentView.xTimesPerYDaysYTextView.text.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
else -> {
|
else -> {
|
||||||
if (contentView.xTimesPerMonthTextView.text.isNotEmpty()) {
|
if (contentView.xTimesPerMonthTextView.text.isNotEmpty()) {
|
||||||
numerator = Integer.parseInt(contentView.xTimesPerMonthTextView.text.toString())
|
numerator = Integer.parseInt(contentView.xTimesPerMonthTextView.text.toString())
|
||||||
@@ -147,10 +170,10 @@ class FrequencyPickerDialog(
|
|||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun check(view: RadioButton?) {
|
private fun check(view: RadioButton) {
|
||||||
uncheckAll()
|
uncheckAll()
|
||||||
view?.isChecked = true
|
view.isChecked = true
|
||||||
view?.requestFocus()
|
view.requestFocus()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
@@ -160,32 +183,34 @@ class FrequencyPickerDialog(
|
|||||||
|
|
||||||
private fun populateViews() {
|
private fun populateViews() {
|
||||||
uncheckAll()
|
uncheckAll()
|
||||||
|
if (freqDenominator == 30 || freqDenominator == 31) {
|
||||||
|
contentView.xTimesPerMonthRadioButton.isChecked = true
|
||||||
|
contentView.xTimesPerMonthTextView.setText(freqNumerator.toString())
|
||||||
|
selectInputField(contentView.xTimesPerMonthTextView)
|
||||||
|
} else {
|
||||||
if (freqNumerator == 1) {
|
if (freqNumerator == 1) {
|
||||||
if (freqDenominator == 1) {
|
if (freqDenominator == 1) {
|
||||||
contentView.everyDayRadioButton.isChecked = true
|
contentView.everyDayRadioButton.isChecked = true
|
||||||
} else {
|
} else {
|
||||||
contentView.everyXDaysRadioButton.isChecked = true
|
contentView.everyXDaysRadioButton.isChecked = true
|
||||||
contentView.everyXDaysTextView.setText(freqDenominator.toString())
|
contentView.everyXDaysTextView.setText(freqDenominator.toString())
|
||||||
focus(contentView.everyXDaysTextView)
|
selectInputField(contentView.everyXDaysTextView)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (freqDenominator == 7) {
|
if (freqDenominator == 7) {
|
||||||
contentView.xTimesPerWeekRadioButton.isChecked = true
|
contentView.xTimesPerWeekRadioButton.isChecked = true
|
||||||
contentView.xTimesPerWeekTextView.setText(freqNumerator.toString())
|
contentView.xTimesPerWeekTextView.setText(freqNumerator.toString())
|
||||||
focus(contentView.xTimesPerWeekTextView)
|
selectInputField(contentView.xTimesPerWeekTextView)
|
||||||
} else if (freqDenominator == 30 || freqDenominator == 31) {
|
|
||||||
contentView.xTimesPerMonthRadioButton.isChecked = true
|
|
||||||
contentView.xTimesPerMonthTextView.setText(freqNumerator.toString())
|
|
||||||
focus(contentView.xTimesPerMonthTextView)
|
|
||||||
} else {
|
} else {
|
||||||
Log.w("FrequencyPickerDialog", "Unknown frequency: $freqNumerator/$freqDenominator")
|
contentView.xTimesPerYDaysRadioButton.isChecked = true
|
||||||
contentView.everyDayRadioButton.isChecked = true
|
contentView.xTimesPerYDaysXTextView.setText(freqNumerator.toString())
|
||||||
|
contentView.xTimesPerYDaysYTextView.setText(freqDenominator.toString())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun focus(view: EditText) {
|
private fun selectInputField(view: EditText) {
|
||||||
view.requestFocus()
|
|
||||||
view.setSelection(view.text.length)
|
view.setSelection(view.text.length)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,11 +219,6 @@ class FrequencyPickerDialog(
|
|||||||
contentView.everyXDaysRadioButton.isChecked = false
|
contentView.everyXDaysRadioButton.isChecked = false
|
||||||
contentView.xTimesPerWeekRadioButton.isChecked = false
|
contentView.xTimesPerWeekRadioButton.isChecked = false
|
||||||
contentView.xTimesPerMonthRadioButton.isChecked = false
|
contentView.xTimesPerMonthRadioButton.isChecked = false
|
||||||
}
|
contentView.xTimesPerYDaysRadioButton.isChecked = false
|
||||||
|
|
||||||
private fun unfocusAll() {
|
|
||||||
contentView.everyXDaysTextView.clearFocus()
|
|
||||||
contentView.xTimesPerWeekTextView.clearFocus()
|
|
||||||
contentView.xTimesPerMonthTextView.clearFocus()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
package org.isoron.uhabits.activities.common.dialogs
|
package org.isoron.uhabits.activities.common.dialogs
|
||||||
|
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
|
import android.content.DialogInterface
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.appcompat.app.AppCompatDialogFragment
|
import androidx.appcompat.app.AppCompatDialogFragment
|
||||||
import org.isoron.platform.gui.AndroidDataView
|
import org.isoron.platform.gui.AndroidDataView
|
||||||
@@ -43,18 +44,19 @@ class HistoryEditorDialog : AppCompatDialogFragment(), CommandRunner.Listener {
|
|||||||
private lateinit var commandRunner: CommandRunner
|
private lateinit var commandRunner: CommandRunner
|
||||||
private lateinit var habit: Habit
|
private lateinit var habit: Habit
|
||||||
private lateinit var preferences: Preferences
|
private lateinit var preferences: Preferences
|
||||||
private lateinit var dataView: AndroidDataView
|
lateinit var dataView: AndroidDataView
|
||||||
|
|
||||||
private var chart: HistoryChart? = null
|
private var chart: HistoryChart? = null
|
||||||
private var onDateClickedListener: OnDateClickedListener? = null
|
private var onDateClickedListener: OnDateClickedListener? = null
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
val component = (activity!!.application as HabitsApplication).component
|
clearCurrentDialog()
|
||||||
|
val component = (requireActivity().application as HabitsApplication).component
|
||||||
commandRunner = component.commandRunner
|
commandRunner = component.commandRunner
|
||||||
habit = component.habitList.getById(arguments!!.getLong("habit"))!!
|
habit = component.habitList.getById(requireArguments().getLong("habit"))!!
|
||||||
preferences = component.preferences
|
preferences = component.preferences
|
||||||
|
|
||||||
val themeSwitcher = AndroidThemeSwitcher(activity!!, preferences)
|
val themeSwitcher = AndroidThemeSwitcher(requireActivity(), preferences)
|
||||||
themeSwitcher.apply()
|
themeSwitcher.apply()
|
||||||
|
|
||||||
chart = HistoryChart(
|
chart = HistoryChart(
|
||||||
@@ -62,20 +64,30 @@ class HistoryEditorDialog : AppCompatDialogFragment(), CommandRunner.Listener {
|
|||||||
firstWeekday = preferences.firstWeekday,
|
firstWeekday = preferences.firstWeekday,
|
||||||
paletteColor = habit.color,
|
paletteColor = habit.color,
|
||||||
series = emptyList(),
|
series = emptyList(),
|
||||||
|
defaultSquare = HistoryChart.Square.OFF,
|
||||||
|
notesIndicators = emptyList(),
|
||||||
theme = themeSwitcher.currentTheme,
|
theme = themeSwitcher.currentTheme,
|
||||||
today = DateUtils.getTodayWithOffset().toLocalDate(),
|
today = DateUtils.getTodayWithOffset().toLocalDate(),
|
||||||
onDateClickedListener = onDateClickedListener ?: OnDateClickedListener { },
|
onDateClickedListener = onDateClickedListener ?: object : OnDateClickedListener {},
|
||||||
padding = 10.0,
|
padding = 10.0,
|
||||||
)
|
)
|
||||||
dataView = AndroidDataView(context!!, null)
|
dataView = AndroidDataView(requireContext(), null)
|
||||||
dataView.view = chart!!
|
dataView.view = chart!!
|
||||||
|
|
||||||
return Dialog(context!!).apply {
|
val dialog = Dialog(requireContext()).apply {
|
||||||
val metrics = resources.displayMetrics
|
val metrics = resources.displayMetrics
|
||||||
val maxHeight = resources.getDimensionPixelSize(R.dimen.history_editor_max_height)
|
val maxHeight = resources.getDimensionPixelSize(R.dimen.history_editor_max_height)
|
||||||
setContentView(dataView)
|
setContentView(dataView)
|
||||||
window!!.setLayout(metrics.widthPixels, min(metrics.heightPixels, maxHeight))
|
window!!.setLayout(metrics.widthPixels, min(metrics.heightPixels, maxHeight))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
currentDialog = dialog
|
||||||
|
return dialog
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDismiss(dialog: DialogInterface) {
|
||||||
|
super.onDismiss(dialog)
|
||||||
|
currentDialog = null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
@@ -101,10 +113,22 @@ class HistoryEditorDialog : AppCompatDialogFragment(), CommandRunner.Listener {
|
|||||||
theme = LightTheme()
|
theme = LightTheme()
|
||||||
)
|
)
|
||||||
chart?.series = model.series
|
chart?.series = model.series
|
||||||
|
chart?.defaultSquare = model.defaultSquare
|
||||||
|
chart?.notesIndicators = model.notesIndicators
|
||||||
dataView.postInvalidate()
|
dataView.postInvalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCommandFinished(command: Command) {
|
override fun onCommandFinished(command: Command) {
|
||||||
refreshData()
|
refreshData()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
// HistoryEditorDialog handles multiple dialogs on its own,
|
||||||
|
// because sometimes we want it to be shown under another dialog (e.g. NumberPopup)
|
||||||
|
var currentDialog: Dialog? = null
|
||||||
|
fun clearCurrentDialog() {
|
||||||
|
currentDialog?.dismiss()
|
||||||
|
currentDialog = null
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,108 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 android.text.InputFilter
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE
|
|
||||||
import android.view.inputmethod.EditorInfo
|
|
||||||
import android.widget.EditText
|
|
||||||
import android.widget.NumberPicker
|
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.appcompat.app.AlertDialog
|
|
||||||
import org.isoron.uhabits.R
|
|
||||||
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior
|
|
||||||
import org.isoron.uhabits.inject.ActivityContext
|
|
||||||
import org.isoron.uhabits.utils.InterfaceUtils
|
|
||||||
import javax.inject.Inject
|
|
||||||
import kotlin.math.roundToLong
|
|
||||||
|
|
||||||
class NumberPickerFactory
|
|
||||||
@Inject constructor(
|
|
||||||
@ActivityContext private val context: Context
|
|
||||||
) {
|
|
||||||
fun create(
|
|
||||||
value: Double,
|
|
||||||
unit: String,
|
|
||||||
callback: ListHabitsBehavior.NumberPickerCallback
|
|
||||||
): AlertDialog {
|
|
||||||
|
|
||||||
val inflater = LayoutInflater.from(context)
|
|
||||||
val view = inflater.inflate(R.layout.number_picker_dialog, null)
|
|
||||||
|
|
||||||
val picker = view.findViewById<NumberPicker>(R.id.picker)
|
|
||||||
val picker2 = view.findViewById<NumberPicker>(R.id.picker2)
|
|
||||||
val tvUnit = view.findViewById<TextView>(R.id.tvUnit)
|
|
||||||
|
|
||||||
val intValue = (value * 100).roundToLong().toInt()
|
|
||||||
|
|
||||||
picker.minValue = 0
|
|
||||||
picker.maxValue = Integer.MAX_VALUE / 100
|
|
||||||
picker.value = intValue / 100
|
|
||||||
picker.wrapSelectorWheel = false
|
|
||||||
|
|
||||||
picker2.minValue = 0
|
|
||||||
picker2.maxValue = 19
|
|
||||||
picker2.setFormatter { v -> String.format("%02d", 5 * v) }
|
|
||||||
picker2.value = intValue % 100 / 5
|
|
||||||
refreshInitialValue(picker2)
|
|
||||||
|
|
||||||
tvUnit.text = unit
|
|
||||||
|
|
||||||
val dialog = AlertDialog.Builder(context)
|
|
||||||
.setView(view)
|
|
||||||
.setTitle(R.string.change_value)
|
|
||||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
|
||||||
picker.clearFocus()
|
|
||||||
val v = picker.value + 0.05 * picker2.value
|
|
||||||
callback.onNumberPicked(v)
|
|
||||||
}
|
|
||||||
.setOnDismissListener {
|
|
||||||
callback.onNumberPickerDismissed()
|
|
||||||
}
|
|
||||||
.create()
|
|
||||||
|
|
||||||
dialog.setOnShowListener {
|
|
||||||
picker.getChildAt(0)?.requestFocus()
|
|
||||||
dialog.window?.setSoftInputMode(SOFT_INPUT_STATE_ALWAYS_VISIBLE)
|
|
||||||
}
|
|
||||||
|
|
||||||
InterfaceUtils.setupEditorAction(
|
|
||||||
picker
|
|
||||||
) { _, actionId, _ ->
|
|
||||||
if (actionId == EditorInfo.IME_ACTION_DONE)
|
|
||||||
dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick()
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
return dialog
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun refreshInitialValue(picker: NumberPicker) {
|
|
||||||
// Workaround for Android bug:
|
|
||||||
// https://code.google.com/p/android/issues/detail?id=35482
|
|
||||||
val f = NumberPicker::class.java.getDeclaredField("mInputText")
|
|
||||||
f.isAccessible = true
|
|
||||||
val inputText = f.get(picker) as EditText
|
|
||||||
inputText.filters = arrayOfNulls<InputFilter>(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||