Compare commits
1 Commits
96e20f751f
...
hiqua-patc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a96b7f0fba |
6
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "gradle"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "monthly"
|
||||||
6
.github/workflows/main.yml
vendored
@@ -12,17 +12,17 @@ jobs:
|
|||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
steps:
|
steps:
|
||||||
- name: Check out source code
|
- name: Check out source code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v1
|
||||||
|
|
||||||
- name: Build project
|
- name: Build project
|
||||||
run: ./build.sh build
|
run: ./build.sh build
|
||||||
|
|
||||||
- name: Run Android tests
|
- name: Run Android tests
|
||||||
run: ./build.sh android-tests-parallel 28 29 30 32 33 34
|
run: ./build.sh android-tests-parallel 28 29 30 31 32 33
|
||||||
|
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
if: always()
|
if: always()
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: build
|
name: build
|
||||||
path: |
|
path: |
|
||||||
|
|||||||
1
.gitignore
vendored
@@ -17,4 +17,3 @@ node_modules
|
|||||||
*xcuserdata*
|
*xcuserdata*
|
||||||
*.sketch
|
*.sketch
|
||||||
crowdin.yml
|
crowdin.yml
|
||||||
kotlin-js-store
|
|
||||||
|
|||||||
48
CHANGELOG.md
@@ -1,53 +1,5 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## [2.3.0] -- 2025-06-23
|
|
||||||
### Added
|
|
||||||
- Add support for Android 15 and 16 (@iSoron)
|
|
||||||
- Show confetti animation (@gokulk16, @iSoron, #1743)
|
|
||||||
- Show streaks for measurable habits (@teckwarz, #2059)
|
|
||||||
- Allow user to unset measurable habits (@leontodd, @kalina559, #1899, #2109)
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Change background widget color for habits with implicit checks (@wobbba, #1915)
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Fix notification when goal type is set to maximum (@manish99verma, #1931)
|
|
||||||
- Never mark "at most" habits as completed for the day (@kalina559, #2077)
|
|
||||||
- Increase minimum widget size (@iSoron, #2118)
|
|
||||||
- Improve Gradle configuration (@jimlyas, #2108)
|
|
||||||
|
|
||||||
## [2.2.0] -- 2024-01-30
|
|
||||||
### Added
|
|
||||||
- Add support for Android 14 (@iSoron, @hiqua)
|
|
||||||
- Allow user to change app language (@leondzn)
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Implement workaround to make notifications non-dismissible in Android 14 (@iSoron, #1872)
|
|
||||||
- Fix splash screen background color in dark mode (@SIKV, #1888)
|
|
||||||
|
|
||||||
## [2.1.3] -- 2023-08-28
|
|
||||||
### Fixed
|
|
||||||
- Use text input on Samsung devices (@iSoron, #1719)
|
|
||||||
- Prevent crash if alarm permission is revoked (@iSoron)
|
|
||||||
- Adjust widget colors (@iSoron)
|
|
||||||
- Fix bug preventing screens from updating at midnight (@iSoron)
|
|
||||||
- Fix skip button in locales that use comma instead of dot (@iSoron, #1721)
|
|
||||||
|
|
||||||
## [2.1.2] -- 2023-05-26
|
|
||||||
### Fixed
|
|
||||||
- Fix bug that caused widget to enter checkmark on wrong date (@iSoron, #1541)
|
|
||||||
- Fix widget corners on Android 12 (@iSoron)
|
|
||||||
- Fix bug that caused notes to be lost when editing a checkmark (@iSoron, #1566)
|
|
||||||
- Prevent soft keyboard from covering entry popup (@iSoron)
|
|
||||||
- Accept comma (instead of dot) in certain locales (@iSoron)
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Remove update delay after entering a checkmark (@iSoron)
|
|
||||||
|
|
||||||
### Removed
|
|
||||||
- Remove stack widgets (@iSoron)
|
|
||||||
|
|
||||||
|
|
||||||
## [2.1.1] -- 2022-09-24
|
## [2.1.1] -- 2022-09-24
|
||||||
### Fixed
|
### Fixed
|
||||||
- Fix Tasker plugin (@iSoron, #1503)
|
- Fix Tasker plugin (@iSoron, #1503)
|
||||||
|
|||||||
24
NOTICE.md
@@ -1,6 +1,6 @@
|
|||||||
# Copyright Notices
|
# Copyright Notices
|
||||||
|
|
||||||
## ActiveAndroid
|
### ActiveAndroid
|
||||||
|
|
||||||
<https://github.com/pardom/ActiveAndroid>
|
<https://github.com/pardom/ActiveAndroid>
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
|
|
||||||
## Android Open Source Project
|
### Android Open Source Project
|
||||||
|
|
||||||
<https://source.android.com/>
|
<https://source.android.com/>
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
|
|
||||||
## FontAwesome
|
### FontAwesome
|
||||||
|
|
||||||
<http://fontawesome.io>
|
<http://fontawesome.io>
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ under the SIL OFL 1.1.
|
|||||||
requirement for fonts to remain under this license does not apply
|
requirement for fonts to remain under this license does not apply
|
||||||
to any document created using the fonts or their derivatives.
|
to any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
## Material Design Icons
|
### Material Design Icons
|
||||||
|
|
||||||
<https://github.com/google/material-design-icons>
|
<https://github.com/google/material-design-icons>
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ Material design icons are the official icon set from Google that are designed
|
|||||||
under the material design guidelines. Available under the Creative Common
|
under the material design guidelines. Available under the Creative Common
|
||||||
Attribution 4.0 International License (CC-BY 4.0).
|
Attribution 4.0 International License (CC-BY 4.0).
|
||||||
|
|
||||||
## Android Flow Layout
|
### Android Flow Layout
|
||||||
|
|
||||||
<https://github.com/ApmeM/android-flowlayout>
|
<https://github.com/ApmeM/android-flowlayout>
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@ Extended linear layout that wrap its content when there is no place in the curre
|
|||||||
License for the specific language governing permissions and limitations
|
License for the specific language governing permissions and limitations
|
||||||
under the License.
|
under the License.
|
||||||
|
|
||||||
## Dagger 2
|
### Dagger 2
|
||||||
|
|
||||||
<https://github.com/google/dagger>
|
<https://github.com/google/dagger>
|
||||||
|
|
||||||
@@ -108,7 +108,7 @@ A fast dependency injector for Android and Java.
|
|||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
|
|
||||||
## AutoFactory
|
### AutoFactory
|
||||||
|
|
||||||
<https://github.com/google/auto/tree/master/factory>
|
<https://github.com/google/auto/tree/master/factory>
|
||||||
|
|
||||||
@@ -128,7 +128,7 @@ A source code generator for JSR-330-compatible factories.
|
|||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
|
|
||||||
## Retrolambda
|
### Retrolambda
|
||||||
|
|
||||||
<https://github.com/orfjackal/retrolambda>
|
<https://github.com/orfjackal/retrolambda>
|
||||||
|
|
||||||
@@ -138,7 +138,7 @@ Backport of Java 8's lambda expressions to Java 7, 6 and 5
|
|||||||
This software is released under the Apache License 2.0.
|
This software is released under the Apache License 2.0.
|
||||||
The license text is at http://www.apache.org/licenses/LICENSE-2.0
|
The license text is at http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
## PebbleKit SDK
|
### PebbleKit SDK
|
||||||
|
|
||||||
<https://github.com/pebble/pebble-android-sdk/>
|
<https://github.com/pebble/pebble-android-sdk/>
|
||||||
|
|
||||||
@@ -147,7 +147,7 @@ Android PebbleKit SDK to talk to the Pebble via Bluetooth
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
Copyright (c) 2014 - 2015 Pebble Technology
|
Copyright (c) 2014 - 2015 Pebble Technology
|
||||||
|
|
||||||
## AppIntro
|
### AppIntro
|
||||||
|
|
||||||
<https://github.com/PaoloRotolo/AppIntro>
|
<https://github.com/PaoloRotolo/AppIntro>
|
||||||
|
|
||||||
@@ -168,7 +168,7 @@ Make a cool intro for your Android app.
|
|||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
|
|
||||||
## ButterKnife
|
### ButterKnife
|
||||||
|
|
||||||
<https://github.com/JakeWharton/butterknife>
|
<https://github.com/JakeWharton/butterknife>
|
||||||
|
|
||||||
@@ -188,7 +188,7 @@ Bind Android views and callbacks to fields and methods
|
|||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
|
|
||||||
## opencsv
|
### opencsv
|
||||||
|
|
||||||
<http://opencsv.sourceforge.net/>
|
<http://opencsv.sourceforge.net/>
|
||||||
|
|
||||||
|
|||||||
39
README.md
@@ -1,13 +1,13 @@
|
|||||||
<h1 align="center">Loop Habit Tracker</h1>
|
<h1 align="center">Loop Habit Tracker</h1>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/iSoron/uhabits/actions?query=workflow%3A%22Build+%26+Test%22">
|
<a href="https://github.com/iSoron/uhabits/actions?query=workflow%3A%22Build+%26+Test%22">
|
||||||
<img alt="Build & Test" src="https://github.com/iSoron/uhabits/workflows/Build%20&%20Test/badge.svg" />
|
<img src="https://github.com/iSoron/uhabits/workflows/Build%20&%20Test/badge.svg" />
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/iSoron/uhabits/releases/latest">
|
<a href="https://github.com/iSoron/uhabits/releases/latest">
|
||||||
<img alt="release" src="https://img.shields.io/github/v/release/iSoron/uhabits" />
|
<img src="https://img.shields.io/github/v/release/iSoron/uhabits" />
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/iSoron/uhabits/discussions">
|
<a href="https://github.com/iSoron/uhabits/discussions">
|
||||||
<img alt="GitHub" src="https://img.shields.io/badge/GitHub-Discussions-%23fc4ebc" />
|
<img src="https://img.shields.io/badge/GitHub-Discussions-%23fc4ebc" />
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -17,8 +17,8 @@ show you how your habits improved over time. It is completely ad-free and open
|
|||||||
source.
|
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/generic/en_badge_web_generic.png" height="80px"/></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="https://f-droid.org/app/org.isoron.uhabits"><img alt="Get it on F-Droid" src="https://f-droid.org/badge/get-it-on.png" height="80px"/></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
|
||||||
@@ -32,23 +32,32 @@ source.
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
* **Beautiful, minimalistic and lightweight interface.** Loop has an elegant and minimalistic interface that is very easy to use, even for first-time users. Highly optimized for speed, the app works well even on older phones.
|
* <b>Beautiful, minimalistic and lightweight interface.</b>
|
||||||
|
Loop has an elegant and minimalistic interface that is very easy to use, even for first-time users. Highly optimized for speed, the app works well even on older phones.
|
||||||
|
|
||||||
* **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.
|
* <b>Habit score.</b>
|
||||||
|
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.
|
||||||
|
|
||||||
* **Flexible schedules.** In addition to daily habits, Loop supports habits with more complex schedules, such as 3 times per week or every other day.
|
* <b>Flexible schedules.</b>
|
||||||
|
In addition to daily habits, Loop supports habits with more complex schedules, such as 3 times per week or every other day.
|
||||||
|
|
||||||
* **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.
|
* <b>Reminders.</b>
|
||||||
|
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.
|
||||||
|
|
||||||
* **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.
|
* <b>Widgets.</b>
|
||||||
|
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.
|
||||||
|
|
||||||
* **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, checkmarks can be added through other apps, such as Tasker.
|
* <b>Take control of your data.</b>
|
||||||
|
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, checkmarks can be added through other apps, such as Tasker.
|
||||||
|
|
||||||
* **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. There are no in-app purchases.
|
* <b>No limitations.</b>
|
||||||
|
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. There are no in-app purchases.
|
||||||
|
|
||||||
* **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).
|
* <b>Completely ad-free and open source.</b>
|
||||||
|
There are no advertisements, annoying notifications or intrusive permissions in this app, and there will never be. The app is completely open-source (GPLv3).
|
||||||
|
|
||||||
* **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.
|
* <b>Works offline and respects your privacy.</b>
|
||||||
|
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.
|
||||||
|
|
||||||
## Installing
|
## Installing
|
||||||
|
|
||||||
@@ -85,7 +94,7 @@ contribute, even if you are not a software developer.
|
|||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
<img align="right" alt="GPL v3" src="https://www.gnu.org/graphics/gplv3-88x31.png">
|
<img align="right" src="https://www.gnu.org/graphics/gplv3-88x31.png">
|
||||||
|
|
||||||
Copyright (C) 2016-2021 Álinson Santos Xavier <isoron@gmail.com>
|
Copyright (C) 2016-2021 Álinson Santos Xavier <isoron@gmail.com>
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,23 @@
|
|||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.agp) apply false
|
val kotlinVersion = "1.7.21"
|
||||||
alias(libs.plugins.kotlin.android) apply false
|
id("com.android.application") version ("7.3.1") apply (false)
|
||||||
alias(libs.plugins.ksp) apply false
|
id("org.jetbrains.kotlin.android") version kotlinVersion apply (false)
|
||||||
alias(libs.plugins.ktlint.plugin) apply false
|
id("org.jetbrains.kotlin.kapt") version kotlinVersion apply (false)
|
||||||
alias(libs.plugins.shadow) apply false
|
id("org.jetbrains.kotlin.android.extensions") version kotlinVersion apply (false)
|
||||||
|
id("org.jetbrains.kotlin.multiplatform") version kotlinVersion apply (false)
|
||||||
|
id("org.jlleitschuh.gradle.ktlint") version "11.0.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
apply {
|
apply {
|
||||||
from("gradle/translators.gradle.kts")
|
from("translators.gradle.kts")
|
||||||
|
}
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
maven(url = "https://plugins.gradle.org/m2/")
|
||||||
|
maven(url = "https://oss.sonatype.org/content/repositories/snapshots/")
|
||||||
|
maven(url = "https://jitpack.io")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
27
build.sh
@@ -21,7 +21,7 @@ ADB="${ANDROID_HOME}/platform-tools/adb"
|
|||||||
ANDROID_OUTPUTS_DIR="uhabits-android/build/outputs"
|
ANDROID_OUTPUTS_DIR="uhabits-android/build/outputs"
|
||||||
AVDMANAGER="${ANDROID_HOME}/cmdline-tools/latest/bin/avdmanager"
|
AVDMANAGER="${ANDROID_HOME}/cmdline-tools/latest/bin/avdmanager"
|
||||||
AVD_PREFIX="uhabitsTest"
|
AVD_PREFIX="uhabitsTest"
|
||||||
EMULATOR="${ANDROID_HOME}/emulator/emulator"
|
EMULATOR="${ANDROID_HOME}/tools/emulator"
|
||||||
GRADLE="./gradlew --stacktrace --quiet"
|
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"
|
||||||
@@ -38,11 +38,6 @@ if [ ! -f "${ANDROID_HOME}/platform-tools/adb" ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ ! -f "$EMULATOR" ]; then
|
|
||||||
echo "Error: Not found: $EMULATOR"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Logging
|
# Logging
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
@@ -69,7 +64,6 @@ fail() {
|
|||||||
core_build() {
|
core_build() {
|
||||||
log_info "Building uhabits-core..."
|
log_info "Building uhabits-core..."
|
||||||
$GRADLE ktlintCheck || fail
|
$GRADLE ktlintCheck || fail
|
||||||
$GRADLE lintDebug || fail
|
|
||||||
$GRADLE :uhabits-core:build || fail
|
$GRADLE :uhabits-core:build || fail
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,7 +181,7 @@ android_test() {
|
|||||||
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=""
|
FAILED_TESTS=""
|
||||||
for i in {1..10}; do
|
for i in {1..5}; do
|
||||||
log_info "Running $size instrumented tests (attempt $i)..."
|
log_info "Running $size instrumented tests (attempt $i)..."
|
||||||
$ADB shell am instrument \
|
$ADB shell am instrument \
|
||||||
-r -e coverage true -e size "$size" $FAILED_TESTS \
|
-r -e coverage true -e size "$size" $FAILED_TESTS \
|
||||||
@@ -222,30 +216,21 @@ android_test_parallel() {
|
|||||||
for API in $*; do
|
for API in $*; do
|
||||||
(
|
(
|
||||||
LOG=build/android-test-$API.log
|
LOG=build/android-test-$API.log
|
||||||
mkdir -p build
|
|
||||||
log_info "API $API: Running tests..."
|
log_info "API $API: Running tests..."
|
||||||
android_test $API 1>$LOG 2>&1
|
if android_test $API 1>$LOG 2>&1; then
|
||||||
ret_code=$?
|
|
||||||
if [ $ret_code = 0 ]; then
|
|
||||||
log_info "API $API: Passed"
|
log_info "API $API: Passed"
|
||||||
else
|
else
|
||||||
log_error "API $API: Failed"
|
log_error "API $API: Failed"
|
||||||
fi
|
fi
|
||||||
pkill -9 -f ${AVD_PREFIX}${API}
|
pkill -9 -f ${AVD_PREFIX}${API}
|
||||||
exit $ret_code
|
|
||||||
)&
|
)&
|
||||||
PIDS+=" $!"
|
PIDS+=" $!"
|
||||||
done
|
done
|
||||||
|
|
||||||
# Check exit codes
|
# Check exit codes
|
||||||
success=0
|
RET_CODE=0
|
||||||
for pid in $PIDS; do
|
for pid in $PIDS; do
|
||||||
wait $pid
|
wait $pid || RET_CODE=1
|
||||||
ret_code=$?
|
|
||||||
if [ $ret_code != 0 ]; then
|
|
||||||
success=1
|
|
||||||
fi
|
|
||||||
echo pid=$pid ret_code=$ret_code success=$success
|
|
||||||
done
|
done
|
||||||
|
|
||||||
# Print all logs
|
# Print all logs
|
||||||
@@ -255,7 +240,7 @@ android_test_parallel() {
|
|||||||
echo "::endgroup::"
|
echo "::endgroup::"
|
||||||
done
|
done
|
||||||
|
|
||||||
return $success
|
return $RET_CODE
|
||||||
}
|
}
|
||||||
|
|
||||||
android_build() {
|
android_build() {
|
||||||
|
|||||||
@@ -9,17 +9,17 @@ This pages describes how to download and build the app from the source code. If
|
|||||||
|
|
||||||
## Build using Android Studio
|
## Build using Android Studio
|
||||||
|
|
||||||
### Step 1: Install git
|
**Step 1: Install git**
|
||||||
|
|
||||||
The package `git` is required for downloading the source code of the app and submitting changes GitHub. Please see [the git book](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) for further instructions. If you are planning to submit pull requests in the future, it is recommended to [generate and configure your SSH keys](https://help.github.com/en/github/authenticating-to-github/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent).
|
The package `git` is required for downloading the source code of the app and submitting changes GitHub. Please see [the git book](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) for further instructions. If you are planning to submit pull requests in the future, it is recommended to [generate and configure your SSH keys](https://help.github.com/en/github/authenticating-to-github/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent).
|
||||||
|
|
||||||
### Step 2: Download and install Android Studio
|
**Step 2: Download and install Android Studio**
|
||||||
|
|
||||||
Although Android Studio can be downloaded [from their official website](https://developer.android.com/studio/), a much better option is to install it through [JetBrains Toolbox](https://www.jetbrains.com/toolbox-app/). This tool, developed by the same developers of Android Studio, allows you to easily upgrade and downgrade the IDE, or switch between stable, beta and canary versions. After downloading and installing JetBrains Toolbox, simply click the install button near Android Studio to install the newest stable version of IDE. Beta and canary versions have not been tested and may not work correctly.
|
Although Android Studio can be downloaded [from their official website](https://developer.android.com/studio/), a much better option is to install it through [JetBrains Toolbox](https://www.jetbrains.com/toolbox-app/). This tool, developed by the same developers of Android Studio, allows you to easily upgrade and downgrade the IDE, or switch between stable, beta and canary versions. After downloading and installing JetBrains Toolbox, simply click the install button near Android Studio to install the newest stable version of IDE. Beta and canary versions have not been tested and may not work correctly.
|
||||||
|
|
||||||
After installation, launch Android Studio. If this is the first time you launch it, you will need to go through a wizard to setup the IDE. The default options should work fine. The wizard will download all additional components necessary for development, including the emulator, so it may take a while.
|
After installation, launch Android Studio. If this is the first time you launch it, you will need to go through a wizard to setup the IDE. The default options should work fine. The wizard will download all additional components necessary for development, including the emulator, so it may take a while.
|
||||||
|
|
||||||
### Step 3: Download the source code
|
**Step 3: Download the source code**
|
||||||
|
|
||||||
To create a complete copy of the source code repository, open the terminal (Linux/macOS) or Git Bash (Windows), navigate to the desired folder, then run:
|
To create a complete copy of the source code repository, open the terminal (Linux/macOS) or Git Bash (Windows), navigate to the desired folder, then run:
|
||||||
```bash
|
```bash
|
||||||
@@ -27,7 +27,7 @@ git clone https://github.com/iSoron/uhabits.git
|
|||||||
```
|
```
|
||||||
The repository will be downloaded to the directory `uhabits`.
|
The repository will be downloaded to the directory `uhabits`.
|
||||||
|
|
||||||
### Step 4: Open and run the project on Android Studio
|
**Step 4: Open and run the project on Android Studio**
|
||||||
|
|
||||||
1. Launch Android Studio and select "Open an existing Android Studio project".
|
1. Launch Android Studio and select "Open an existing Android Studio project".
|
||||||
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".
|
||||||
@@ -41,7 +41,7 @@ The repository will be downloaded to the directory `uhabits`.
|
|||||||
|
|
||||||
The following instructions were tested on **Ubuntu Linux 18.04 LTS** and may need to be modified for other operating systems.
|
The following instructions were tested on **Ubuntu Linux 18.04 LTS** and may need to be modified for other operating systems.
|
||||||
|
|
||||||
### Step 1: Install basic packages
|
**Step 1: Install basic packages**
|
||||||
|
|
||||||
To build the application, some basic packages are required. The package `git` is required to download the source code, while `openjdk-8-jdk-headless` is required for compiling Java and Kotlin files.
|
To build the application, some basic packages are required. The package `git` is required to download the source code, while `openjdk-8-jdk-headless` is required for compiling Java and Kotlin files.
|
||||||
|
|
||||||
@@ -53,14 +53,14 @@ sudo apt-get install -y git openjdk-8-jdk-headless
|
|||||||
**IMPORTANT:** Newer JDK versions have not been tested and may not work correctly.
|
**IMPORTANT:** Newer JDK versions have not been tested and may not work correctly.
|
||||||
|
|
||||||
|
|
||||||
### Step 2: Install Android SDK tools
|
**Step 2: Install Android SDK tools**
|
||||||
|
|
||||||
The Android SDK tools contains many necessary tools for developing and debugging Android applications. It can be obtained as part of Android Studio, but, for simple command line usage, it can also be downloaded individually.
|
The Android SDK tools contains many necessary tools for developing and debugging Android applications. It can be obtained as part of Android Studio, but, for simple command line usage, it can also be downloaded individually.
|
||||||
|
|
||||||
1. Download the file `sdk-tools-linux-4333796.zip` (or a newer version) from <https://developer.android.com/studio/#downloads>, and extract it somewhere. In this guide, we assume that it was extracted to `/opt/android-sdk/tools`; that is, the script `/opt/android-sdk/tools/bin/sdkmanager` should exist.
|
1. Download the file `sdk-tools-linux-4333796.zip` (or a newer version) from https://developer.android.com/studio/#downloads, and extract it somewhere. In this guide, we assume that it was extracted to `/opt/android-sdk/tools`; that is, the script `/opt/android-sdk/tools/bin/sdkmanager` should exist.
|
||||||
|
|
||||||
2. Append the following lines to `~/.profile`, so that other tools can locate your Android SDK installation. It is necessary to restart your terminal for these changes to take effect.
|
2. Append the following lines to `~/.profile`, so that other tools can locate your Android SDK installation. It is necessary to restart your terminal for these changes to take effect.
|
||||||
```bash
|
```
|
||||||
export PATH="$PATH:/opt/android-sdk/tools/bin"
|
export PATH="$PATH:/opt/android-sdk/tools/bin"
|
||||||
export PATH="$PATH:/opt/android-sdk/platform-tools"
|
export PATH="$PATH:/opt/android-sdk/platform-tools"
|
||||||
export ANDROID_HOME="/opt/android-sdk"
|
export ANDROID_HOME="/opt/android-sdk"
|
||||||
@@ -71,7 +71,7 @@ export ANDROID_HOME="/opt/android-sdk"
|
|||||||
yes | sdkmanager --licenses
|
yes | sdkmanager --licenses
|
||||||
```
|
```
|
||||||
|
|
||||||
### Step 3: Download the source code
|
**Step 3: Download the source code**
|
||||||
|
|
||||||
To create a complete copy of the source code repository, navigate to your home directory and run:
|
To create a complete copy of the source code repository, navigate to your home directory and run:
|
||||||
```bash
|
```bash
|
||||||
@@ -79,11 +79,13 @@ git clone https://github.com/iSoron/uhabits.git
|
|||||||
```
|
```
|
||||||
The repository will be downloaded to the directory `uhabits`.
|
The repository will be downloaded to the directory `uhabits`.
|
||||||
|
|
||||||
### Step 4: Compile the source code
|
**Step 4: Compile the source code**
|
||||||
|
|
||||||
1. Navigate to the directory `uhabits`
|
1. Navigate to the directory `uhabits`
|
||||||
2. Run `./gradlew assembleDebug --stacktrace`
|
2. Run `./gradlew assembleDebug --stacktrace`
|
||||||
|
|
||||||
If the compilation is successful, a debug APK will be generated somewhere inside the folder `uhabits-android/build/`. Currently, the full path is `./uhabits-android/build/outputs/apk/debug/uhabits-android-debug.apk`, but it may change in the future.
|
If the compilation is successful, a debug APK will be generated somewhere inside the folder `uhabits-android/build/`. Currently, the full path is the following, but it may change in the future:
|
||||||
|
|
||||||
|
./uhabits-android/build/outputs/apk/debug/uhabits-android-debug.apk
|
||||||
|
|
||||||
The APK can be installed using the tool `adb`, which should have been automatically installed at `/opt/android-sdk/platform-tools/adb` during compilation of the project.
|
The APK can be installed using the tool `adb`, which should have been automatically installed at `/opt/android-sdk/platform-tools/adb` during compilation of the project.
|
||||||
|
|||||||
@@ -12,6 +12,17 @@
|
|||||||
|
|
||||||
Please see `docs/BUILD.md` and `docs/TEST.md`
|
Please see `docs/BUILD.md` and `docs/TEST.md`
|
||||||
|
|
||||||
|
## Directory Layout
|
||||||
|
|
||||||
|
* `docs` Documentation for developers.
|
||||||
|
* `landing` Source code for our [landing page](http://loophabits.org/).
|
||||||
|
* `uhabits-android` Android-specific code.
|
||||||
|
* `uhabits-core` Common code used by all platforms (Android, iOS).
|
||||||
|
* `uhabits-core-legacy` Proof-of-concept module, developed to evaluate the feasibility of using Kotlin multiplatform for the app; not currently used, and it will be removed soon, once all useful code is ported to the other modules.
|
||||||
|
* `uhabits-ios` Experimental iOS port of Loop. Not currently used in production.
|
||||||
|
* `uhabits-server` Source code for any server-side components the app (for example, device sync).
|
||||||
|
* `uhabits-web` Experimental web port of Loop. Not currently used in production.
|
||||||
|
|
||||||
## Branching Policy
|
## Branching Policy
|
||||||
|
|
||||||
This repository uses the [git-flow branching model](https://nvie.com/posts/a-successful-git-branching-model/). Basically, there are two main branches, `dev` and `master`. All the development takes place in the `dev` branch. After the new features have been implemented and tested, they are merged into the `master` branch and a new version of the app is released. Please submit your pull requests against the `dev` branch.
|
This repository uses the [git-flow branching model](https://nvie.com/posts/a-successful-git-branching-model/). Basically, there are two main branches, `dev` and `master`. All the development takes place in the `dev` branch. After the new features have been implemented and tested, they are merged into the `master` branch and a new version of the app is released. Please submit your pull requests against the `dev` branch.
|
||||||
|
|||||||
@@ -3,9 +3,3 @@ org.gradle.daemon=true
|
|||||||
org.gradle.jvmargs=-Xms2048m -Xmx2048m
|
org.gradle.jvmargs=-Xms2048m -Xmx2048m
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
android.defaults.buildfeatures.buildconfig=true
|
|
||||||
android.nonTransitiveRClass=false
|
|
||||||
android.nonFinalResIds=false
|
|
||||||
org.gradle.configureondemand=true
|
|
||||||
org.gradle.warning.mode=all
|
|
||||||
org.gradle.caching=true
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
[versions]
|
|
||||||
agp = "8.9.2"
|
|
||||||
annotation = "1.9.1"
|
|
||||||
appcompat = "1.7.0"
|
|
||||||
appintro = "6.3.1"
|
|
||||||
commonsCodec = "1.16.0"
|
|
||||||
commonsIo = "1.3.2"
|
|
||||||
commonsLang3 = "3.14.0"
|
|
||||||
dagger = "2.55"
|
|
||||||
desugar = "2.1.4"
|
|
||||||
dexmaker = "2.28.3"
|
|
||||||
espresso = "3.6.1"
|
|
||||||
guava = "33.2.1-android"
|
|
||||||
hamcrest = "2.2"
|
|
||||||
jsr250 = "1.0"
|
|
||||||
jsr305 = "3.0.2"
|
|
||||||
junit = "1.2.1"
|
|
||||||
junitJupiter = "5.10.1"
|
|
||||||
junitVersion = "4.13.2"
|
|
||||||
konfetti-xml = "2.0.2"
|
|
||||||
kotlin = "2.1.10"
|
|
||||||
kotlinxCoroutinesCoreCommon = "1.3.8"
|
|
||||||
ksp = "2.1.10-1.0.30"
|
|
||||||
ktlint-plugin = "11.6.1"
|
|
||||||
ktor = "1.6.8"
|
|
||||||
ktxCoroutine = "1.10.1"
|
|
||||||
legacy-support = "1.0.0"
|
|
||||||
material = "1.12.0"
|
|
||||||
mockito-kotlin = "5.4.0"
|
|
||||||
opencsv = "5.9"
|
|
||||||
rules = "1.6.1"
|
|
||||||
shadow = "8.1.1"
|
|
||||||
sqliteJdbc = "3.45.1.0"
|
|
||||||
uiautomator = "2.3.0"
|
|
||||||
|
|
||||||
[libraries]
|
|
||||||
annotation = { group = "androidx.annotation", name = "annotation", version.ref = "annotation" }
|
|
||||||
appIntro = { group = "com.github.AppIntro", name = "AppIntro", version.ref = "appintro" }
|
|
||||||
appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
|
|
||||||
commons-codec = { module = "commons-codec:commons-codec", version.ref = "commonsCodec" }
|
|
||||||
commons-io = { module = "org.apache.commons:commons-io", version.ref = "commonsIo" }
|
|
||||||
commons-lang3 = { module = "org.apache.commons:commons-lang3", version.ref = "commonsLang3" }
|
|
||||||
dagger = { group = "com.google.dagger", name = "dagger", version.ref = "dagger" }
|
|
||||||
dagger-compiler = { group = "com.google.dagger", name = "dagger-compiler", version.ref = "dagger" }
|
|
||||||
desugar_jdk_libs = { group = "com.android.tools", name = "desugar_jdk_libs", version.ref = "desugar" }
|
|
||||||
dexmaker-mockito = { group = "com.linkedin.dexmaker", name = "dexmaker-mockito", version.ref = "dexmaker" }
|
|
||||||
espresso-contrib = { group = "androidx.test.espresso", name = "espresso-contrib", version.ref = "espresso" }
|
|
||||||
espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espresso" }
|
|
||||||
guava = { group = "com.google.guava", name = "guava", version.ref = "guava" }
|
|
||||||
hamcrest = { module = "org.hamcrest:hamcrest", version.ref = "hamcrest" }
|
|
||||||
jsr250-api = { group = "javax.annotation", name = "jsr250-api", version.ref = "jsr250" }
|
|
||||||
jsr305 = { group = "com.google.code.findbugs", name = "jsr305", version.ref = "jsr305" }
|
|
||||||
junit = { group = "androidx.test.ext", name = "junit", version.ref = "junit" }
|
|
||||||
junit-junit = { module = "junit:junit", version.ref = "junitVersion" }
|
|
||||||
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junitJupiter" }
|
|
||||||
konfetti-xml = { group = "nl.dionsegijn", name = "konfetti-xml", version.ref = "konfetti-xml" }
|
|
||||||
kotlin-stdlib-jdk8 = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib-jdk8", version.ref = "kotlin" }
|
|
||||||
kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "ktxCoroutine" }
|
|
||||||
kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "ktxCoroutine" }
|
|
||||||
kotlinx-coroutines-core-common = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core-common", version.ref = "kotlinxCoroutinesCoreCommon" }
|
|
||||||
kotlinx-coroutines-core-jvm = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm", version.ref = "ktxCoroutine" }
|
|
||||||
ktor-client-android = { group = "io.ktor", name = "ktor-client-android", version.ref = "ktor" }
|
|
||||||
ktor-client-core = { group = "io.ktor", name = "ktor-client-core", version.ref = "ktor" }
|
|
||||||
ktor-client-jackson = { group = "io.ktor", name = "ktor-client-jackson", version.ref = "ktor" }
|
|
||||||
ktor-client-json = { group = "io.ktor", name = "ktor-client-json", version.ref = "ktor" }
|
|
||||||
ktor-client-mock = { group = "io.ktor", name = "ktor-client-mock", version.ref = "ktor" }
|
|
||||||
ktor-jackson = { group = "io.ktor", name = "ktor-jackson", version.ref = "ktor" }
|
|
||||||
legacy-preference-v14 = { group = "androidx.legacy", name = "legacy-preference-v14", version.ref = "legacy-support" }
|
|
||||||
legacy-support-v4 = { group = "androidx.legacy", name = "legacy-support-v4", version.ref = "legacy-support" }
|
|
||||||
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
|
|
||||||
mockito-kotlin = { group = "org.mockito.kotlin", name = "mockito-kotlin", version.ref = "mockito-kotlin" }
|
|
||||||
opencsv = { group = "com.opencsv", name = "opencsv", version.ref = "opencsv" }
|
|
||||||
rules = { group = "androidx.test", name = "rules", version.ref = "rules" }
|
|
||||||
sqlite-jdbc = { module = "org.xerial:sqlite-jdbc", version.ref = "sqliteJdbc" }
|
|
||||||
uiautomator = { group = "androidx.test.uiautomator", name = "uiautomator", version.ref = "uiautomator" }
|
|
||||||
|
|
||||||
[bundles]
|
|
||||||
androidTest = [
|
|
||||||
"annotation",
|
|
||||||
"dagger",
|
|
||||||
"dexmaker-mockito",
|
|
||||||
"espresso-contrib",
|
|
||||||
"espresso-core",
|
|
||||||
"junit",
|
|
||||||
"ktor-client-mock",
|
|
||||||
"ktor-jackson",
|
|
||||||
"mockito-kotlin",
|
|
||||||
"rules",
|
|
||||||
"uiautomator"
|
|
||||||
]
|
|
||||||
test = [
|
|
||||||
"dagger",
|
|
||||||
"junit-junit",
|
|
||||||
"mockito-kotlin",
|
|
||||||
]
|
|
||||||
|
|
||||||
[plugins]
|
|
||||||
agp = { id = "com.android.application", version.ref = "agp" }
|
|
||||||
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
|
||||||
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
|
|
||||||
ktlint-plugin = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint-plugin" }
|
|
||||||
shadow = { id = "com.github.johnrengelman.shadow", version.ref = "shadow" }
|
|
||||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
|
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
|
||||||
|
|||||||
@@ -1,32 +1,13 @@
|
|||||||
pluginManagement {
|
pluginManagement {
|
||||||
repositories {
|
repositories {
|
||||||
gradlePluginPortal()
|
gradlePluginPortal()
|
||||||
google {
|
google()
|
||||||
content {
|
}
|
||||||
includeGroupByRegex("com\\.android.*")
|
resolutionStrategy.eachPlugin {
|
||||||
includeGroupByRegex("com\\.google.*")
|
if (requested.id.id == "com.android.application") {
|
||||||
includeGroupByRegex("androidx.*")
|
useModule("com.android.tools.build:gradle:${requested.version}")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
include(":uhabits-android", ":uhabits-core")
|
include(":uhabits-android", ":uhabits-core", ":uhabits-server")
|
||||||
dependencyResolutionManagement {
|
|
||||||
@Suppress("UnstableApiUsage")
|
|
||||||
repositories {
|
|
||||||
google {
|
|
||||||
content {
|
|
||||||
includeGroupByRegex("com\\.android.*")
|
|
||||||
includeGroupByRegex("com\\.google.*")
|
|
||||||
includeGroupByRegex("androidx.*")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mavenCentral()
|
|
||||||
maven(url = "https://plugins.gradle.org/m2/")
|
|
||||||
maven(url = "https://oss.sonatype.org/content/repositories/snapshots/")
|
|
||||||
maven(url = "https://jitpack.io")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
include(":uhabits-android", ":uhabits-core")
|
|
||||||
|
|||||||
30
tools/androidStringsToKt.sh
Executable file
@@ -0,0 +1,30 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
input=$1
|
||||||
|
locale_name=$2
|
||||||
|
|
||||||
|
cat <<END
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
|
// THIS FILE WAS AUTOMATICALLY GENERATED
|
||||||
|
//
|
||||||
|
// Please do not submit pull request to modify it. Corrections to translations
|
||||||
|
// may be submitted at https://translate.loophabits.org/
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
|
|
||||||
|
package org.isoron.uhabits.i18n
|
||||||
|
|
||||||
|
END
|
||||||
|
|
||||||
|
prefix="override "
|
||||||
|
if [ "$locale_name" == "" ]; then
|
||||||
|
prefix="open "
|
||||||
|
echo "open class Strings() {"
|
||||||
|
else
|
||||||
|
echo "class Strings$locale_name : Strings() {"
|
||||||
|
fi
|
||||||
|
|
||||||
|
grep "<string name" "$1" | \
|
||||||
|
grep -v translatable | \
|
||||||
|
sed 's/&/\&/g' | \
|
||||||
|
sed 's/^.*name="\([^"]*\)">\([^<]*\)<.*/ '"$prefix"'val \1 = "\2"/'
|
||||||
|
|
||||||
|
echo "}"
|
||||||
46
tools/convertAllStrings.sh
Executable file
@@ -0,0 +1,46 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
INPUT_DIR=../android/uhabits-android/src/main/res/
|
||||||
|
OUTPUT_DIR=../core/src/commonMain/kotlin/org/isoron/uhabits/i18n/
|
||||||
|
|
||||||
|
convert() {
|
||||||
|
./androidStringsToKt.sh $INPUT_DIR/$1/strings.xml "$2" > $OUTPUT_DIR/Strings$2.kt
|
||||||
|
}
|
||||||
|
|
||||||
|
#convert values ""
|
||||||
|
convert values-ar Arabic
|
||||||
|
convert values-bg Bulgarian
|
||||||
|
convert values-ca Catalan
|
||||||
|
convert values-cs Czech
|
||||||
|
convert values-da Danish
|
||||||
|
convert values-de German
|
||||||
|
convert values-el Greek
|
||||||
|
convert values-eo Esperanto
|
||||||
|
convert values-es Spanish
|
||||||
|
convert values-eu Basque
|
||||||
|
convert values-fa Persian
|
||||||
|
convert values-fi Finnish
|
||||||
|
convert values-fr French
|
||||||
|
convert values-hi Hindi
|
||||||
|
convert values-hr Croatian
|
||||||
|
convert values-hu Hungarian
|
||||||
|
convert values-in Indonesian
|
||||||
|
convert values-it Italian
|
||||||
|
convert values-iw Hebrew
|
||||||
|
convert values-ja Japanese
|
||||||
|
convert values-ko Korean
|
||||||
|
convert values-nl Dutch
|
||||||
|
convert values-no-rNO Norwegian
|
||||||
|
convert values-pl Polish
|
||||||
|
convert values-pt-rBR PortugueseBR
|
||||||
|
convert values-pt-rPT PortuguesePT
|
||||||
|
convert values-ro Romanian
|
||||||
|
convert values-ru Russian
|
||||||
|
convert values-sl Slovak
|
||||||
|
convert values-sr Serbian
|
||||||
|
convert values-sv Swedish
|
||||||
|
convert values-tr Turkish
|
||||||
|
convert values-uk Ukrainian
|
||||||
|
convert values-vi Vietnamese
|
||||||
|
convert values-zh-rCN ChineseCN
|
||||||
|
convert values-zh-rTW ChineseTW
|
||||||
@@ -1,536 +1,415 @@
|
|||||||
Name,Languages,"Translated (Words)","Target Words","Approved (Words)",Voted,"""+"" votes received","""-"" votes received","Winning (Words)",Joined
|
Name,Languages,"Translated (Words)","Target Words","Approved (Words)",Voted,"""+"" votes received","""-"" votes received","Winning (Words)",Joined
|
||||||
"Alinson Xavier (iSoron)","Portuguese, Brazilian; Japanese; Chinese Simplified; Italian; Spanish; Portuguese; French; Hungarian; Chinese Traditional; Turkish; Russian; Arabic; Polish; German; Korean; Bulgarian; Catalan; Greek; Slovenian; Hindi; Serbian (Cyrillic); Ukrainian; Czech; Danish; Dutch; Indonesian; Croatian; Romanian; Swedish; Basque; Persian; Finnish; Vietnamese; Tamil; Telugu; Hebrew; Esperanto; Afrikaans; Norwegian; Armenian; Slovak; Serbian (Latin); Uyghur",15497,18825,1308,0,2094,111,4315,"2016-03-05 18:35:27"
|
"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)",2072,1852,2139,12,30,0,2015,"2021-02-03 14:26:07"
|
"Slobodan Simić (Слободан Симић) (slsimic)","Serbian (Latin); Serbian (Cyrillic)",2054,1831,2114,12,33,0,1991,"2021-02-03 14:26:07"
|
||||||
dukelc,Slovak,1107,1052,0,0,0,0,0,"2020-08-27 14:02:41"
|
"Oglaigh Rystard (oglaignaheireann)","Ukrainian; Portuguese; Catalan; Greek; Basque; Romanian; Italian",1103,1037,1327,1,13,6,954,"2017-03-31 09:13:19"
|
||||||
"Oglaigh Rystard (oglaignaheireann)","Ukrainian; Portuguese; Catalan; Greek; Basque; Romanian; Italian",1103,1037,1327,1,22,13,954,"2017-03-31 09:13:19"
|
dukelc,Slovak,1046,993,0,0,0,0,0,"2020-08-27 14:02:41"
|
||||||
Avalysion,Georgian,1057,895,0,0,0,0,0,"2023-06-30 20:05:15"
|
"David (Cliff122)",Swedish,1040,1019,725,6,0,0,700,"2020-01-21 13:56:55"
|
||||||
"Mathew TK (mathew2006)",Malayalam,1057,1885,0,0,0,0,0,"2023-12-16 01:57:49"
|
"Omer I.S. (omeritzics)",Hebrew,1040,927,1122,14,1,0,975,"2020-10-11 20:10:51"
|
||||||
REMOVED_USER,Hebrew,1051,944,1122,14,1,0,954,"2020-10-11 20:10:51"
|
|
||||||
"David (Cliff122)",Swedish,1040,1019,725,6,37,0,700,"2020-01-21 13:56:55"
|
|
||||||
"Intan Ayunda (Intan_Ayunda)",Indonesian,818,811,985,0,0,0,729,"2020-10-14 07:51:58"
|
"Intan Ayunda (Intan_Ayunda)",Indonesian,818,811,985,0,0,0,729,"2020-10-14 07:51:58"
|
||||||
dusanstrgar,Slovenian,770,730,0,0,0,0,0,"2017-03-31 10:30:28"
|
"Mihail Stefanov (MStefanov)",Bulgarian,755,794,3,0,2,0,2,"2017-03-31 16:09:02"
|
||||||
"Mihail Stefanov (MStefanov)",Bulgarian,755,794,3,0,2,4,2,"2017-03-31 16:09:02"
|
|
||||||
Osoitz,Basque,751,683,0,9,0,0,3,"2018-01-23 14:07:47"
|
|
||||||
KMakoto,"Chinese Traditional",745,1146,949,0,0,0,745,"2019-10-22 04:19:52"
|
KMakoto,"Chinese Traditional",745,1146,949,0,0,0,745,"2019-10-22 04:19:52"
|
||||||
Tomairuka,Japanese,709,1842,1033,43,12,0,585,"2020-12-12 12:14:22"
|
"Evren (evrenkiymaz)",Turkish,688,604,0,71,28,22,0,"2020-10-04 03:39:16"
|
||||||
"Evren (evrenkiymaz)",Turkish,688,604,0,71,31,22,0,"2020-10-04 03:39:16"
|
andaryon,Czech,681,606,0,108,0,0,0,"2021-11-25 10:20:45"
|
||||||
andaryon,Czech,681,606,0,108,21,0,0,"2021-11-25 10:20:45"
|
|
||||||
"Antti Kallio (antti.kallio)",Finnish,668,539,0,5,0,0,0,"2021-07-03 05:54:44"
|
"Antti Kallio (antti.kallio)",Finnish,668,539,0,5,0,0,0,"2021-07-03 05:54:44"
|
||||||
"David Nos (david.nos)","Catalan; Spanish",667,731,0,0,1,0,0,"2020-01-04 10:15:36"
|
"David Nos (david.nos)","Catalan; Spanish",667,731,0,0,1,0,0,"2020-01-04 10:15:36"
|
||||||
androide74,Italian,662,681,0,2,0,0,0,"2020-02-06 15:46:28"
|
androide74,Italian,662,681,0,2,0,0,0,"2020-02-06 15:46:28"
|
||||||
"Dmitriy Bogdanov (di72nn)",Russian,643,589,1197,0,50,0,515,"2017-03-31 10:00:48"
|
Osoitz,Basque,655,595,0,9,0,0,3,"2018-01-23 14:07:47"
|
||||||
reyhoon,Persian,624,759,0,1,3,2,0,"2020-10-01 18:17:23"
|
"Dmitriy Bogdanov (di72nn)",Russian,643,589,1197,0,36,0,515,"2017-03-31 10:00:48"
|
||||||
|
Tomairuka,Japanese,633,1636,909,43,0,0,564,"2020-12-12 12:14:22"
|
||||||
|
reyhoon,Persian,624,759,0,1,3,1,0,"2020-10-01 18:17:23"
|
||||||
"Saeed Esmaili (saaeed.es20)",Persian,586,795,0,5,4,0,0,"2020-11-26 15:41:15"
|
"Saeed Esmaili (saaeed.es20)",Persian,586,795,0,5,4,0,0,"2020-11-26 15:41:15"
|
||||||
fabian.bouchal,German,548,527,0,6,4,5,72,"2020-01-07 06:43:37"
|
fabian.bouchal,German,548,527,0,6,0,3,72,"2020-01-07 06:43:37"
|
||||||
"Isti (eisti)",Hungarian,528,476,0,0,1,0,0,"2020-12-03 12:02:51"
|
"Isti (eisti)",Hungarian,528,476,0,0,0,0,0,"2020-12-03 12:02:51"
|
||||||
Aravinth_Earth,Tamil,511,961,0,12,0,0,0,"2024-02-23 12:37:28"
|
boban77,Czech,509,461,0,2,29,0,0,"2020-04-30 13:18:24"
|
||||||
boban77,Czech,509,461,0,2,45,0,0,"2020-04-30 13:18:24"
|
|
||||||
"Martim Parente (martimparente)",Portuguese,505,542,0,38,0,0,0,"2020-08-26 10:22:11"
|
"Martim Parente (martimparente)",Portuguese,505,542,0,38,0,0,0,"2020-08-26 10:22:11"
|
||||||
"Yoav Argov (YoavArgov)",Hebrew,501,461,0,0,1,8,91,"2017-04-28 07:23:01"
|
"Yoav Argov (YoavArgov)",Hebrew,501,461,0,0,1,8,91,"2017-04-28 07:23:01"
|
||||||
REMOVED_USER,Norwegian,501,498,501,0,148,0,501,"2017-07-05 19:02:25"
|
REMOVED_USER,Norwegian,501,498,501,0,148,0,501,"2017-07-05 19:02:25"
|
||||||
"chrrris1987 (Chrrris1987)",Dutch,467,478,0,23,0,0,0,"2020-02-03 05:26:04"
|
"chrrris1987 (Chrrris1987)",Dutch,467,478,0,23,0,0,0,"2020-02-03 05:26:04"
|
||||||
"黄克 (hk13127)","Chinese Simplified",461,765,0,1,0,0,22,"2020-01-17 23:16:03"
|
"黄克 (hk13127)","Chinese Simplified",461,765,0,1,0,0,24,"2020-01-17 23:16:03"
|
||||||
"Huy Ngo (huyngo)",Vietnamese,461,695,0,1,0,0,0,"2020-01-26 11:58:36"
|
"Huy Ngo (huyngo)",Vietnamese,461,695,0,1,0,0,0,"2020-01-26 11:58:36"
|
||||||
"Arkadiusz Bubak (epitek)",Polish,458,416,52,24,15,4,0,"2020-11-05 05:11:58"
|
"Arkadiusz Bubak (epitek)",Polish,458,416,52,24,9,4,0,"2020-11-05 05:11:58"
|
||||||
"Radek Kuklík (kuklik.radek)",Czech,453,392,0,100,0,0,0,"2022-10-08 10:04:24"
|
|
||||||
marco.baturan,Esperanto,452,452,0,0,0,0,0,"2020-06-23 02:49:46"
|
marco.baturan,Esperanto,452,452,0,0,0,0,0,"2020-06-23 02:49:46"
|
||||||
"Sief Tarek (sieftarek135)",Arabic,447,455,0,0,1,4,0,"2021-02-07 14:35:21"
|
"Sief Tarek (sieftarek135)",Arabic,447,455,0,0,0,0,0,"2021-02-07 14:35:21"
|
||||||
"Alparslan Şakçi (sakci)",Turkish,436,372,0,118,2,0,0,"2022-01-14 12:03:11"
|
"Alparslan Şakçi (sakci)",Turkish,436,372,0,118,1,0,0,"2022-01-14 12:03:11"
|
||||||
JY3,"Chinese Simplified",432,741,298,0,2,0,227,"2021-03-08 08:53:35"
|
JY3,"Chinese Simplified",427,727,295,0,1,0,222,"2021-03-08 08:53:35"
|
||||||
"Samuel Guay (SamGuay)",French,426,486,0,6,0,0,0,"2020-06-25 07:14:38"
|
"Samuel Guay (SamGuay)",French,426,486,0,6,0,0,0,"2020-06-25 07:14:38"
|
||||||
"Diana Karaseva (Sun_Dianka)",Russian,399,373,0,10,1,0,209,"2020-01-30 06:40:02"
|
"Diana Karaseva (Sun_Dianka)",Russian,399,373,0,10,1,0,209,"2020-01-30 06:40:02"
|
||||||
"Alexander Jansson (dalecarlian)",Swedish,396,406,507,0,29,6,399,"2017-06-21 01:37:32"
|
"Alexander Jansson (dalecarlian)",Swedish,396,406,507,0,0,3,399,"2017-06-21 01:37:32"
|
||||||
luiandresgonzalez,Spanish,383,403,0,1,28,0,0,"2020-07-11 14:20:44"
|
luiandresgonzalez,Spanish,383,403,0,1,28,0,0,"2020-07-11 14:20:44"
|
||||||
"Thamara Andrade (tkcandrade)","Portuguese, Brazilian",380,387,0,0,1,0,239,"2020-01-09 19:35:48"
|
"Thamara Andrade (tkcandrade)","Portuguese, Brazilian",380,387,0,0,1,0,239,"2020-01-09 19:35:48"
|
||||||
"Sølv Ræven (soelvraeven)",Danish,370,370,0,0,0,0,0,"2020-11-28 16:46:18"
|
"Sølv Ræven (soelvraeven)",Danish,370,370,0,0,0,0,0,"2020-11-28 16:46:18"
|
||||||
strikeCunny2245,Icelandic,363,368,0,0,0,0,0,"2023-08-07 08:05:53"
|
|
||||||
"Anh Quân (dangquanuet)",Vietnamese,362,530,0,42,2,0,0,"2017-10-29 12:27:44"
|
"Anh Quân (dangquanuet)",Vietnamese,362,530,0,42,2,0,0,"2017-10-29 12:27:44"
|
||||||
"Alexander Haronitakis (kanakis)",Greek,349,372,0,0,0,0,0,"2023-09-13 04:10:43"
|
|
||||||
gapszi,Hungarian,348,301,0,86,0,0,0,"2019-04-08 01:35:54"
|
gapszi,Hungarian,348,301,0,86,0,0,0,"2019-04-08 01:35:54"
|
||||||
"Mahdi Nasiri (mahdi.nasiri)",Persian,343,465,0,39,3,1,0,"2017-07-14 09:17:25"
|
"Mahdi Nasiri (mahdi.nasiri)",Persian,343,465,0,39,3,1,0,"2017-07-14 09:17:25"
|
||||||
Seoyul,Korean,339,825,0,0,27,0,0,"2017-06-21 08:11:39"
|
Seoyul,Korean,339,825,0,0,27,0,0,"2017-06-21 08:11:39"
|
||||||
"Magimai Prakasam (magimai)",Tamil,336,831,0,12,2,0,0,"2018-04-15 21:16:08"
|
"Magimai Prakasam (magimai)",Tamil,336,831,0,12,0,0,0,"2018-04-15 21:16:08"
|
||||||
"Star7 (Star7-crowdin)","Chinese Simplified",330,561,0,0,0,0,0,"2025-02-10 08:41:34"
|
"Michael Malak (MichaelKMalak)",Arabic,304,271,0,0,1,0,0,"2020-05-26 19:47:58"
|
||||||
Susanamesa,Spanish,306,342,0,11,0,0,0,"2023-01-01 23:56:35"
|
Blinkin,Dutch,297,334,0,5,0,0,0,"2021-06-14 10:30:05"
|
||||||
"Michael Malak (MichaelKMalak)",Arabic,304,271,0,0,4,2,0,"2020-05-26 19:47:58"
|
|
||||||
"Elina Salminen (salminen.elina.m)",Finnish,297,227,0,0,0,0,0,"2021-01-06 01:28:57"
|
"Elina Salminen (salminen.elina.m)",Finnish,297,227,0,0,0,0,0,"2021-01-06 01:28:57"
|
||||||
Blinkin,Dutch,297,334,0,5,1,0,0,"2021-06-14 10:30:05"
|
ayane.m,Japanese,292,863,0,1,5,0,22,"2019-11-20 03:28:26"
|
||||||
ayane.m,Japanese,292,863,0,1,8,0,23,"2019-11-20 03:28:26"
|
"Marius Teufelweich (teufelweich)",German,267,272,611,4,13,1,146,"2021-03-12 04:11:38"
|
||||||
c.m,Greek,276,293,0,124,0,0,0,"2024-07-13 14:49:43"
|
|
||||||
"Marius Teufelweich (teufelweich)",German,267,272,611,4,23,3,146,"2021-03-12 04:11:38"
|
|
||||||
"Sumin Son (todaypp)",Korean,266,221,0,7,0,0,0,"2023-06-09 05:28:30"
|
|
||||||
hypnotichemionus,"Chinese Simplified",249,430,0,0,8,0,19,"2020-03-08 01:46:25"
|
hypnotichemionus,"Chinese Simplified",249,430,0,0,8,0,19,"2020-03-08 01:46:25"
|
||||||
"Israa Z (sosozozo)",Arabic,240,266,0,81,14,0,3,"2017-11-27 14:10:50"
|
|
||||||
cobalt59,German,237,234,0,1,24,1,132,"2017-06-05 05:18:33"
|
cobalt59,German,237,234,0,1,24,1,132,"2017-06-05 05:18:33"
|
||||||
beriain,Basque,234,235,0,0,2,0,0,"2017-03-31 15:42:28"
|
beriain,Basque,234,235,0,0,2,0,0,"2017-03-31 15:42:28"
|
||||||
pnhpnh,Vietnamese,225,343,0,1,3,0,0,"2017-11-27 12:06:07"
|
pnhpnh,Vietnamese,225,343,0,1,3,0,0,"2017-11-27 12:06:07"
|
||||||
"Dika Fitrian Dwi Putra (OsamuDazai)",Indonesian,221,215,0,0,0,0,48,"2020-07-13 04:40:27"
|
"Dika Fitrian Dwi Putra (OsamuDazai)",Indonesian,221,215,0,0,0,0,48,"2020-07-13 04:40:27"
|
||||||
easyrepro,Telugu,214,297,0,0,4,0,0,"2020-06-12 12:52:10"
|
easyrepro,Telugu,214,297,0,0,4,0,0,"2020-06-12 12:52:10"
|
||||||
taras-ko,Ukrainian,211,183,0,1,4,0,19,"2017-10-26 16:52:22"
|
taras-ko,Ukrainian,211,183,0,1,4,0,19,"2017-10-26 16:52:22"
|
||||||
"vinayak sharma (vinayak0504)",Hindi,211,456,0,0,0,0,0,"2023-05-18 18:31:51"
|
|
||||||
sojusnik,German,207,200,1,0,30,0,66,"2017-04-03 17:11:56"
|
sojusnik,German,207,200,1,0,30,0,66,"2017-04-03 17:11:56"
|
||||||
"Andrij Mizyk (andm)",Ukrainian,204,178,0,40,1,0,53,"2021-04-01 03:56:20"
|
"Andrij Mizyk (andmizyk)",Ukrainian,204,178,0,40,0,0,53,"2021-04-01 03:56:20"
|
||||||
"Heru Yen (heruyen)",Indonesian,201,201,0,0,0,0,25,"2020-06-29 18:39:15"
|
"Heru Yen (heruyen)",Indonesian,201,201,0,0,0,0,25,"2020-06-29 18:39:15"
|
||||||
"Vijaykumar Borkar (vjkumar)",Hindi,200,364,0,11,0,0,0,"2021-08-06 16:12:15"
|
"Vijaykumar Borkar (vjkumar)",Hindi,200,364,0,11,0,0,0,"2021-08-06 16:12:15"
|
||||||
_translator,French,199,227,0,11,0,0,0,"2021-07-06 07:54:12"
|
_translator,French,199,227,0,11,0,0,0,"2021-07-06 07:54:12"
|
||||||
bearsdens,Romanian,198,210,0,0,0,0,0,"2022-08-28 17:08:33"
|
|
||||||
Ishmaeel,Turkish,193,174,0,129,17,6,0,"2017-10-04 03:54:00"
|
Ishmaeel,Turkish,193,174,0,129,17,6,0,"2017-10-04 03:54:00"
|
||||||
REMOVED_USER,Spanish,192,201,0,2,5,0,0,"2021-05-21 17:58:22"
|
oscfd,Spanish,192,201,0,2,4,0,0,"2021-05-21 17:58:22"
|
||||||
bruhwut,Vietnamese,189,292,0,1,0,0,0,"2021-05-21 07:16:30"
|
bruhwut,Vietnamese,189,292,0,1,0,0,0,"2021-05-21 07:16:30"
|
||||||
"Aputsiak Niels Janussen (aputtu)",Danish,187,200,0,0,0,0,0,"2019-08-28 05:47:42"
|
"Aputsiak Niels Janussen (aputtu)",Danish,187,200,0,0,0,0,0,"2019-08-28 05:47:42"
|
||||||
fbruna17,Danish,181,179,0,1,0,0,0,"2021-01-28 15:48:47"
|
fbruna17,Danish,181,179,0,1,0,0,0,"2021-01-28 15:48:47"
|
||||||
Bryanx,Dutch,179,168,0,5,2,0,0,"2019-11-21 17:08:12"
|
Bryanx,Dutch,179,168,0,5,2,0,0,"2019-11-21 17:08:12"
|
||||||
"Ivan Vlahov (vlahovivan)",Croatian,179,176,0,0,0,0,0,"2024-01-25 08:12:11"
|
"Omry Cohen (omrycohen)",Hebrew,175,156,0,1,0,0,33,"2021-01-18 07:33:23"
|
||||||
"Omry Cohen (omrycohen)",Hebrew,175,156,0,1,0,0,12,"2021-01-18 07:33:23"
|
|
||||||
"Pierre GALIEGUE (pierre.galiegue)",French,171,194,0,24,4,0,0,"2020-08-16 11:41:35"
|
"Pierre GALIEGUE (pierre.galiegue)",French,171,194,0,24,4,0,0,"2020-08-16 11:41:35"
|
||||||
plitwin,Polish,168,151,0,2,55,0,49,"2021-01-20 06:18:37"
|
plitwin,Polish,168,151,0,2,31,0,49,"2021-01-20 06:18:37"
|
||||||
DionysosDV,Greek,165,153,0,0,27,4,0,"2021-02-27 19:05:25"
|
DionysosDV,Greek,165,153,0,0,0,0,0,"2021-02-27 19:05:25"
|
||||||
"Gustavo Lima (GustavoLima)",Portuguese,158,177,0,1,4,10,0,"2020-08-26 10:35:05"
|
"Gustavo Lima (GustavoLima)",Portuguese,158,177,0,1,4,10,0,"2020-08-26 10:35:05"
|
||||||
"Alex V. (elvitalex)",Romanian,154,166,0,24,0,0,0,"2022-08-03 17:40:00"
|
|
||||||
"Ravi Rami (ramiravi)",Hindi,151,248,0,0,0,0,0,"2021-10-10 09:19:40"
|
"Ravi Rami (ramiravi)",Hindi,151,248,0,0,0,0,0,"2021-10-10 09:19:40"
|
||||||
"Lương Vĩnh Khang (LuongVinhKhang)",Vietnamese,144,256,0,0,46,1,0,"2017-08-10 10:05:58"
|
"Lương Vĩnh Khang (LuongVinhKhang)",Vietnamese,144,256,0,0,46,1,0,"2017-08-10 10:05:58"
|
||||||
azzamsa,Indonesian,142,136,0,48,0,1,26,"2017-06-16 18:29:45"
|
azzamsa,Indonesian,142,136,0,48,0,1,26,"2017-06-16 18:29:45"
|
||||||
"yoding (yodingc)","Chinese Traditional; Chinese Simplified",141,271,0,10,0,0,0,"2021-07-07 01:45:45"
|
"yoding (yodingc)","Chinese Traditional; Chinese Simplified",141,271,0,10,0,0,0,"2021-07-07 01:45:45"
|
||||||
"Neysa Nasywa (neysanasywa)",Indonesian,140,141,0,0,0,0,60,"2020-11-18 10:32:10"
|
"Neysa Nasywa (neysanasywa)",Indonesian,140,141,0,0,0,0,60,"2020-11-18 10:32:10"
|
||||||
mohmans,Arabic,139,141,0,12,7,1,0,"2020-11-23 02:48:00"
|
mohmans,Arabic,139,141,0,12,1,0,0,"2020-11-23 02:48:00"
|
||||||
"Eilif Adelvice (adelvice)",Spanish,139,154,0,96,6,0,0,"2021-08-05 07:20:21"
|
"Eilif Adelvice (adelvice)",Spanish,139,154,0,96,1,0,0,"2021-08-05 07:20:21"
|
||||||
"Mohammed Imthath (mimthath4)",Tamil,136,274,0,0,13,1,0,"2018-02-15 22:41:15"
|
"Mohammed Imthath (mimthath4)",Tamil,136,274,0,0,11,0,0,"2018-02-15 22:41:15"
|
||||||
carllacan,Catalan,134,155,0,2,0,0,0,"2021-11-13 13:12:07"
|
carllacan,Catalan,134,155,0,2,0,0,0,"2021-11-13 13:12:07"
|
||||||
roptat,French,132,154,0,112,89,5,0,"2017-04-19 16:54:47"
|
roptat,French,132,154,0,112,89,5,0,"2017-04-19 16:54:47"
|
||||||
"Trần Thái (tranhoangthai2001)",Vietnamese,127,186,0,8,1,0,0,"2018-03-01 10:51:39"
|
"Trần Thái (tranhoangthai2001)",Vietnamese,127,186,0,8,1,0,0,"2018-03-01 10:51:39"
|
||||||
"OP Smosher (teenwolffan44)","Serbian (Cyrillic)",124,122,0,0,0,0,18,"2020-11-05 09:41:35"
|
"OP Smosher (teenwolffan44)","Serbian (Cyrillic)",124,122,0,0,0,0,18,"2020-11-05 09:41:35"
|
||||||
"Tad Wohlrapp (TadWohlrapp)",German,124,122,0,0,2,0,0,"2022-06-28 04:55:41"
|
|
||||||
4001982248998,Esperanto,122,119,0,0,0,0,0,"2017-10-08 04:13:02"
|
4001982248998,Esperanto,122,119,0,0,0,0,0,"2017-10-08 04:13:02"
|
||||||
"StoP4Me (Lcqp)",Romanian,121,119,0,0,6,0,0,"2018-05-06 18:51:59"
|
"StoP4Me (Lcqp)",Romanian,121,119,0,0,3,0,0,"2018-05-06 18:51:59"
|
||||||
alalloush,Arabic,118,129,0,2,17,6,0,"2017-03-31 12:37:17"
|
alalloush,Arabic,118,129,0,2,14,3,0,"2017-03-31 12:37:17"
|
||||||
"Brenda Correa (brenda.14)",Spanish,117,127,0,0,0,1,0,"2022-05-16 02:34:13"
|
|
||||||
Sebastian05067,Spanish,114,133,0,55,32,0,0,"2017-05-14 00:48:16"
|
|
||||||
"Tanya (MagicUnderHood)",Russian,114,98,0,19,0,0,54,"2019-04-21 10:44:03"
|
"Tanya (MagicUnderHood)",Russian,114,98,0,19,0,0,54,"2019-04-21 10:44:03"
|
||||||
REMOVED_USER,Arabic,111,106,0,22,23,3,0,"2018-01-05 07:01:45"
|
Sebastian05067,Spanish,114,133,0,55,28,0,0,"2017-05-14 00:48:16"
|
||||||
|
REMOVED_USER,Arabic,111,106,0,22,22,2,0,"2018-01-05 07:01:45"
|
||||||
|
"Iabin Arteaga (iabin)",Spanish,108,111,0,4,21,0,0,"2017-08-26 21:08:54"
|
||||||
"Ivan Krušlin (krux3r)",Croatian,108,122,503,0,0,0,108,"2017-03-31 09:15:24"
|
"Ivan Krušlin (krux3r)",Croatian,108,122,503,0,0,0,108,"2017-03-31 09:15:24"
|
||||||
"Iabin Arteaga (iabin)",Spanish,108,111,0,4,21,1,0,"2017-08-26 21:08:54"
|
2kaafone,Finnish,105,90,0,0,0,0,0,"2019-08-12 06:58:48"
|
||||||
REMOVED_USER,Finnish,105,90,0,0,0,0,0,"2019-08-12 06:58:48"
|
|
||||||
"Adam Jurkiewicz (hasztagg)",Polish,104,105,529,0,0,0,104,"2017-03-31 09:50:51"
|
"Adam Jurkiewicz (hasztagg)",Polish,104,105,529,0,0,0,104,"2017-03-31 09:50:51"
|
||||||
"PILHA PARK (pilhaha)",Korean,101,88,0,0,0,0,0,"2023-06-30 23:51:41"
|
|
||||||
"just a name bro (justanamebr0)",Danish,98,109,0,0,1,0,0,"2019-06-19 11:57:55"
|
"just a name bro (justanamebr0)",Danish,98,109,0,0,1,0,0,"2019-06-19 11:57:55"
|
||||||
"Nam Nguyen (namnl2706)",Vietnamese,95,137,0,0,0,0,0,"2020-08-18 23:02:33"
|
"Nam Nguyen (namnl2706)",Vietnamese,95,137,0,0,0,0,0,"2020-08-18 23:02:33"
|
||||||
"손유정 (yuwon1213)",Korean,95,57,0,0,1,3,0,"2021-03-30 05:25:33"
|
"손유정 (yuwon1213)",Korean,95,57,0,0,1,0,0,"2021-03-30 05:25:33"
|
||||||
ranmagen,Hebrew,91,78,0,0,0,0,0,"2021-02-16 05:44:31"
|
ranmagen,Hebrew,91,78,0,0,0,0,0,"2021-02-16 05:44:31"
|
||||||
LoneWanderer,"Chinese Traditional",90,137,0,4,0,0,0,"2020-09-29 05:24:48"
|
LoneWanderer,"Chinese Traditional",90,137,0,4,0,0,0,"2020-09-29 05:24:48"
|
||||||
ikkaz,Indonesian,89,84,0,5,0,0,4,"2019-09-02 19:58:54"
|
ikkaz,Indonesian,89,84,0,5,0,0,4,"2019-09-02 19:58:54"
|
||||||
"Vo - (voyl)","Chinese Traditional",89,126,0,0,5,0,0,"2020-09-02 23:34:42"
|
"Vo - (voyl)","Chinese Traditional",89,126,0,0,5,0,0,"2020-09-02 23:34:42"
|
||||||
"Irene K (Heaun)",Korean,88,75,0,25,2,0,0,"2020-03-16 11:31:12"
|
"Irene K (Heaun)",Korean,88,75,0,25,0,0,0,"2020-03-16 11:31:12"
|
||||||
Prosta4ok_ua,Ukrainian,87,84,0,1,0,0,17,"2020-01-23 19:43:41"
|
Prosta4ok_ua,Ukrainian,87,84,0,1,0,0,17,"2020-01-23 19:43:41"
|
||||||
"Kumar Anand (kumar0500)",Hindi,87,125,0,0,0,0,0,"2020-11-07 02:46:09"
|
"Kumar Anand (kumar0500)",Hindi,87,125,0,0,0,0,0,"2020-11-07 02:46:09"
|
||||||
G.kio,Russian,87,82,0,0,1,0,0,"2023-08-24 19:43:00"
|
|
||||||
"Ohad Edri (ohadalte)",Hebrew,85,79,0,0,1,3,13,"2020-07-04 03:42:09"
|
"Ohad Edri (ohadalte)",Hebrew,85,79,0,0,1,3,13,"2020-07-04 03:42:09"
|
||||||
"Radu Cebotari (wildProgrammer)",Romanian,84,92,0,1,1,0,0,"2020-02-05 01:20:00"
|
helectron,Persian,84,102,0,1,0,0,0,"2021-03-02 04:10:51"
|
||||||
helectron,Persian,84,102,0,1,1,0,0,"2021-03-02 04:10:51"
|
"Radu Cebotari (wildProgrammer)",Romanian,84,92,0,1,0,0,0,"2020-02-05 01:20:00"
|
||||||
"Bruces Lee (aplusbdesign)",Korean,82,66,0,0,2,0,0,"2021-08-23 11:27:18"
|
"Bruces Lee (aplusbdesign)",Korean,82,66,0,0,0,0,0,"2021-08-23 11:27:18"
|
||||||
Tiralka,French,79,91,0,92,1,0,0,"2018-02-09 18:39:01"
|
"Israa Z (sosozozo)",Arabic,79,87,0,43,14,0,3,"2017-11-27 14:10:50"
|
||||||
"Jacob Roller (jdr28070)",Korean,79,61,0,0,1,1,0,"2020-01-03 11:36:40"
|
|
||||||
"Sofia Neves (sofiasonev)","Portuguese, Brazilian",79,84,0,1,0,0,46,"2020-03-12 18:19:46"
|
"Sofia Neves (sofiasonev)","Portuguese, Brazilian",79,84,0,1,0,0,46,"2020-03-12 18:19:46"
|
||||||
|
"Jacob Roller (jdr28070)",Korean,79,61,0,0,1,0,0,"2020-01-03 11:36:40"
|
||||||
|
Tiralka,French,79,91,0,92,1,0,0,"2018-02-09 18:39:01"
|
||||||
"Toni Mustonen (toni.mustonen)",Finnish,78,72,0,0,5,0,0,"2017-09-02 05:34:12"
|
"Toni Mustonen (toni.mustonen)",Finnish,78,72,0,0,5,0,0,"2017-09-02 05:34:12"
|
||||||
"Fauz Aladeem (topfauz)",Arabic,76,77,0,0,0,1,0,"2020-02-21 22:46:12"
|
|
||||||
"Michael (quelbs)",German,76,75,0,1,0,0,39,"2020-08-18 07:39:26"
|
"Michael (quelbs)",German,76,75,0,1,0,0,39,"2020-08-18 07:39:26"
|
||||||
"Anna Maria Stålberg (stalberg.annamaria)",Swedish,76,77,0,111,4,0,0,"2023-01-16 04:08:25"
|
"Fauz Aladeem (topfauz)",Arabic,76,77,0,0,0,1,0,"2020-02-21 22:46:12"
|
||||||
"Radoslaw Biernacki (radoslaw.biernacki)",Polish,70,74,0,56,5,0,1,"2020-12-15 17:55:31"
|
"Radoslaw Biernacki (radoslaw.biernacki)",Polish,70,74,0,56,1,0,1,"2020-12-15 17:55:31"
|
||||||
"Oliver Gronowski (OliverGronowski)",German,70,69,0,5,3,0,0,"2021-05-14 16:37:10"
|
"Oliver Gronowski (OliverGronowski)",German,70,69,0,5,2,0,0,"2021-05-14 16:37:10"
|
||||||
mimizuk,Japanese,68,193,0,0,2,1,39,"2022-05-18 10:10:38"
|
|
||||||
Ryeore,Polish,68,61,0,66,0,0,0,"2022-07-27 12:22:33"
|
|
||||||
RealDonald,Dutch,67,69,0,121,10,0,0,"2017-06-23 20:10:12"
|
RealDonald,Dutch,67,69,0,121,10,0,0,"2017-06-23 20:10:12"
|
||||||
"Dpd Eng (dpdeng)",Korean,67,48,0,0,1,0,0,"2022-10-15 10:56:44"
|
|
||||||
sirekanyan,"Armenian; Russian",66,65,0,0,0,0,0,"2020-04-18 11:32:52"
|
sirekanyan,"Armenian; Russian",66,65,0,0,0,0,0,"2020-04-18 11:32:52"
|
||||||
"Константин К. (kocyak1991)",Russian,64,60,0,0,1,2,0,"2018-06-10 13:39:37"
|
"Константин К. (kocyak1991)",Russian,64,60,0,0,1,2,0,"2018-06-10 13:39:37"
|
||||||
yukitsubaki,Japanese,64,185,0,32,0,0,36,"2020-01-01 13:17:44"
|
|
||||||
Suuis,Hindi,64,110,0,0,0,0,0,"2023-07-02 11:50:22"
|
|
||||||
"Laura Sophie (laurasophie20)",German,62,67,0,4,0,0,0,"2018-01-06 14:21:24"
|
"Laura Sophie (laurasophie20)",German,62,67,0,4,0,0,0,"2018-01-06 14:21:24"
|
||||||
raden20,Indonesian,61,62,177,0,1,0,64,"2017-04-09 22:04:23"
|
raden20,Indonesian,61,62,177,0,1,0,64,"2017-04-09 22:04:23"
|
||||||
"y (veggente)",Korean,61,56,0,0,0,1,0,"2022-06-28 10:41:18"
|
"Peter Williams (williamspete001)",Japanese,60,173,0,2,0,0,3,"2020-01-01 13:17:44"
|
||||||
"Jan Wojtecki (j4nw)",Polish,58,46,0,0,0,0,26,"2017-11-02 05:42:14"
|
"Jan Wojtecki (j4nw)",Polish,58,46,0,0,0,0,26,"2017-11-02 05:42:14"
|
||||||
"Deepak Bharathi (deepakbharathi1994)",Tamil,56,107,0,0,13,4,0,"2017-09-17 08:00:31"
|
"Deepak Bharathi (deepakbharathi1994)",Tamil,56,107,0,0,11,4,0,"2017-09-17 08:00:31"
|
||||||
"Андрій Козицький (andriikozytskyi1108)",Ukrainian,52,52,0,0,1,0,0,"2018-10-22 01:45:08"
|
"Андрій Козицький (andriikozytskyi1108)",Ukrainian,52,52,0,0,1,0,0,"2018-10-22 01:45:08"
|
||||||
"Nil riera (nilriera2000)",Catalan,52,61,0,1,2,0,0,"2021-06-22 16:37:44"
|
"Nil riera (nilriera2000)",Catalan,52,61,0,1,2,0,0,"2021-06-22 16:37:44"
|
||||||
REMOVED_USER,Italian,51,52,0,2,0,0,0,"2017-08-21 05:15:31"
|
|
||||||
govindap,"Japanese; Hindi",51,114,0,6,3,1,0,"2020-06-02 20:15:52"
|
|
||||||
"Neoone (Neooneqq)",Romanian,51,54,0,0,0,0,0,"2022-05-05 20:42:11"
|
"Neoone (Neooneqq)",Romanian,51,54,0,0,0,0,0,"2022-05-05 20:42:11"
|
||||||
|
REMOVED_USER,Italian,51,52,0,2,0,0,0,"2017-08-21 05:15:31"
|
||||||
|
govindap,"Japanese; Hindi",51,114,0,6,1,0,0,"2020-06-02 20:15:52"
|
||||||
"Mare Geldenhuys (mare.geldenhuys)",Afrikaans,50,57,0,0,0,0,0,"2017-10-20 18:00:14"
|
"Mare Geldenhuys (mare.geldenhuys)",Afrikaans,50,57,0,0,0,0,0,"2017-10-20 18:00:14"
|
||||||
|
"Mahmoud Magdy (M7moudManson)",Arabic,49,60,0,6,8,1,0,"2021-08-21 09:01:38"
|
||||||
"Behnood HRazy (behnoodhr)",Persian,49,70,0,0,0,0,0,"2017-11-25 10:57:21"
|
"Behnood HRazy (behnoodhr)",Persian,49,70,0,0,0,0,0,"2017-11-25 10:57:21"
|
||||||
"Mahmoud Magdy (M7moudManson)",Arabic,49,60,0,6,12,6,0,"2021-08-21 09:01:38"
|
|
||||||
J3ll3nl,Dutch,48,48,0,0,17,1,3,"2017-03-31 11:56:09"
|
J3ll3nl,Dutch,48,48,0,0,17,1,3,"2017-03-31 11:56:09"
|
||||||
"tat bz (Tat_i)",German,48,56,0,55,0,1,27,"2021-03-26 05:12:54"
|
"tat bz (Tat_i)",German,48,56,0,55,0,1,27,"2021-03-26 05:12:54"
|
||||||
"Andrew Firnes (Anechan)",Russian,47,47,0,3,0,0,29,"2019-09-18 09:51:59"
|
|
||||||
andowero,Czech,47,38,0,0,8,0,0,"2020-01-20 02:29:01"
|
|
||||||
vach,Armenian,47,36,0,0,0,0,0,"2020-04-18 16:53:12"
|
vach,Armenian,47,36,0,0,0,0,0,"2020-04-18 16:53:12"
|
||||||
|
"Andrew Firnes (Anechan)",Russian,47,47,0,3,0,0,29,"2019-09-18 09:51:59"
|
||||||
|
andowero,Czech,47,38,0,0,3,0,0,"2020-01-20 02:29:01"
|
||||||
"Rahul Shishodia (rahul.shishodia.10)",Hindi,46,85,0,6,5,1,0,"2018-12-24 22:18:19"
|
"Rahul Shishodia (rahul.shishodia.10)",Hindi,46,85,0,6,5,1,0,"2018-12-24 22:18:19"
|
||||||
"Coni Ragni (coni2ragnii)",Spanish,46,46,0,0,0,0,0,"2021-02-28 20:18:37"
|
"Coni Ragni (coni2ragnii)",Spanish,46,46,0,0,0,0,0,"2021-02-28 20:18:37"
|
||||||
Cp0204,"Chinese Simplified",45,72,0,0,0,0,0,"2019-08-20 11:04:27"
|
Cp0204,"Chinese Simplified",45,72,0,0,0,0,0,"2019-08-20 11:04:27"
|
||||||
"cc (cavaz)",Italian,44,41,0,0,0,0,0,"2017-04-01 04:21:08"
|
"cc (cavaz)",Italian,44,41,0,0,0,0,0,"2017-04-01 04:21:08"
|
||||||
"Andrej Šutovský (16andrej.s)",Slovak,44,41,0,0,0,0,0,"2022-06-12 18:01:16"
|
"Boban Jagertraum (boban40)",Czech,43,38,0,2,18,1,0,"2017-03-31 09:39:16"
|
||||||
"Boban Jagertraum (boban40)",Czech,43,38,0,2,31,1,0,"2017-03-31 09:39:16"
|
"Kamil Dziadek (prso94)",Polish,43,39,0,0,6,0,0,"2020-04-06 17:12:06"
|
||||||
"Kamil Dziadek (prso94)",Polish,43,39,0,0,11,0,0,"2020-04-06 17:12:06"
|
|
||||||
"Balázs Keresztury (belidzs)",Hungarian,42,41,501,0,7,0,38,"2017-04-06 02:40:24"
|
|
||||||
andreea.muscalagiu,Romanian,42,52,0,1,0,0,0,"2017-10-22 07:19:49"
|
andreea.muscalagiu,Romanian,42,52,0,1,0,0,0,"2017-10-22 07:19:49"
|
||||||
"Me Me (gentelwom)",Arabic,42,40,0,0,0,0,0,"2020-11-08 20:44:01"
|
"Me Me (gentelwom)",Arabic,42,40,0,0,0,0,0,"2020-11-08 20:44:01"
|
||||||
|
"Balázs Keresztury (belidzs)",Hungarian,42,41,501,0,7,0,38,"2017-04-06 02:40:24"
|
||||||
|
"Mateusz Duda (MateuszDuda)",Polish,42,42,0,0,6,0,0,"2021-08-17 11:27:11"
|
||||||
"Ali Elsheikh (aelsheikh1987)",Arabic,42,41,0,0,0,0,0,"2021-06-16 10:17:26"
|
"Ali Elsheikh (aelsheikh1987)",Arabic,42,41,0,0,0,0,0,"2021-06-16 10:17:26"
|
||||||
"Mateusz Duda (MateuszDuda)",Polish,42,42,0,0,20,0,0,"2021-08-17 11:27:11"
|
|
||||||
"Mr Habti (donhabti)",Arabic,41,40,0,0,0,0,0,"2023-02-20 10:52:50"
|
|
||||||
"Sofia Veijonen (Suklaa) (sofia.veijonen)",Finnish,40,33,0,0,0,0,0,"2018-03-07 09:24:22"
|
|
||||||
"Neeraj Verma (verma.neeraj.in)",Hindi,40,65,0,0,1,0,0,"2018-07-23 07:16:41"
|
|
||||||
"Ali Zali (stm19951995)",Persian,40,60,0,0,0,0,0,"2020-03-23 19:57:26"
|
"Ali Zali (stm19951995)",Persian,40,60,0,0,0,0,0,"2020-03-23 19:57:26"
|
||||||
|
"Sofia Veijonen (Suklaa) (sofia.veijonen)",Finnish,40,33,0,0,0,0,0,"2018-03-07 09:24:22"
|
||||||
|
dusanstrgar,Slovenian,39,41,0,0,0,0,0,"2017-03-31 10:30:28"
|
||||||
"Limin Lu (liminlu)","Chinese Simplified",39,79,503,0,0,0,39,"2017-03-31 09:49:35"
|
"Limin Lu (liminlu)","Chinese Simplified",39,79,503,0,0,0,39,"2017-03-31 09:49:35"
|
||||||
Anshoe,Tamil,38,65,0,14,3,0,0,"2018-01-02 11:06:52"
|
Anshoe,Tamil,38,65,0,14,0,0,0,"2018-01-02 11:06:52"
|
||||||
"Pavel Protasov (pvphome)",Russian,38,33,0,0,0,0,0,"2024-02-09 03:40:33"
|
|
||||||
anasshm,Arabic,37,36,0,9,0,0,0,"2019-01-27 04:07:22"
|
anasshm,Arabic,37,36,0,9,0,0,0,"2019-01-27 04:07:22"
|
||||||
hrexen,Armenian,37,37,0,0,0,0,0,"2020-12-09 02:30:34"
|
hrexen,Armenian,37,37,0,0,0,0,0,"2020-12-09 02:30:34"
|
||||||
REMOVED_USER,Swedish,36,33,0,5,2,0,0,"2018-09-29 17:47:33"
|
"Abdulrahman (D7M)",Arabic,36,39,0,0,0,0,0,"2020-01-29 18:55:30"
|
||||||
"Abdulrahman (D7M)",Arabic,36,39,0,0,4,0,0,"2020-01-29 18:55:30"
|
REMOVED_USER,Swedish,36,33,0,5,1,0,0,"2018-09-29 17:47:33"
|
||||||
"Maria Chushnyakova (maria.ch)",Russian,36,31,0,3,0,0,0,"2021-08-17 03:23:58"
|
|
||||||
xphsis,Basque,36,31,0,0,0,0,0,"2022-01-02 08:16:19"
|
xphsis,Basque,36,31,0,0,0,0,0,"2022-01-02 08:16:19"
|
||||||
"長谷川知里 (chase0213)",Japanese,34,138,0,13,0,0,32,"2018-12-14 10:52:44"
|
"Maria Chushnyakova (maria.ch)",Russian,36,31,0,3,0,0,0,"2021-08-17 03:23:58"
|
||||||
"milad farahani (miladfarmahini90)",Persian,33,44,0,18,1,0,3,"2017-08-31 16:09:00"
|
"長谷川知里 (chase0213)",Japanese,34,138,0,13,0,0,24,"2018-12-14 10:52:44"
|
||||||
|
"Piotr Łuczyński (peterluczynski)",Polish,33,30,0,6,10,0,2,"2020-01-29 07:27:40"
|
||||||
"Luis E. Perichon (luisperichon)",Spanish,33,40,0,104,0,0,0,"2017-09-04 13:46:06"
|
"Luis E. Perichon (luisperichon)",Spanish,33,40,0,104,0,0,0,"2017-09-04 13:46:06"
|
||||||
"Piotr Łuczyński (peterluczynski)",Polish,33,30,0,6,17,0,2,"2020-01-29 07:27:40"
|
"milad farahani (miladfarmahini90)",Persian,33,44,0,18,1,0,3,"2017-08-31 16:09:00"
|
||||||
JoeLi,"Chinese Traditional",31,70,0,12,0,0,24,"2017-06-25 05:32:48"
|
JoeLi,"Chinese Traditional",31,70,0,12,0,0,24,"2017-06-25 05:32:48"
|
||||||
REMOVED_USER,Russian,31,30,0,2,4,0,3,"2018-12-03 23:55:47"
|
|
||||||
andriikozytskyi2625,Ukrainian,31,23,0,0,0,0,0,"2019-07-08 00:16:41"
|
andriikozytskyi2625,Ukrainian,31,23,0,0,0,0,0,"2019-07-08 00:16:41"
|
||||||
Moastafa,Arabic,31,25,0,0,0,4,0,"2020-07-06 11:37:53"
|
REMOVED_USER,Russian,31,30,0,2,4,0,3,"2018-12-03 23:55:47"
|
||||||
"hamza gamal (hamzagamal4444)",Arabic,31,28,0,0,1,0,0,"2020-08-03 15:23:34"
|
Moastafa,Arabic,31,25,0,0,0,0,0,"2020-07-06 11:37:53"
|
||||||
|
"hamza gamal (hamzagamal4444)",Arabic,31,28,0,0,0,0,0,"2020-08-03 15:23:34"
|
||||||
yancyn,"Chinese Simplified",30,40,0,0,0,0,1,"2020-05-18 20:06:03"
|
yancyn,"Chinese Simplified",30,40,0,0,0,0,1,"2020-05-18 20:06:03"
|
||||||
"Siniša Sabljić (ssabljic)",Croatian,30,37,0,0,0,0,0,"2023-11-13 15:51:00"
|
|
||||||
"Ruud Schouten (ruudschouten)",Dutch,29,32,0,41,3,0,0,"2017-07-22 17:49:17"
|
"Ruud Schouten (ruudschouten)",Dutch,29,32,0,41,3,0,0,"2017-07-22 17:49:17"
|
||||||
"비니몬youtube (khj01025276475)",Korean,29,25,0,0,0,1,0,"2020-02-09 20:44:35"
|
"비니몬youtube (khj01025276475)",Korean,29,25,0,0,0,0,0,"2020-02-09 20:44:35"
|
||||||
avelneve,Indonesian,29,28,0,0,0,0,0,"2022-04-13 13:26:10"
|
avelneve,Indonesian,29,28,0,0,0,0,0,"2022-04-13 13:26:10"
|
||||||
bzhn,Ukrainian,29,26,0,0,4,0,0,"2022-06-18 17:09:13"
|
|
||||||
"Gergő Mihály (mihalygergo97)",Hungarian,28,69,0,0,0,0,0,"2024-02-13 08:35:57"
|
|
||||||
"Niraj Yadav (neverforgetniraj)",Hindi,26,48,0,0,0,0,0,"2017-04-11 02:26:50"
|
"Niraj Yadav (neverforgetniraj)",Hindi,26,48,0,0,0,0,0,"2017-04-11 02:26:50"
|
||||||
"Guillaume Collic (gcollic)",French,26,28,0,126,11,0,0,"2017-05-05 16:13:00"
|
|
||||||
"Jonny I (jonny99dj)",Italian,26,26,0,5,0,0,0,"2017-10-07 07:35:34"
|
|
||||||
"Aaron Dalton (Perlkonig)",French,26,25,0,141,1,0,0,"2018-01-14 12:58:19"
|
"Aaron Dalton (Perlkonig)",French,26,25,0,141,1,0,0,"2018-01-14 12:58:19"
|
||||||
Pan_Filuta,Czech,25,21,0,5,11,0,3,"2017-04-29 12:55:14"
|
"Jonny I (jonny99dj)",Italian,26,26,0,5,0,0,0,"2017-10-07 07:35:34"
|
||||||
|
"Guillaume Collic (gcollic)",French,26,28,0,126,11,0,0,"2017-05-05 16:13:00"
|
||||||
|
Pan_Filuta,Czech,25,21,0,5,8,0,3,"2017-04-29 12:55:14"
|
||||||
"Eddie (eddieattaboy)","Chinese Traditional",25,34,0,1,0,0,0,"2020-11-04 21:48:05"
|
"Eddie (eddieattaboy)","Chinese Traditional",25,34,0,1,0,0,0,"2020-11-04 21:48:05"
|
||||||
REMOVED_USER,Catalan,24,25,0,2,0,0,0,"2019-06-26 14:59:47"
|
"eduard83 (barbany.eduard)",Catalan,24,25,0,2,0,0,0,"2019-06-26 14:59:47"
|
||||||
|
"A Aa (ylayzlmimashisafyoutub)",Arabic,23,33,0,34,1,1,0,"2021-09-27 15:34:26"
|
||||||
"Caner Başaran (basarancaner)",Turkish,23,21,0,0,26,1,0,"2017-04-09 06:34:59"
|
"Caner Başaran (basarancaner)",Turkish,23,21,0,0,26,1,0,"2017-04-09 06:34:59"
|
||||||
"Ľuboš Čaky (lubos.caky)",Slovak,23,22,0,0,0,0,0,"2019-07-02 16:51:44"
|
"Ľuboš Čaky (lubos.caky)",Slovak,23,22,0,0,0,0,0,"2019-07-02 16:51:44"
|
||||||
"A Aa (ylayzlmimashisafyoutub)",Arabic,23,33,0,34,3,2,0,"2021-09-27 15:34:26"
|
"Neeraj Verma (verma.neeraj.in)",Hindi,22,37,0,0,1,0,0,"2018-07-23 07:16:41"
|
||||||
|
gnu-ewm,Polish,22,23,0,6,2,0,0,"2021-02-24 03:42:01"
|
||||||
hodanli,Turkish,22,26,0,0,1,0,0,"2017-11-03 14:33:41"
|
hodanli,Turkish,22,26,0,0,1,0,0,"2017-11-03 14:33:41"
|
||||||
REMOVED_USER,Polish,22,23,0,6,9,0,0,"2021-02-24 03:42:01"
|
|
||||||
"Alcarkse (alexis.brusle)",French,21,25,0,7,11,0,0,"2017-08-06 09:32:29"
|
"Alcarkse (alexis.brusle)",French,21,25,0,7,11,0,0,"2017-08-06 09:32:29"
|
||||||
"Hugo Nogueira (hfrnogueira86)",Portuguese,21,22,0,0,0,0,0,"2023-01-09 22:51:24"
|
|
||||||
"Shashwat (goforgold)",Hindi,20,33,0,0,0,0,0,"2020-05-17 10:34:42"
|
"Shashwat (goforgold)",Hindi,20,33,0,0,0,0,0,"2020-05-17 10:34:42"
|
||||||
olbotta,Italian,20,25,0,2,0,0,0,"2021-06-06 04:22:55"
|
olbotta,Italian,20,25,0,2,0,0,0,"2021-06-06 04:22:55"
|
||||||
|
can13,Turkish,19,14,0,8,0,0,0,"2021-01-03 10:39:03"
|
||||||
|
"사자솥 (toke1597)",Korean,19,19,0,0,0,0,0,"2020-02-04 13:36:11"
|
||||||
|
KenKailer,Arabic,19,25,0,0,0,0,0,"2022-05-10 06:16:54"
|
||||||
"İsa Eş (IsaEs)",Turkish,19,17,0,0,6,2,0,"2017-06-20 07:30:22"
|
"İsa Eş (IsaEs)",Turkish,19,17,0,0,6,2,0,"2017-06-20 07:30:22"
|
||||||
"Magdalena Urbańczyk (madziia139)",Polish,19,19,0,0,0,0,0,"2017-10-21 03:01:04"
|
"Magdalena Urbańczyk (madziia139)",Polish,19,19,0,0,0,0,0,"2017-10-21 03:01:04"
|
||||||
sheeCesu,French,19,18,0,48,4,0,0,"2017-12-21 17:01:39"
|
sheeCesu,French,19,18,0,48,4,0,0,"2017-12-21 17:01:39"
|
||||||
"사자솥 (toke1597)",Korean,19,19,0,0,0,0,0,"2020-02-04 13:36:11"
|
|
||||||
can13,Turkish,19,14,0,8,0,0,0,"2021-01-03 10:39:03"
|
|
||||||
KenKailer,Arabic,19,25,0,0,0,0,0,"2022-05-10 06:16:54"
|
|
||||||
axikman11111,Uyghur,18,19,0,0,0,0,0,"2018-10-13 12:25:31"
|
axikman11111,Uyghur,18,19,0,0,0,0,0,"2018-10-13 12:25:31"
|
||||||
"Sanjay Krishna (sjaykh)",Malayalam,18,42,0,0,0,0,0,"2024-06-01 08:57:37"
|
Adeline31,French,17,20,0,3,0,0,0,"2019-12-06 00:00:11"
|
||||||
"Chuang-Chen Chiu (peterChiu9952)","Chinese Traditional",18,28,0,0,0,0,0,"2024-07-02 09:36:59"
|
|
||||||
AlexanderS,German,18,16,0,8,0,0,0,"2025-02-02 16:58:09"
|
|
||||||
"Ceara Lopez (cealopez)",Spanish,17,18,0,0,5,1,0,"2017-08-22 22:56:13"
|
|
||||||
Adeline31,French,17,20,0,3,1,0,0,"2019-12-06 00:00:11"
|
|
||||||
takoyakibento,Korean,17,13,0,3,0,0,0,"2020-08-01 08:44:15"
|
|
||||||
"Hoon Jung (hooni100)",Korean,17,10,0,0,0,0,0,"2021-01-03 02:26:54"
|
"Hoon Jung (hooni100)",Korean,17,10,0,0,0,0,0,"2021-01-03 02:26:54"
|
||||||
Annelotte,Dutch,17,20,0,0,0,0,0,"2022-11-09 08:33:20"
|
takoyakibento,Korean,17,13,0,3,0,0,0,"2020-08-01 08:44:15"
|
||||||
wppoqqqi,Korean,17,18,0,4,0,0,0,"2024-11-30 04:22:21"
|
"Ceara Lopez (cealopez)",Spanish,17,18,0,0,5,1,0,"2017-08-22 22:56:13"
|
||||||
"Şamil Ateşoğlu (m.samilatesoglu)",Turkish,16,22,0,11,6,3,0,"2017-07-05 18:37:08"
|
bretzel15,German,16,20,0,0,0,0,0,"2020-04-06 02:49:14"
|
||||||
DebatablySane,Bulgarian,16,15,0,48,0,0,0,"2017-07-10 15:13:18"
|
DebatablySane,Bulgarian,16,15,0,48,0,0,0,"2017-07-10 15:13:18"
|
||||||
bretzel15,German,16,20,0,0,1,0,0,"2020-04-06 02:49:14"
|
"Şamil Ateşoğlu (m.samilatesoglu)",Turkish,16,22,0,11,6,3,0,"2017-07-05 18:37:08"
|
||||||
engineeringforgood,Russian,16,15,0,0,0,0,16,"2021-01-22 03:32:35"
|
engineeringforgood,Russian,16,15,0,0,0,0,16,"2021-01-22 03:32:35"
|
||||||
"M7md Salahaddin (m7mdsalahaddin)",Arabic,16,16,0,1,0,0,0,"2024-07-17 18:49:07"
|
|
||||||
"Bhava Tharini (bhavidanush)",Tamil,15,37,0,0,0,0,0,"2019-10-09 05:43:11"
|
"Bhava Tharini (bhavidanush)",Tamil,15,37,0,0,0,0,0,"2019-10-09 05:43:11"
|
||||||
"Ch. (sftblw)",Korean,15,17,0,1,0,0,0,"2023-01-25 19:22:34"
|
"Maro Chr (caprisunglasses)",Greek,14,17,0,0,0,0,0,"2021-08-17 06:53:33"
|
||||||
iamsurajbobade,Hindi,14,30,0,0,0,0,0,"2018-05-21 11:23:27"
|
|
||||||
"Zeynep Esen (nezihaesen50)",Turkish,14,13,0,0,0,0,0,"2020-01-28 07:05:15"
|
"Zeynep Esen (nezihaesen50)",Turkish,14,13,0,0,0,0,0,"2020-01-28 07:05:15"
|
||||||
|
iamsurajbobade,Hindi,14,30,0,0,0,0,0,"2018-05-21 11:23:27"
|
||||||
|
"Faiz Ahamed (faiznewton)",Tamil,14,31,0,0,0,0,0,"2021-05-06 23:06:46"
|
||||||
"Sanji Vinsmock (mukanzhanbolat4)",Russian,14,14,0,0,0,0,0,"2020-02-18 12:38:54"
|
"Sanji Vinsmock (mukanzhanbolat4)",Russian,14,14,0,0,0,0,0,"2020-02-18 12:38:54"
|
||||||
|
"Zeeshan Rabbani (Zeera)",Hindi,14,25,0,0,0,0,0,"2020-09-15 11:32:01"
|
||||||
|
"pi hobbes (uwe_silv)",Japanese,14,46,0,0,0,0,0,"2022-01-15 02:57:14"
|
||||||
"Anastasia Borchuk (al2.borchuk)",Russian,14,14,0,0,0,0,0,"2020-04-14 13:22:49"
|
"Anastasia Borchuk (al2.borchuk)",Russian,14,14,0,0,0,0,0,"2020-04-14 13:22:49"
|
||||||
"Fikret Bilici (fikretbilici)",Turkish,14,13,0,0,0,0,0,"2020-06-21 17:16:11"
|
"Fikret Bilici (fikretbilici)",Turkish,14,13,0,0,0,0,0,"2020-06-21 17:16:11"
|
||||||
"EuiHo Hwang (euiho.hwang)",Korean,14,16,0,0,1,0,0,"2020-06-23 02:40:01"
|
"EuiHo Hwang (euiho.hwang)",Korean,14,16,0,0,0,0,0,"2020-06-23 02:40:01"
|
||||||
"Zeeshan Rabbani (Zeera)",Hindi,14,25,0,0,0,0,0,"2020-09-15 11:32:01"
|
|
||||||
"Faiz Ahamed (faiznewton)",Tamil,14,31,0,0,0,0,0,"2021-05-06 23:06:46"
|
|
||||||
"Maro Chr (caprisunglasses)",Greek,14,17,0,0,0,0,0,"2021-08-17 06:53:33"
|
|
||||||
"pi hobbes (uwe_silv)",Japanese,14,46,0,0,0,0,14,"2022-01-15 02:57:14"
|
|
||||||
"Mar Tous (mtousfernandez)",Catalan,14,18,0,0,0,0,0,"2022-08-16 17:55:28"
|
|
||||||
Ferhatt,Turkish,14,13,0,3,0,0,0,"2022-12-13 12:52:20"
|
|
||||||
alchemiker,German,14,13,0,0,0,0,0,"2024-09-03 10:16:04"
|
|
||||||
"Nenad Vukotic (vukotic.nenad)","Serbian (Cyrillic)",13,13,0,1,2,6,0,"2019-01-31 14:29:15"
|
|
||||||
"Dave (xdave)",Hungarian,13,11,0,0,0,0,0,"2020-03-02 20:56:50"
|
|
||||||
"Uwe Mönks (schirinowski)",German,13,12,0,0,0,0,0,"2021-02-18 04:00:41"
|
"Uwe Mönks (schirinowski)",German,13,12,0,0,0,0,0,"2021-02-18 04:00:41"
|
||||||
Herbie_23,Italian,13,15,0,0,0,0,0,"2022-01-17 17:35:40"
|
"Dave (xdave)",Hungarian,13,11,0,0,0,0,0,"2020-03-02 20:56:50"
|
||||||
"Ana Kelly Vale (anakvale)","Portuguese, Brazilian",13,21,0,4,0,0,2,"2022-03-30 00:15:37"
|
"Ana Kelly Vale (anakvale)","Portuguese, Brazilian",13,21,0,4,0,0,2,"2022-03-30 00:15:37"
|
||||||
"Minsu (cknblue)",Korean,13,10,0,1,0,0,0,"2022-05-18 00:26:54"
|
GiorgioHerbie,Italian,13,15,0,0,0,0,0,"2022-01-17 17:35:40"
|
||||||
"shreyas (techiespace)",Hindi,12,20,0,0,0,0,0,"2018-06-10 01:14:26"
|
"Nenad Vukotic (vukotic.nenad)","Serbian (Cyrillic)",13,13,0,1,2,6,0,"2019-01-31 14:29:15"
|
||||||
soura2,Arabic,12,13,0,0,0,0,0,"2020-01-13 19:23:47"
|
soura2,Arabic,12,13,0,0,0,0,0,"2020-01-13 19:23:47"
|
||||||
"Ammar Naif (Ammar_Naif)",Arabic,12,12,0,6,0,0,0,"2022-01-15 05:16:41"
|
"shreyas (techiespace)",Hindi,12,20,0,0,0,0,0,"2018-06-10 01:14:26"
|
||||||
"Jo Chuang (josephch405)","Chinese Traditional",11,24,0,0,0,0,11,"2017-06-16 20:21:06"
|
"Jo Chuang (josephch405)","Chinese Traditional",11,24,0,0,0,0,11,"2017-06-16 20:21:06"
|
||||||
Vmrc,French,11,12,0,2,0,0,0,"2020-11-02 05:35:06"
|
Vmrc,French,11,12,0,2,0,0,0,"2020-11-02 05:35:06"
|
||||||
|
"Ammar Naif (Ammar_Naif)",Arabic,11,11,0,4,0,0,0,"2022-01-15 05:16:41"
|
||||||
"Sonu Sharma (riteetude)",Hindi,11,23,0,0,0,0,0,"2021-05-30 19:38:00"
|
"Sonu Sharma (riteetude)",Hindi,11,23,0,0,0,0,0,"2021-05-30 19:38:00"
|
||||||
"Lucas Depetris (lucasdepetrisd)","Spanish; Catalan; Italian; French",11,12,0,12,0,0,0,"2023-10-24 21:16:21"
|
|
||||||
"Mihael Wagner (miha.wagner)",Slovenian,10,9,0,7,0,0,0,"2017-10-18 18:26:29"
|
|
||||||
"Anonymous edgy nerd (yamentaad)",Arabic,10,13,0,1,0,0,0,"2018-05-06 09:23:57"
|
|
||||||
"Edwin van Rooij (edwinvrooij)",Dutch,10,13,0,17,0,0,0,"2018-11-05 03:59:10"
|
"Edwin van Rooij (edwinvrooij)",Dutch,10,13,0,17,0,0,0,"2018-11-05 03:59:10"
|
||||||
"Brian Camacho (bmcamacho)",Polish,10,11,0,0,1,1,0,"2020-08-03 02:27:28"
|
"Brian Camacho (bmcamacho)",Polish,10,11,0,0,1,1,0,"2020-08-03 02:27:28"
|
||||||
|
"Mihael Wagner (miha.wagner)",Slovenian,10,9,0,7,0,0,0,"2017-10-18 18:26:29"
|
||||||
|
"Hrant Hakobian (hrastgh1)",Armenian,10,9,0,0,0,0,0,"2021-08-29 15:22:10"
|
||||||
"sathvic k (sathvictripleseven)",Telugu,10,17,0,0,0,0,0,"2020-09-11 08:11:32"
|
"sathvic k (sathvictripleseven)",Telugu,10,17,0,0,0,0,0,"2020-09-11 08:11:32"
|
||||||
"Ahmed Mosaad (ahmed.mosaad2018)",Arabic,10,12,0,6,0,0,0,"2021-02-03 18:45:43"
|
"Ahmed Mosaad (ahmed.mosaad2018)",Arabic,10,12,0,6,0,0,0,"2021-02-03 18:45:43"
|
||||||
"Hrant Hakobian (hrastgh1)",Armenian,10,9,0,0,0,0,0,"2021-08-29 15:22:10"
|
"Anonymous edgy nerd (yamentaad)",Arabic,10,13,0,1,0,0,0,"2018-05-06 09:23:57"
|
||||||
"Milan Siebenbürger (lennyd)",Czech,10,7,0,1,1,0,0,"2022-01-30 07:09:42"
|
|
||||||
"Zesar Cebrián (Txorrota)",Spanish,10,44,0,0,0,0,0,"2022-02-09 01:34:32"
|
"Zesar Cebrián (Txorrota)",Spanish,10,44,0,0,0,0,0,"2022-02-09 01:34:32"
|
||||||
"Слави Велчев (BRO36S) (gg13656)",Bulgarian,10,11,0,9,0,0,0,"2025-01-09 08:47:04"
|
"Milan Siebenbürger (lennyd)",Czech,10,7,0,1,0,0,0,"2022-01-30 07:09:42"
|
||||||
"Sourire Lucide (sourire_lucide)",Russian,9,10,0,0,1,0,0,"2018-03-22 01:37:55"
|
|
||||||
"Suhaili Hassan (kucingsyg96)",Indonesian,9,10,0,0,0,0,0,"2018-06-10 11:55:09"
|
"Suhaili Hassan (kucingsyg96)",Indonesian,9,10,0,0,0,0,0,"2018-06-10 11:55:09"
|
||||||
"Martin Vostatek (martinvostatek)",Czech,9,8,0,32,3,0,0,"2019-01-21 13:52:36"
|
"Sourire Lucide (sourire_lucide)",Russian,9,10,0,0,1,0,0,"2018-03-22 01:37:55"
|
||||||
"Seweryn Piotrowski (Draxxsx)",Polish,9,10,0,0,26,0,0,"2020-01-02 09:55:48"
|
"Martin Vostatek (martinvostatek)",Czech,9,8,0,32,2,0,0,"2019-01-21 13:52:36"
|
||||||
omoise,French,9,11,0,1,0,0,0,"2023-12-26 14:27:17"
|
"Seweryn Piotrowski (Draxxsx)",Polish,9,10,0,0,19,0,0,"2020-01-02 09:55:48"
|
||||||
|
"Jakob Weickmann (jweickm)",Japanese,8,21,0,0,0,0,0,"2021-10-05 11:10:25"
|
||||||
Rex123,Persian,8,8,0,0,0,0,0,"2017-07-01 00:47:42"
|
Rex123,Persian,8,8,0,0,0,0,0,"2017-07-01 00:47:42"
|
||||||
"Jakob Weickmann (jweickm)",Japanese,8,21,0,0,0,0,8,"2021-10-05 11:10:25"
|
|
||||||
"Andrey ZaXeLoN (waragaa)",Russian,7,7,0,8,1,0,0,"2017-09-18 21:37:42"
|
"Andrey ZaXeLoN (waragaa)",Russian,7,7,0,8,1,0,0,"2017-09-18 21:37:42"
|
||||||
"Vladimir Pavlychev (vovs03)",Russian,7,9,0,0,0,0,0,"2017-12-18 02:46:56"
|
|
||||||
"Konstantin (KZhidovinov)",Russian,7,7,0,0,0,0,0,"2020-01-29 13:35:12"
|
"Konstantin (KZhidovinov)",Russian,7,7,0,0,0,0,0,"2020-01-29 13:35:12"
|
||||||
pkorove,Greek,7,7,0,0,1,0,0,"2020-03-07 11:36:12"
|
|
||||||
ftfoi,Norwegian,7,6,0,0,0,0,0,"2020-04-11 20:42:35"
|
ftfoi,Norwegian,7,6,0,0,0,0,0,"2020-04-11 20:42:35"
|
||||||
"Андрій Козицький (andriikozytskyi3807)",Ukrainian,7,12,0,2,0,0,0,"2020-09-26 20:31:56"
|
"Vladimir Pavlychev (vovs03)",Russian,7,9,0,0,0,0,0,"2017-12-18 02:46:56"
|
||||||
"Felipe Chagas (chagretes)","Portuguese, Brazilian",7,8,0,0,3,0,5,"2022-01-10 12:20:25"
|
"Felipe Chagas (chagretes)","Portuguese, Brazilian",7,8,0,0,3,0,5,"2022-01-10 12:20:25"
|
||||||
"Tomáš Miklovič (zyppi)",Slovak,7,7,0,0,0,0,0,"2022-09-11 15:12:06"
|
"Андрій Козицький (andriikozytskyi3807)",Ukrainian,7,12,0,2,0,0,0,"2020-09-26 20:31:56"
|
||||||
"Sam (SorodonSorodon)",German,6,6,0,13,0,0,0,"2017-04-14 11:09:27"
|
pkorove,Greek,7,7,0,0,0,0,0,"2020-03-07 11:36:12"
|
||||||
ChloeLiang,Japanese,6,22,0,0,1,0,3,"2017-08-08 05:02:59"
|
ChloeLiang,Japanese,6,22,0,0,1,0,3,"2017-08-08 05:02:59"
|
||||||
|
"Sam (SorodonSorodon)",German,6,6,0,13,0,0,0,"2017-04-14 11:09:27"
|
||||||
"닉닉 (seohu9466)",Korean,6,14,0,13,0,0,0,"2017-10-09 23:08:15"
|
"닉닉 (seohu9466)",Korean,6,14,0,13,0,0,0,"2017-10-09 23:08:15"
|
||||||
|
"Sarita Cajas (sarayanacajas)",Spanish,6,4,0,0,1,0,0,"2021-05-14 14:27:59"
|
||||||
erfan2927,Persian,6,6,0,0,0,0,0,"2018-04-09 02:12:44"
|
erfan2927,Persian,6,6,0,0,0,0,0,"2018-04-09 02:12:44"
|
||||||
"Burak Ceylan (7burakceylan)",Turkish,6,6,0,0,0,0,0,"2018-05-20 17:24:19"
|
"Burak Ceylan (7burakceylan)",Turkish,6,6,0,0,0,0,0,"2018-05-20 17:24:19"
|
||||||
"Sarita Cajas (sarayanacajas)",Spanish,6,4,0,0,1,0,0,"2021-05-14 14:27:59"
|
|
||||||
"خالد (mkhrafi1999)",Arabic,6,3,0,6,0,0,0,"2023-06-25 00:36:13"
|
|
||||||
"Matthias Joly (joly.matt12)",French,5,8,0,27,1,0,0,"2017-08-28 09:53:59"
|
|
||||||
andriikozytskyi2018,Ukrainian,5,5,0,0,0,0,0,"2017-09-03 05:24:43"
|
andriikozytskyi2018,Ukrainian,5,5,0,0,0,0,0,"2017-09-03 05:24:43"
|
||||||
"Дмитрий Хапенков (d.khapenkov)",Russian,5,5,0,6,4,0,2,"2018-01-06 23:00:43"
|
|
||||||
"Guerra Ivaneth (rossanaiva-04)",Spanish,5,7,0,0,0,0,0,"2019-02-03 16:48:59"
|
|
||||||
"Micaela Pighin (micaelapiighin)",Spanish,5,6,0,1,0,0,0,"2019-10-09 23:32:42"
|
|
||||||
"Manuel (Mannivu)",Italian,5,6,0,0,0,0,0,"2021-01-03 11:00:33"
|
|
||||||
"Tomáš Hrabáček (Hrabyyy)",Czech,5,3,0,0,2,0,0,"2021-05-27 11:58:11"
|
|
||||||
"Vitor Henrique (vitorhcl)","Portuguese, Brazilian",5,8,0,1,0,0,0,"2022-03-08 20:00:59"
|
"Vitor Henrique (vitorhcl)","Portuguese, Brazilian",5,8,0,1,0,0,0,"2022-03-08 20:00:59"
|
||||||
SubhamJena,Hindi,5,12,0,3,0,0,0,"2023-04-19 11:13:19"
|
"Matthias Joly (joly.matt12)",French,5,8,0,27,1,0,0,"2017-08-28 09:53:59"
|
||||||
|
"Tomáš Hrabáček (Hrabyyy)",Czech,5,3,0,0,1,0,0,"2021-05-27 11:58:11"
|
||||||
|
"Guerra Ivaneth (rossanaiva-04)",Spanish,5,7,0,0,0,0,0,"2019-02-03 16:48:59"
|
||||||
|
"Дмитрий Хапенков (d.khapenkov)",Russian,5,5,0,6,4,0,2,"2018-01-06 23:00:43"
|
||||||
|
"Micaela Pighin (micaelapiighin)",Spanish,5,6,0,1,0,0,0,"2019-10-09 23:32:42"
|
||||||
|
"Manuel Tassi (Mannivu)",Italian,5,6,0,0,0,0,0,"2021-01-03 11:00:33"
|
||||||
|
"Neko123 (emandic11)","Serbian (Cyrillic)",4,4,0,57,0,0,0,"2021-04-21 15:33:29"
|
||||||
"Lopo Isaac Fernández (rocapata)",Spanish,4,3,0,0,0,0,0,"2018-09-20 11:46:22"
|
"Lopo Isaac Fernández (rocapata)",Spanish,4,3,0,0,0,0,0,"2018-09-20 11:46:22"
|
||||||
"Eli Besirov (elibesirov07)",Turkish,4,4,0,0,0,0,0,"2019-03-25 07:12:34"
|
"Eli Besirov (elibesirov07)",Turkish,4,4,0,0,0,0,0,"2019-03-25 07:12:34"
|
||||||
bziuum,Polish,4,4,0,0,6,0,0,"2020-09-01 09:08:01"
|
|
||||||
marmo,German,4,4,0,0,0,0,0,"2021-01-13 01:16:35"
|
marmo,German,4,4,0,0,0,0,0,"2021-01-13 01:16:35"
|
||||||
"Neko123 (emandic11)","Serbian (Cyrillic)",4,4,0,57,0,0,1,"2021-04-21 15:33:29"
|
bziuum,Polish,4,4,0,0,3,0,0,"2020-09-01 09:08:01"
|
||||||
"Mo Heydari (Mrheydari)",Dutch,4,4,0,0,0,0,0,"2023-02-21 04:54:49"
|
"Craig Foobar (craig.foobar)",German,3,3,0,25,0,0,0,"2022-02-20 16:55:47"
|
||||||
"Srekaravarshan N K (srekaravarshan)",Tamil,4,4,0,0,0,0,0,"2023-04-22 06:08:57"
|
Katarin,Ukrainian,3,3,0,0,0,0,0,"2022-03-17 14:44:59"
|
||||||
"Thoum Ptrgnt (thomas.petrignet)",French,3,3,0,2,0,3,0,"2017-09-23 19:25:52"
|
"Sarath S (CyberShark)",Tamil,3,7,0,0,0,0,0,"2020-08-27 22:43:16"
|
||||||
carsten_kafke,German,3,3,0,43,0,0,3,"2017-10-27 13:27:47"
|
|
||||||
"Vagner Roberto (vagner.trompete)","Portuguese, Brazilian",3,3,0,0,0,0,0,"2017-12-30 17:54:26"
|
"Vagner Roberto (vagner.trompete)","Portuguese, Brazilian",3,3,0,0,0,0,0,"2017-12-30 17:54:26"
|
||||||
"Igor Piskun (i_piskun)",Ukrainian,3,3,0,0,0,0,0,"2018-01-19 15:20:27"
|
"Igor Piskun (i_piskun)",Ukrainian,3,3,0,0,0,0,0,"2018-01-19 15:20:27"
|
||||||
"Andrea Bianchi (andreawhite1597)",Italian,3,1,0,1,0,0,0,"2018-01-21 17:45:48"
|
|
||||||
"Gabriel Cavalcante (gabrielc.alves14)","Portuguese, Brazilian",3,4,0,0,0,0,0,"2018-08-06 22:24:54"
|
|
||||||
"joabe gabriel (joabegabrielcma1)","Portuguese, Brazilian",3,4,0,0,0,0,0,"2018-08-21 09:08:59"
|
|
||||||
"Martin Zimdahl (zimdahlmartin)",Swedish,3,2,0,0,2,0,3,"2018-09-15 04:39:22"
|
|
||||||
REMOVED_USER,"Portuguese, Brazilian",3,4,0,0,0,0,0,"2018-11-18 09:02:37"
|
|
||||||
"Oleg Kogut (kogut_oleg)",Ukrainian,3,3,0,0,0,0,0,"2018-12-28 14:31:02"
|
|
||||||
"Cláudio Bernardo (claudiobernardo.ti)","Portuguese, Brazilian",3,4,0,1,0,0,0,"2019-01-08 14:41:10"
|
"Cláudio Bernardo (claudiobernardo.ti)","Portuguese, Brazilian",3,4,0,1,0,0,0,"2019-01-08 14:41:10"
|
||||||
atomjani,Hungarian,3,3,0,0,0,0,0,"2019-01-19 00:49:25"
|
|
||||||
"Hiohana Rilary (hiohanarilary)","Portuguese, Brazilian",3,4,0,0,0,0,0,"2019-07-31 20:42:20"
|
|
||||||
"Péter Bernát (bernatp)",Hungarian,3,2,0,0,0,0,0,"2019-11-30 15:50:33"
|
|
||||||
"Unnie Here (Carb)",Hindi,3,8,0,0,0,0,0,"2020-03-18 23:34:35"
|
"Unnie Here (Carb)",Hindi,3,8,0,0,0,0,0,"2020-03-18 23:34:35"
|
||||||
"Sarath S (CyberShark)",Tamil,3,7,0,0,0,0,0,"2020-08-27 22:43:16"
|
REMOVED_USER,"Portuguese, Brazilian",3,4,0,0,0,0,0,"2018-11-18 09:02:37"
|
||||||
|
"Thoum Ptrgnt (thomas.petrignet)",French,3,3,0,2,0,3,0,"2017-09-23 19:25:52"
|
||||||
|
"Oleg Kogut (kogut_oleg)",Ukrainian,3,3,0,0,0,0,0,"2018-12-28 14:31:02"
|
||||||
|
carsten_kafke,German,3,3,0,43,0,0,3,"2017-10-27 13:27:47"
|
||||||
Magidxz,Arabic,3,3,0,0,0,0,0,"2021-01-05 05:02:54"
|
Magidxz,Arabic,3,3,0,0,0,0,0,"2021-01-05 05:02:54"
|
||||||
|
"Péter Bernát (bernatp)",Hungarian,3,2,0,0,0,0,0,"2019-11-30 15:50:33"
|
||||||
|
"joabe gabriel (joabegabrielcma1)","Portuguese, Brazilian",3,4,0,0,0,0,0,"2018-08-21 09:08:59"
|
||||||
|
"Gabriel Cavalcante (gabrielc.alves14)","Portuguese, Brazilian",3,4,0,0,0,0,0,"2018-08-06 22:24:54"
|
||||||
|
"Martin Zimdahl (zimdahlmartin)",Swedish,3,2,0,0,1,0,3,"2018-09-15 04:39:22"
|
||||||
|
atomjani,Hungarian,3,3,0,0,0,0,0,"2019-01-19 00:49:25"
|
||||||
"mohammadali barati (mabaraty)",Persian,3,3,0,0,0,0,0,"2021-07-10 05:54:44"
|
"mohammadali barati (mabaraty)",Persian,3,3,0,0,0,0,0,"2021-07-10 05:54:44"
|
||||||
|
"Hiohana Rilary (hiohanarilary)","Portuguese, Brazilian",3,4,0,0,0,0,0,"2019-07-31 20:42:20"
|
||||||
"Tejaswini Boppana (Tejaswini)",Telugu,3,1,0,0,0,0,0,"2021-08-27 23:48:55"
|
"Tejaswini Boppana (Tejaswini)",Telugu,3,1,0,0,0,0,0,"2021-08-27 23:48:55"
|
||||||
"Craig Foobar (craig.foobar)",German,3,3,0,25,1,0,0,"2022-02-20 16:55:47"
|
"Andrea Bianchi (andreawhite1597)",Italian,3,1,0,1,0,0,0,"2018-01-21 17:45:48"
|
||||||
Katarin,Ukrainian,3,3,0,0,0,0,0,"2022-03-17 14:44:59"
|
"Ño Bí Tã (pt614553)",Arabic,2,8,0,1,0,2,0,"2021-05-22 20:41:01"
|
||||||
ayet,Arabic,3,3,0,0,0,0,0,"2023-08-16 20:18:12"
|
"Judith Ayala (Azul1612)",Spanish,2,1,0,0,0,1,0,"2021-05-18 17:07:19"
|
||||||
|
"Valerij D (vala.dobler)",German,2,2,0,0,0,0,0,"2018-09-22 09:38:27"
|
||||||
|
"Balthazar Aubard (Balatzar)",French,2,5,0,0,1,0,0,"2017-09-23 01:42:57"
|
||||||
|
"Ahmed Bazazo (ahmedbazazo)",Arabic,2,2,0,0,0,0,0,"2022-02-19 20:11:09"
|
||||||
|
"Ali Zaida (alizaeda92)",Arabic,2,2,0,0,0,0,0,"2019-12-01 11:47:00"
|
||||||
|
"FAy FAy (fayfayfay52)","Chinese Traditional",2,5,0,0,0,0,0,"2017-10-06 08:53:21"
|
||||||
|
Soroor_SI,Persian,2,2,0,0,0,0,0,"2018-06-10 06:28:27"
|
||||||
|
chavs1997,Russian,2,2,0,9,0,0,0,"2018-05-18 16:58:19"
|
||||||
|
"Naveen jai krishna (njsbpolymer1)",Tamil,2,5,0,0,0,0,0,"2020-01-10 14:19:41"
|
||||||
|
omerfarukbas,Turkish,2,3,0,19,2,0,0,"2017-08-14 16:10:35"
|
||||||
|
"Ilyas Fekhar (il47yas)",Arabic,2,2,0,0,0,0,0,"2018-04-17 22:00:41"
|
||||||
|
"Héctor Mañas García (hectodium)",Catalan,2,3,0,0,0,0,0,"2021-10-02 20:32:09"
|
||||||
|
"Walid Baazia (walidbaazia2005)",Arabic,2,1,0,0,0,0,0,"2021-01-27 12:47:34"
|
||||||
|
"fatemeh s (fargolseifoori3)",Persian,2,2,0,0,0,0,0,"2019-01-31 12:06:57"
|
||||||
|
"hesamiranii (esam.matouri)",Persian,2,2,0,0,0,0,0,"2018-09-22 16:33:36"
|
||||||
REMOVED_USER,Ukrainian,2,2,0,0,0,0,0,"2017-06-15 12:24:44"
|
REMOVED_USER,Ukrainian,2,2,0,0,0,0,0,"2017-06-15 12:24:44"
|
||||||
"Alex Stein (diefaust1993)",Russian,2,2,0,4,4,0,2,"2017-07-13 06:56:17"
|
"Alex Stein (diefaust1993)",Russian,2,2,0,4,4,0,2,"2017-07-13 06:56:17"
|
||||||
omerfarukbas,Turkish,2,3,0,19,2,0,0,"2017-08-14 16:10:35"
|
|
||||||
"Balthazar Aubard (Balatzar)",French,2,5,0,0,1,0,0,"2017-09-23 01:42:57"
|
|
||||||
"FAy FAy (fayfayfay52)","Chinese Traditional",2,5,0,0,0,0,0,"2017-10-06 08:53:21"
|
|
||||||
"Ilyas Fekhar (il47yas)",Arabic,2,2,0,0,0,0,0,"2018-04-17 22:00:41"
|
|
||||||
amei,"Portuguese, Brazilian",2,2,0,0,0,0,0,"2018-04-19 19:42:28"
|
amei,"Portuguese, Brazilian",2,2,0,0,0,0,0,"2018-04-19 19:42:28"
|
||||||
"Jimmy Young (Jimmyyoung)","Chinese Traditional",2,4,0,0,0,0,2,"2018-04-22 04:00:19"
|
|
||||||
chavs1997,Russian,2,2,0,9,0,0,0,"2018-05-18 16:58:19"
|
|
||||||
Soroor_SI,Persian,2,2,0,0,0,0,0,"2018-06-10 06:28:27"
|
|
||||||
"Valerij D (vala.dobler)",German,2,2,0,0,0,0,0,"2018-09-22 09:38:27"
|
|
||||||
"hesamiranii (esam.matouri)",Persian,2,2,0,0,0,0,0,"2018-09-22 16:33:36"
|
|
||||||
"fatemeh s (fargolseifoori3)",Persian,2,2,0,0,0,0,0,"2019-01-31 12:06:57"
|
|
||||||
"Danial Agh (danialagh)",Persian,2,3,0,0,0,0,0,"2019-03-30 13:24:16"
|
|
||||||
"조화정 (yunjoo337)",Korean,2,2,0,0,0,0,0,"2019-06-16 22:25:31"
|
|
||||||
"Ali Zaida (alizaeda92)",Arabic,2,2,0,0,0,0,0,"2019-12-01 11:47:00"
|
|
||||||
"Naveen jai krishna (njsbpolymer1)",Tamil,2,5,0,0,1,0,0,"2020-01-10 14:19:41"
|
|
||||||
"أم محمد تقي (souadboudia19)",Arabic,2,2,0,0,0,0,0,"2020-06-13 15:24:17"
|
"أم محمد تقي (souadboudia19)",Arabic,2,2,0,0,0,0,0,"2020-06-13 15:24:17"
|
||||||
"Walid Baazia (walidbaazia2005)",Arabic,2,1,0,0,0,0,0,"2021-01-27 12:47:34"
|
LNDDYL,"Chinese Traditional",2,4,0,0,0,0,2,"2018-04-22 04:00:19"
|
||||||
"Judith Ayala (Azul1612)",Spanish,2,1,0,0,0,1,0,"2021-05-18 17:07:19"
|
"조화정 (yunjoo337)",Korean,2,2,0,0,0,0,0,"2019-06-16 22:25:31"
|
||||||
"Ño Bí Tã (pt614553)",Arabic,2,8,0,1,0,2,0,"2021-05-22 20:41:01"
|
|
||||||
"Héctor Mañas García (hectodium)",Catalan,2,3,0,0,0,0,0,"2021-10-02 20:32:09"
|
|
||||||
"Sidali Aymen (sidaliaymen950)",Arabic,2,2,0,0,0,0,0,"2022-01-31 18:50:59"
|
"Sidali Aymen (sidaliaymen950)",Arabic,2,2,0,0,0,0,0,"2022-01-31 18:50:59"
|
||||||
"Ahmed Bazazo (ahmedbazazo)",Arabic,2,2,0,0,0,0,0,"2022-02-19 20:11:09"
|
"Danial Agh (danialagh)",Persian,2,3,0,0,0,0,0,"2019-03-30 13:24:16"
|
||||||
"HypemanKEK (rocasta.dodvi)",Russian,2,2,0,0,0,0,0,"2023-01-13 03:45:06"
|
|
||||||
"Gmas (gustmaes1)",Dutch,2,2,0,0,0,0,0,"2023-01-26 13:52:54"
|
|
||||||
"Катерина Ховалкина (katerina959)",Russian,2,2,0,1,0,0,0,"2023-05-18 14:50:30"
|
|
||||||
"Pratheba Devanathan (devprath05)",Tamil,2,2,0,0,1,0,0,"2023-06-05 12:35:45"
|
|
||||||
"Gordon James Campbell (gordonjamescampbell)",Catalan,2,2,0,0,0,0,0,"2023-06-23 23:23:46"
|
|
||||||
"Suren Airyanam (suren.airyanam)",Persian,2,2,0,0,0,0,0,"2023-10-21 18:01:41"
|
|
||||||
axq,German,2,1,0,1,0,0,0,"2024-02-29 17:14:13"
|
|
||||||
"Hades Mori (Hades_Mori)",Ukrainian,2,2,0,0,0,0,0,"2024-07-03 07:14:58"
|
|
||||||
iSoron2,"Portuguese, Brazilian",1,1,0,0,0,0,0,"2017-03-18 17:56:29"
|
iSoron2,"Portuguese, Brazilian",1,1,0,0,0,0,0,"2017-03-18 17:56:29"
|
||||||
"Anton (tT0NG)","Chinese Traditional",1,2,0,0,0,0,1,"2017-07-06 14:18:39"
|
"Anton (tT0NG)","Chinese Traditional",1,2,0,0,0,0,1,"2017-07-06 14:18:39"
|
||||||
axd,Spanish,1,1,0,15,1,0,0,"2017-09-12 05:48:51"
|
|
||||||
"Wibi Cahyo (wbcahyoh)",Indonesian,1,3,0,0,0,0,0,"2017-12-14 06:35:58"
|
|
||||||
"Alan Jeon (skyisle)",Korean,1,2,0,8,0,0,0,"2018-01-09 10:46:00"
|
|
||||||
"박찌 (perpact20)",Korean,1,1,0,0,0,0,0,"2018-02-10 10:11:44"
|
|
||||||
"Patrick Pimenta (trickap1)","Portuguese, Brazilian",1,1,0,0,0,0,0,"2018-12-01 14:31:21"
|
|
||||||
"Kan Black (kanblack.va)",Vietnamese,1,2,0,0,0,1,0,"2019-01-15 03:50:10"
|
|
||||||
"Maria Fefelova (mashafefel)",Russian,1,1,0,0,0,0,0,"2019-05-18 02:03:56"
|
|
||||||
"Anastasiia Bondarenko (nastasya.bondarenko.97)",Russian,1,1,0,0,0,0,0,"2019-06-07 17:43:08"
|
|
||||||
REMOVED_USER,Russian,1,2,0,6,1,0,1,"2019-12-26 05:37:01"
|
|
||||||
"Luca Gori (grolcu)",Italian,1,2,0,0,0,0,0,"2020-09-26 23:26:15"
|
"Luca Gori (grolcu)",Italian,1,2,0,0,0,0,0,"2020-09-26 23:26:15"
|
||||||
|
axd,Spanish,1,1,0,15,0,0,0,"2017-09-12 05:48:51"
|
||||||
|
REMOVED_USER,Russian,1,2,0,6,1,0,1,"2019-12-26 05:37:01"
|
||||||
jonesses,German,1,1,0,1,0,0,1,"2021-01-01 08:03:18"
|
jonesses,German,1,1,0,1,0,0,1,"2021-01-01 08:03:18"
|
||||||
"monir s (ms.alam)",Persian,1,1,0,1,0,0,0,"2022-05-23 06:34:00"
|
"Alan Jeon (skyisle)",Korean,1,2,0,8,0,0,0,"2018-01-09 10:46:00"
|
||||||
"Daniel Luque (LuqueDaniel)",Spanish,1,1,0,0,0,0,0,"2022-06-30 20:37:07"
|
"Maria Fefelova (mashafefel)",Russian,1,1,0,0,0,0,0,"2019-05-18 02:03:56"
|
||||||
Liboide,Spanish,1,1,0,0,0,0,0,"2022-11-03 23:57:45"
|
"Patrick Pimenta (trickap1)","Portuguese, Brazilian",1,1,0,0,0,0,0,"2018-12-01 14:31:21"
|
||||||
"K RITE (kriteshojha5)",Hindi,1,1,0,1,0,0,0,"2024-08-14 17:28:55"
|
"박찌 (perpact20)",Korean,1,1,0,0,0,0,0,"2018-02-10 10:11:44"
|
||||||
"Angelos (angelos1993)",Arabic,1,1,0,0,0,0,0,"2024-11-20 07:38:18"
|
"Kan Black (kanblack.va)",Vietnamese,1,2,0,0,0,1,0,"2019-01-15 03:50:10"
|
||||||
"Bora Atıcı (Brtc)",Turkish,1,2,0,0,0,0,0,"2025-03-07 20:33:19"
|
"Anastasiia Bondarenko (nastasya.bondarenko.97)",Russian,1,1,0,0,0,0,0,"2019-06-07 17:43:08"
|
||||||
|
"Wibi Cahyo (wbcahyoh)",Indonesian,1,3,0,0,0,0,0,"2017-12-14 06:35:58"
|
||||||
|
sanyoniket,,0,0,0,0,0,0,0,"2019-07-23 12:58:40"
|
||||||
|
"Sri Harsha Bhogi (sriharshabhogi)",,0,0,0,0,0,0,0,"2018-09-02 05:31:53"
|
||||||
|
Irsgram,Russian,0,0,0,1,0,0,0,"2019-09-30 16:42:20"
|
||||||
|
"Baran Özavcı (n2141n)",Turkish,0,0,0,1,0,0,0,"2022-02-26 04:32:51"
|
||||||
|
"Masataka Yakura (myakura)",Japanese,0,0,0,1,0,0,0,"2021-09-03 22:10:36"
|
||||||
|
ava_rfie,Persian,0,0,0,1,0,0,0,"2019-06-09 16:19:24"
|
||||||
|
T-v-Gerwen,Dutch,0,0,0,47,0,0,0,"2018-03-02 10:26:33"
|
||||||
|
"George Merkulov (george142.emarket)",Russian,0,0,0,11,0,0,0,"2019-06-09 19:47:02"
|
||||||
|
philfr49,French,0,0,0,2,0,0,0,"2018-09-03 14:20:32"
|
||||||
|
"عبد الناصر سعيد الثبيتي (asaeed)",,0,0,0,0,0,0,0,"2018-03-13 02:09:35"
|
||||||
|
"Thomas Orlita (Thomas995)",Czech,0,0,0,1,0,0,0,"2017-12-24 04:08:27"
|
||||||
|
"Edmunds Edmundam (edmundam)",,0,0,0,0,0,0,0,"2020-06-01 14:18:18"
|
||||||
|
"Elmo (oberknecht)",,0,0,0,0,0,0,0,"2020-04-16 08:45:50"
|
||||||
|
"Равиль Мифтахов (ravilmif47)",Russian,0,0,0,1,0,0,0,"2019-08-12 21:58:30"
|
||||||
|
"Manny Farsangy (manifarsangi)",Persian,0,0,0,12,0,0,0,"2021-08-10 05:32:28"
|
||||||
|
"Samuel Przeździęk (samek22)",Polish,0,0,0,1,0,0,0,"2021-08-01 00:49:01"
|
||||||
|
"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,"2018-08-24 00:17:43"
|
||||||
|
REMOVED_USER,,0,0,0,0,0,0,0,"2020-02-01 03:47:48"
|
||||||
|
"Arjun K. (arjunkdot)",,0,0,0,0,0,0,0,"2020-09-20 11:16:18"
|
||||||
|
EwanB,,0,0,0,0,0,0,0,"2019-11-19 10:04:38"
|
||||||
|
shuvo786,,0,0,0,0,0,0,0,"2019-11-13 00:18:12"
|
||||||
|
"Pro AAA (pro1010)",Arabic,0,0,0,1,0,0,0,"2022-02-14 03:32:44"
|
||||||
|
"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"
|
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"
|
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"
|
"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"
|
"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"
|
Kamalakannan,,0,0,0,0,0,0,0,"2017-05-14 11:40:23"
|
||||||
"Éjbãss Übbeî (littlebittlebottle)",Norwegian,0,0,0,152,0,0,0,"2017-07-05 21:12:02"
|
|
||||||
"Dagna Q (dagnaq)",,0,0,0,0,0,0,0,"2017-08-06 01:42:52"
|
|
||||||
"Petros Bleyan (coolbleyan)",Russian,0,0,0,14,0,0,0,"2017-08-18 18:37:18"
|
|
||||||
Itch,,0,0,0,0,0,0,0,"2017-10-16 09:18:42"
|
|
||||||
"Muhammet Furkan ALMACI (furkan.almaci)",Turkish,0,0,0,1,0,0,0,"2017-10-29 13:44:56"
|
|
||||||
"Balaji Jayaraman (jkbalaji1103)",,0,0,0,0,0,0,0,"2017-10-30 22:12:27"
|
|
||||||
"Wellington Ribeiro (wellington.rib)",,0,0,0,0,0,0,0,"2017-11-16 07:32:25"
|
|
||||||
"Javid IRAN (twitteriran98)",Persian,0,0,0,1,0,0,0,"2017-11-25 16:47:25"
|
|
||||||
REMOVED_USER,Czech,0,0,0,1,0,0,0,"2017-12-24 04:08:27"
|
|
||||||
AhmedDz,Arabic,0,0,0,1,0,0,0,"2017-12-31 10:12:31"
|
|
||||||
"박인호 (wphestiraid)",Korean,0,0,0,2,0,0,0,"2018-01-05 00:33:14"
|
|
||||||
REMOVED_USER,,0,0,0,0,0,0,0,"2018-01-05 16:56:12"
|
|
||||||
NairaDNV,Spanish,0,0,0,9,0,0,0,"2018-01-05 19:10:33"
|
|
||||||
Raulbertassi,,0,0,0,0,0,0,0,"2018-01-07 17:23:18"
|
|
||||||
"Katherine Alexandra Flórez Ramírez (katherine.florez12)",Spanish,0,0,0,46,0,0,0,"2018-01-20 02:18:32"
|
|
||||||
farbod66,Persian,0,0,0,1,0,0,0,"2018-01-20 11:04:23"
|
farbod66,Persian,0,0,0,1,0,0,0,"2018-01-20 11:04:23"
|
||||||
droidahmed,Arabic,0,0,0,7,0,0,0,"2018-01-31 02:18:49"
|
|
||||||
"Yasin Okumus (lacivert)",Turkish,0,0,0,1,0,0,0,"2018-02-07 04:13:51"
|
|
||||||
"Sarah BCNN (fsarahboucenna)",French,0,0,0,16,0,0,0,"2018-02-11 11:07:36"
|
|
||||||
T-v-Gerwen,Dutch,0,0,0,47,0,0,0,"2018-03-02 10:26:33"
|
|
||||||
"عبد الناصر سعيد الثبيتي (asaeed)",,0,0,0,0,0,0,0,"2018-03-13 02:09:35"
|
|
||||||
REMOVED_USER,Czech,0,0,0,18,0,0,0,"2018-03-27 06:19:52"
|
|
||||||
BongTran,Vietnamese,0,0,0,2,0,0,0,"2018-04-24 05:16:07"
|
|
||||||
"Ahmed Nazir (ahmednazir333)",,0,0,0,0,0,0,0,"2018-05-06 12:10:27"
|
|
||||||
mdrobulis,,0,0,0,0,0,0,0,"2018-05-24 01:40:42"
|
|
||||||
REMOVED_USER,,0,0,0,0,0,0,0,"2018-08-24 00:17:43"
|
|
||||||
"Sri Harsha Bhogi (sriharshabhogi)",,0,0,0,0,0,0,0,"2018-09-02 05:31:53"
|
|
||||||
philfr49,French,0,0,0,2,0,0,0,"2018-09-03 14:20:32"
|
|
||||||
"Ahnaf Tajwar (atn4404)",,0,0,0,0,0,0,0,"2018-10-16 11:13:30"
|
|
||||||
Elham1361,,0,0,0,0,0,0,0,"2018-10-27 12:01:06"
|
|
||||||
REMOVED_USER,,0,0,0,0,0,0,0,"2018-10-27 15:34:36"
|
|
||||||
"Никита Карамов (nikita.karamoff)",Russian,0,0,0,10,0,0,0,"2018-10-29 03:57:21"
|
|
||||||
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"
|
|
||||||
"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"
|
|
||||||
ava_rfie,Persian,0,0,0,1,0,0,0,"2019-06-09 16:19:24"
|
|
||||||
"George Merkulov (george142.emarket)",Russian,0,0,0,11,0,0,0,"2019-06-09 19:47:02"
|
|
||||||
sanyoniket,,0,0,0,0,0,0,0,"2019-07-23 12:58:40"
|
|
||||||
"Arttu Ylhävuori (arttu.ylhavuori)",,0,0,0,0,0,0,0,"2019-07-24 15:03:42"
|
|
||||||
"Равиль Мифтахов (ravilmif47)",Russian,0,0,0,1,0,0,0,"2019-08-12 21:58:30"
|
|
||||||
Irsgram,Russian,0,0,0,1,0,0,0,"2019-09-30 16:42:20"
|
|
||||||
"Matus Zdansky (matuszdansky)",,0,0,0,0,0,0,0,"2019-10-20 13:52:24"
|
|
||||||
shuvo786,,0,0,0,0,0,0,0,"2019-11-13 00:18:12"
|
|
||||||
EwanB,,0,0,0,0,0,0,0,"2019-11-19 10:04:38"
|
|
||||||
"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"
|
|
||||||
REMOVED_USER,,0,0,0,0,0,0,0,"2020-02-01 03:47:48"
|
|
||||||
mushin,,0,0,0,0,0,0,0,"2020-02-02 04:08:05"
|
|
||||||
valney.faria,"Portuguese, Brazilian",0,0,0,1,0,0,0,"2020-02-02 14:45:02"
|
|
||||||
"Elmo (oberknecht)",,0,0,0,0,0,0,0,"2020-04-16 08:45:50"
|
|
||||||
"Карлен Шаухаев (KarlenShaukhaev)",,0,0,0,0,0,0,0,"2020-04-27 08:53:49"
|
|
||||||
"Edmunds Edmundam (edmundam)",,0,0,0,0,0,0,0,"2020-06-01 14:18:18"
|
|
||||||
"Алтынбек Наурызғали (altinbeknaurizgali)",Russian,0,0,0,1,0,0,0,"2020-08-12 13:03:49"
|
|
||||||
dongchen.yue,German,0,0,0,4,0,0,0,"2020-09-12 15:05:59"
|
|
||||||
rooban23,,0,0,0,0,0,0,0,"2020-09-15 11:49:14"
|
|
||||||
REMOVED_USER,,0,0,0,0,0,0,0,"2020-09-17 09:10:09"
|
|
||||||
"Arjun K. (arjunkdot)",,0,0,0,0,0,0,0,"2020-09-20 11:16:18"
|
|
||||||
"Rivo Zängov (Eraser)",,0,0,0,0,0,0,0,"2020-10-13 04:38:26"
|
|
||||||
"reza golestanzadeh (reza.golestanzadeh)",Persian,0,0,0,1,0,0,0,"2020-10-21 12:07:20"
|
|
||||||
Sandhu564.,,0,0,0,0,0,0,0,"2020-12-14 01:27:45"
|
|
||||||
dimateos,,0,0,0,0,0,0,0,"2021-01-10 06:29:52"
|
|
||||||
"Mateusz Teteruk (mttet)",Polish,0,0,0,1,0,0,0,"2021-01-23 13:09:59"
|
|
||||||
"LeMeD (LeMeS)",French,0,0,0,2,0,0,0,"2021-02-06 15:35:00"
|
|
||||||
"Quentin Hibon (hiq)",,0,0,0,0,0,0,0,"2021-02-07 16:39:31"
|
|
||||||
"vi ve (VimalV)",,0,0,0,0,0,0,0,"2021-02-08 02:35:45"
|
"vi ve (VimalV)",,0,0,0,0,0,0,0,"2021-02-08 02:35:45"
|
||||||
Fazy1380,,0,0,0,0,0,0,0,"2021-04-10 11:02:53"
|
"Éjbãss Übbeî (littlebittlebottle)",Norwegian,0,0,0,152,0,0,0,"2017-07-05 21:12:02"
|
||||||
"Saiprasath B (Saiprasath)",,0,0,0,0,0,0,0,"2021-07-11 11:10:41"
|
"LeMeD (LeMeS)",French,0,0,0,2,0,0,0,"2021-02-06 15:35:00"
|
||||||
"darkkingredian (rediancool)",,0,0,0,0,0,0,0,"2021-07-27 16:04:32"
|
BongTran,Vietnamese,0,0,0,2,0,0,0,"2018-04-24 05:16:07"
|
||||||
"Samuel Przeździęk (samek22)",Polish,0,0,0,1,0,0,0,"2021-08-01 00:49:01"
|
REMOVED_USER,Czech,0,0,0,18,0,0,0,"2018-03-27 06:19:52"
|
||||||
"Manny Farsangy (manifarsangi)",Persian,0,0,0,12,0,0,0,"2021-08-10 05:32:28"
|
mushin,,0,0,0,0,0,0,0,"2020-02-02 04:08:05"
|
||||||
"Masataka Yakura (myakura)",Japanese,0,0,0,1,0,0,0,"2021-09-03 22:10:36"
|
"Mateusz Teteruk (mttet)",Polish,0,0,0,1,0,0,0,"2021-01-23 13:09:59"
|
||||||
"Said Tahsin Dane (tasomaniac)",,0,0,0,0,0,0,0,"2021-09-25 05:31:01"
|
"Sarah BCNN (fsarahboucenna)",French,0,0,0,16,0,0,0,"2018-02-11 11:07:36"
|
||||||
"akmal shafiq (mohdakmalshafiq)",,0,0,0,0,0,0,0,"2021-11-01 01:04:50"
|
droidahmed,Arabic,0,0,0,7,0,0,0,"2018-01-31 02:18:49"
|
||||||
catemlitten,Japanese,0,0,0,1,0,0,0,"2021-11-17 15:06:02"
|
"Никита Карамов (nikita.karamoff)",Russian,0,0,0,10,0,0,0,"2018-10-29 03:57:21"
|
||||||
HemanthMeda,Telugu,0,0,0,4,0,0,0,"2021-12-01 14:02:14"
|
|
||||||
"Yunsu Kim (yunsukim86)",Korean,0,0,0,2,0,0,0,"2022-01-14 06:33:43"
|
|
||||||
"Sylwuskak (sylwuskak)",Polish,0,0,0,1,0,0,0,"2022-01-25 04:19:53"
|
|
||||||
"Pro AAA (pro1010)",Arabic,0,0,0,1,0,0,0,"2022-02-14 03:32:44"
|
|
||||||
"Baran Özavcı (n2141n)",Turkish,0,0,0,1,0,0,0,"2022-02-26 04:32:51"
|
|
||||||
deepbird,,0,0,0,0,0,0,0,"2022-04-11 03:21:05"
|
|
||||||
ashik8113,,0,0,0,0,0,0,0,"2022-04-13 11:58:26"
|
|
||||||
"manu (manuL96)",,0,0,0,0,0,0,0,"2022-05-06 23:34:55"
|
|
||||||
"Agnel (agnelwaghela)",,0,0,0,0,0,0,0,"2022-06-19 10:32:33"
|
|
||||||
"Ruslan Zaynetdinov (theRuslan)",Russian,0,0,0,6,0,0,0,"2022-07-06 10:48:31"
|
|
||||||
"Marcel (flaced)",German,0,0,0,1,0,0,0,"2022-08-19 15:19:26"
|
|
||||||
pixx1,German,0,0,0,6,0,0,0,"2022-08-22 00:38:13"
|
|
||||||
"Onder Nuray (ondernuray)",,0,0,0,0,0,0,0,"2022-08-30 08:16:27"
|
|
||||||
"Md Faridul Islam (mdfaridulislam509)",,0,0,0,0,0,0,0,"2022-09-04 06:25:20"
|
|
||||||
Felone,,0,0,0,0,0,0,0,"2022-09-10 08:04:02"
|
|
||||||
Sawwwwwplan,,0,0,0,0,0,0,0,"2022-09-10 11:35:14"
|
|
||||||
"Fatima (fatima.a)",Arabic,0,0,0,1,0,0,0,"2022-09-20 09:48:25"
|
|
||||||
DomiAlt,,0,0,0,0,0,0,0,"2022-10-02 15:44:26"
|
|
||||||
Erwindly,Polish,0,0,0,1,0,0,0,"2022-11-01 04:12:58"
|
|
||||||
"Deniz Çelik (DenizCelik)",,0,0,0,0,0,0,0,"2022-11-09 02:13:11"
|
|
||||||
Watashiwanilli,,0,0,0,0,0,0,0,"2022-11-14 10:12:21"
|
|
||||||
"Maxence LIGAN (Daddy_Caramel)",,0,0,0,0,0,0,0,"2022-12-23 04:43:30"
|
|
||||||
wstroobandt,Dutch,0,0,0,1,0,0,0,"2023-01-03 17:52:42"
|
|
||||||
"ニキホロブ蒼井 (abhigyahazra)",,0,0,0,0,0,0,0,"2023-01-08 21:37:53"
|
|
||||||
"Afrim Kamberi (afrimkamberi)",,0,0,0,0,0,0,0,"2023-01-17 05:20:06"
|
|
||||||
ViacheslavLabs,Ukrainian,0,0,0,5,0,0,0,"2023-01-21 15:34:40"
|
|
||||||
Basta,,0,0,0,0,0,0,0,"2023-03-04 16:13:57"
|
|
||||||
raselh,,0,0,0,0,0,0,0,"2023-03-15 03:33:21"
|
|
||||||
"Adrian Miozga (AdrianMiozga)",Polish,0,0,0,13,0,0,0,"2023-03-25 17:00:53"
|
|
||||||
"Atlas Steel (atlas.steel.erbil)",,0,0,0,0,0,0,0,"2023-05-16 05:23:32"
|
|
||||||
"Sathyakumar Ps (sathyakumar63)",,0,0,0,0,0,0,0,"2023-05-24 11:44:23"
|
|
||||||
"Abdul nafih (nafih_zain_)",,0,0,0,0,0,0,0,"2023-05-25 06:24:31"
|
|
||||||
"Bernhard (bernikr)",German,0,0,0,1,0,0,0,"2023-06-12 11:18:18"
|
|
||||||
"Anna Boda (annaboda08)",Hungarian,0,0,0,1,0,0,0,"2023-07-03 13:28:12"
|
|
||||||
"Rubén CH (chruben45)",Spanish,0,0,0,1,0,0,0,"2023-07-24 12:39:51"
|
|
||||||
"Vinicius (exteraDev)",,0,0,0,0,0,0,0,"2023-08-08 15:47:45"
|
|
||||||
"mugdad alhammad (alhammad5057)",,0,0,0,0,0,0,0,"2023-08-13 22:36:01"
|
|
||||||
"Arthur Waldes (arthurwaldes)",,0,0,0,0,0,0,0,"2023-08-22 15:38:12"
|
|
||||||
"Mahmoud Hatem (mahmoudhatem)",Arabic,0,0,0,1,0,0,0,"2023-09-12 05:12:34"
|
|
||||||
"Jacques Francky Salomon (jacquesfranckysalomon)",,0,0,0,0,0,0,0,"2023-10-14 20:55:30"
|
|
||||||
"Mohammad Hashem (hashemchattogram)",,0,0,0,0,0,0,0,"2023-10-27 02:15:39"
|
|
||||||
Aylinddd,Persian,0,0,0,1,0,0,0,"2023-11-07 22:50:37"
|
|
||||||
Dinock,German,0,0,0,1,0,0,0,"2023-11-10 07:08:23"
|
|
||||||
zelfoxx,German,0,0,0,12,0,0,0,"2023-11-22 11:11:44"
|
|
||||||
REMOVED_USER,,0,0,0,0,0,0,0,"2023-11-28 17:42:55"
|
|
||||||
"Dennis Li (Dennis_Li)","Chinese Simplified",0,0,0,1,0,0,0,"2023-12-24 21:12:43"
|
|
||||||
REMOVED_USER,Russian,0,0,0,5,0,0,0,"2024-01-01 17:15:06"
|
|
||||||
"Daniill l (daniill)",,0,0,0,0,0,0,0,"2024-01-12 02:42:52"
|
|
||||||
"NONE NAME (RagnarGraves)",Spanish,0,0,0,1,0,0,0,"2024-02-18 01:39:39"
|
|
||||||
lu21232512,,0,0,0,0,0,0,0,"2024-04-15 12:40:05"
|
|
||||||
"Oscariño (oscarinhooo)",,0,0,0,0,0,0,0,"2024-07-07 08:47:25"
|
|
||||||
"Vladimir Sveshnikov (proloxy)",Russian,0,0,0,6,0,0,0,"2024-07-12 03:27:39"
|
|
||||||
L0f3n,Swedish,0,0,0,2,0,0,0,"2024-08-06 17:13:50"
|
|
||||||
"Christer Fletcher (chrfle)",Swedish,0,0,0,2,0,0,0,"2024-08-15 04:15:49"
|
|
||||||
"Moaid alzawy (dodoalzawy200)",Arabic,0,0,0,1,0,0,0,"2024-09-18 12:17:31"
|
|
||||||
"Eduardo Ferreira (eduu)",,0,0,0,0,0,0,0,"2024-10-27 19:09:20"
|
|
||||||
"عبد الملك بلكم (aboodpvp555)",Arabic,0,0,0,1,0,0,0,"2024-12-12 02:42:14"
|
|
||||||
4Kio,Russian,0,0,0,1,0,0,0,"2024-12-12 12:31:25"
|
|
||||||
"رودينه (rwdynh874)",Arabic,0,0,0,1,0,0,0,"2024-12-24 18:31:33"
|
|
||||||
"zahra ghasemi (z.ghasemizahra)",Persian,0,0,0,1,0,0,0,"2024-12-29 04:36:27"
|
|
||||||
"Mustafa Işıksız (mustafalordon27)",Turkish,0,0,0,1,0,0,0,"2025-01-15 13:52:32"
|
|
||||||
"Ahlem Ben (ahlemben1513)",Arabic,0,0,0,1,0,0,0,"2025-02-12 11:09:57"
|
|
||||||
"Francisco Parra (Frn_7)",Spanish,0,0,0,1,0,0,0,"2025-02-18 22:57:05"
|
|
||||||
"mizumoto (yuta-mizumoto)",Japanese,0,0,0,2,0,0,0,"2025-03-03 18:35:56"
|
|
||||||
|
|||||||
|
@@ -18,36 +18,27 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.agp)
|
id("com.github.triplet.play") version "3.7.0"
|
||||||
alias(libs.plugins.kotlin.android)
|
id("com.android.application")
|
||||||
alias(libs.plugins.ksp)
|
id("org.jetbrains.kotlin.android")
|
||||||
alias(libs.plugins.ktlint.plugin)
|
id("org.jetbrains.kotlin.kapt")
|
||||||
|
id("org.jetbrains.kotlin.android.extensions")
|
||||||
|
id("org.jlleitschuh.gradle.ktlint")
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.compileLint {
|
tasks.compileLint {
|
||||||
dependsOn("updateTranslators")
|
dependsOn("updateTranslators")
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
Added on top of kotlinOptions to work around this issue:
|
|
||||||
https://youtrack.jetbrains.com/issue/KTIJ-24311/task-current-target-is-17-and-kaptGenerateStubsProductionDebugKotlin-task-current-target-is-1.8-jvm-target-compatibility-should#focus=Comments-27-6798448.0-0
|
|
||||||
Updating gradle might fix this, so try again in the future to remove this and run:
|
|
||||||
./gradlew --rerun-tasks :uhabits-android:kaptGenerateStubsReleaseKotlin
|
|
||||||
If this doesn't produce any warning, try to remove it.
|
|
||||||
*/
|
|
||||||
kotlin {
|
|
||||||
jvmToolchain(17)
|
|
||||||
}
|
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "org.isoron.uhabits"
|
|
||||||
compileSdk = 36
|
compileSdk = 32
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
versionCode = 20300
|
versionCode = 20101
|
||||||
versionName = "2.3.0"
|
versionName = "2.1.1"
|
||||||
minSdk = 28
|
minSdk = 28
|
||||||
targetSdk = 36
|
targetSdk = 31
|
||||||
applicationId = "org.isoron.uhabits"
|
applicationId = "org.isoron.uhabits"
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
@@ -64,7 +55,7 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
getByName("release") {
|
||||||
isMinifyEnabled = 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) {
|
||||||
@@ -72,45 +63,71 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
debug {
|
getByName("debug") {
|
||||||
enableUnitTestCoverage = true
|
isTestCoverageEnabled = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
isCoreLibraryDesugaringEnabled = true
|
isCoreLibraryDesugaringEnabled = true
|
||||||
targetCompatibility(JavaVersion.VERSION_17)
|
targetCompatibility(JavaVersion.VERSION_1_8)
|
||||||
sourceCompatibility(JavaVersion.VERSION_17)
|
sourceCompatibility(JavaVersion.VERSION_1_8)
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlinOptions.jvmTarget = JavaVersion.VERSION_17.toString()
|
buildFeatures {
|
||||||
buildFeatures.viewBinding = true
|
viewBinding = true
|
||||||
lint.abortOnError = false
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compileOnly(libs.jsr250.api)
|
val daggerVersion = "2.44.2"
|
||||||
coreLibraryDesugaring(libs.desugar.jdk.libs)
|
val kotlinVersion = "1.7.21"
|
||||||
implementation(libs.appIntro)
|
val kxCoroutinesVersion = "1.6.4"
|
||||||
implementation(libs.jsr305)
|
val ktorVersion = "1.6.8"
|
||||||
implementation(libs.dagger)
|
val espressoVersion = "3.5.0"
|
||||||
implementation(libs.guava)
|
|
||||||
implementation(libs.ktor.client.android)
|
|
||||||
implementation(libs.ktor.client.core)
|
|
||||||
implementation(libs.ktor.client.jackson)
|
|
||||||
implementation(libs.ktor.client.json)
|
|
||||||
implementation(libs.kotlin.stdlib.jdk8)
|
|
||||||
implementation(libs.kotlinx.coroutines.android)
|
|
||||||
implementation(libs.kotlinx.coroutines.core)
|
|
||||||
implementation(libs.appcompat)
|
|
||||||
implementation(libs.legacy.preference.v14)
|
|
||||||
implementation(libs.legacy.support.v4)
|
|
||||||
implementation(libs.material)
|
|
||||||
implementation(libs.opencsv)
|
|
||||||
implementation(libs.konfetti.xml)
|
|
||||||
implementation(project(":uhabits-core"))
|
|
||||||
ksp(libs.dagger.compiler)
|
|
||||||
|
|
||||||
androidTestImplementation(libs.bundles.androidTest)
|
androidTestImplementation("androidx.test.espresso:espresso-contrib:$espressoVersion")
|
||||||
testImplementation(libs.bundles.test)
|
androidTestImplementation("androidx.test.espresso:espresso-core:$espressoVersion")
|
||||||
|
androidTestImplementation("com.google.dagger:dagger:$daggerVersion")
|
||||||
|
androidTestImplementation("com.linkedin.dexmaker:dexmaker-mockito:2.28.3")
|
||||||
|
androidTestImplementation("io.ktor:ktor-client-mock:$ktorVersion")
|
||||||
|
androidTestImplementation("io.ktor:ktor-jackson:$ktorVersion")
|
||||||
|
androidTestImplementation("androidx.annotation:annotation:1.5.0")
|
||||||
|
androidTestImplementation("androidx.test.ext:junit:1.1.4")
|
||||||
|
androidTestImplementation("androidx.test.uiautomator:uiautomator:2.2.0")
|
||||||
|
androidTestImplementation("androidx.test:rules:1.5.0")
|
||||||
|
androidTestImplementation("com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0")
|
||||||
|
compileOnly("javax.annotation:jsr250-api:1.0")
|
||||||
|
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.2.2")
|
||||||
|
implementation("com.github.AppIntro:AppIntro:6.2.0")
|
||||||
|
implementation("com.google.code.findbugs:jsr305:3.0.2")
|
||||||
|
implementation("com.google.dagger:dagger:$daggerVersion")
|
||||||
|
implementation("com.google.guava:guava:31.1-android")
|
||||||
|
implementation("io.ktor:ktor-client-android:$ktorVersion")
|
||||||
|
implementation("io.ktor:ktor-client-core:$ktorVersion")
|
||||||
|
implementation("io.ktor:ktor-client-jackson:$ktorVersion")
|
||||||
|
implementation("io.ktor:ktor-client-json:$ktorVersion")
|
||||||
|
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion")
|
||||||
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$kxCoroutinesVersion")
|
||||||
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kxCoroutinesVersion")
|
||||||
|
implementation("androidx.appcompat:appcompat:1.5.1")
|
||||||
|
implementation("androidx.legacy:legacy-preference-v14:1.0.0")
|
||||||
|
implementation("androidx.legacy:legacy-support-v4:1.0.0")
|
||||||
|
implementation("com.google.android.material:material:1.8.0")
|
||||||
|
implementation("com.opencsv:opencsv:5.7.1")
|
||||||
|
implementation(project(":uhabits-core"))
|
||||||
|
kapt("com.google.dagger:dagger-compiler:$daggerVersion")
|
||||||
|
kaptAndroidTest("com.google.dagger:dagger-compiler:$daggerVersion")
|
||||||
|
testImplementation("com.google.dagger:dagger:$daggerVersion")
|
||||||
|
testImplementation("junit:junit:4.13.2")
|
||||||
|
testImplementation("com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0")
|
||||||
|
}
|
||||||
|
|
||||||
|
kapt {
|
||||||
|
correctErrorTypes = true
|
||||||
|
}
|
||||||
|
|
||||||
|
play {
|
||||||
|
serviceAccountCredentials.set(file("../.secret/gcp-key.json"))
|
||||||
|
track.set("alpha")
|
||||||
}
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 8.9 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 15 KiB |
@@ -39,7 +39,6 @@ open class BaseUserInterfaceTest {
|
|||||||
private lateinit var prefs: Preferences
|
private lateinit var prefs: Preferences
|
||||||
private lateinit var fixtures: HabitFixtures
|
private lateinit var fixtures: HabitFixtures
|
||||||
private lateinit var cache: HabitCardListCache
|
private lateinit var cache: HabitCardListCache
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
|
|||||||
@@ -153,22 +153,18 @@ open class BaseViewTest : BaseAndroidTest() {
|
|||||||
var filename = filename
|
var filename = filename
|
||||||
var dir = getSDCardDir("test-screenshots")
|
var dir = getSDCardDir("test-screenshots")
|
||||||
if (dir == null) dir = AndroidDirFinder(targetContext).getFilesDir("test-screenshots")
|
if (dir == null) dir = AndroidDirFinder(targetContext).getFilesDir("test-screenshots")
|
||||||
if (dir == null) {
|
if (dir == null) throw RuntimeException(
|
||||||
throw RuntimeException(
|
"Could not find suitable dir for screenshots"
|
||||||
"Could not find suitable dir for screenshots"
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
filename = filename.replace("\\.png$".toRegex(), "$suffix.png")
|
filename = filename.replace("\\.png$".toRegex(), "$suffix.png")
|
||||||
val absolutePath = String.format("%s/%s", dir.absolutePath, filename)
|
val absolutePath = String.format("%s/%s", dir.absolutePath, filename)
|
||||||
val parent = File(absolutePath).parentFile
|
val parent = File(absolutePath).parentFile
|
||||||
if (!parent.exists() && !parent.mkdirs()) {
|
if (!parent.exists() && !parent.mkdirs()) throw RuntimeException(
|
||||||
throw RuntimeException(
|
String.format(
|
||||||
String.format(
|
"Could not create dir: %s",
|
||||||
"Could not create dir: %s",
|
parent.absolutePath
|
||||||
parent.absolutePath
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
)
|
||||||
val out = FileOutputStream(absolutePath)
|
val out = FileOutputStream(absolutePath)
|
||||||
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out)
|
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out)
|
||||||
return absolutePath
|
return absolutePath
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
package org.isoron.uhabits
|
package org.isoron.uhabits
|
||||||
|
|
||||||
|
import com.nhaarman.mockitokotlin2.mock
|
||||||
import dagger.Component
|
import dagger.Component
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
@@ -34,7 +35,6 @@ import org.isoron.uhabits.inject.ActivityScope
|
|||||||
import org.isoron.uhabits.inject.HabitModule
|
import org.isoron.uhabits.inject.HabitModule
|
||||||
import org.isoron.uhabits.inject.HabitsActivityModule
|
import org.isoron.uhabits.inject.HabitsActivityModule
|
||||||
import org.isoron.uhabits.inject.HabitsApplicationComponent
|
import org.isoron.uhabits.inject.HabitsApplicationComponent
|
||||||
import org.mockito.kotlin.mock
|
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
class TestModule {
|
class TestModule {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ package org.isoron.uhabits.acceptance.steps
|
|||||||
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Build.VERSION.SDK_INT
|
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
|
||||||
@@ -32,11 +33,11 @@ import androidx.test.espresso.matcher.ViewMatchers
|
|||||||
import androidx.test.uiautomator.By
|
import androidx.test.uiautomator.By
|
||||||
import androidx.test.uiautomator.UiSelector
|
import androidx.test.uiautomator.UiSelector
|
||||||
import androidx.test.uiautomator.Until
|
import androidx.test.uiautomator.Until
|
||||||
|
import junit.framework.Assert.assertTrue
|
||||||
import org.hamcrest.CoreMatchers
|
import org.hamcrest.CoreMatchers
|
||||||
import org.isoron.uhabits.BaseUserInterfaceTest
|
import org.isoron.uhabits.BaseUserInterfaceTest
|
||||||
import org.isoron.uhabits.R
|
import org.isoron.uhabits.R
|
||||||
import org.isoron.uhabits.activities.habits.list.ListHabitsActivity
|
import org.isoron.uhabits.activities.habits.list.ListHabitsActivity
|
||||||
import org.junit.Assert.assertTrue
|
|
||||||
|
|
||||||
object CommonSteps : BaseUserInterfaceTest() {
|
object CommonSteps : BaseUserInterfaceTest() {
|
||||||
fun pressBack() {
|
fun pressBack() {
|
||||||
@@ -132,7 +133,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 (SDK_INT <= Build.VERSION_CODES.M) {
|
if (SDK_INT <= Build.VERSION_CODES.M) {
|
||||||
browserPkg = "com.android.browser"
|
browserPkg = "com.android.browser"
|
||||||
@@ -147,19 +148,15 @@ object CommonSteps : BaseUserInterfaceTest() {
|
|||||||
Screen.LIST_HABITS ->
|
Screen.LIST_HABITS ->
|
||||||
Espresso.onView(ViewMatchers.withClassName(CoreMatchers.endsWith("ListHabitsRootView")))
|
Espresso.onView(ViewMatchers.withClassName(CoreMatchers.endsWith("ListHabitsRootView")))
|
||||||
.check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
|
.check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
|
||||||
|
|
||||||
Screen.SHOW_HABIT ->
|
Screen.SHOW_HABIT ->
|
||||||
Espresso.onView(ViewMatchers.withId(R.id.subtitleCard))
|
Espresso.onView(ViewMatchers.withId(R.id.subtitleCard))
|
||||||
.check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
|
.check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
|
||||||
|
|
||||||
Screen.EDIT_HABIT ->
|
Screen.EDIT_HABIT ->
|
||||||
Espresso.onView(ViewMatchers.withId(R.id.questionInput))
|
Espresso.onView(ViewMatchers.withId(R.id.questionInput))
|
||||||
.check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
|
.check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
|
||||||
|
|
||||||
Screen.SELECT_HABIT_TYPE ->
|
Screen.SELECT_HABIT_TYPE ->
|
||||||
Espresso.onView(ViewMatchers.withText(R.string.yes_or_no_example))
|
Espresso.onView(ViewMatchers.withText(R.string.yes_or_no_example))
|
||||||
.check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
|
.check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
|
||||||
|
|
||||||
else -> throw IllegalStateException()
|
else -> throw IllegalStateException()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,9 +21,9 @@ package org.isoron.uhabits.acceptance.steps
|
|||||||
import android.os.Build.VERSION.SDK_INT
|
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.assertTrue
|
||||||
import org.isoron.uhabits.BaseUserInterfaceTest
|
import org.isoron.uhabits.BaseUserInterfaceTest
|
||||||
import org.junit.Assert.assertFalse
|
|
||||||
import org.junit.Assert.assertTrue
|
|
||||||
|
|
||||||
object WidgetSteps {
|
object WidgetSteps {
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
*/
|
*/
|
||||||
package org.isoron.uhabits.activities.common.views
|
package org.isoron.uhabits.activities.common.views
|
||||||
|
|
||||||
import android.view.MotionEvent
|
|
||||||
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.isoron.uhabits.BaseViewTest
|
import org.isoron.uhabits.BaseViewTest
|
||||||
@@ -53,8 +52,7 @@ class FrequencyChartTest : BaseViewTest() {
|
|||||||
@Test
|
@Test
|
||||||
@Throws(Throwable::class)
|
@Throws(Throwable::class)
|
||||||
fun testRender_withDataOffset() {
|
fun testRender_withDataOffset() {
|
||||||
val e = MotionEvent.obtain(0, 0, 0, 0f, 0f, 0)
|
view.onScroll(null, null, -dpToPixels(150), 0f)
|
||||||
view.onScroll(e, e, -dpToPixels(150), 0f)
|
|
||||||
view.invalidate()
|
view.invalidate()
|
||||||
assertRenders(view, BASE_PATH + "renderDataOffset.png")
|
assertRenders(view, BASE_PATH + "renderDataOffset.png")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
*/
|
*/
|
||||||
package org.isoron.uhabits.activities.common.views
|
package org.isoron.uhabits.activities.common.views
|
||||||
|
|
||||||
import android.view.MotionEvent
|
|
||||||
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.isoron.uhabits.BaseViewTest
|
import org.isoron.uhabits.BaseViewTest
|
||||||
@@ -45,7 +44,7 @@ class ScoreChartTest : BaseViewTest() {
|
|||||||
habit = habit,
|
habit = habit,
|
||||||
firstWeekday = prefs.firstWeekdayInt,
|
firstWeekday = prefs.firstWeekdayInt,
|
||||||
spinnerPosition = 0,
|
spinnerPosition = 0,
|
||||||
theme = LightTheme()
|
theme = LightTheme(),
|
||||||
)
|
)
|
||||||
view = ScoreChart(targetContext).apply {
|
view = ScoreChart(targetContext).apply {
|
||||||
setScores(state.scores)
|
setScores(state.scores)
|
||||||
@@ -64,8 +63,7 @@ class ScoreChartTest : BaseViewTest() {
|
|||||||
@Test
|
@Test
|
||||||
@Throws(Throwable::class)
|
@Throws(Throwable::class)
|
||||||
fun testRender_withDataOffset() {
|
fun testRender_withDataOffset() {
|
||||||
val e = MotionEvent.obtain(0, 0, 0, 0f, 0f, 0)
|
view.onScroll(null, null, -dpToPixels(150), 0f)
|
||||||
view.onScroll(e, e, -dpToPixels(150), 0f)
|
|
||||||
view.invalidate()
|
view.invalidate()
|
||||||
assertRenders(view, BASE_PATH + "renderDataOffset.png")
|
assertRenders(view, BASE_PATH + "renderDataOffset.png")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ import org.junit.runner.RunWith
|
|||||||
@MediumTest
|
@MediumTest
|
||||||
class StreakChartTest : BaseViewTest() {
|
class StreakChartTest : BaseViewTest() {
|
||||||
private lateinit var view: StreakChart
|
private lateinit var view: StreakChart
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
override fun setUp() {
|
override fun setUp() {
|
||||||
super.setUp()
|
super.setUp()
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ package org.isoron.uhabits.activities.habits.list.views
|
|||||||
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.isoron.uhabits.BaseViewTest
|
import org.isoron.uhabits.BaseViewTest
|
||||||
import org.junit.Ignore
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
@@ -35,22 +35,23 @@ class EmptyListViewTest : BaseViewTest() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val path = "habits/list/EmptyListView"
|
private val path = "habits/list/EmptyListView"
|
||||||
|
private val view: EmptyListView = EmptyListView(targetContext)
|
||||||
|
|
||||||
|
@Before
|
||||||
|
override fun setUp() {
|
||||||
|
super.setUp()
|
||||||
|
measureView(view, dpToPixels(200), dpToPixels(200))
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore("non-deterministic failure")
|
|
||||||
fun testRender_done() {
|
fun testRender_done() {
|
||||||
val view = EmptyListView(targetContext)
|
|
||||||
view.showDone()
|
view.showDone()
|
||||||
measureView(view, dpToPixels(200), dpToPixels(200))
|
|
||||||
assertRenders(view, "$path/done.png")
|
assertRenders(view, "$path/done.png")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore("non-deterministic failure")
|
|
||||||
fun testRender_empty() {
|
fun testRender_empty() {
|
||||||
val view = EmptyListView(targetContext)
|
|
||||||
view.showEmpty()
|
view.showEmpty()
|
||||||
measureView(view, dpToPixels(200), dpToPixels(200))
|
|
||||||
assertRenders(view, "$path/empty.png")
|
assertRenders(view, "$path/empty.png")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ 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 }
|
onEdit = { edited = true }
|
||||||
}
|
}
|
||||||
measureView(view, dpToPixels(48), dpToPixels(48))
|
measureView(view, dpToPixels(48), dpToPixels(48))
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -20,15 +20,15 @@ package org.isoron.uhabits.activities.habits.list.views
|
|||||||
|
|
||||||
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 com.nhaarman.mockitokotlin2.doReturn
|
||||||
|
import com.nhaarman.mockitokotlin2.mock
|
||||||
|
import com.nhaarman.mockitokotlin2.verify
|
||||||
|
import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions
|
||||||
|
import com.nhaarman.mockitokotlin2.whenever
|
||||||
import org.isoron.uhabits.BaseViewTest
|
import org.isoron.uhabits.BaseViewTest
|
||||||
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
|
||||||
import org.mockito.kotlin.doReturn
|
|
||||||
import org.mockito.kotlin.mock
|
|
||||||
import org.mockito.kotlin.verify
|
|
||||||
import org.mockito.kotlin.verifyNoMoreInteractions
|
|
||||||
import org.mockito.kotlin.whenever
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
@MediumTest
|
@MediumTest
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ package org.isoron.uhabits.activities.habits.list.views
|
|||||||
|
|
||||||
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 com.nhaarman.mockitokotlin2.doReturn
|
||||||
|
import com.nhaarman.mockitokotlin2.mock
|
||||||
|
import com.nhaarman.mockitokotlin2.whenever
|
||||||
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
|
||||||
@@ -27,9 +30,6 @@ import org.isoron.uhabits.core.ui.screens.habits.list.HintList
|
|||||||
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
|
||||||
import org.mockito.kotlin.doReturn
|
|
||||||
import org.mockito.kotlin.mock
|
|
||||||
import org.mockito.kotlin.whenever
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
@MediumTest
|
@MediumTest
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ class FrequencyCardViewTest : BaseViewTest() {
|
|||||||
FrequencyCardPresenter.buildState(
|
FrequencyCardPresenter.buildState(
|
||||||
habit = habit,
|
habit = habit,
|
||||||
firstWeekday = 0,
|
firstWeekday = 0,
|
||||||
theme = LightTheme()
|
theme = LightTheme(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
measureView(view, 800f, 600f)
|
measureView(view, 800f, 600f)
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ class HistoryCardViewTest : BaseViewTest() {
|
|||||||
HistoryCardPresenter.buildState(
|
HistoryCardPresenter.buildState(
|
||||||
habit = habit,
|
habit = habit,
|
||||||
firstWeekday = SUNDAY,
|
firstWeekday = SUNDAY,
|
||||||
theme = LightTheme()
|
theme = LightTheme(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
measureView(view, 800f, 600f)
|
measureView(view, 800f, 600f)
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ class OverviewCardViewTest : BaseViewTest() {
|
|||||||
scoreYearDiff = 0.74f,
|
scoreYearDiff = 0.74f,
|
||||||
totalCount = 44,
|
totalCount = 44,
|
||||||
color = PaletteColor(7),
|
color = PaletteColor(7),
|
||||||
theme = LightTheme()
|
theme = LightTheme(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
measureView(view, 800f, 300f)
|
measureView(view, 800f, 300f)
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ class ScoreCardViewTest : BaseViewTest() {
|
|||||||
habit = habit,
|
habit = habit,
|
||||||
firstWeekday = 0,
|
firstWeekday = 0,
|
||||||
spinnerPosition = 0,
|
spinnerPosition = 0,
|
||||||
theme = LightTheme()
|
theme = LightTheme(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
measureView(view, 800f, 600f)
|
measureView(view, 800f, 600f)
|
||||||
|
|||||||
@@ -48,7 +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()
|
theme = LightTheme(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
measureView(view, 800f, 600f)
|
measureView(view, 800f, 600f)
|
||||||
|
|||||||
@@ -53,7 +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),
|
||||||
theme = LightTheme()
|
theme = LightTheme(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
measureView(view, 800f, 200f)
|
measureView(view, 800f, 200f)
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ class ListHabitsRegressionTest : BaseUserInterfaceTest() {
|
|||||||
"Wake up early",
|
"Wake up early",
|
||||||
"Meditate",
|
"Meditate",
|
||||||
"Read books",
|
"Read books",
|
||||||
"Track time"
|
"Track time",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,11 +33,9 @@ import java.io.IOException
|
|||||||
@MediumTest
|
@MediumTest
|
||||||
class CheckmarkWidgetViewTest : BaseViewTest() {
|
class CheckmarkWidgetViewTest : BaseViewTest() {
|
||||||
private lateinit var view: CheckmarkWidgetView
|
private lateinit var view: CheckmarkWidgetView
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
override fun setUp() {
|
override fun setUp() {
|
||||||
super.setUp()
|
super.setUp()
|
||||||
similarityCutoff = 0.00025
|
|
||||||
setTheme(R.style.WidgetTheme)
|
setTheme(R.style.WidgetTheme)
|
||||||
val habit = fixtures.createShortHabit()
|
val habit = fixtures.createShortHabit()
|
||||||
val computedEntries = habit.computedEntries
|
val computedEntries = habit.computedEntries
|
||||||
|
|||||||
@@ -16,13 +16,13 @@
|
|||||||
~ 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 <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"
|
||||||
|
package="org.isoron.uhabits">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS" />
|
||||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
|
||||||
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
||||||
<uses-permission android:name="android.permission.USE_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" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".HabitsApplication"
|
android:name=".HabitsApplication"
|
||||||
@@ -30,7 +30,6 @@
|
|||||||
android:backupAgent=".HabitsBackupAgent"
|
android:backupAgent=".HabitsBackupAgent"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/main_activity_title"
|
android:label="@string/main_activity_title"
|
||||||
android:localeConfig="@xml/locales_config"
|
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/AppBaseTheme">
|
android:theme="@style/AppBaseTheme">
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import java.util.Locale;
|
|||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
|||||||
@@ -23,13 +23,14 @@ import android.graphics.Paint.*;
|
|||||||
import android.os.*;
|
import android.os.*;
|
||||||
import androidx.core.view.*;
|
import androidx.core.view.*;
|
||||||
import androidx.core.view.accessibility.*;
|
import androidx.core.view.accessibility.*;
|
||||||
|
import androidx.core.widget.*;
|
||||||
import android.text.format.*;
|
import android.text.format.*;
|
||||||
import android.view.*;
|
import android.view.*;
|
||||||
import android.view.accessibility.*;
|
import android.view.accessibility.*;
|
||||||
|
|
||||||
import androidx.customview.widget.ExploreByTouchHelper;
|
import androidx.customview.widget.ExploreByTouchHelper;
|
||||||
|
|
||||||
|
import com.android.*;
|
||||||
import com.android.datetimepicker.*;
|
import com.android.datetimepicker.*;
|
||||||
import com.android.datetimepicker.date.MonthAdapter.*;
|
import com.android.datetimepicker.date.MonthAdapter.*;
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import android.graphics.Paint.*;
|
|||||||
import android.util.*;
|
import android.util.*;
|
||||||
import android.view.*;
|
import android.view.*;
|
||||||
|
|
||||||
|
import com.android.*;
|
||||||
import com.android.datetimepicker.*;
|
import com.android.datetimepicker.*;
|
||||||
|
|
||||||
import org.isoron.uhabits.R;
|
import org.isoron.uhabits.R;
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import android.view.View.*;
|
|||||||
import android.view.accessibility.*;
|
import android.view.accessibility.*;
|
||||||
import android.widget.*;
|
import android.widget.*;
|
||||||
|
|
||||||
|
import com.android.*;
|
||||||
import com.android.datetimepicker.*;
|
import com.android.datetimepicker.*;
|
||||||
|
|
||||||
import org.isoron.uhabits.R;
|
import org.isoron.uhabits.R;
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ class AndroidCanvas : Canvas {
|
|||||||
y1.toDp(),
|
y1.toDp(),
|
||||||
x2.toDp(),
|
x2.toDp(),
|
||||||
y2.toDp(),
|
y2.toDp(),
|
||||||
paint
|
paint,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ class AndroidCanvas : Canvas {
|
|||||||
text,
|
text,
|
||||||
x.toDp(),
|
x.toDp(),
|
||||||
y.toDp() + 0.6f * mHeight,
|
y.toDp() + 0.6f * mHeight,
|
||||||
textPaint
|
textPaint,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ class AndroidCanvas : Canvas {
|
|||||||
y: Double,
|
y: Double,
|
||||||
width: Double,
|
width: Double,
|
||||||
height: Double,
|
height: Double,
|
||||||
cornerRadius: Double
|
cornerRadius: Double,
|
||||||
) {
|
) {
|
||||||
paint.style = Paint.Style.FILL
|
paint.style = Paint.Style.FILL
|
||||||
innerCanvas.drawRoundRect(
|
innerCanvas.drawRoundRect(
|
||||||
@@ -93,7 +93,7 @@ class AndroidCanvas : Canvas {
|
|||||||
(y + height).toDp(),
|
(y + height).toDp(),
|
||||||
cornerRadius.toDp(),
|
cornerRadius.toDp(),
|
||||||
cornerRadius.toDp(),
|
cornerRadius.toDp(),
|
||||||
paint
|
paint,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,7 +108,7 @@ class AndroidCanvas : Canvas {
|
|||||||
y.toDp(),
|
y.toDp(),
|
||||||
(x + width).toDp(),
|
(x + width).toDp(),
|
||||||
(y + height).toDp(),
|
(y + height).toDp(),
|
||||||
paint
|
paint,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,7 +148,7 @@ class AndroidCanvas : Canvas {
|
|||||||
centerY: Double,
|
centerY: Double,
|
||||||
radius: Double,
|
radius: Double,
|
||||||
startAngle: Double,
|
startAngle: Double,
|
||||||
swipeAngle: Double
|
swipeAngle: Double,
|
||||||
) {
|
) {
|
||||||
paint.style = Paint.Style.FILL
|
paint.style = Paint.Style.FILL
|
||||||
innerCanvas.drawArc(
|
innerCanvas.drawArc(
|
||||||
@@ -159,14 +159,14 @@ class AndroidCanvas : Canvas {
|
|||||||
-startAngle.toFloat(),
|
-startAngle.toFloat(),
|
||||||
-swipeAngle.toFloat(),
|
-swipeAngle.toFloat(),
|
||||||
true,
|
true,
|
||||||
paint
|
paint,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun fillCircle(
|
override fun fillCircle(
|
||||||
centerX: Double,
|
centerX: Double,
|
||||||
centerY: Double,
|
centerY: Double,
|
||||||
radius: Double
|
radius: Double,
|
||||||
) {
|
) {
|
||||||
paint.style = Paint.Style.FILL
|
paint.style = Paint.Style.FILL
|
||||||
innerCanvas.drawCircle(centerX.toDp(), centerY.toDp(), radius.toDp(), paint)
|
innerCanvas.drawCircle(centerX.toDp(), centerY.toDp(), radius.toDp(), paint)
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ import kotlin.math.max
|
|||||||
*/
|
*/
|
||||||
class AndroidDataView(
|
class AndroidDataView(
|
||||||
context: Context,
|
context: Context,
|
||||||
attrs: AttributeSet? = null
|
attrs: AttributeSet? = null,
|
||||||
) : AndroidView<DataView>(context, attrs),
|
) : AndroidView<DataView>(context, attrs),
|
||||||
GestureDetector.OnGestureListener,
|
GestureDetector.OnGestureListener,
|
||||||
ValueAnimator.AnimatorUpdateListener {
|
ValueAnimator.AnimatorUpdateListener {
|
||||||
@@ -44,23 +44,23 @@ class AndroidDataView(
|
|||||||
addUpdateListener(this@AndroidDataView)
|
addUpdateListener(this@AndroidDataView)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTouchEvent(event: MotionEvent) = detector.onTouchEvent(event)
|
override fun onTouchEvent(event: MotionEvent?) = detector.onTouchEvent(event)
|
||||||
override fun onDown(e: MotionEvent) = true
|
override fun onDown(e: MotionEvent?) = true
|
||||||
override fun onShowPress(e: MotionEvent) = Unit
|
override fun onShowPress(e: MotionEvent?) = Unit
|
||||||
|
|
||||||
override fun onSingleTapUp(e: MotionEvent): Boolean {
|
override fun onSingleTapUp(e: MotionEvent?): Boolean {
|
||||||
return handleClick(e, true)
|
return handleClick(e, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLongPress(e: MotionEvent) {
|
override fun onLongPress(e: MotionEvent?) {
|
||||||
handleClick(e)
|
handleClick(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onScroll(
|
override fun onScroll(
|
||||||
e1: MotionEvent?,
|
e1: MotionEvent?,
|
||||||
e2: MotionEvent,
|
e2: MotionEvent?,
|
||||||
dx: Float,
|
dx: Float,
|
||||||
dy: Float
|
dy: Float,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
if (abs(dx) > abs(dy)) {
|
if (abs(dx) > abs(dy)) {
|
||||||
val parent = parent
|
val parent = parent
|
||||||
@@ -80,9 +80,9 @@ class AndroidDataView(
|
|||||||
|
|
||||||
override fun onFling(
|
override fun onFling(
|
||||||
e1: MotionEvent?,
|
e1: MotionEvent?,
|
||||||
e2: MotionEvent,
|
e2: MotionEvent?,
|
||||||
velocityX: Float,
|
velocityX: Float,
|
||||||
velocityY: Float
|
velocityY: Float,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
scroller.fling(
|
scroller.fling(
|
||||||
scroller.currX,
|
scroller.currX,
|
||||||
@@ -100,7 +100,7 @@ class AndroidDataView(
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onAnimationUpdate(animation: ValueAnimator) {
|
override fun onAnimationUpdate(animation: ValueAnimator?) {
|
||||||
if (!scroller.isFinished) {
|
if (!scroller.isFinished) {
|
||||||
scroller.computeScrollOffset()
|
scroller.computeScrollOffset()
|
||||||
updateDataOffset()
|
updateDataOffset()
|
||||||
@@ -127,11 +127,11 @@ class AndroidDataView(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleClick(e: MotionEvent, isSingleTap: Boolean = false): Boolean {
|
private fun handleClick(e: MotionEvent?, isSingleTap: Boolean = false): Boolean {
|
||||||
val x: Float
|
val x: Float
|
||||||
val y: Float
|
val y: Float
|
||||||
try {
|
try {
|
||||||
val pointerId = e.getPointerId(0)
|
val pointerId = e!!.getPointerId(0)
|
||||||
x = e.getX(pointerId)
|
x = e.getX(pointerId)
|
||||||
y = e.getY(pointerId)
|
y = e.getY(pointerId)
|
||||||
} catch (ex: RuntimeException) {
|
} catch (ex: RuntimeException) {
|
||||||
@@ -140,11 +140,8 @@ class AndroidDataView(
|
|||||||
// e.getPointerId.
|
// e.getPointerId.
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (isSingleTap) {
|
if (isSingleTap) view?.onClick(x / canvas.innerDensity, y / canvas.innerDensity)
|
||||||
view?.onClick(x / canvas.innerDensity, y / canvas.innerDensity)
|
else view?.onLongClick(x / canvas.innerDensity, y / canvas.innerDensity)
|
||||||
} else {
|
|
||||||
view?.onLongClick(x / canvas.innerDensity, y / canvas.innerDensity)
|
|
||||||
}
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,6 @@ fun Color.toInt(): Int {
|
|||||||
(255 * this.alpha).roundToInt(),
|
(255 * this.alpha).roundToInt(),
|
||||||
(255 * this.red).roundToInt(),
|
(255 * this.red).roundToInt(),
|
||||||
(255 * this.green).roundToInt(),
|
(255 * this.green).roundToInt(),
|
||||||
(255 * this.blue).roundToInt()
|
(255 * this.blue).roundToInt(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import android.util.AttributeSet
|
|||||||
|
|
||||||
open class AndroidView<T : View>(
|
open class AndroidView<T : View>(
|
||||||
context: Context,
|
context: Context,
|
||||||
attrs: AttributeSet? = null
|
attrs: AttributeSet? = null,
|
||||||
) : android.view.View(context, attrs) {
|
) : android.view.View(context, attrs) {
|
||||||
|
|
||||||
var view: T? = null
|
var view: T? = null
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ import org.isoron.uhabits.inject.ActivityScope
|
|||||||
class AndroidThemeSwitcher
|
class AndroidThemeSwitcher
|
||||||
constructor(
|
constructor(
|
||||||
@ActivityContext val context: Context,
|
@ActivityContext val context: Context,
|
||||||
preferences: Preferences
|
preferences: Preferences,
|
||||||
) : ThemeSwitcher(preferences) {
|
) : ThemeSwitcher(preferences) {
|
||||||
|
|
||||||
override var currentTheme: Theme = LightTheme()
|
override var currentTheme: Theme = LightTheme()
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import org.isoron.uhabits.utils.startActivitySafely
|
|||||||
class AboutScreen(
|
class AboutScreen(
|
||||||
private val activity: AboutActivity,
|
private val activity: AboutActivity,
|
||||||
private val intents: IntentFactory,
|
private val intents: IntentFactory,
|
||||||
private val prefs: Preferences
|
private val prefs: Preferences,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private var developerCountdown = 5
|
private var developerCountdown = 5
|
||||||
|
|||||||
@@ -26,14 +26,13 @@ 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.applyRootViewInsets
|
|
||||||
import org.isoron.uhabits.utils.currentTheme
|
import org.isoron.uhabits.utils.currentTheme
|
||||||
import org.isoron.uhabits.utils.setupToolbar
|
import org.isoron.uhabits.utils.setupToolbar
|
||||||
|
|
||||||
@SuppressLint("ViewConstructor")
|
@SuppressLint("ViewConstructor")
|
||||||
class AboutView(
|
class AboutView(
|
||||||
context: Context,
|
context: Context,
|
||||||
private val screen: AboutScreen
|
private val screen: AboutScreen,
|
||||||
) : FrameLayout(context) {
|
) : FrameLayout(context) {
|
||||||
|
|
||||||
private var binding = AboutBinding.inflate(LayoutInflater.from(context))
|
private var binding = AboutBinding.inflate(LayoutInflater.from(context))
|
||||||
@@ -44,7 +43,7 @@ class AboutView(
|
|||||||
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()
|
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() }
|
||||||
@@ -55,6 +54,5 @@ class AboutView(
|
|||||||
binding.tvTranslate.setOnClickListener { screen.showTranslationWebsite() }
|
binding.tvTranslate.setOnClickListener { screen.showTranslationWebsite() }
|
||||||
binding.tvVersion.setOnClickListener { screen.onPressDeveloperCountdown() }
|
binding.tvVersion.setOnClickListener { screen.onPressDeveloperCountdown() }
|
||||||
binding.tvVersion.text = String.format(version, BuildConfig.VERSION_NAME)
|
binding.tvVersion.text = String.format(version, BuildConfig.VERSION_NAME)
|
||||||
applyRootViewInsets()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,61 +20,109 @@
|
|||||||
package org.isoron.uhabits.activities.common.dialogs
|
package org.isoron.uhabits.activities.common.dialogs
|
||||||
|
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.os.Bundle
|
import android.content.Context
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
import android.view.View.GONE
|
import android.view.View.GONE
|
||||||
import android.view.View.VISIBLE
|
import android.view.View.VISIBLE
|
||||||
import androidx.appcompat.app.AppCompatDialogFragment
|
|
||||||
import org.isoron.uhabits.HabitsApplication
|
|
||||||
import org.isoron.uhabits.R
|
import org.isoron.uhabits.R
|
||||||
import org.isoron.uhabits.core.models.Entry.Companion.NO
|
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.SKIP
|
||||||
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.isoron.uhabits.core.preferences.Preferences
|
||||||
import org.isoron.uhabits.databinding.CheckmarkPopupBinding
|
import org.isoron.uhabits.databinding.CheckmarkPopupBinding
|
||||||
import org.isoron.uhabits.utils.InterfaceUtils.getFontAwesome
|
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
|
import org.isoron.uhabits.utils.sres
|
||||||
|
|
||||||
class CheckmarkDialog : AppCompatDialogFragment() {
|
const val POPUP_WIDTH = 4 * 48f + 16f
|
||||||
var onToggle: (Int, String) -> Unit = { _, _ -> }
|
const val POPUP_HEIGHT = 48f * 2.5f + 8f
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
class CheckmarkPopup(
|
||||||
val appComponent = (requireActivity().application as HabitsApplication).component
|
private val context: Context,
|
||||||
val prefs = appComponent.preferences
|
private val color: Int,
|
||||||
val view = CheckmarkPopupBinding.inflate(LayoutInflater.from(context))
|
private var notes: String,
|
||||||
val color = requireArguments().getInt("color")
|
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 {
|
arrayOf(view.yesBtn, view.skipBtn).forEach {
|
||||||
it.setTextColor(color)
|
it.setTextColor(color)
|
||||||
}
|
}
|
||||||
arrayOf(view.noBtn, view.unknownBtn).forEach {
|
arrayOf(view.noBtn, view.unknownBtn).forEach {
|
||||||
it.setTextColor(view.root.sres.getColor(R.attr.contrast60))
|
it.setTextColor(view.root.sres.getColor(R.attr.contrast60))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initTypefaces() {
|
||||||
arrayOf(view.yesBtn, view.noBtn, view.skipBtn, view.unknownBtn).forEach {
|
arrayOf(view.yesBtn, view.noBtn, view.skipBtn, view.unknownBtn).forEach {
|
||||||
it.typeface = getFontAwesome(requireContext())
|
it.typeface = getFontAwesome(context)
|
||||||
}
|
}
|
||||||
view.notes.setText(requireArguments().getString("notes")!!)
|
}
|
||||||
|
|
||||||
|
private fun hideDisabledButtons() {
|
||||||
if (!prefs.isSkipEnabled) view.skipBtn.visibility = GONE
|
if (!prefs.isSkipEnabled) view.skipBtn.visibility = GONE
|
||||||
if (!prefs.areQuestionMarksEnabled) view.unknownBtn.visibility = GONE
|
if (!prefs.areQuestionMarksEnabled) view.unknownBtn.visibility = GONE
|
||||||
view.booleanButtons.visibility = VISIBLE
|
}
|
||||||
val dialog = Dialog(requireContext())
|
|
||||||
|
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.setContentView(view.root)
|
||||||
dialog.window?.apply {
|
dialog.window?.apply {
|
||||||
|
setLayout(
|
||||||
|
view.root.dp(POPUP_WIDTH).toInt(),
|
||||||
|
view.root.dp(POPUP_HEIGHT).toInt()
|
||||||
|
)
|
||||||
setBackgroundDrawableResource(android.R.color.transparent)
|
setBackgroundDrawableResource(android.R.color.transparent)
|
||||||
}
|
}
|
||||||
fun onClick(v: Int) {
|
fun onClick(v: Int) {
|
||||||
val notes = view.notes.text.toString().trim()
|
this.value = v
|
||||||
onToggle(v, notes)
|
save()
|
||||||
requireDialog().dismiss()
|
|
||||||
}
|
}
|
||||||
view.yesBtn.setOnClickListener { onClick(YES_MANUAL) }
|
view.yesBtn.setOnClickListener { onClick(YES_MANUAL) }
|
||||||
view.noBtn.setOnClickListener { onClick(NO) }
|
view.noBtn.setOnClickListener { onClick(NO) }
|
||||||
view.skipBtn.setOnClickListener { onClick(SKIP) }
|
view.skipBtn.setOnClickListener { onClick(SKIP) }
|
||||||
view.unknownBtn.setOnClickListener { onClick(UNKNOWN) }
|
view.unknownBtn.setOnClickListener { onClick(UNKNOWN) }
|
||||||
view.notes.setOnEditorActionListener { v, actionId, event ->
|
dialog.setCanceledOnTouchOutside(true)
|
||||||
onClick(requireArguments().getInt("value"))
|
dialog.dimBehind()
|
||||||
true
|
dialog.dismissCurrentAndShow()
|
||||||
}
|
}
|
||||||
|
|
||||||
return dialog
|
fun save() {
|
||||||
|
onToggle(value, view.notes.text.toString().trim())
|
||||||
|
dialog.dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -19,7 +19,6 @@
|
|||||||
package org.isoron.uhabits.activities.common.dialogs
|
package org.isoron.uhabits.activities.common.dialogs
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.android.colorpicker.ColorPickerDialog.SIZE_SMALL
|
|
||||||
import org.isoron.platform.gui.toInt
|
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
|
||||||
@@ -40,7 +39,7 @@ class ColorPickerDialogFactory @Inject constructor(@param:ActivityContext privat
|
|||||||
res.getPalette(),
|
res.getPalette(),
|
||||||
androidColor,
|
androidColor,
|
||||||
4,
|
4,
|
||||||
SIZE_SMALL
|
com.android.colorpicker.ColorPickerDialog.SIZE_SMALL
|
||||||
)
|
)
|
||||||
return dialog
|
return dialog
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,101 +22,97 @@ 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.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.RadioButton
|
import android.widget.RadioButton
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatDialogFragment
|
import androidx.appcompat.app.AppCompatDialogFragment
|
||||||
|
import kotlinx.android.synthetic.main.frequency_picker_dialog.view.*
|
||||||
import org.isoron.uhabits.R
|
import org.isoron.uhabits.R
|
||||||
import org.isoron.uhabits.databinding.FrequencyPickerDialogBinding
|
|
||||||
|
|
||||||
class FrequencyPickerDialog(
|
class FrequencyPickerDialog(
|
||||||
var freqNumerator: Int,
|
var freqNumerator: Int,
|
||||||
var freqDenominator: Int
|
var freqDenominator: Int
|
||||||
) : AppCompatDialogFragment() {
|
) : AppCompatDialogFragment() {
|
||||||
private var _binding: FrequencyPickerDialogBinding? = null
|
|
||||||
private val binding get() = _binding!!
|
|
||||||
|
|
||||||
|
lateinit var contentView: View
|
||||||
var onFrequencyPicked: (num: Int, den: Int) -> Unit = { _, _ -> }
|
var onFrequencyPicked: (num: Int, den: Int) -> Unit = { _, _ -> }
|
||||||
|
|
||||||
constructor() : this(1, 1)
|
constructor() : this(1, 1)
|
||||||
|
|
||||||
override fun onDestroyView() {
|
|
||||||
super.onDestroyView()
|
|
||||||
_binding = null
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
_binding = FrequencyPickerDialogBinding.inflate(LayoutInflater.from(requireActivity()))
|
val inflater = LayoutInflater.from(requireActivity())
|
||||||
|
contentView = inflater.inflate(R.layout.frequency_picker_dialog, null)
|
||||||
|
|
||||||
addBeforeAfterText(
|
addBeforeAfterText(
|
||||||
this.getString(R.string.every_x_days),
|
this.getString(R.string.every_x_days),
|
||||||
binding.everyXDaysContainer
|
contentView.everyXDaysContainer,
|
||||||
)
|
)
|
||||||
|
|
||||||
addBeforeAfterText(
|
addBeforeAfterText(
|
||||||
this.getString(R.string.x_times_per_week),
|
this.getString(R.string.x_times_per_week),
|
||||||
binding.xTimesPerWeekContainer
|
contentView.xTimesPerWeekContainer,
|
||||||
)
|
)
|
||||||
|
|
||||||
addBeforeAfterText(
|
addBeforeAfterText(
|
||||||
this.getString(R.string.x_times_per_month),
|
this.getString(R.string.x_times_per_month),
|
||||||
binding.xTimesPerMonthContainer
|
contentView.xTimesPerMonthContainer,
|
||||||
)
|
)
|
||||||
|
|
||||||
addBeforeAfterText(
|
addBeforeAfterText(
|
||||||
this.getString(R.string.x_times_per_y_days),
|
this.getString(R.string.x_times_per_y_days),
|
||||||
binding.xTimesPerYDaysContainer
|
contentView.xTimesPerYDaysContainer,
|
||||||
)
|
)
|
||||||
|
|
||||||
binding.everyDayRadioButton.setOnClickListener {
|
contentView.everyDayRadioButton.setOnClickListener {
|
||||||
check(binding.everyDayRadioButton)
|
check(contentView.everyDayRadioButton)
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.everyXDaysRadioButton.setOnClickListener {
|
contentView.everyXDaysRadioButton.setOnClickListener {
|
||||||
check(binding.everyXDaysRadioButton)
|
check(contentView.everyXDaysRadioButton)
|
||||||
val everyXDaysTextView = binding.everyXDaysTextView
|
val everyXDaysTextView = contentView.everyXDaysTextView
|
||||||
selectInputField(everyXDaysTextView)
|
selectInputField(everyXDaysTextView)
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.everyXDaysTextView.setOnFocusChangeListener { v, hasFocus ->
|
contentView.everyXDaysTextView.setOnFocusChangeListener { v, hasFocus ->
|
||||||
if (hasFocus) check(binding.everyXDaysRadioButton)
|
if (hasFocus) check(contentView.everyXDaysRadioButton)
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.xTimesPerWeekRadioButton.setOnClickListener {
|
contentView.xTimesPerWeekRadioButton.setOnClickListener {
|
||||||
check(binding.xTimesPerWeekRadioButton)
|
check(contentView.xTimesPerWeekRadioButton)
|
||||||
selectInputField(binding.xTimesPerWeekTextView)
|
selectInputField(contentView.xTimesPerWeekTextView)
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.xTimesPerWeekTextView.setOnFocusChangeListener { v, hasFocus ->
|
contentView.xTimesPerWeekTextView.setOnFocusChangeListener { v, hasFocus ->
|
||||||
if (hasFocus) check(binding.xTimesPerWeekRadioButton)
|
if (hasFocus) check(contentView.xTimesPerWeekRadioButton)
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.xTimesPerMonthRadioButton.setOnClickListener {
|
contentView.xTimesPerMonthRadioButton.setOnClickListener {
|
||||||
check(binding.xTimesPerMonthRadioButton)
|
check(contentView.xTimesPerMonthRadioButton)
|
||||||
selectInputField(binding.xTimesPerMonthTextView)
|
selectInputField(contentView.xTimesPerMonthTextView)
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.xTimesPerMonthTextView.setOnFocusChangeListener { v, hasFocus ->
|
contentView.xTimesPerMonthTextView.setOnFocusChangeListener { v, hasFocus ->
|
||||||
if (hasFocus) check(binding.xTimesPerMonthRadioButton)
|
if (hasFocus) check(contentView.xTimesPerMonthRadioButton)
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.xTimesPerYDaysRadioButton.setOnClickListener {
|
contentView.xTimesPerYDaysRadioButton.setOnClickListener {
|
||||||
check(binding.xTimesPerYDaysRadioButton)
|
check(contentView.xTimesPerYDaysRadioButton)
|
||||||
selectInputField(binding.xTimesPerYDaysXTextView)
|
selectInputField(contentView.xTimesPerYDaysXTextView)
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.xTimesPerYDaysXTextView.setOnFocusChangeListener { v, hasFocus ->
|
contentView.xTimesPerYDaysXTextView.setOnFocusChangeListener { v, hasFocus ->
|
||||||
if (hasFocus) check(binding.xTimesPerYDaysRadioButton)
|
if (hasFocus) check(contentView.xTimesPerYDaysRadioButton)
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.xTimesPerYDaysYTextView.setOnFocusChangeListener { v, hasFocus ->
|
contentView.xTimesPerYDaysYTextView.setOnFocusChangeListener { v, hasFocus ->
|
||||||
if (hasFocus) check(binding.xTimesPerYDaysRadioButton)
|
if (hasFocus) check(contentView.xTimesPerYDaysRadioButton)
|
||||||
}
|
}
|
||||||
|
|
||||||
return AlertDialog.Builder(requireActivity())
|
return AlertDialog.Builder(requireActivity())
|
||||||
.setView(binding.root)
|
.setView(contentView)
|
||||||
.setPositiveButton(R.string.save) { _, _ -> onSaveClicked() }
|
.setPositiveButton(R.string.save) { _, _ -> onSaveClicked() }
|
||||||
.create()
|
.create()
|
||||||
}
|
}
|
||||||
@@ -128,8 +124,7 @@ class FrequencyPickerDialog(
|
|||||||
val parts = str.split("%d")
|
val parts = str.split("%d")
|
||||||
for (i in parts.indices) {
|
for (i in parts.indices) {
|
||||||
container.addView(
|
container.addView(
|
||||||
TextView(activity).apply { text = parts[i].trim() },
|
TextView(activity).apply { text = parts[i].trim() }, 2 * i + 1,
|
||||||
2 * i + 1
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -138,35 +133,31 @@ class FrequencyPickerDialog(
|
|||||||
var numerator = 1
|
var numerator = 1
|
||||||
var denominator = 1
|
var denominator = 1
|
||||||
when {
|
when {
|
||||||
binding.everyDayRadioButton.isChecked -> {
|
contentView.everyDayRadioButton.isChecked -> {
|
||||||
// NOP
|
// NOP
|
||||||
}
|
}
|
||||||
|
contentView.everyXDaysRadioButton.isChecked -> {
|
||||||
binding.everyXDaysRadioButton.isChecked -> {
|
if (contentView.everyXDaysTextView.text.isNotEmpty()) {
|
||||||
if (binding.everyXDaysTextView.text.isNotEmpty()) {
|
denominator = Integer.parseInt(contentView.everyXDaysTextView.text.toString())
|
||||||
denominator = Integer.parseInt(binding.everyXDaysTextView.text.toString())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
contentView.xTimesPerWeekRadioButton.isChecked -> {
|
||||||
binding.xTimesPerWeekRadioButton.isChecked -> {
|
if (contentView.xTimesPerWeekTextView.text.isNotEmpty()) {
|
||||||
if (binding.xTimesPerWeekTextView.text.isNotEmpty()) {
|
numerator = Integer.parseInt(contentView.xTimesPerWeekTextView.text.toString())
|
||||||
numerator = Integer.parseInt(binding.xTimesPerWeekTextView.text.toString())
|
|
||||||
denominator = 7
|
denominator = 7
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
contentView.xTimesPerYDaysRadioButton.isChecked -> {
|
||||||
binding.xTimesPerYDaysRadioButton.isChecked -> {
|
if (contentView.xTimesPerYDaysXTextView.text.isNotEmpty() && contentView.xTimesPerYDaysYTextView.text.isNotEmpty()) {
|
||||||
if (binding.xTimesPerYDaysXTextView.text.isNotEmpty() && binding.xTimesPerYDaysYTextView.text.isNotEmpty()) {
|
|
||||||
numerator =
|
numerator =
|
||||||
Integer.parseInt(binding.xTimesPerYDaysXTextView.text.toString())
|
Integer.parseInt(contentView.xTimesPerYDaysXTextView.text.toString())
|
||||||
denominator =
|
denominator =
|
||||||
Integer.parseInt(binding.xTimesPerYDaysYTextView.text.toString())
|
Integer.parseInt(contentView.xTimesPerYDaysYTextView.text.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
if (binding.xTimesPerMonthTextView.text.isNotEmpty()) {
|
if (contentView.xTimesPerMonthTextView.text.isNotEmpty()) {
|
||||||
numerator = Integer.parseInt(binding.xTimesPerMonthTextView.text.toString())
|
numerator = Integer.parseInt(contentView.xTimesPerMonthTextView.text.toString())
|
||||||
denominator = 30
|
denominator = 30
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -193,27 +184,27 @@ class FrequencyPickerDialog(
|
|||||||
private fun populateViews() {
|
private fun populateViews() {
|
||||||
uncheckAll()
|
uncheckAll()
|
||||||
if (freqDenominator == 30 || freqDenominator == 31) {
|
if (freqDenominator == 30 || freqDenominator == 31) {
|
||||||
binding.xTimesPerMonthRadioButton.isChecked = true
|
contentView.xTimesPerMonthRadioButton.isChecked = true
|
||||||
binding.xTimesPerMonthTextView.setText(freqNumerator.toString())
|
contentView.xTimesPerMonthTextView.setText(freqNumerator.toString())
|
||||||
selectInputField(binding.xTimesPerMonthTextView)
|
selectInputField(contentView.xTimesPerMonthTextView)
|
||||||
} else {
|
} else {
|
||||||
if (freqNumerator == 1) {
|
if (freqNumerator == 1) {
|
||||||
if (freqDenominator == 1) {
|
if (freqDenominator == 1) {
|
||||||
binding.everyDayRadioButton.isChecked = true
|
contentView.everyDayRadioButton.isChecked = true
|
||||||
} else {
|
} else {
|
||||||
binding.everyXDaysRadioButton.isChecked = true
|
contentView.everyXDaysRadioButton.isChecked = true
|
||||||
binding.everyXDaysTextView.setText(freqDenominator.toString())
|
contentView.everyXDaysTextView.setText(freqDenominator.toString())
|
||||||
selectInputField(binding.everyXDaysTextView)
|
selectInputField(contentView.everyXDaysTextView)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (freqDenominator == 7) {
|
if (freqDenominator == 7) {
|
||||||
binding.xTimesPerWeekRadioButton.isChecked = true
|
contentView.xTimesPerWeekRadioButton.isChecked = true
|
||||||
binding.xTimesPerWeekTextView.setText(freqNumerator.toString())
|
contentView.xTimesPerWeekTextView.setText(freqNumerator.toString())
|
||||||
selectInputField(binding.xTimesPerWeekTextView)
|
selectInputField(contentView.xTimesPerWeekTextView)
|
||||||
} else {
|
} else {
|
||||||
binding.xTimesPerYDaysRadioButton.isChecked = true
|
contentView.xTimesPerYDaysRadioButton.isChecked = true
|
||||||
binding.xTimesPerYDaysXTextView.setText(freqNumerator.toString())
|
contentView.xTimesPerYDaysXTextView.setText(freqNumerator.toString())
|
||||||
binding.xTimesPerYDaysYTextView.setText(freqDenominator.toString())
|
contentView.xTimesPerYDaysYTextView.setText(freqDenominator.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -224,10 +215,10 @@ class FrequencyPickerDialog(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun uncheckAll() {
|
private fun uncheckAll() {
|
||||||
binding.everyDayRadioButton.isChecked = false
|
contentView.everyDayRadioButton.isChecked = false
|
||||||
binding.everyXDaysRadioButton.isChecked = false
|
contentView.everyXDaysRadioButton.isChecked = false
|
||||||
binding.xTimesPerWeekRadioButton.isChecked = false
|
contentView.xTimesPerWeekRadioButton.isChecked = false
|
||||||
binding.xTimesPerMonthRadioButton.isChecked = false
|
contentView.xTimesPerMonthRadioButton.isChecked = false
|
||||||
binding.xTimesPerYDaysRadioButton.isChecked = false
|
contentView.xTimesPerYDaysRadioButton.isChecked = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ class HistoryEditorDialog : AppCompatDialogFragment(), CommandRunner.Listener {
|
|||||||
theme = themeSwitcher.currentTheme,
|
theme = themeSwitcher.currentTheme,
|
||||||
today = DateUtils.getTodayWithOffset().toLocalDate(),
|
today = DateUtils.getTodayWithOffset().toLocalDate(),
|
||||||
onDateClickedListener = onDateClickedListener ?: object : OnDateClickedListener {},
|
onDateClickedListener = onDateClickedListener ?: object : OnDateClickedListener {},
|
||||||
padding = 10.0
|
padding = 10.0,
|
||||||
)
|
)
|
||||||
dataView = AndroidDataView(requireContext(), null)
|
dataView = AndroidDataView(requireContext(), null)
|
||||||
dataView.view = chart!!
|
dataView.view = chart!!
|
||||||
|
|||||||
@@ -1,128 +0,0 @@
|
|||||||
package org.isoron.uhabits.activities.common.dialogs
|
|
||||||
|
|
||||||
import android.app.Dialog
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.provider.Settings
|
|
||||||
import android.text.method.DigitsKeyListener
|
|
||||||
import android.view.KeyEvent
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.MotionEvent
|
|
||||||
import android.view.View
|
|
||||||
import android.view.inputmethod.EditorInfo
|
|
||||||
import androidx.appcompat.app.AppCompatDialogFragment
|
|
||||||
import org.isoron.uhabits.HabitsApplication
|
|
||||||
import org.isoron.uhabits.R
|
|
||||||
import org.isoron.uhabits.core.models.Entry
|
|
||||||
import org.isoron.uhabits.databinding.CheckmarkPopupBinding
|
|
||||||
import org.isoron.uhabits.utils.InterfaceUtils
|
|
||||||
import org.isoron.uhabits.utils.getCenter
|
|
||||||
import org.isoron.uhabits.utils.requestFocusWithKeyboard
|
|
||||||
import org.isoron.uhabits.utils.sres
|
|
||||||
import java.text.DecimalFormat
|
|
||||||
import java.text.DecimalFormatSymbols
|
|
||||||
import java.text.NumberFormat
|
|
||||||
import java.text.ParseException
|
|
||||||
|
|
||||||
class NumberDialog : AppCompatDialogFragment() {
|
|
||||||
|
|
||||||
var onToggle: (Double, String) -> Unit = { _, _ -> }
|
|
||||||
var onDismiss: () -> Unit = {}
|
|
||||||
|
|
||||||
private var originalNotes: String = ""
|
|
||||||
private var originalValue: Double = 0.0
|
|
||||||
private lateinit var view: CheckmarkPopupBinding
|
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
|
||||||
val appComponent = (requireActivity().application as HabitsApplication).component
|
|
||||||
val prefs = appComponent.preferences
|
|
||||||
view = CheckmarkPopupBinding.inflate(LayoutInflater.from(context))
|
|
||||||
arrayOf(view.yesBtn).forEach {
|
|
||||||
it.setTextColor(requireArguments().getInt("color"))
|
|
||||||
}
|
|
||||||
arrayOf(view.noBtn, view.unknownBtnNumber).forEach {
|
|
||||||
it.setTextColor(view.root.sres.getColor(R.attr.contrast60))
|
|
||||||
}
|
|
||||||
arrayOf(view.yesBtn, view.noBtn, view.unknownBtnNumber).forEach {
|
|
||||||
it.typeface = InterfaceUtils.getFontAwesome(requireContext())
|
|
||||||
}
|
|
||||||
if (!prefs.isSkipEnabled) view.skipBtnNumber.visibility = View.GONE
|
|
||||||
if (!prefs.areQuestionMarksEnabled) view.unknownBtnNumber.visibility = View.GONE
|
|
||||||
view.numberButtons.visibility = View.VISIBLE
|
|
||||||
fixDecimalSeparator(view)
|
|
||||||
originalNotes = requireArguments().getString("notes")!!
|
|
||||||
originalValue = requireArguments().getDouble("value")
|
|
||||||
view.notes.setText(originalNotes)
|
|
||||||
view.value.setText(
|
|
||||||
when {
|
|
||||||
originalValue < 0.01 -> "0"
|
|
||||||
else -> DecimalFormat("#.##").format(originalValue)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
view.value.setOnKeyListener { _, keyCode, event ->
|
|
||||||
if (event.action == MotionEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER) {
|
|
||||||
save()
|
|
||||||
return@setOnKeyListener true
|
|
||||||
}
|
|
||||||
return@setOnKeyListener false
|
|
||||||
}
|
|
||||||
view.saveBtn.setOnClickListener {
|
|
||||||
save()
|
|
||||||
}
|
|
||||||
view.skipBtnNumber.setOnClickListener {
|
|
||||||
view.value.setText(DecimalFormat("#.###").format((Entry.SKIP.toDouble() / 1000)))
|
|
||||||
save()
|
|
||||||
}
|
|
||||||
|
|
||||||
view.unknownBtnNumber.setOnClickListener {
|
|
||||||
view.value.setText(DecimalFormat("#.###").format((Entry.UNKNOWN.toDouble() / 1000)))
|
|
||||||
save()
|
|
||||||
}
|
|
||||||
|
|
||||||
view.notes.setOnEditorActionListener { v, actionId, event ->
|
|
||||||
save()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
view.value.requestFocusWithKeyboard()
|
|
||||||
val dialog = Dialog(requireContext())
|
|
||||||
dialog.setContentView(view.root)
|
|
||||||
dialog.window?.apply {
|
|
||||||
setBackgroundDrawableResource(android.R.color.transparent)
|
|
||||||
}
|
|
||||||
dialog.setOnDismissListener { onDismiss() }
|
|
||||||
return dialog
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun fixDecimalSeparator(view: CheckmarkPopupBinding) {
|
|
||||||
// https://stackoverflow.com/a/34256139
|
|
||||||
val separator = DecimalFormatSymbols.getInstance().decimalSeparator
|
|
||||||
view.value.keyListener = DigitsKeyListener.getInstance("0123456789$separator")
|
|
||||||
|
|
||||||
// https://github.com/flutter/flutter/issues/61175
|
|
||||||
val currKeyboard = Settings.Secure.getString(
|
|
||||||
requireContext().contentResolver,
|
|
||||||
Settings.Secure.DEFAULT_INPUT_METHOD
|
|
||||||
)
|
|
||||||
if (currKeyboard.contains("swiftkey") || currKeyboard.contains("samsung")) {
|
|
||||||
view.value.inputType = EditorInfo.TYPE_CLASS_TEXT
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun save() {
|
|
||||||
var value = originalValue
|
|
||||||
try {
|
|
||||||
val numberFormat = NumberFormat.getInstance()
|
|
||||||
val valueStr = view.value.text.toString()
|
|
||||||
value = if (valueStr.isNotEmpty()) {
|
|
||||||
numberFormat.parse(valueStr)!!.toDouble()
|
|
||||||
} else {
|
|
||||||
Entry.UNKNOWN.toDouble() / 1000
|
|
||||||
}
|
|
||||||
} catch (e: ParseException) {
|
|
||||||
// NOP
|
|
||||||
}
|
|
||||||
val notes = view.notes.text.toString()
|
|
||||||
val location = view.saveBtn.getCenter()
|
|
||||||
onToggle(value, notes)
|
|
||||||
requireDialog().dismiss()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
/*
|
||||||
|
* 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.KeyEvent.KEYCODE_ENTER
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.MotionEvent.ACTION_DOWN
|
||||||
|
import android.view.View
|
||||||
|
import android.view.View.GONE
|
||||||
|
import android.view.View.VISIBLE
|
||||||
|
import org.isoron.uhabits.core.models.Entry
|
||||||
|
import org.isoron.uhabits.core.preferences.Preferences
|
||||||
|
import org.isoron.uhabits.databinding.CheckmarkPopupBinding
|
||||||
|
import org.isoron.uhabits.utils.dimBehind
|
||||||
|
import org.isoron.uhabits.utils.dismissCurrentAndShow
|
||||||
|
import org.isoron.uhabits.utils.dp
|
||||||
|
import org.isoron.uhabits.utils.requestFocusWithKeyboard
|
||||||
|
import java.text.DecimalFormat
|
||||||
|
|
||||||
|
class NumberPopup(
|
||||||
|
private val context: Context,
|
||||||
|
private var notes: String,
|
||||||
|
private var value: Double,
|
||||||
|
private val prefs: Preferences,
|
||||||
|
private val anchor: View,
|
||||||
|
) {
|
||||||
|
var onToggle: (Double, String) -> Unit = { _, _ -> }
|
||||||
|
var onDismiss: () -> Unit = {}
|
||||||
|
private val originalValue = value
|
||||||
|
private lateinit var dialog: Dialog
|
||||||
|
|
||||||
|
private val view = CheckmarkPopupBinding.inflate(LayoutInflater.from(context)).apply {
|
||||||
|
// Required for round corners
|
||||||
|
container.clipToOutline = true
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
view.numberButtons.visibility = VISIBLE
|
||||||
|
hideDisabledButtons()
|
||||||
|
populate()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun hideDisabledButtons() {
|
||||||
|
if (!prefs.isSkipEnabled) view.skipBtnNumber.visibility = GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun populate() {
|
||||||
|
view.notes.setText(notes)
|
||||||
|
view.value.setText(
|
||||||
|
when {
|
||||||
|
value < 0.01 -> "0"
|
||||||
|
else -> DecimalFormat("#.##").format(value)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
dialog.setOnDismissListener {
|
||||||
|
onDismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
view.value.setOnKeyListener { _, keyCode, event ->
|
||||||
|
if (event.action == ACTION_DOWN && keyCode == KEYCODE_ENTER) {
|
||||||
|
save()
|
||||||
|
return@setOnKeyListener true
|
||||||
|
}
|
||||||
|
return@setOnKeyListener false
|
||||||
|
}
|
||||||
|
view.saveBtn.setOnClickListener {
|
||||||
|
save()
|
||||||
|
}
|
||||||
|
view.skipBtnNumber.setOnClickListener {
|
||||||
|
view.value.setText((Entry.SKIP.toDouble() / 1000).toString())
|
||||||
|
save()
|
||||||
|
}
|
||||||
|
view.value.requestFocusWithKeyboard()
|
||||||
|
dialog.setCanceledOnTouchOutside(true)
|
||||||
|
dialog.dimBehind()
|
||||||
|
dialog.dismissCurrentAndShow()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun save() {
|
||||||
|
val value = view.value.text.toString().toDoubleOrNull() ?: originalValue
|
||||||
|
val notes = view.notes.text.toString()
|
||||||
|
onToggle(value, notes)
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -181,9 +181,8 @@ class FrequencyChart : ScrollableChart {
|
|||||||
rect[0f, 0f, baseSize.toFloat()] = baseSize.toFloat()
|
rect[0f, 0f, baseSize.toFloat()] = baseSize.toFloat()
|
||||||
rect.offset(prevRect!!.left, prevRect!!.top + baseSize * j)
|
rect.offset(prevRect!!.left, prevRect!!.top + baseSize * j)
|
||||||
val i = localeWeekdayList[j] % 7
|
val i = localeWeekdayList[j] % 7
|
||||||
if (values != null) {
|
if (values != null)
|
||||||
drawMarker(canvas, rect, values[i], weekDaysInMonth[i])
|
drawMarker(canvas, rect, values[i], weekDaysInMonth[i])
|
||||||
}
|
|
||||||
rect.offset(0f, rowHeight)
|
rect.offset(0f, rowHeight)
|
||||||
}
|
}
|
||||||
drawFooter(canvas, rect, date)
|
drawFooter(canvas, rect, date)
|
||||||
@@ -197,14 +196,12 @@ class FrequencyChart : ScrollableChart {
|
|||||||
rect.centerY() - 0.1f * em,
|
rect.centerY() - 0.1f * em,
|
||||||
pText!!
|
pText!!
|
||||||
)
|
)
|
||||||
if (date[Calendar.MONTH] == 1) {
|
if (date[Calendar.MONTH] == 1) canvas.drawText(
|
||||||
canvas.drawText(
|
dfYear!!.format(time),
|
||||||
dfYear!!.format(time),
|
rect.centerX(),
|
||||||
rect.centerX(),
|
rect.centerY() + 0.9f * em,
|
||||||
rect.centerY() + 0.9f * em,
|
pText!!
|
||||||
pText!!
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun drawGrid(canvas: Canvas, rGrid: RectF?) {
|
private fun drawGrid(canvas: Canvas, rGrid: RectF?) {
|
||||||
|
|||||||
@@ -58,7 +58,6 @@ class RingView : View {
|
|||||||
private var em = 0f
|
private var em = 0f
|
||||||
private var text: String?
|
private var text: String?
|
||||||
private var textSize: Float
|
private var textSize: Float
|
||||||
private var isStrokedTextEnabled: Boolean = false
|
|
||||||
private var enableFontAwesome = false
|
private var enableFontAwesome = false
|
||||||
private var internalDrawingCache: Bitmap? = null
|
private var internalDrawingCache: Bitmap? = null
|
||||||
private var cacheCanvas: Canvas? = null
|
private var cacheCanvas: Canvas? = null
|
||||||
@@ -132,10 +131,6 @@ class RingView : View {
|
|||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setIsStrokedTextEnabled(isStroked: Boolean) {
|
|
||||||
this.isStrokedTextEnabled = isStroked
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDraw(canvas: Canvas) {
|
override fun onDraw(canvas: Canvas) {
|
||||||
super.onDraw(canvas)
|
super.onDraw(canvas)
|
||||||
val activeCanvas: Canvas?
|
val activeCanvas: Canvas?
|
||||||
@@ -153,23 +148,13 @@ class RingView : View {
|
|||||||
pRing!!.color = inactiveColor!!
|
pRing!!.color = inactiveColor!!
|
||||||
activeCanvas.drawArc(rect!!, angle - 90, 360 - angle, true, pRing!!)
|
activeCanvas.drawArc(rect!!, angle - 90, 360 - angle, true, pRing!!)
|
||||||
if (thickness > 0) {
|
if (thickness > 0) {
|
||||||
if (isTransparencyEnabled) {
|
if (isTransparencyEnabled) pRing!!.xfermode = XFERMODE_CLEAR else pRing!!.color =
|
||||||
pRing!!.xfermode = XFERMODE_CLEAR
|
backgroundColor!!
|
||||||
} else {
|
|
||||||
pRing!!.color =
|
|
||||||
backgroundColor!!
|
|
||||||
}
|
|
||||||
rect!!.inset(thickness, thickness)
|
rect!!.inset(thickness, thickness)
|
||||||
activeCanvas.drawArc(rect!!, 0f, 360f, true, pRing!!)
|
activeCanvas.drawArc(rect!!, 0f, 360f, true, pRing!!)
|
||||||
pRing!!.xfermode = null
|
pRing!!.xfermode = null
|
||||||
pRing!!.color = color
|
pRing!!.color = color
|
||||||
pRing!!.textSize = textSize
|
pRing!!.textSize = textSize
|
||||||
|
|
||||||
if (isStrokedTextEnabled) {
|
|
||||||
pRing!!.style = Paint.Style.STROKE
|
|
||||||
pRing!!.strokeWidth = textSize / 15f
|
|
||||||
}
|
|
||||||
|
|
||||||
if (enableFontAwesome) pRing!!.typeface = getFontAwesome(context)
|
if (enableFontAwesome) pRing!!.typeface = getFontAwesome(context)
|
||||||
activeCanvas.drawText(
|
activeCanvas.drawText(
|
||||||
text!!,
|
text!!,
|
||||||
@@ -204,7 +189,7 @@ class RingView : View {
|
|||||||
val res = StyledResources(context)
|
val res = StyledResources(context)
|
||||||
if (backgroundColor == null) backgroundColor = res.getColor(R.attr.cardBgColor)
|
if (backgroundColor == null) backgroundColor = res.getColor(R.attr.cardBgColor)
|
||||||
if (inactiveColor == null) inactiveColor = res.getColor(R.attr.contrast100)
|
if (inactiveColor == null) inactiveColor = res.getColor(R.attr.contrast100)
|
||||||
inactiveColor = setAlpha(inactiveColor!!, 0.15f)
|
inactiveColor = setAlpha(inactiveColor!!, 0.1f)
|
||||||
rect = RectF()
|
rect = RectF()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ abstract class ScrollableChart : View, GestureDetector.OnGestureListener, Animat
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onFling(
|
override fun onFling(
|
||||||
e1: MotionEvent?,
|
e1: MotionEvent,
|
||||||
e2: MotionEvent,
|
e2: MotionEvent,
|
||||||
velocityX: Float,
|
velocityX: Float,
|
||||||
velocityY: Float
|
velocityY: Float
|
||||||
@@ -116,7 +116,7 @@ abstract class ScrollableChart : View, GestureDetector.OnGestureListener, Animat
|
|||||||
return BundleSavedState(superState, bundle)
|
return BundleSavedState(superState, bundle)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onScroll(e1: MotionEvent?, e2: MotionEvent, dx: Float, dy: Float): Boolean {
|
override fun onScroll(e1: MotionEvent?, e2: MotionEvent?, dx: Float, dy: Float): Boolean {
|
||||||
var dx = dx
|
var dx = dx
|
||||||
if (scrollerBucketSize == 0) return false
|
if (scrollerBucketSize == 0) return false
|
||||||
if (abs(dx) > abs(dy)) {
|
if (abs(dx) > abs(dy)) {
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ package org.isoron.uhabits.activities.habits.edit
|
|||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
|
import android.graphics.Color
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.Html
|
import android.text.Html
|
||||||
import android.text.Spanned
|
import android.text.Spanned
|
||||||
@@ -34,6 +35,11 @@ import androidx.appcompat.app.AppCompatActivity
|
|||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import com.android.datetimepicker.time.RadialPickerLayout
|
import com.android.datetimepicker.time.RadialPickerLayout
|
||||||
import com.android.datetimepicker.time.TimePickerDialog
|
import com.android.datetimepicker.time.TimePickerDialog
|
||||||
|
import kotlinx.android.synthetic.main.activity_edit_habit.nameInput
|
||||||
|
import kotlinx.android.synthetic.main.activity_edit_habit.notesInput
|
||||||
|
import kotlinx.android.synthetic.main.activity_edit_habit.questionInput
|
||||||
|
import kotlinx.android.synthetic.main.activity_edit_habit.targetInput
|
||||||
|
import kotlinx.android.synthetic.main.activity_edit_habit.unitInput
|
||||||
import org.isoron.platform.gui.toInt
|
import org.isoron.platform.gui.toInt
|
||||||
import org.isoron.uhabits.HabitsApplication
|
import org.isoron.uhabits.HabitsApplication
|
||||||
import org.isoron.uhabits.R
|
import org.isoron.uhabits.R
|
||||||
@@ -52,8 +58,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
|
import org.isoron.uhabits.core.models.WeekdayList
|
||||||
import org.isoron.uhabits.databinding.ActivityEditHabitBinding
|
import org.isoron.uhabits.databinding.ActivityEditHabitBinding
|
||||||
import org.isoron.uhabits.utils.applyRootViewInsets
|
import org.isoron.uhabits.utils.ColorUtils
|
||||||
import org.isoron.uhabits.utils.applyToolbarInsets
|
|
||||||
import org.isoron.uhabits.utils.dismissCurrentAndShow
|
import org.isoron.uhabits.utils.dismissCurrentAndShow
|
||||||
import org.isoron.uhabits.utils.formatTime
|
import org.isoron.uhabits.utils.formatTime
|
||||||
import org.isoron.uhabits.utils.toFormattedString
|
import org.isoron.uhabits.utils.toFormattedString
|
||||||
@@ -94,8 +99,6 @@ class EditHabitActivity : AppCompatActivity() {
|
|||||||
themeSwitcher.apply()
|
themeSwitcher.apply()
|
||||||
|
|
||||||
binding = ActivityEditHabitBinding.inflate(layoutInflater)
|
binding = ActivityEditHabitBinding.inflate(layoutInflater)
|
||||||
binding.root.applyRootViewInsets()
|
|
||||||
binding.toolbar.applyToolbarInsets()
|
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
if (intent.hasExtra("habitId")) {
|
if (intent.hasExtra("habitId")) {
|
||||||
@@ -268,9 +271,9 @@ class EditHabitActivity : AppCompatActivity() {
|
|||||||
habit.copyFrom(original)
|
habit.copyFrom(original)
|
||||||
}
|
}
|
||||||
|
|
||||||
habit.name = binding.nameInput.text.trim().toString()
|
habit.name = nameInput.text.trim().toString()
|
||||||
habit.question = binding.questionInput.text.trim().toString()
|
habit.question = questionInput.text.trim().toString()
|
||||||
habit.description = binding.notesInput.text.trim().toString()
|
habit.description = notesInput.text.trim().toString()
|
||||||
habit.color = color
|
habit.color = color
|
||||||
if (reminderHour >= 0) {
|
if (reminderHour >= 0) {
|
||||||
habit.reminder = Reminder(reminderHour, reminderMin, reminderDays)
|
habit.reminder = Reminder(reminderHour, reminderMin, reminderDays)
|
||||||
@@ -280,9 +283,9 @@ class EditHabitActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
habit.frequency = Frequency(freqNum, freqDen)
|
habit.frequency = Frequency(freqNum, freqDen)
|
||||||
if (habitType == HabitType.NUMERICAL) {
|
if (habitType == HabitType.NUMERICAL) {
|
||||||
habit.targetValue = binding.targetInput.text.toString().toDouble()
|
habit.targetValue = targetInput.text.toString().toDouble()
|
||||||
habit.targetType = targetType
|
habit.targetType = targetType
|
||||||
habit.unit = binding.unitInput.text.trim().toString()
|
habit.unit = unitInput.text.trim().toString()
|
||||||
}
|
}
|
||||||
habit.type = habitType
|
habit.type = habitType
|
||||||
|
|
||||||
@@ -305,13 +308,13 @@ class EditHabitActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
private fun validate(): Boolean {
|
private fun validate(): Boolean {
|
||||||
var isValid = true
|
var isValid = true
|
||||||
if (binding.nameInput.text.isEmpty()) {
|
if (nameInput.text.isEmpty()) {
|
||||||
binding.nameInput.error = getFormattedValidationError(R.string.validation_cannot_be_blank)
|
nameInput.error = getFormattedValidationError(R.string.validation_cannot_be_blank)
|
||||||
isValid = false
|
isValid = false
|
||||||
}
|
}
|
||||||
if (habitType == HabitType.NUMERICAL) {
|
if (habitType == HabitType.NUMERICAL) {
|
||||||
if (binding.targetInput.text.isEmpty()) {
|
if (targetInput.text.isEmpty()) {
|
||||||
binding.targetInput.error = getString(R.string.validation_cannot_be_blank)
|
targetInput.error = getString(R.string.validation_cannot_be_blank)
|
||||||
isValid = false
|
isValid = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -354,7 +357,8 @@ class EditHabitActivity : AppCompatActivity() {
|
|||||||
androidColor = themeSwitcher.currentTheme.color(color).toInt()
|
androidColor = themeSwitcher.currentTheme.color(color).toInt()
|
||||||
binding.colorButton.backgroundTintList = ColorStateList.valueOf(androidColor)
|
binding.colorButton.backgroundTintList = ColorStateList.valueOf(androidColor)
|
||||||
if (!themeSwitcher.isNightMode) {
|
if (!themeSwitcher.isNightMode) {
|
||||||
window.statusBarColor = androidColor
|
val darkerAndroidColor = ColorUtils.mixColors(Color.BLACK, androidColor, 0.15f)
|
||||||
|
window.statusBarColor = darkerAndroidColor
|
||||||
binding.toolbar.setBackgroundColor(androidColor)
|
binding.toolbar.setBackgroundColor(androidColor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,17 +19,12 @@
|
|||||||
|
|
||||||
package org.isoron.uhabits.activities.habits.list
|
package org.isoron.uhabits.activities.habits.list
|
||||||
|
|
||||||
import android.Manifest.permission.POST_NOTIFICATIONS
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager.PERMISSION_GRANTED
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.content.ContextCompat.checkSelfPermission
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import org.isoron.uhabits.BaseExceptionHandler
|
import org.isoron.uhabits.BaseExceptionHandler
|
||||||
@@ -45,8 +40,6 @@ import org.isoron.uhabits.inject.ActivityContextModule
|
|||||||
import org.isoron.uhabits.inject.DaggerHabitsActivityComponent
|
import org.isoron.uhabits.inject.DaggerHabitsActivityComponent
|
||||||
import org.isoron.uhabits.inject.HabitsActivityComponent
|
import org.isoron.uhabits.inject.HabitsActivityComponent
|
||||||
import org.isoron.uhabits.inject.HabitsApplicationComponent
|
import org.isoron.uhabits.inject.HabitsApplicationComponent
|
||||||
import org.isoron.uhabits.utils.applyRootViewInsets
|
|
||||||
import org.isoron.uhabits.utils.dismissCurrentDialog
|
|
||||||
import org.isoron.uhabits.utils.restartWithFade
|
import org.isoron.uhabits.utils.restartWithFade
|
||||||
|
|
||||||
class ListHabitsActivity : AppCompatActivity(), Preferences.Listener {
|
class ListHabitsActivity : AppCompatActivity(), Preferences.Listener {
|
||||||
@@ -62,16 +55,6 @@ class ListHabitsActivity : AppCompatActivity(), Preferences.Listener {
|
|||||||
lateinit var midnightTimer: MidnightTimer
|
lateinit var midnightTimer: MidnightTimer
|
||||||
private val scope = CoroutineScope(Dispatchers.Main)
|
private val scope = CoroutineScope(Dispatchers.Main)
|
||||||
|
|
||||||
private var permissionAlreadyRequested = false
|
|
||||||
private val permissionLauncher =
|
|
||||||
registerForActivityResult(RequestPermission()) { isGranted: Boolean ->
|
|
||||||
if (isGranted) {
|
|
||||||
scheduleReminders()
|
|
||||||
} else {
|
|
||||||
Log.i("ListHabitsActivity", "POST_NOTIFICATIONS denied")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private lateinit var menu: ListHabitsMenu
|
private lateinit var menu: ListHabitsMenu
|
||||||
|
|
||||||
override fun onQuestionMarksChanged() {
|
override fun onQuestionMarksChanged() {
|
||||||
@@ -101,7 +84,6 @@ class ListHabitsActivity : AppCompatActivity(), Preferences.Listener {
|
|||||||
menu = component.listHabitsMenu
|
menu = component.listHabitsMenu
|
||||||
Thread.setDefaultUncaughtExceptionHandler(BaseExceptionHandler(this))
|
Thread.setDefaultUncaughtExceptionHandler(BaseExceptionHandler(this))
|
||||||
component.listHabitsBehavior.onStartup()
|
component.listHabitsBehavior.onStartup()
|
||||||
rootView.applyRootViewInsets()
|
|
||||||
setContentView(rootView)
|
setContentView(rootView)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,7 +91,6 @@ class ListHabitsActivity : AppCompatActivity(), Preferences.Listener {
|
|||||||
midnightTimer.onPause()
|
midnightTimer.onPause()
|
||||||
screen.onDetached()
|
screen.onDetached()
|
||||||
adapter.cancelRefresh()
|
adapter.cancelRefresh()
|
||||||
dismissCurrentDialog()
|
|
||||||
super.onPause()
|
super.onPause()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,26 +99,6 @@ class ListHabitsActivity : AppCompatActivity(), Preferences.Listener {
|
|||||||
screen.onAttached()
|
screen.onAttached()
|
||||||
rootView.postInvalidate()
|
rootView.postInvalidate()
|
||||||
midnightTimer.onResume()
|
midnightTimer.onResume()
|
||||||
|
|
||||||
if (appComponent.reminderScheduler.hasHabitsWithReminders()) {
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
|
||||||
scheduleReminders()
|
|
||||||
} else {
|
|
||||||
if (checkSelfPermission(this, POST_NOTIFICATIONS) == PERMISSION_GRANTED) {
|
|
||||||
scheduleReminders()
|
|
||||||
} else {
|
|
||||||
// If we have not requested the permission yet, request it. Otherwide do
|
|
||||||
// nothing. This check is necessary to avoid an infinite onResume loop in case
|
|
||||||
// the user denies the permission.
|
|
||||||
if (!permissionAlreadyRequested) {
|
|
||||||
Log.i("ListHabitsActivity", "Requestion permission: POST_NOTIFICATIONS")
|
|
||||||
permissionLauncher.launch(POST_NOTIFICATIONS)
|
|
||||||
permissionAlreadyRequested = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
taskRunner.run {
|
taskRunner.run {
|
||||||
try {
|
try {
|
||||||
AutoBackup(this@ListHabitsActivity).run()
|
AutoBackup(this@ListHabitsActivity).run()
|
||||||
@@ -153,10 +114,6 @@ class ListHabitsActivity : AppCompatActivity(), Preferences.Listener {
|
|||||||
super.onResume()
|
super.onResume()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun scheduleReminders() {
|
|
||||||
appComponent.reminderScheduler.scheduleAll()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(m: Menu): Boolean {
|
override fun onCreateOptionsMenu(m: Menu): Boolean {
|
||||||
menu.onCreate(menuInflater, m)
|
menu.onCreate(menuInflater, m)
|
||||||
return true
|
return true
|
||||||
@@ -167,7 +124,6 @@ class ListHabitsActivity : AppCompatActivity(), Preferences.Listener {
|
|||||||
return menu.onItemSelected(item)
|
return menu.onItemSelected(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated("Deprecated in Java")
|
|
||||||
override fun onActivityResult(request: Int, result: Int, data: Intent?) {
|
override fun onActivityResult(request: Int, result: Int, data: Intent?) {
|
||||||
super.onActivityResult(request, result, data)
|
super.onActivityResult(request, result, data)
|
||||||
screen.onResult(request, result, data)
|
screen.onResult(request, result, data)
|
||||||
@@ -180,7 +136,7 @@ class ListHabitsActivity : AppCompatActivity(), Preferences.Listener {
|
|||||||
val timestamp = intent.extras?.getLong("timestamp")
|
val timestamp = intent.extras?.getLong("timestamp")
|
||||||
if (habitId != null && timestamp != null) {
|
if (habitId != null && timestamp != null) {
|
||||||
val habit = appComponent.habitList.getById(habitId)!!
|
val habit = appComponent.habitList.getById(habitId)!!
|
||||||
component.listHabitsBehavior.onEdit(habit, Timestamp(timestamp), 0f, 0f)
|
component.listHabitsBehavior.onEdit(habit, Timestamp(timestamp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
intent = null
|
intent = null
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import android.content.Context
|
|||||||
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import android.widget.RelativeLayout
|
import android.widget.RelativeLayout
|
||||||
import nl.dionsegijn.konfetti.xml.KonfettiView
|
|
||||||
import org.isoron.uhabits.R
|
import org.isoron.uhabits.R
|
||||||
import org.isoron.uhabits.activities.common.views.ScrollableChart
|
import org.isoron.uhabits.activities.common.views.ScrollableChart
|
||||||
import org.isoron.uhabits.activities.common.views.TaskProgressBar
|
import org.isoron.uhabits.activities.common.views.TaskProgressBar
|
||||||
@@ -70,9 +69,6 @@ class ListHabitsRootView @Inject constructor(
|
|||||||
val listView: HabitCardListView = habitCardListViewFactory.create()
|
val listView: HabitCardListView = habitCardListViewFactory.create()
|
||||||
val llEmpty = EmptyListView(context)
|
val llEmpty = EmptyListView(context)
|
||||||
val tbar = buildToolbar()
|
val tbar = buildToolbar()
|
||||||
val konfettiView = KonfettiView(context).apply {
|
|
||||||
translationZ = 10f
|
|
||||||
}
|
|
||||||
val progressBar = TaskProgressBar(context, runner)
|
val progressBar = TaskProgressBar(context, runner)
|
||||||
val hintView: HintView
|
val hintView: HintView
|
||||||
val header = HeaderView(context, preferences, midnightTimer)
|
val header = HeaderView(context, preferences, midnightTimer)
|
||||||
@@ -84,7 +80,6 @@ class ListHabitsRootView @Inject constructor(
|
|||||||
|
|
||||||
val rootView = RelativeLayout(context).apply {
|
val rootView = RelativeLayout(context).apply {
|
||||||
background = sres.getDrawable(R.attr.windowBackgroundColor)
|
background = sres.getDrawable(R.attr.windowBackgroundColor)
|
||||||
addAtTop(konfettiView)
|
|
||||||
addAtTop(tbar)
|
addAtTop(tbar)
|
||||||
addBelow(header, tbar)
|
addBelow(header, tbar)
|
||||||
addBelow(listView, header, height = MATCH_PARENT)
|
addBelow(listView, header, height = MATCH_PARENT)
|
||||||
@@ -99,7 +94,7 @@ class ListHabitsRootView @Inject constructor(
|
|||||||
title = resources.getString(R.string.main_activity_title),
|
title = resources.getString(R.string.main_activity_title),
|
||||||
color = PaletteColor(17),
|
color = PaletteColor(17),
|
||||||
displayHomeAsUpEnabled = false,
|
displayHomeAsUpEnabled = false,
|
||||||
theme = currentTheme()
|
theme = currentTheme(),
|
||||||
)
|
)
|
||||||
addView(rootView, MATCH_PARENT, MATCH_PARENT)
|
addView(rootView, MATCH_PARENT, MATCH_PARENT)
|
||||||
listAdapter.setListView(listView)
|
listAdapter.setListView(listView)
|
||||||
|
|||||||
@@ -22,18 +22,14 @@ package org.isoron.uhabits.activities.habits.list
|
|||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import dagger.Lazy
|
import dagger.Lazy
|
||||||
import nl.dionsegijn.konfetti.core.Party
|
|
||||||
import nl.dionsegijn.konfetti.core.Position
|
|
||||||
import nl.dionsegijn.konfetti.core.emitter.Emitter
|
|
||||||
import org.isoron.platform.gui.toInt
|
import org.isoron.platform.gui.toInt
|
||||||
import org.isoron.uhabits.R
|
import org.isoron.uhabits.R
|
||||||
import org.isoron.uhabits.activities.common.dialogs.CheckmarkDialog
|
import org.isoron.uhabits.activities.common.dialogs.CheckmarkPopup
|
||||||
import org.isoron.uhabits.activities.common.dialogs.ColorPickerDialogFactory
|
import org.isoron.uhabits.activities.common.dialogs.ColorPickerDialogFactory
|
||||||
import org.isoron.uhabits.activities.common.dialogs.ConfirmDeleteDialog
|
import org.isoron.uhabits.activities.common.dialogs.ConfirmDeleteDialog
|
||||||
import org.isoron.uhabits.activities.common.dialogs.NumberDialog
|
import org.isoron.uhabits.activities.common.dialogs.NumberPopup
|
||||||
import org.isoron.uhabits.activities.habits.edit.HabitTypeDialog
|
import org.isoron.uhabits.activities.habits.edit.HabitTypeDialog
|
||||||
import org.isoron.uhabits.activities.habits.list.views.HabitCardListAdapter
|
import org.isoron.uhabits.activities.habits.list.views.HabitCardListAdapter
|
||||||
import org.isoron.uhabits.core.commands.ArchiveHabitsCommand
|
import org.isoron.uhabits.core.commands.ArchiveHabitsCommand
|
||||||
@@ -66,7 +62,6 @@ import org.isoron.uhabits.intents.IntentFactory
|
|||||||
import org.isoron.uhabits.tasks.ExportDBTaskFactory
|
import org.isoron.uhabits.tasks.ExportDBTaskFactory
|
||||||
import org.isoron.uhabits.tasks.ImportDataTask
|
import org.isoron.uhabits.tasks.ImportDataTask
|
||||||
import org.isoron.uhabits.tasks.ImportDataTaskFactory
|
import org.isoron.uhabits.tasks.ImportDataTaskFactory
|
||||||
import org.isoron.uhabits.utils.ColorUtils
|
|
||||||
import org.isoron.uhabits.utils.copyTo
|
import org.isoron.uhabits.utils.copyTo
|
||||||
import org.isoron.uhabits.utils.currentTheme
|
import org.isoron.uhabits.utils.currentTheme
|
||||||
import org.isoron.uhabits.utils.dismissCurrentAndShow
|
import org.isoron.uhabits.utils.dismissCurrentAndShow
|
||||||
@@ -76,7 +71,6 @@ import org.isoron.uhabits.utils.showSendEmailScreen
|
|||||||
import org.isoron.uhabits.utils.showSendFileScreen
|
import org.isoron.uhabits.utils.showSendFileScreen
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
const val RESULT_IMPORT_DATA = 101
|
const val RESULT_IMPORT_DATA = 101
|
||||||
@@ -101,7 +95,7 @@ class ListHabitsScreen
|
|||||||
private val colorPickerFactory: ColorPickerDialogFactory,
|
private val colorPickerFactory: ColorPickerDialogFactory,
|
||||||
private val behavior: Lazy<ListHabitsBehavior>,
|
private val behavior: Lazy<ListHabitsBehavior>,
|
||||||
private val preferences: Preferences,
|
private val preferences: Preferences,
|
||||||
private val rootView: Lazy<ListHabitsRootView>
|
private val rootView: Lazy<ListHabitsRootView>,
|
||||||
) : CommandRunner.Listener,
|
) : CommandRunner.Listener,
|
||||||
ListHabitsBehavior.Screen,
|
ListHabitsBehavior.Screen,
|
||||||
ListHabitsMenuBehavior.Screen,
|
ListHabitsMenuBehavior.Screen,
|
||||||
@@ -223,30 +217,6 @@ class ListHabitsScreen
|
|||||||
activity.showSendFileScreen(filename)
|
activity.showSendFileScreen(filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showConfetti(color: PaletteColor, x: Float, y: Float) {
|
|
||||||
if (x == 0f && y == 0f) return
|
|
||||||
if (preferences.isConfettiAnimationDisabled) return
|
|
||||||
val baseColor = themeSwitcher.currentTheme!!.color(color).toInt()
|
|
||||||
rootView.get().konfettiView.start(
|
|
||||||
Party(
|
|
||||||
speed = 0f,
|
|
||||||
maxSpeed = 16f,
|
|
||||||
damping = 0.9f,
|
|
||||||
spread = 360,
|
|
||||||
angle = 0,
|
|
||||||
colors = listOf(
|
|
||||||
ColorUtils.changeHue(baseColor, 180f),
|
|
||||||
ColorUtils.changeHue(baseColor, 20f),
|
|
||||||
ColorUtils.changeHue(baseColor, -20f),
|
|
||||||
baseColor
|
|
||||||
),
|
|
||||||
position = Position.Absolute(x, y),
|
|
||||||
emitter = Emitter(duration = 25, TimeUnit.MILLISECONDS).max(25),
|
|
||||||
timeToLive = 0
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun showSettingsScreen() {
|
override fun showSettingsScreen() {
|
||||||
val intent = intentFactory.startSettingsActivity(activity)
|
val intent = intentFactory.startSettingsActivity(activity)
|
||||||
activity.startActivityForResult(intent, REQUEST_SETTINGS)
|
activity.startActivityForResult(intent, REQUEST_SETTINGS)
|
||||||
@@ -263,14 +233,17 @@ class ListHabitsScreen
|
|||||||
notes: String,
|
notes: String,
|
||||||
callback: ListHabitsBehavior.NumberPickerCallback
|
callback: ListHabitsBehavior.NumberPickerCallback
|
||||||
) {
|
) {
|
||||||
val fm = (context as AppCompatActivity).supportFragmentManager
|
val view = rootView.get()
|
||||||
val dialog = NumberDialog()
|
NumberPopup(
|
||||||
dialog.arguments = Bundle().apply {
|
context = context,
|
||||||
putDouble("value", value)
|
prefs = preferences,
|
||||||
putString("notes", notes)
|
anchor = view,
|
||||||
|
notes = notes,
|
||||||
|
value = value,
|
||||||
|
).apply {
|
||||||
|
onToggle = { value, notes -> callback.onNumberPicked(value, notes) }
|
||||||
|
show()
|
||||||
}
|
}
|
||||||
dialog.onToggle = { v, n -> callback.onNumberPicked(v, n) }
|
|
||||||
dialog.dismissCurrentAndShow(fm, "numberDialog")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showCheckmarkPopup(
|
override fun showCheckmarkPopup(
|
||||||
@@ -279,16 +252,18 @@ class ListHabitsScreen
|
|||||||
color: PaletteColor,
|
color: PaletteColor,
|
||||||
callback: ListHabitsBehavior.CheckMarkDialogCallback
|
callback: ListHabitsBehavior.CheckMarkDialogCallback
|
||||||
) {
|
) {
|
||||||
val theme = rootView.get().currentTheme()
|
val view = rootView.get()
|
||||||
val fm = (context as AppCompatActivity).supportFragmentManager
|
CheckmarkPopup(
|
||||||
val dialog = CheckmarkDialog()
|
context = context,
|
||||||
dialog.arguments = Bundle().apply {
|
prefs = preferences,
|
||||||
putInt("color", theme.color(color).toInt())
|
anchor = view,
|
||||||
putInt("value", selectedValue)
|
color = view.currentTheme().color(color).toInt(),
|
||||||
putString("notes", notes)
|
notes = notes,
|
||||||
|
value = selectedValue,
|
||||||
|
).apply {
|
||||||
|
onToggle = { value, notes -> callback.onNotesSaved(value, notes) }
|
||||||
|
show()
|
||||||
}
|
}
|
||||||
dialog.onToggle = { v, n -> callback.onNotesSaved(v, n) }
|
|
||||||
dialog.dismissCurrentAndShow(fm, "checkmarkDialog")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getExecuteString(command: Command): String? {
|
private fun getExecuteString(command: Command): String? {
|
||||||
@@ -350,11 +325,8 @@ class ListHabitsScreen
|
|||||||
private fun onExportDB() {
|
private fun onExportDB() {
|
||||||
taskRunner.execute(
|
taskRunner.execute(
|
||||||
exportDBFactory.create { filename ->
|
exportDBFactory.create { filename ->
|
||||||
if (filename != null) {
|
if (filename != null) activity.showSendFileScreen(filename)
|
||||||
activity.showSendFileScreen(filename)
|
else activity.showMessage(activity.resources.getString(R.string.could_not_export))
|
||||||
} else {
|
|
||||||
activity.showMessage(activity.resources.getString(R.string.could_not_export))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,11 +60,8 @@ abstract class ButtonPanelView<T : View>(
|
|||||||
repeat(buttonCount) { buttons.add(createButton()) }
|
repeat(buttonCount) { buttons.add(createButton()) }
|
||||||
|
|
||||||
removeAllViews()
|
removeAllViews()
|
||||||
if (reverse) {
|
if (reverse) buttons.reversed().forEach { addView(it) }
|
||||||
buttons.reversed().forEach { addView(it) }
|
else buttons.forEach { addView(it) }
|
||||||
} else {
|
|
||||||
buttons.forEach { addView(it) }
|
|
||||||
}
|
|
||||||
setupButtons()
|
setupButtons()
|
||||||
requestLayout()
|
requestLayout()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ import org.isoron.uhabits.utils.sres
|
|||||||
import org.isoron.uhabits.utils.toMeasureSpec
|
import org.isoron.uhabits.utils.toMeasureSpec
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
const val TOGGLE_DELAY_MILLIS = 2000L
|
||||||
|
|
||||||
class CheckmarkButtonViewFactory
|
class CheckmarkButtonViewFactory
|
||||||
@Inject constructor(
|
@Inject constructor(
|
||||||
@ActivityContext val context: Context,
|
@ActivityContext val context: Context,
|
||||||
@@ -77,7 +79,7 @@ class CheckmarkButtonView(
|
|||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
var onToggle: (Int, String) -> Unit = { _, _ -> }
|
var onToggle: (Int, String, Long) -> Unit = { _, _, _ -> }
|
||||||
|
|
||||||
var onEdit: () -> Unit = { }
|
var onEdit: () -> Unit = { }
|
||||||
|
|
||||||
@@ -88,31 +90,25 @@ class CheckmarkButtonView(
|
|||||||
setOnLongClickListener(this)
|
setOnLongClickListener(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun performToggle() {
|
fun performToggle(delay: Long) {
|
||||||
value = Entry.nextToggleValue(
|
value = Entry.nextToggleValue(
|
||||||
value = value,
|
value = value,
|
||||||
isSkipEnabled = preferences.isSkipEnabled,
|
isSkipEnabled = preferences.isSkipEnabled,
|
||||||
areQuestionMarksEnabled = preferences.areQuestionMarksEnabled
|
areQuestionMarksEnabled = preferences.areQuestionMarksEnabled
|
||||||
)
|
)
|
||||||
onToggle(value, notes)
|
onToggle(value, notes, delay)
|
||||||
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onClick(v: View) {
|
override fun onClick(v: View) {
|
||||||
if (preferences.isShortToggleEnabled) {
|
if (preferences.isShortToggleEnabled) performToggle(TOGGLE_DELAY_MILLIS)
|
||||||
performToggle()
|
else onEdit()
|
||||||
} else {
|
|
||||||
onEdit()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLongClick(v: View): Boolean {
|
override fun onLongClick(v: View): Boolean {
|
||||||
if (preferences.isShortToggleEnabled) {
|
if (preferences.isShortToggleEnabled) onEdit()
|
||||||
onEdit()
|
else performToggle(TOGGLE_DELAY_MILLIS)
|
||||||
} else {
|
|
||||||
performToggle()
|
|
||||||
}
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,11 +142,8 @@ class CheckmarkButtonView(
|
|||||||
paint.color = when (value) {
|
paint.color = when (value) {
|
||||||
YES_MANUAL, YES_AUTO, SKIP -> color
|
YES_MANUAL, YES_AUTO, SKIP -> color
|
||||||
NO -> {
|
NO -> {
|
||||||
if (preferences.areQuestionMarksEnabled) {
|
if (preferences.areQuestionMarksEnabled) mediumContrastColor
|
||||||
mediumContrastColor
|
else lowContrastColor
|
||||||
} else {
|
|
||||||
lowContrastColor
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else -> lowContrastColor
|
else -> lowContrastColor
|
||||||
}
|
}
|
||||||
@@ -158,11 +151,8 @@ class CheckmarkButtonView(
|
|||||||
SKIP -> R.string.fa_skipped
|
SKIP -> R.string.fa_skipped
|
||||||
NO -> R.string.fa_times
|
NO -> R.string.fa_times
|
||||||
UNKNOWN -> {
|
UNKNOWN -> {
|
||||||
if (preferences.areQuestionMarksEnabled) {
|
if (preferences.areQuestionMarksEnabled) R.string.fa_question
|
||||||
R.string.fa_question
|
else R.string.fa_times
|
||||||
} else {
|
|
||||||
R.string.fa_times
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else -> R.string.fa_check
|
else -> R.string.fa_check
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ class CheckmarkPanelView(
|
|||||||
setupButtons()
|
setupButtons()
|
||||||
}
|
}
|
||||||
|
|
||||||
var onToggle: (Timestamp, Int, String) -> Unit = { _, _, _ -> }
|
var onToggle: (Timestamp, Int, String, Long) -> Unit = { _, _, _, _ -> }
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
setupButtons()
|
setupButtons()
|
||||||
@@ -89,7 +89,7 @@ class CheckmarkPanelView(
|
|||||||
else -> ""
|
else -> ""
|
||||||
}
|
}
|
||||||
button.color = color
|
button.color = color
|
||||||
button.onToggle = { value, notes -> onToggle(timestamp, value, notes) }
|
button.onToggle = { value, notes, delay -> onToggle(timestamp, value, notes, delay) }
|
||||||
button.onEdit = { onEdit(timestamp) }
|
button.onEdit = { onEdit(timestamp) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -156,11 +156,10 @@ class HabitCardListController @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun notifyListener() {
|
private fun notifyListener() {
|
||||||
if (activeMode is SelectionMode) {
|
if (activeMode is SelectionMode)
|
||||||
selectionMenu.get().onSelectionChange()
|
selectionMenu.get().onSelectionChange()
|
||||||
} else {
|
else
|
||||||
selectionMenu.get().onSelectionFinish()
|
selectionMenu.get().onSelectionFinish()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
package org.isoron.uhabits.activities.habits.list.views
|
package org.isoron.uhabits.activities.habits.list.views
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.PointF
|
|
||||||
import android.graphics.text.LineBreaker.BREAK_STRATEGY_BALANCED
|
import android.graphics.text.LineBreaker.BREAK_STRATEGY_BALANCED
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Build.VERSION.SDK_INT
|
import android.os.Build.VERSION.SDK_INT
|
||||||
@@ -58,6 +57,13 @@ class HabitCardViewFactory
|
|||||||
fun create() = HabitCardView(context, checkmarkPanelFactory, numberPanelFactory, behavior)
|
fun create() = HabitCardView(context, checkmarkPanelFactory, numberPanelFactory, behavior)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class DelayedToggle(
|
||||||
|
var habit: Habit,
|
||||||
|
var timestamp: Timestamp,
|
||||||
|
var value: Int,
|
||||||
|
var notes: String
|
||||||
|
)
|
||||||
|
|
||||||
class HabitCardView(
|
class HabitCardView(
|
||||||
@ActivityContext context: Context,
|
@ActivityContext context: Context,
|
||||||
checkmarkPanelFactory: CheckmarkPanelViewFactory,
|
checkmarkPanelFactory: CheckmarkPanelViewFactory,
|
||||||
@@ -130,6 +136,7 @@ class HabitCardView(
|
|||||||
private var scoreRing: RingView
|
private var scoreRing: RingView
|
||||||
|
|
||||||
private var currentToggleTaskId = 0
|
private var currentToggleTaskId = 0
|
||||||
|
private var queuedToggles = mutableListOf<DelayedToggle>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
scoreRing = RingView(context).apply {
|
scoreRing = RingView(context).apply {
|
||||||
@@ -153,24 +160,16 @@ class HabitCardView(
|
|||||||
}
|
}
|
||||||
|
|
||||||
checkmarkPanel = checkmarkPanelFactory.create().apply {
|
checkmarkPanel = checkmarkPanelFactory.create().apply {
|
||||||
onToggle = { timestamp, value, notes ->
|
onToggle = { timestamp, value, notes, delay ->
|
||||||
triggerRipple(timestamp)
|
if (delay > 0) triggerRipple(timestamp)
|
||||||
val location = getAbsoluteButtonLocation(timestamp)
|
|
||||||
habit?.let {
|
habit?.let {
|
||||||
behavior.onToggle(
|
val taskId = queueToggle(it, timestamp, value, notes);
|
||||||
it,
|
{ runPendingToggles(taskId) }.delay(delay)
|
||||||
timestamp,
|
|
||||||
value,
|
|
||||||
notes,
|
|
||||||
location.x,
|
|
||||||
location.y
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onEdit = { timestamp ->
|
onEdit = { timestamp ->
|
||||||
triggerRipple(timestamp)
|
triggerRipple(timestamp)
|
||||||
val location = getAbsoluteButtonLocation(timestamp)
|
habit?.let { behavior.onEdit(it, timestamp) }
|
||||||
habit?.let { behavior.onEdit(it, timestamp, location.x, location.y) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,8 +177,7 @@ class HabitCardView(
|
|||||||
visibility = GONE
|
visibility = GONE
|
||||||
onEdit = { timestamp ->
|
onEdit = { timestamp ->
|
||||||
triggerRipple(timestamp)
|
triggerRipple(timestamp)
|
||||||
val location = getAbsoluteButtonLocation(timestamp)
|
habit?.let { behavior.onEdit(it, timestamp) }
|
||||||
habit?.let { behavior.onEdit(it, timestamp, location.x, location.y) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,6 +205,25 @@ class HabitCardView(
|
|||||||
addView(innerFrame)
|
addView(innerFrame)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun runPendingToggles(id: Int) {
|
||||||
|
if (currentToggleTaskId != id) return
|
||||||
|
for ((h, t, v, n) in queuedToggles) behavior.onToggle(h, t, v, n)
|
||||||
|
queuedToggles.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun queueToggle(
|
||||||
|
it: Habit,
|
||||||
|
timestamp: Timestamp,
|
||||||
|
value: Int,
|
||||||
|
notes: String,
|
||||||
|
): Int {
|
||||||
|
currentToggleTaskId += 1
|
||||||
|
queuedToggles.add(DelayedToggle(it, timestamp, value, notes))
|
||||||
|
return currentToggleTaskId
|
||||||
|
}
|
||||||
|
|
||||||
override fun onModelChange() {
|
override fun onModelChange() {
|
||||||
Handler(Looper.getMainLooper()).post {
|
Handler(Looper.getMainLooper()).post {
|
||||||
habit?.let { copyAttributesFrom(it) }
|
habit?.let { copyAttributesFrom(it) }
|
||||||
@@ -219,37 +236,12 @@ class HabitCardView(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun triggerRipple(timestamp: Timestamp) {
|
fun triggerRipple(timestamp: Timestamp) {
|
||||||
val location = getRelativeButtonLocation(timestamp)
|
|
||||||
triggerRipple(location.x, location.y)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getRelativeButtonLocation(timestamp: Timestamp): PointF {
|
|
||||||
val today = DateUtils.getTodayWithOffset()
|
val today = DateUtils.getTodayWithOffset()
|
||||||
val offset = timestamp.daysUntil(today) - dataOffset
|
val offset = timestamp.daysUntil(today) - dataOffset
|
||||||
val panel = when (habit!!.isNumerical) {
|
val button = checkmarkPanel.buttons[offset]
|
||||||
true -> numberPanel
|
|
||||||
false -> checkmarkPanel
|
|
||||||
}
|
|
||||||
val button = panel.buttons[offset]
|
|
||||||
val y = button.height / 2.0f
|
val y = button.height / 2.0f
|
||||||
val x = panel.x + button.x + (button.width / 2).toFloat()
|
val x = checkmarkPanel.x + button.x + (button.width / 2).toFloat()
|
||||||
return PointF(x, y)
|
triggerRipple(x, y)
|
||||||
}
|
|
||||||
|
|
||||||
private fun getAbsoluteButtonLocation(timestamp: Timestamp): PointF {
|
|
||||||
val containerLocation = IntArray(2)
|
|
||||||
this.getLocationOnScreen(containerLocation)
|
|
||||||
val relButtonLocation = getRelativeButtonLocation(timestamp)
|
|
||||||
val windowInsets = rootWindowInsets
|
|
||||||
val statusBarHeight = if (SDK_INT <= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
|
|
||||||
windowInsets?.systemWindowInsetTop ?: 0
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
return PointF(
|
|
||||||
containerLocation[0].toFloat() + relButtonLocation.x,
|
|
||||||
containerLocation[1].toFloat() + relButtonLocation.y - statusBarHeight
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onAttachedToWindow() {
|
override fun onAttachedToWindow() {
|
||||||
@@ -263,6 +255,7 @@ class HabitCardView(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun copyAttributesFrom(h: Habit) {
|
private fun copyAttributesFrom(h: Habit) {
|
||||||
|
|
||||||
fun getActiveColor(habit: Habit): Int {
|
fun getActiveColor(habit: Habit): Int {
|
||||||
return when (habit.isArchived) {
|
return when (habit.isArchived) {
|
||||||
true -> sres.getColor(R.attr.contrast60)
|
true -> sres.getColor(R.attr.contrast60)
|
||||||
@@ -308,6 +301,7 @@ class HabitCardView(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun updateBackground(isSelected: Boolean) {
|
private fun updateBackground(isSelected: Boolean) {
|
||||||
|
|
||||||
val background = when (isSelected) {
|
val background = when (isSelected) {
|
||||||
true -> R.drawable.selected_box
|
true -> R.drawable.selected_box
|
||||||
false -> R.drawable.ripple
|
false -> R.drawable.ripple
|
||||||
|
|||||||
@@ -124,24 +124,19 @@ class HeaderView(
|
|||||||
rect.set(0f, 0f, width, height)
|
rect.set(0f, 0f, width, height)
|
||||||
rect.offset(canvas.width.toFloat() - dp(3.0f), 0f)
|
rect.offset(canvas.width.toFloat() - dp(3.0f), 0f)
|
||||||
|
|
||||||
if (isReversed) {
|
if (isReversed) rect.offset(-(index + 1) * width, 0f)
|
||||||
rect.offset(-(index + 1) * width, 0f)
|
else rect.offset((index - buttonCount) * width, 0f)
|
||||||
} else {
|
|
||||||
rect.offset((index - buttonCount) * width, 0f)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isRTL()) {
|
if (isRTL()) rect.set(
|
||||||
rect.set(
|
canvas.width - rect.right,
|
||||||
canvas.width - rect.right,
|
rect.top,
|
||||||
rect.top,
|
canvas.width - rect.left,
|
||||||
canvas.width - rect.left,
|
rect.bottom
|
||||||
rect.bottom
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val y1 = rect.centerY() - 0.25 * em
|
val y1 = rect.centerY() - 0.25 * em
|
||||||
val y2 = rect.centerY() + 1.25 * em
|
val y2 = rect.centerY() + 1.25 * em
|
||||||
val lines = DateUtils.formatHeaderDate(day).uppercase().split("\n")
|
val lines = DateUtils.formatHeaderDate(day).toUpperCase().split("\n")
|
||||||
canvas.drawText(lines[0], rect.centerX(), y1.toFloat(), paint)
|
canvas.drawText(lines[0], rect.centerX(), y1.toFloat(), paint)
|
||||||
canvas.drawText(lines[1], rect.centerX(), y2.toFloat(), paint)
|
canvas.drawText(lines[1], rect.centerX(), y2.toFloat(), paint)
|
||||||
day.add(GregorianCalendar.DAY_OF_MONTH, -1)
|
day.add(GregorianCalendar.DAY_OF_MONTH, -1)
|
||||||
|
|||||||
@@ -34,10 +34,10 @@ import org.isoron.uhabits.HabitsApplication
|
|||||||
import org.isoron.uhabits.R
|
import org.isoron.uhabits.R
|
||||||
import org.isoron.uhabits.activities.AndroidThemeSwitcher
|
import org.isoron.uhabits.activities.AndroidThemeSwitcher
|
||||||
import org.isoron.uhabits.activities.HabitsDirFinder
|
import org.isoron.uhabits.activities.HabitsDirFinder
|
||||||
import org.isoron.uhabits.activities.common.dialogs.CheckmarkDialog
|
import org.isoron.uhabits.activities.common.dialogs.CheckmarkPopup
|
||||||
import org.isoron.uhabits.activities.common.dialogs.ConfirmDeleteDialog
|
import org.isoron.uhabits.activities.common.dialogs.ConfirmDeleteDialog
|
||||||
import org.isoron.uhabits.activities.common.dialogs.HistoryEditorDialog
|
import org.isoron.uhabits.activities.common.dialogs.HistoryEditorDialog
|
||||||
import org.isoron.uhabits.activities.common.dialogs.NumberDialog
|
import org.isoron.uhabits.activities.common.dialogs.NumberPopup
|
||||||
import org.isoron.uhabits.core.commands.Command
|
import org.isoron.uhabits.core.commands.Command
|
||||||
import org.isoron.uhabits.core.commands.CommandRunner
|
import org.isoron.uhabits.core.commands.CommandRunner
|
||||||
import org.isoron.uhabits.core.models.Habit
|
import org.isoron.uhabits.core.models.Habit
|
||||||
@@ -49,10 +49,8 @@ import org.isoron.uhabits.core.ui.screens.habits.show.ShowHabitMenuPresenter
|
|||||||
import org.isoron.uhabits.core.ui.screens.habits.show.ShowHabitPresenter
|
import org.isoron.uhabits.core.ui.screens.habits.show.ShowHabitPresenter
|
||||||
import org.isoron.uhabits.core.ui.views.OnDateClickedListener
|
import org.isoron.uhabits.core.ui.views.OnDateClickedListener
|
||||||
import org.isoron.uhabits.intents.IntentFactory
|
import org.isoron.uhabits.intents.IntentFactory
|
||||||
import org.isoron.uhabits.utils.applyRootViewInsets
|
|
||||||
import org.isoron.uhabits.utils.currentTheme
|
import org.isoron.uhabits.utils.currentTheme
|
||||||
import org.isoron.uhabits.utils.dismissCurrentAndShow
|
import org.isoron.uhabits.utils.dismissCurrentAndShow
|
||||||
import org.isoron.uhabits.utils.dismissCurrentDialog
|
|
||||||
import org.isoron.uhabits.utils.showMessage
|
import org.isoron.uhabits.utils.showMessage
|
||||||
import org.isoron.uhabits.utils.showSendFileScreen
|
import org.isoron.uhabits.utils.showSendFileScreen
|
||||||
import org.isoron.uhabits.widgets.WidgetUpdater
|
import org.isoron.uhabits.widgets.WidgetUpdater
|
||||||
@@ -89,7 +87,7 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
|
|||||||
habit = habit,
|
habit = habit,
|
||||||
habitList = habitList,
|
habitList = habitList,
|
||||||
preferences = preferences,
|
preferences = preferences,
|
||||||
screen = screen
|
screen = screen,
|
||||||
)
|
)
|
||||||
|
|
||||||
view = ShowHabitView(this)
|
view = ShowHabitView(this)
|
||||||
@@ -100,17 +98,16 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
|
|||||||
habitList = habitList,
|
habitList = habitList,
|
||||||
screen = screen,
|
screen = screen,
|
||||||
system = HabitsDirFinder(AndroidDirFinder(this)),
|
system = HabitsDirFinder(AndroidDirFinder(this)),
|
||||||
taskRunner = appComponent.taskRunner
|
taskRunner = appComponent.taskRunner,
|
||||||
)
|
)
|
||||||
|
|
||||||
menu = ShowHabitMenu(
|
menu = ShowHabitMenu(
|
||||||
activity = this,
|
activity = this,
|
||||||
presenter = menuPresenter,
|
presenter = menuPresenter,
|
||||||
preferences = preferences
|
preferences = preferences,
|
||||||
)
|
)
|
||||||
|
|
||||||
view.setListener(presenter)
|
view.setListener(presenter)
|
||||||
view.applyRootViewInsets()
|
|
||||||
setContentView(view)
|
setContentView(view)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,7 +129,6 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
dismissCurrentDialog()
|
|
||||||
commandRunner.removeListener(this)
|
commandRunner.removeListener(this)
|
||||||
super.onPause()
|
super.onPause()
|
||||||
}
|
}
|
||||||
@@ -152,7 +148,7 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
|
|||||||
ShowHabitPresenter.buildState(
|
ShowHabitPresenter.buildState(
|
||||||
habit = habit,
|
habit = habit,
|
||||||
preferences = preferences,
|
preferences = preferences,
|
||||||
theme = themeSwitcher.currentTheme
|
theme = themeSwitcher.currentTheme,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -174,32 +170,41 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
|
|||||||
override fun showNumberPopup(
|
override fun showNumberPopup(
|
||||||
value: Double,
|
value: Double,
|
||||||
notes: String,
|
notes: String,
|
||||||
|
preferences: Preferences,
|
||||||
callback: ListHabitsBehavior.NumberPickerCallback
|
callback: ListHabitsBehavior.NumberPickerCallback
|
||||||
) {
|
) {
|
||||||
val dialog = NumberDialog()
|
val anchor = getPopupAnchor() ?: return
|
||||||
dialog.arguments = Bundle().apply {
|
NumberPopup(
|
||||||
putDouble("value", value)
|
context = this@ShowHabitActivity,
|
||||||
putString("notes", notes)
|
prefs = preferences,
|
||||||
|
notes = notes,
|
||||||
|
anchor = anchor,
|
||||||
|
value = value,
|
||||||
|
).apply {
|
||||||
|
onToggle = { v, n -> callback.onNumberPicked(v, n) }
|
||||||
|
show()
|
||||||
}
|
}
|
||||||
dialog.onToggle = { v, n -> callback.onNumberPicked(v, n) }
|
|
||||||
dialog.dismissCurrentAndShow(supportFragmentManager, "numberDialog")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showCheckmarkPopup(
|
override fun showCheckmarkPopup(
|
||||||
selectedValue: Int,
|
selectedValue: Int,
|
||||||
notes: String,
|
notes: String,
|
||||||
|
preferences: Preferences,
|
||||||
color: PaletteColor,
|
color: PaletteColor,
|
||||||
callback: ListHabitsBehavior.CheckMarkDialogCallback
|
callback: ListHabitsBehavior.CheckMarkDialogCallback
|
||||||
) {
|
) {
|
||||||
val theme = view.currentTheme()
|
val anchor = getPopupAnchor() ?: return
|
||||||
val dialog = CheckmarkDialog()
|
CheckmarkPopup(
|
||||||
dialog.arguments = Bundle().apply {
|
context = this@ShowHabitActivity,
|
||||||
putInt("color", theme.color(color).toInt())
|
prefs = preferences,
|
||||||
putInt("value", selectedValue)
|
notes = notes,
|
||||||
putString("notes", notes)
|
color = view.currentTheme().color(color).toInt(),
|
||||||
|
anchor = anchor,
|
||||||
|
value = selectedValue,
|
||||||
|
).apply {
|
||||||
|
onToggle = { v, n -> callback.onNotesSaved(v, n) }
|
||||||
|
show()
|
||||||
}
|
}
|
||||||
dialog.onToggle = { v, n -> callback.onNotesSaved(v, n) }
|
|
||||||
dialog.dismissCurrentAndShow(supportFragmentManager, "checkmarkDialog")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getPopupAnchor(): View? {
|
private fun getPopupAnchor(): View? {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import org.isoron.uhabits.core.ui.screens.habits.show.ShowHabitMenuPresenter
|
|||||||
class ShowHabitMenu(
|
class ShowHabitMenu(
|
||||||
val activity: ShowHabitActivity,
|
val activity: ShowHabitActivity,
|
||||||
val presenter: ShowHabitMenuPresenter,
|
val presenter: ShowHabitMenuPresenter,
|
||||||
val preferences: Preferences
|
val preferences: Preferences,
|
||||||
) {
|
) {
|
||||||
fun onCreateOptionsMenu(menu: Menu): Boolean {
|
fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
activity.menuInflater.inflate(R.menu.show_habit, menu)
|
activity.menuInflater.inflate(R.menu.show_habit, menu)
|
||||||
|
|||||||
@@ -25,14 +25,12 @@ import android.widget.FrameLayout
|
|||||||
import org.isoron.uhabits.core.ui.screens.habits.show.ShowHabitPresenter
|
import org.isoron.uhabits.core.ui.screens.habits.show.ShowHabitPresenter
|
||||||
import org.isoron.uhabits.core.ui.screens.habits.show.ShowHabitState
|
import org.isoron.uhabits.core.ui.screens.habits.show.ShowHabitState
|
||||||
import org.isoron.uhabits.databinding.ShowHabitBinding
|
import org.isoron.uhabits.databinding.ShowHabitBinding
|
||||||
import org.isoron.uhabits.utils.applyToolbarInsets
|
|
||||||
import org.isoron.uhabits.utils.setupToolbar
|
import org.isoron.uhabits.utils.setupToolbar
|
||||||
|
|
||||||
class ShowHabitView(context: Context) : FrameLayout(context) {
|
class ShowHabitView(context: Context) : FrameLayout(context) {
|
||||||
private val binding = ShowHabitBinding.inflate(LayoutInflater.from(context))
|
private val binding = ShowHabitBinding.inflate(LayoutInflater.from(context))
|
||||||
|
|
||||||
init {
|
init {
|
||||||
binding.toolbar.applyToolbarInsets()
|
|
||||||
addView(binding.root)
|
addView(binding.root)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,7 +39,7 @@ class ShowHabitView(context: Context) : FrameLayout(context) {
|
|||||||
binding.toolbar,
|
binding.toolbar,
|
||||||
title = data.title,
|
title = data.title,
|
||||||
color = data.color,
|
color = data.color,
|
||||||
theme = data.theme
|
theme = data.theme,
|
||||||
)
|
)
|
||||||
binding.subtitleCard.setState(data.subtitle)
|
binding.subtitleCard.setState(data.subtitle)
|
||||||
binding.overviewCard.setState(data.overview)
|
binding.overviewCard.setState(data.overview)
|
||||||
@@ -54,6 +52,7 @@ class ShowHabitView(context: Context) : FrameLayout(context) {
|
|||||||
binding.barCard.setState(data.bar)
|
binding.barCard.setState(data.bar)
|
||||||
if (data.isNumerical) {
|
if (data.isNumerical) {
|
||||||
binding.overviewCard.visibility = GONE
|
binding.overviewCard.visibility = GONE
|
||||||
|
binding.streakCard.visibility = GONE
|
||||||
} else {
|
} else {
|
||||||
binding.targetCard.visibility = GONE
|
binding.targetCard.visibility = GONE
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ class BarCardView(context: Context, attrs: AttributeSet) : LinearLayout(context,
|
|||||||
|
|
||||||
fun setState(state: BarCardState) {
|
fun setState(state: BarCardState) {
|
||||||
val androidColor = state.theme.color(state.color).toInt()
|
val androidColor = state.theme.color(state.color).toInt()
|
||||||
binding.chart.view = BarChart(state.theme, JavaLocalDateFormatter(Locale.getDefault())).apply {
|
binding.chart.view = BarChart(state.theme, JavaLocalDateFormatter(Locale.US)).apply {
|
||||||
series = mutableListOf(state.entries.map { it.value / 1000.0 })
|
series = mutableListOf(state.entries.map { it.value / 1000.0 })
|
||||||
colors = mutableListOf(theme.color(state.color.paletteIndex))
|
colors = mutableListOf(theme.color(state.color.paletteIndex))
|
||||||
axis = state.entries.map { it.timestamp.toLocalDate() }
|
axis = state.entries.map { it.timestamp.toLocalDate() }
|
||||||
@@ -63,7 +63,7 @@ class BarCardView(context: Context, attrs: AttributeSet) : LinearLayout(context,
|
|||||||
parent: AdapterView<*>?,
|
parent: AdapterView<*>?,
|
||||||
view: View?,
|
view: View?,
|
||||||
position: Int,
|
position: Int,
|
||||||
id: Long
|
id: Long,
|
||||||
) {
|
) {
|
||||||
presenter.onBoolSpinnerPosition(position)
|
presenter.onBoolSpinnerPosition(position)
|
||||||
}
|
}
|
||||||
@@ -77,7 +77,7 @@ class BarCardView(context: Context, attrs: AttributeSet) : LinearLayout(context,
|
|||||||
parent: AdapterView<*>?,
|
parent: AdapterView<*>?,
|
||||||
view: View?,
|
view: View?,
|
||||||
position: Int,
|
position: Int,
|
||||||
id: Long
|
id: Long,
|
||||||
) {
|
) {
|
||||||
presenter.onNumericalSpinnerPosition(position)
|
presenter.onNumericalSpinnerPosition(position)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ class HistoryCardView(context: Context, attrs: AttributeSet) : LinearLayout(cont
|
|||||||
series = state.series,
|
series = state.series,
|
||||||
defaultSquare = state.defaultSquare,
|
defaultSquare = state.defaultSquare,
|
||||||
notesIndicators = state.notesIndicators,
|
notesIndicators = state.notesIndicators,
|
||||||
firstWeekday = state.firstWeekday
|
firstWeekday = state.firstWeekday,
|
||||||
)
|
)
|
||||||
binding.chart.postInvalidate()
|
binding.chart.postInvalidate()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class SubtitleCardView(context: Context, attrs: AttributeSet) : LinearLayout(con
|
|||||||
binding.frequencyLabel.text = formatFrequency(
|
binding.frequencyLabel.text = formatFrequency(
|
||||||
state.frequency.numerator,
|
state.frequency.numerator,
|
||||||
state.frequency.denominator,
|
state.frequency.denominator,
|
||||||
resources
|
resources,
|
||||||
)
|
)
|
||||||
binding.questionLabel.setTextColor(color)
|
binding.questionLabel.setTextColor(color)
|
||||||
binding.questionLabel.text = state.question
|
binding.questionLabel.text = state.question
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import org.isoron.uhabits.R
|
|||||||
import org.isoron.uhabits.activities.AndroidThemeSwitcher
|
import org.isoron.uhabits.activities.AndroidThemeSwitcher
|
||||||
import org.isoron.uhabits.core.models.PaletteColor
|
import org.isoron.uhabits.core.models.PaletteColor
|
||||||
import org.isoron.uhabits.databinding.SettingsActivityBinding
|
import org.isoron.uhabits.databinding.SettingsActivityBinding
|
||||||
import org.isoron.uhabits.utils.applyRootViewInsets
|
|
||||||
import org.isoron.uhabits.utils.setupToolbar
|
import org.isoron.uhabits.utils.setupToolbar
|
||||||
|
|
||||||
class SettingsActivity : AppCompatActivity() {
|
class SettingsActivity : AppCompatActivity() {
|
||||||
@@ -41,9 +40,8 @@ class SettingsActivity : AppCompatActivity() {
|
|||||||
toolbar = binding.toolbar,
|
toolbar = binding.toolbar,
|
||||||
title = resources.getString(R.string.settings),
|
title = resources.getString(R.string.settings),
|
||||||
color = PaletteColor(11),
|
color = PaletteColor(11),
|
||||||
theme = themeSwitcher.currentTheme
|
theme = themeSwitcher.currentTheme,
|
||||||
)
|
)
|
||||||
binding.root.applyRootViewInsets()
|
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,8 +53,6 @@ class SettingsFragment : PreferenceFragmentCompat(), OnSharedPreferenceChangeLis
|
|||||||
private var ringtoneManager: RingtoneManager? = null
|
private var ringtoneManager: RingtoneManager? = null
|
||||||
private lateinit var prefs: Preferences
|
private lateinit var prefs: Preferences
|
||||||
private var widgetUpdater: WidgetUpdater? = null
|
private var widgetUpdater: WidgetUpdater? = null
|
||||||
|
|
||||||
@Deprecated("Deprecated in Java")
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
if (requestCode == RINGTONE_REQUEST_CODE) {
|
if (requestCode == RINGTONE_REQUEST_CODE) {
|
||||||
ringtoneManager!!.update(data)
|
ringtoneManager!!.update(data)
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import androidx.appcompat.app.AppCompatActivity
|
|||||||
import org.isoron.uhabits.HabitsApplication
|
import org.isoron.uhabits.HabitsApplication
|
||||||
import org.isoron.uhabits.activities.AndroidThemeSwitcher
|
import org.isoron.uhabits.activities.AndroidThemeSwitcher
|
||||||
import org.isoron.uhabits.core.models.HabitMatcher
|
import org.isoron.uhabits.core.models.HabitMatcher
|
||||||
import org.isoron.uhabits.utils.applyRootViewInsets
|
|
||||||
|
|
||||||
class EditSettingActivity : AppCompatActivity() {
|
class EditSettingActivity : AppCompatActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@@ -33,7 +32,7 @@ class EditSettingActivity : AppCompatActivity() {
|
|||||||
val habits = app.component.habitList.getFiltered(
|
val habits = app.component.habitList.getFiltered(
|
||||||
HabitMatcher(
|
HabitMatcher(
|
||||||
isArchivedAllowed = false,
|
isArchivedAllowed = false,
|
||||||
isCompletedAllowed = true
|
isCompletedAllowed = true,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
AndroidThemeSwitcher(this, app.component.preferences).apply()
|
AndroidThemeSwitcher(this, app.component.preferences).apply()
|
||||||
@@ -44,9 +43,8 @@ class EditSettingActivity : AppCompatActivity() {
|
|||||||
context = this,
|
context = this,
|
||||||
habitList = app.component.habitList,
|
habitList = app.component.habitList,
|
||||||
onSave = controller::onSave,
|
onSave = controller::onSave,
|
||||||
args = args
|
args = args,
|
||||||
)
|
)
|
||||||
view.applyRootViewInsets()
|
|
||||||
setContentView(view)
|
setContentView(view)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ class EditSettingRootView(
|
|||||||
title = resources.getString(R.string.app_name),
|
title = resources.getString(R.string.app_name),
|
||||||
color = PaletteColor(11),
|
color = PaletteColor(11),
|
||||||
displayHomeAsUpEnabled = false,
|
displayHomeAsUpEnabled = false,
|
||||||
theme = currentTheme()
|
theme = currentTheme(),
|
||||||
)
|
)
|
||||||
populateHabitSpinner()
|
populateHabitSpinner()
|
||||||
binding.habitSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
binding.habitSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||||
@@ -69,7 +69,7 @@ class EditSettingRootView(
|
|||||||
val habit = habitList.getByPosition(binding.habitSpinner.selectedItemPosition)
|
val habit = habitList.getByPosition(binding.habitSpinner.selectedItemPosition)
|
||||||
val action = mapSpinnerPositionToAction(
|
val action = mapSpinnerPositionToAction(
|
||||||
isNumerical = habit.isNumerical,
|
isNumerical = habit.isNumerical,
|
||||||
itemPosition = binding.actionSpinner.selectedItemPosition
|
itemPosition = binding.actionSpinner.selectedItemPosition,
|
||||||
)
|
)
|
||||||
onSave(habit, action)
|
onSave(habit, action)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,34 +27,22 @@ class AndroidCursor(private val cursor: android.database.Cursor) : Cursor {
|
|||||||
override fun moveToNext() = cursor.moveToNext()
|
override fun moveToNext() = cursor.moveToNext()
|
||||||
|
|
||||||
override fun getInt(index: Int): Int? {
|
override fun getInt(index: Int): Int? {
|
||||||
return if (cursor.isNull(index)) {
|
return if (cursor.isNull(index)) null
|
||||||
null
|
else cursor.getInt(index)
|
||||||
} else {
|
|
||||||
cursor.getInt(index)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getLong(index: Int): Long? {
|
override fun getLong(index: Int): Long? {
|
||||||
return if (cursor.isNull(index)) {
|
return if (cursor.isNull(index)) null
|
||||||
null
|
else cursor.getLong(index)
|
||||||
} else {
|
|
||||||
cursor.getLong(index)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getDouble(index: Int): Double? {
|
override fun getDouble(index: Int): Double? {
|
||||||
return if (cursor.isNull(index)) {
|
return if (cursor.isNull(index)) null
|
||||||
null
|
else cursor.getDouble(index)
|
||||||
} else {
|
|
||||||
cursor.getDouble(index)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getString(index: Int): String? {
|
override fun getString(index: Int): String? {
|
||||||
return if (cursor.isNull(index)) {
|
return if (cursor.isNull(index)) null
|
||||||
null
|
else cursor.getString(index)
|
||||||
} else {
|
|
||||||
cursor.getString(index)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import java.io.File
|
|||||||
|
|
||||||
class AndroidDatabase(
|
class AndroidDatabase(
|
||||||
private val db: SQLiteDatabase,
|
private val db: SQLiteDatabase,
|
||||||
override val file: File?
|
override val file: File?,
|
||||||
) : Database {
|
) : Database {
|
||||||
|
|
||||||
override fun beginTransaction() = db.beginTransaction()
|
override fun beginTransaction() = db.beginTransaction()
|
||||||
@@ -45,7 +45,7 @@ class AndroidDatabase(
|
|||||||
tableName: String,
|
tableName: String,
|
||||||
values: Map<String, Any?>,
|
values: Map<String, Any?>,
|
||||||
where: String,
|
where: String,
|
||||||
vararg params: String
|
vararg params: String,
|
||||||
): Int {
|
): Int {
|
||||||
val contValues = mapToContentValues(values)
|
val contValues = mapToContentValues(values)
|
||||||
return db.update(tableName, contValues, where, params)
|
return db.update(tableName, contValues, where, params)
|
||||||
@@ -59,7 +59,7 @@ class AndroidDatabase(
|
|||||||
override fun delete(
|
override fun delete(
|
||||||
tableName: String,
|
tableName: String,
|
||||||
where: String,
|
where: String,
|
||||||
vararg params: String
|
vararg params: String,
|
||||||
) {
|
) {
|
||||||
db.delete(tableName, where, params)
|
db.delete(tableName, where, params)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class AndroidDatabaseOpener @Inject constructor() : DatabaseOpener {
|
|||||||
db = SQLiteDatabase.openDatabase(
|
db = SQLiteDatabase.openDatabase(
|
||||||
file.absolutePath,
|
file.absolutePath,
|
||||||
null,
|
null,
|
||||||
SQLiteDatabase.OPEN_READWRITE
|
SQLiteDatabase.OPEN_READWRITE,
|
||||||
),
|
),
|
||||||
file = file
|
file = file
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -25,6 +25,5 @@ import dagger.Provides
|
|||||||
@Module
|
@Module
|
||||||
class ActivityContextModule(
|
class ActivityContextModule(
|
||||||
@get:Provides
|
@get:Provides
|
||||||
@get:ActivityContext
|
@get:ActivityContext val context: Context
|
||||||
val context: Context
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -26,6 +26,5 @@ import dagger.Provides
|
|||||||
class AppContextModule(
|
class AppContextModule(
|
||||||
@get:Provides
|
@get:Provides
|
||||||
@get:AppContext
|
@get:AppContext
|
||||||
@param:AppContext
|
@param:AppContext val context: Context
|
||||||
val context: Context
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -53,9 +53,8 @@ class IntentParser
|
|||||||
var timestamp = intent.getLongExtra("timestamp", today)
|
var timestamp = intent.getLongExtra("timestamp", today)
|
||||||
timestamp = DateUtils.getStartOfDay(timestamp)
|
timestamp = DateUtils.getStartOfDay(timestamp)
|
||||||
|
|
||||||
if (timestamp < 0 || timestamp > today) {
|
if (timestamp < 0 || timestamp > today)
|
||||||
throw IllegalArgumentException("timestamp is not valid")
|
throw IllegalArgumentException("timestamp is not valid")
|
||||||
}
|
|
||||||
|
|
||||||
return Timestamp(timestamp)
|
return Timestamp(timestamp)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import android.app.AlarmManager.RTC_WAKEUP
|
|||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Context.ALARM_SERVICE
|
import android.content.Context.ALARM_SERVICE
|
||||||
import android.os.Build
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import org.isoron.uhabits.core.AppScope
|
import org.isoron.uhabits.core.AppScope
|
||||||
import org.isoron.uhabits.core.models.Habit
|
import org.isoron.uhabits.core.models.Habit
|
||||||
@@ -57,10 +56,6 @@ class IntentScheduler
|
|||||||
)
|
)
|
||||||
return SchedulerResult.IGNORED
|
return SchedulerResult.IGNORED
|
||||||
}
|
}
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !manager.canScheduleExactAlarms()) {
|
|
||||||
Log.e("IntentScheduler", "No permission to schedule exact alarms")
|
|
||||||
return SchedulerResult.IGNORED
|
|
||||||
}
|
|
||||||
manager.setExactAndAllowWhileIdle(alarmType, timestamp, intent)
|
manager.setExactAndAllowWhileIdle(alarmType, timestamp, intent)
|
||||||
return SchedulerResult.OK
|
return SchedulerResult.OK
|
||||||
}
|
}
|
||||||
|
|||||||