Compare commits

..

43 Commits

Author SHA1 Message Date
458c9f3b15 Merge branch 'release/2.1.0' into dev 2022-08-17 05:53:48 -05:00
dfa74960b3 Merge branch 'release/2.1.0' into dev 2022-08-17 05:51:36 -05:00
f082842fbe Update Gradle, AGP & Kotlin 2022-08-17 05:36:37 -05:00
d4d818a085 Upgrade dependencies 2022-08-16 05:42:29 -05:00
dependabot[bot]
f074d0331d Bump sqlite-jdbc from 3.36.0.3 to 3.39.2.0 (#1449)
Bumps [sqlite-jdbc](https://github.com/xerial/sqlite-jdbc) from 3.36.0.3 to 3.39.2.0.
- [Release notes](https://github.com/xerial/sqlite-jdbc/releases)
- [Changelog](https://github.com/xerial/sqlite-jdbc/blob/master/CHANGELOG)
- [Commits](https://github.com/xerial/sqlite-jdbc/compare/3.36.0.3...3.39.2.0)

---
updated-dependencies:
- dependency-name: org.xerial:sqlite-jdbc
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-08-15 06:36:31 -05:00
Jakub Kalinowski
f94bc62a94 Replaced activity!! with requireActivity, context!! with requireContext etc. (#1458) 2022-08-15 06:36:03 -05:00
b0097fa45e Merge pull request #1450 from iSoron/dependabot/gradle/daggerVersion-2.43.2
Bump daggerVersion from 2.43.1 to 2.43.2
2022-08-14 05:09:35 -05:00
dependabot[bot]
15fa1fea8c Bump daggerVersion from 2.43.1 to 2.43.2
Bumps `daggerVersion` from 2.43.1 to 2.43.2.

Updates `dagger` from 2.43.1 to 2.43.2
- [Release notes](https://github.com/google/dagger/releases)
- [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/dagger/compare/dagger-2.43.1...dagger-2.43.2)

Updates `dagger-compiler` from 2.43.1 to 2.43.2
- [Release notes](https://github.com/google/dagger/releases)
- [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/dagger/compare/dagger-2.43.1...dagger-2.43.2)

---
updated-dependencies:
- dependency-name: com.google.dagger:dagger
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: com.google.dagger:dagger-compiler
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-13 16:00:55 +00:00
31368cff45 Merge pull request #1453 from iSoron/dependabot/gradle/org.jetbrains.kotlin-kotlin-stdlib-jdk8-1.7.10
Bump kotlin-stdlib-jdk8 from 1.7.0 to 1.7.10
2022-08-13 11:00:35 -05:00
b44dd96dd3 Merge pull request #1444 from iSoron/dependabot/gradle/org.jetbrains.kotlinx-kotlinx-coroutines-core-jvm-1.6.4
Bump kotlinx-coroutines-core-jvm from 1.6.3 to 1.6.4
2022-08-13 10:59:51 -05:00
dependabot[bot]
08fab0cd8d Bump kotlin-stdlib-jdk8 from 1.7.0 to 1.7.10
Bumps [kotlin-stdlib-jdk8](https://github.com/JetBrains/kotlin) from 1.7.0 to 1.7.10.
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/v1.7.10/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v1.7.0...v1.7.10)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlin:kotlin-stdlib-jdk8
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-13 09:28:01 +00:00
dependabot[bot]
a142685d2e Bump kotlinx-coroutines-core-jvm from 1.6.3 to 1.6.4
Bumps [kotlinx-coroutines-core-jvm](https://github.com/Kotlin/kotlinx.coroutines) from 1.6.3 to 1.6.4.
- [Release notes](https://github.com/Kotlin/kotlinx.coroutines/releases)
- [Changelog](https://github.com/Kotlin/kotlinx.coroutines/blob/master/CHANGES.md)
- [Commits](https://github.com/Kotlin/kotlinx.coroutines/compare/1.6.3...1.6.4)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-13 09:27:25 +00:00
a7a95f2030 Merge pull request #1443 from iSoron/dependabot/gradle/org.jetbrains.kotlin-kotlin-stdlib-1.7.10
Bump kotlin-stdlib from 1.7.0 to 1.7.10
2022-08-13 04:26:42 -05:00
dependabot[bot]
d3c90481be Bump kotlin-stdlib from 1.7.0 to 1.7.10
Bumps [kotlin-stdlib](https://github.com/JetBrains/kotlin) from 1.7.0 to 1.7.10.
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/v1.7.10/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v1.7.0...v1.7.10)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlin:kotlin-stdlib
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-11 10:22:15 +00:00
f9bb0d7d7b Merge pull request #1445 from iSoron/dependabot/gradle/com.linkedin.dexmaker-dexmaker-mockito-2.28.3
Bump dexmaker-mockito from 2.28.1 to 2.28.3
2022-08-11 05:20:29 -05:00
f23a1bedee Merge pull request #1442 from iSoron/dependabot/gradle/daggerVersion-2.43.1
Bump daggerVersion from 2.42 to 2.43.1
2022-08-11 05:20:16 -05:00
dcf31ba115 Merge pull request #1441 from iSoron/dependabot/gradle/kxCoroutinesVersion-1.6.4
Bump kxCoroutinesVersion from 1.6.3 to 1.6.4
2022-08-11 05:20:04 -05:00
dependabot[bot]
5409a324e8 Bump dexmaker-mockito from 2.28.1 to 2.28.3
Bumps [dexmaker-mockito](https://github.com/linkedin/dexmaker) from 2.28.1 to 2.28.3.
- [Release notes](https://github.com/linkedin/dexmaker/releases)
- [Changelog](https://github.com/linkedin/dexmaker/blob/main/CHANGELOG.md)
- [Commits](https://github.com/linkedin/dexmaker/compare/2.28.1...2.28.3)

---
updated-dependencies:
- dependency-name: com.linkedin.dexmaker:dexmaker-mockito
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-01 03:04:42 +00:00
dependabot[bot]
504362e680 Bump daggerVersion from 2.42 to 2.43.1
Bumps `daggerVersion` from 2.42 to 2.43.1.

Updates `dagger` from 2.42 to 2.43.1
- [Release notes](https://github.com/google/dagger/releases)
- [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/dagger/compare/dagger-2.42...dagger-2.43.1)

Updates `dagger-compiler` from 2.42 to 2.43.1
- [Release notes](https://github.com/google/dagger/releases)
- [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/dagger/compare/dagger-2.42...dagger-2.43.1)

---
updated-dependencies:
- dependency-name: com.google.dagger:dagger
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: com.google.dagger:dagger-compiler
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-01 03:03:08 +00:00
dependabot[bot]
0ce2f8fae2 Bump kxCoroutinesVersion from 1.6.3 to 1.6.4
Bumps `kxCoroutinesVersion` from 1.6.3 to 1.6.4.

Updates `kotlinx-coroutines-android` from 1.6.3 to 1.6.4
- [Release notes](https://github.com/Kotlin/kotlinx.coroutines/releases)
- [Changelog](https://github.com/Kotlin/kotlinx.coroutines/blob/master/CHANGES.md)
- [Commits](https://github.com/Kotlin/kotlinx.coroutines/compare/1.6.3...1.6.4)

Updates `kotlinx-coroutines-core` from 1.6.3 to 1.6.4
- [Release notes](https://github.com/Kotlin/kotlinx.coroutines/releases)
- [Changelog](https://github.com/Kotlin/kotlinx.coroutines/blob/master/CHANGES.md)
- [Commits](https://github.com/Kotlin/kotlinx.coroutines/compare/1.6.3...1.6.4)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlinx:kotlinx-coroutines-android
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.jetbrains.kotlinx:kotlinx-coroutines-core
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-01 03:02:48 +00:00
2fc6c67432 Merge pull request #1425 from eduebernal/freq-display-normal
fix marker scaling in frequency display
2022-07-30 18:30:53 -05:00
459cf02642 Merge pull request #1370 from iSoron/number-popup
Replace NumberPickerDialog by NumberPopup
2022-07-30 18:30:32 -05:00
44cb64601f Update test screenshots 2022-07-30 17:28:46 -05:00
eduebernal
dd47d4cf08 fix getWeekdaysInMonth test 2022-07-06 15:09:10 -07:00
eduebernal
8912a9d73c add test for getWeekdaysInMonth 2022-07-06 10:48:58 -07:00
eduebernal
9bd1c6f685 change getWeekdaysInMonth method 2022-07-06 10:48:19 -07:00
eduebernal
e15e06034c remove unused maxFreq var and get method 2022-07-06 10:46:50 -07:00
eduebernal
a6180a5049 fix marker scaling in freq display 2022-07-05 21:41:27 -07:00
dependabot[bot]
7c69b17e77 Bump kxCoroutinesVersion from 1.6.2 to 1.6.3
Bumps `kxCoroutinesVersion` from 1.6.2 to 1.6.3.

Updates `kotlinx-coroutines-android` from 1.6.2 to 1.6.3
- [Release notes](https://github.com/Kotlin/kotlinx.coroutines/releases)
- [Changelog](https://github.com/Kotlin/kotlinx.coroutines/blob/master/CHANGES.md)
- [Commits](https://github.com/Kotlin/kotlinx.coroutines/compare/1.6.2...1.6.3)

Updates `kotlinx-coroutines-core` from 1.6.2 to 1.6.3
- [Release notes](https://github.com/Kotlin/kotlinx.coroutines/releases)
- [Changelog](https://github.com/Kotlin/kotlinx.coroutines/blob/master/CHANGES.md)
- [Commits](https://github.com/Kotlin/kotlinx.coroutines/compare/1.6.2...1.6.3)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlinx:kotlinx-coroutines-android
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.jetbrains.kotlinx:kotlinx-coroutines-core
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-04 07:42:04 +02:00
dependabot[bot]
602a40a532 Bump simpleclient_httpserver from 0.15.0 to 0.16.0
Bumps [simpleclient_httpserver](https://github.com/prometheus/client_java) from 0.15.0 to 0.16.0.
- [Release notes](https://github.com/prometheus/client_java/releases)
- [Commits](https://github.com/prometheus/client_java/compare/parent-0.15.0...parent-0.16.0)

---
updated-dependencies:
- dependency-name: io.prometheus:simpleclient_httpserver
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-03 21:27:22 +02:00
dependabot[bot]
e00998f913 Bump annotation from 1.3.0 to 1.4.0
Bumps annotation from 1.3.0 to 1.4.0.

---
updated-dependencies:
- dependency-name: androidx.annotation:annotation
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-03 21:26:54 +02:00
dependabot[bot]
af5914c2db Bump kotlinx-coroutines-core-jvm from 1.6.2 to 1.6.3
Bumps [kotlinx-coroutines-core-jvm](https://github.com/Kotlin/kotlinx.coroutines) from 1.6.2 to 1.6.3.
- [Release notes](https://github.com/Kotlin/kotlinx.coroutines/releases)
- [Changelog](https://github.com/Kotlin/kotlinx.coroutines/blob/master/CHANGES.md)
- [Commits](https://github.com/Kotlin/kotlinx.coroutines/compare/1.6.2...1.6.3)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-01 12:52:10 +02:00
dependabot[bot]
6c5f70638a Bump kotlin-stdlib-jdk8 from 1.6.21 to 1.7.0
Bumps [kotlin-stdlib-jdk8](https://github.com/JetBrains/kotlin) from 1.6.21 to 1.7.0.
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v1.6.21...v1.7.0)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlin:kotlin-stdlib-jdk8
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-01 12:51:50 +02:00
dependabot[bot]
ba88552919 Bump appcompat from 1.4.1 to 1.4.2
Bumps appcompat from 1.4.1 to 1.4.2.

---
updated-dependencies:
- dependency-name: androidx.appcompat:appcompat
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-01 07:09:30 +02:00
dependabot[bot]
8c90c4f68d Bump kotlin-stdlib from 1.6.21 to 1.7.0
Bumps [kotlin-stdlib](https://github.com/JetBrains/kotlin) from 1.6.21 to 1.7.0.
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v1.6.21...v1.7.0)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlin:kotlin-stdlib
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-01 07:09:11 +02:00
dependabot[bot]
08eb1a600d Bump simpleclient_hotspot from 0.15.0 to 0.16.0
Bumps [simpleclient_hotspot](https://github.com/prometheus/client_java) from 0.15.0 to 0.16.0.
- [Release notes](https://github.com/prometheus/client_java/releases)
- [Commits](https://github.com/prometheus/client_java/compare/parent-0.15.0...parent-0.16.0)

---
updated-dependencies:
- dependency-name: io.prometheus:simpleclient_hotspot
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-01 07:08:57 +02:00
dependabot[bot]
8ea0480d4a Bump simpleclient from 0.15.0 to 0.16.0
Bumps [simpleclient](https://github.com/prometheus/client_java) from 0.15.0 to 0.16.0.
- [Release notes](https://github.com/prometheus/client_java/releases)
- [Commits](https://github.com/prometheus/client_java/compare/parent-0.15.0...parent-0.16.0)

---
updated-dependencies:
- dependency-name: io.prometheus:simpleclient
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-01 07:08:46 +02:00
Hielke Morsink
36ee39589e Fix small typo in build instructions 2022-06-28 20:02:46 +02:00
dependabot[bot]
ac7a721940 Bump daggerVersion from 2.41 to 2.42
Bumps `daggerVersion` from 2.41 to 2.42.

Updates `dagger` from 2.41 to 2.42
- [Release notes](https://github.com/google/dagger/releases)
- [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/dagger/compare/dagger-2.41...dagger-2.42)

Updates `dagger-compiler` from 2.41 to 2.42
- [Release notes](https://github.com/google/dagger/releases)
- [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/dagger/compare/dagger-2.41...dagger-2.42)

---
updated-dependencies:
- dependency-name: com.google.dagger:dagger
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: com.google.dagger:dagger-compiler
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-02 10:30:33 +02:00
dependabot[bot]
b36ca8673a Bump kxCoroutinesVersion from 1.6.1 to 1.6.2
Bumps `kxCoroutinesVersion` from 1.6.1 to 1.6.2.

Updates `kotlinx-coroutines-android` from 1.6.1 to 1.6.2
- [Release notes](https://github.com/Kotlin/kotlinx.coroutines/releases)
- [Changelog](https://github.com/Kotlin/kotlinx.coroutines/blob/master/CHANGES.md)
- [Commits](https://github.com/Kotlin/kotlinx.coroutines/compare/1.6.1...1.6.2)

Updates `kotlinx-coroutines-core` from 1.6.1 to 1.6.2
- [Release notes](https://github.com/Kotlin/kotlinx.coroutines/releases)
- [Changelog](https://github.com/Kotlin/kotlinx.coroutines/blob/master/CHANGES.md)
- [Commits](https://github.com/Kotlin/kotlinx.coroutines/compare/1.6.1...1.6.2)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlinx:kotlinx-coroutines-android
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.jetbrains.kotlinx:kotlinx-coroutines-core
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-02 10:30:27 +02:00
dependabot[bot]
7cfac486f9 Bump org.jlleitschuh.gradle.ktlint from 10.2.1 to 10.3.0
Bumps org.jlleitschuh.gradle.ktlint from 10.2.1 to 10.3.0.

---
updated-dependencies:
- dependency-name: org.jlleitschuh.gradle.ktlint
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-01 23:07:05 +02:00
dependabot[bot]
21dd413ab5 Bump material from 1.5.0 to 1.6.1
Bumps [material](https://github.com/material-components/material-components-android) from 1.5.0 to 1.6.1.
- [Release notes](https://github.com/material-components/material-components-android/releases)
- [Commits](https://github.com/material-components/material-components-android/compare/1.5.0...1.6.1)

---
updated-dependencies:
- dependency-name: com.google.android.material:material
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-01 23:04:11 +02:00
dependabot[bot]
39e10638b5 Bump kotlinx-coroutines-core-jvm from 1.6.1 to 1.6.2
Bumps [kotlinx-coroutines-core-jvm](https://github.com/Kotlin/kotlinx.coroutines) from 1.6.1 to 1.6.2.
- [Release notes](https://github.com/Kotlin/kotlinx.coroutines/releases)
- [Changelog](https://github.com/Kotlin/kotlinx.coroutines/blob/master/CHANGES.md)
- [Commits](https://github.com/Kotlin/kotlinx.coroutines/compare/1.6.1...1.6.2)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-01 23:04:01 +02:00
66 changed files with 404 additions and 474 deletions

View File

@@ -17,7 +17,7 @@ jobs:
run: ./build.sh build run: ./build.sh build
- name: Run Android tests - name: Run Android tests
run: ./build.sh android-tests-parallel 24 25 26 28 30 31 run: ./build.sh android-tests-parallel 23 24 25 26 27 28 30 31
- name: Upload artifacts - name: Upload artifacts
if: always() if: always()

View File

@@ -1,25 +1,6 @@
# Changelog # Changelog
## [2.1.2] -- 2023-05-26 ## [2.1.0] -- Unreleased
### 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
### Fixed
- Fix Tasker plugin (@iSoron, #1503)
## [2.1.0] -- 2022-09-10
### Added ### Added
- Allow user to add notes to specific dates (@vbh, #1103) - Allow user to add notes to specific dates (@vbh, #1103)
- Allow user to track "at most" numerical habits (@KristianTashkov, #1101) - Allow user to track "at most" numerical habits (@KristianTashkov, #1101)
@@ -28,7 +9,6 @@
- Improve number picker (@hiqua, @iSoron, #1082, #1370) - Improve number picker (@hiqua, @iSoron, #1082, #1370)
- Add new checkmark and number picker (@iSoron, #1370) - Add new checkmark and number picker (@iSoron, #1370)
- Allow user to import numerical habits from HabitBull (@hiqua, #1278) - Allow user to import numerical habits from HabitBull (@hiqua, #1278)
- Add support for Android 13 themed icons (@cheeeeer, #1497)
### Removed ### Removed
- Hide snooze button Android 12 notifications (@hiqua, #1226) - Hide snooze button Android 12 notifications (@hiqua, #1226)
@@ -48,7 +28,6 @@
- Fix small issues in calendar chart (@kalina559, #1314) - Fix small issues in calendar chart (@kalina559, #1314)
- Resort habit list after edit (@hiqua, #1350) - Resort habit list after edit (@hiqua, #1350)
- Fix marker scaling in frequency display (@eduebernal, #1425) - Fix marker scaling in frequency display (@eduebernal, #1425)
- Fix widgets not working correctly on API 33 (@iSoron, #1488)
### Refactoring & Testing ### Refactoring & Testing
- Replace raster icons by vector assets (@kalina559) - Replace raster icons by vector assets (@kalina559)

View File

@@ -1,11 +1,11 @@
plugins { plugins {
val kotlinVersion = "1.6.10" val kotlinVersion = "1.7.10"
id("com.android.application") version ("7.0.3") apply (false) id("com.android.application") version ("7.3.0-rc01") apply (false)
id("org.jetbrains.kotlin.android") version kotlinVersion apply (false) id("org.jetbrains.kotlin.android") version kotlinVersion apply (false)
id("org.jetbrains.kotlin.kapt") version kotlinVersion apply (false) id("org.jetbrains.kotlin.kapt") version kotlinVersion apply (false)
id("org.jetbrains.kotlin.android.extensions") version kotlinVersion apply (false) id("org.jetbrains.kotlin.android.extensions") version kotlinVersion apply (false)
id("org.jetbrains.kotlin.multiplatform") version kotlinVersion apply (false) id("org.jetbrains.kotlin.multiplatform") version kotlinVersion apply (false)
id("org.jlleitschuh.gradle.ktlint") version "10.2.1" id("org.jlleitschuh.gradle.ktlint") version "10.3.0"
} }
apply { apply {

View File

@@ -217,28 +217,20 @@ android_test_parallel() {
( (
LOG=build/android-test-$API.log LOG=build/android-test-$API.log
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
@@ -248,7 +240,7 @@ android_test_parallel() {
echo "::endgroup::" echo "::endgroup::"
done done
return $success return $RET_CODE
} }
android_build() { android_build() {

View File

@@ -33,7 +33,7 @@ The repository will be downloaded to the directory `uhabits`.
2. When the IDE asks you for the project location, select `uhabits` and click "Ok". 2. When the IDE asks you for the project location, select `uhabits` and click "Ok".
3. Android Studio will spend some time indexing the project. When this is complete, click the toolbar icon "Sync Project with Gradle File", located near the right corner of the top toolbar. 3. Android Studio will spend some time indexing the project. When this is complete, click the toolbar icon "Sync Project with Gradle File", located near the right corner of the top toolbar.
4. The operation will likely fail several times due to missing Android SDK components. Each time it fails, click the link "Install missing platforms", "Install build tools", etc, and try again. 4. The operation will likely fail several times due to missing Android SDK components. Each time it fails, click the link "Install missing platforms", "Install build tools", etc, and try again.
5. To test the application, create a virtual Android device using the menu "Tools" and "AVD Manager". The default options should work fine, but free to customize the device. 5. To test the application, create a virtual Android device using the menu "Tools" and "AVD Manager". The default options should work fine, but feel free to customize the device.
6. Click the menu "Run" and "uhabits-android". The application should launch. 6. Click the menu "Run" and "uhabits-android". The application should launch.

View File

@@ -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-7.2-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

View File

@@ -32,11 +32,11 @@ tasks.compileLint {
android { android {
compileSdk = 31 compileSdk = 32
defaultConfig { defaultConfig {
versionCode = 20102 versionCode = 20100
versionName = "2.1.2" versionName = "2.1.0"
minSdk = 23 minSdk = 23
targetSdk = 31 targetSdk = 31
applicationId = "org.isoron.uhabits" applicationId = "org.isoron.uhabits"
@@ -68,12 +68,6 @@ android {
} }
} }
lint {
isCheckReleaseBuilds = false
isAbortOnError = false
disable("GoogleAppIndexingWarning")
}
compileOptions { compileOptions {
isCoreLibraryDesugaringEnabled = true isCoreLibraryDesugaringEnabled = true
targetCompatibility(JavaVersion.VERSION_1_8) targetCompatibility(JavaVersion.VERSION_1_8)
@@ -86,19 +80,19 @@ android {
} }
dependencies { dependencies {
val daggerVersion = "2.41" val daggerVersion = "2.43.2"
val kotlinVersion = "1.6.21" val kotlinVersion = "1.7.10"
val kxCoroutinesVersion = "1.6.1" val kxCoroutinesVersion = "1.6.4"
val ktorVersion = "1.6.8" val ktorVersion = "1.6.8"
val espressoVersion = "3.4.0" val espressoVersion = "3.4.0"
androidTestImplementation("androidx.test.espresso:espresso-contrib:$espressoVersion") androidTestImplementation("androidx.test.espresso:espresso-contrib:$espressoVersion")
androidTestImplementation("androidx.test.espresso:espresso-core:$espressoVersion") androidTestImplementation("androidx.test.espresso:espresso-core:$espressoVersion")
androidTestImplementation("com.google.dagger:dagger:$daggerVersion") androidTestImplementation("com.google.dagger:dagger:$daggerVersion")
androidTestImplementation("com.linkedin.dexmaker:dexmaker-mockito:2.28.1") androidTestImplementation("com.linkedin.dexmaker:dexmaker-mockito:2.28.3")
androidTestImplementation("io.ktor:ktor-client-mock:$ktorVersion") androidTestImplementation("io.ktor:ktor-client-mock:$ktorVersion")
androidTestImplementation("io.ktor:ktor-jackson:$ktorVersion") androidTestImplementation("io.ktor:ktor-jackson:$ktorVersion")
androidTestImplementation("androidx.annotation:annotation:1.3.0") androidTestImplementation("androidx.annotation:annotation:1.4.0")
androidTestImplementation("androidx.test.ext:junit:1.1.3") androidTestImplementation("androidx.test.ext:junit:1.1.3")
androidTestImplementation("androidx.test.uiautomator:uiautomator:2.2.0") androidTestImplementation("androidx.test.uiautomator:uiautomator:2.2.0")
androidTestImplementation("androidx.test:rules:1.4.0") androidTestImplementation("androidx.test:rules:1.4.0")
@@ -116,10 +110,10 @@ dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$kxCoroutinesVersion") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$kxCoroutinesVersion")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kxCoroutinesVersion") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kxCoroutinesVersion")
implementation("androidx.appcompat:appcompat:1.4.1") implementation("androidx.appcompat:appcompat:1.5.0")
implementation("androidx.legacy:legacy-preference-v14:1.0.0") implementation("androidx.legacy:legacy-preference-v14:1.0.0")
implementation("androidx.legacy:legacy-support-v4:1.0.0") implementation("androidx.legacy:legacy-support-v4:1.0.0")
implementation("com.google.android.material:material:1.5.0") implementation("com.google.android.material:material:1.6.1")
implementation("com.opencsv:opencsv:5.6") implementation("com.opencsv:opencsv:5.6")
implementation(project(":uhabits-core")) implementation(project(":uhabits-core"))
kapt("com.google.dagger:dagger-compiler:$daggerVersion") kapt("com.google.dagger:dagger-compiler:$daggerVersion")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -53,6 +53,7 @@ object ListHabitsSteps {
clickViewWithId(R.id.action_filter) clickViewWithId(R.id.action_filter)
CommonSteps.clickText(R.string.hide_completed) CommonSteps.clickText(R.string.hide_completed)
} }
else -> throw RuntimeException()
} }
device.waitForIdle() device.waitForIdle()
} }

View File

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

View File

@@ -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()

View File

@@ -36,7 +36,6 @@ class CheckmarkWidgetViewTest : BaseViewTest() {
@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

View File

@@ -270,7 +270,7 @@
<!-- Locale/Tasker --> <!-- Locale/Tasker -->
<receiver <receiver
android:name=".automation.FireSettingReceiver" android:name=".automation.FireSettingReceiver"
android:exported="true"> android:exported="false">
<intent-filter> <intent-filter>
<action android:name="com.twofortyfouram.locale.intent.action.FIRE_SETTING" /> <action android:name="com.twofortyfouram.locale.intent.action.FIRE_SETTING" />
</intent-filter> </intent-filter>

View File

@@ -20,60 +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,
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(requireArguments().getInt("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()
} }
} }

View File

@@ -28,7 +28,7 @@ import org.isoron.uhabits.utils.toPaletteColor
class ColorPickerDialog : ColorPickerDialog() { class ColorPickerDialog : ColorPickerDialog() {
fun setListener(callback: OnColorPickedCallback) { fun setListener(callback: OnColorPickedCallback) {
super.setOnColorSelectedListener { c: Int -> super.setOnColorSelectedListener { c: Int ->
val pc = c.toPaletteColor(context!!) val pc = c.toPaletteColor(requireContext())
callback.onColorPicked(pc) callback.onColorPicked(pc)
} }
} }

View File

@@ -51,12 +51,12 @@ class HistoryEditorDialog : AppCompatDialogFragment(), CommandRunner.Listener {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
clearCurrentDialog() clearCurrentDialog()
val component = (activity!!.application as HabitsApplication).component val component = (requireActivity().application as HabitsApplication).component
commandRunner = component.commandRunner commandRunner = component.commandRunner
habit = component.habitList.getById(arguments!!.getLong("habit"))!! habit = component.habitList.getById(requireArguments().getLong("habit"))!!
preferences = component.preferences preferences = component.preferences
val themeSwitcher = AndroidThemeSwitcher(activity!!, preferences) val themeSwitcher = AndroidThemeSwitcher(requireActivity(), preferences)
themeSwitcher.apply() themeSwitcher.apply()
chart = HistoryChart( chart = HistoryChart(
@@ -71,10 +71,10 @@ class HistoryEditorDialog : AppCompatDialogFragment(), CommandRunner.Listener {
onDateClickedListener = onDateClickedListener ?: object : OnDateClickedListener {}, onDateClickedListener = onDateClickedListener ?: object : OnDateClickedListener {},
padding = 10.0, padding = 10.0,
) )
dataView = AndroidDataView(context!!, null) dataView = AndroidDataView(requireContext(), null)
dataView.view = chart!! dataView.view = chart!!
val dialog = Dialog(context!!).apply { val dialog = Dialog(requireContext()).apply {
val metrics = resources.displayMetrics val metrics = resources.displayMetrics
val maxHeight = resources.getDimensionPixelSize(R.dimen.history_editor_max_height) val maxHeight = resources.getDimensionPixelSize(R.dimen.history_editor_max_height)
setContentView(dataView) setContentView(dataView)

View File

@@ -1,104 +0,0 @@
package org.isoron.uhabits.activities.common.dialogs
import android.app.Dialog
import android.os.Bundle
import android.text.method.DigitsKeyListener
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
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.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, view.skipBtn).forEach {
it.setTextColor(requireArguments().getInt("color"))
}
arrayOf(view.noBtn, view.unknownBtn).forEach {
it.setTextColor(view.root.sres.getColor(R.attr.contrast60))
}
arrayOf(view.yesBtn, view.noBtn, view.skipBtn, view.unknownBtn).forEach {
it.typeface = InterfaceUtils.getFontAwesome(requireContext())
}
if (!prefs.isSkipEnabled) view.skipBtnNumber.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((Entry.SKIP.toDouble() / 1000).toString())
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")
}
fun save() {
var value = originalValue
try {
val numberFormat = NumberFormat.getInstance()
val valueStr = view.value.text.toString()
value = numberFormat.parse(valueStr)!!.toDouble()
} catch (e: ParseException) {
// NOP
}
val notes = view.notes.text.toString()
onToggle(value, notes)
requireDialog().dismiss()
}
}

View File

@@ -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()
}
}

View File

@@ -60,7 +60,7 @@ class WeekdayPickerDialog :
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val builder = AlertDialog.Builder( val builder = AlertDialog.Builder(
activity!! requireActivity()
) )
builder builder
.setTitle(R.string.select_weekdays) .setTitle(R.string.select_weekdays)

View File

@@ -63,9 +63,7 @@ class FrequencyChart : ScrollableChart {
private var primaryColor = 0 private var primaryColor = 0
private var isBackgroundTransparent = false private var isBackgroundTransparent = false
private lateinit var frequency: HashMap<Timestamp, Array<Int>> private lateinit var frequency: HashMap<Timestamp, Array<Int>>
private var maxFreq = 0
private var firstWeekday = Calendar.SUNDAY private var firstWeekday = Calendar.SUNDAY
private var isNumerical: Boolean = false
constructor(context: Context?) : super(context) { constructor(context: Context?) : super(context) {
init() init()
@@ -82,14 +80,8 @@ class FrequencyChart : ScrollableChart {
postInvalidate() postInvalidate()
} }
fun setIsNumerical(type: Boolean) {
isNumerical = type
postInvalidate()
}
fun setFrequency(frequency: java.util.HashMap<Timestamp, Array<Int>>) { fun setFrequency(frequency: java.util.HashMap<Timestamp, Array<Int>>) {
this.frequency = frequency this.frequency = frequency
maxFreq = getMaxFreq(frequency)
postInvalidate() postInvalidate()
} }
@@ -98,15 +90,6 @@ class FrequencyChart : ScrollableChart {
postInvalidate() postInvalidate()
} }
private fun getMaxFreq(frequency: HashMap<Timestamp, Array<Int>>): Int {
var maxValue = 1
for (values in frequency.values) for (value in values) maxValue = max(
value,
maxValue
)
return maxValue
}
fun setIsBackgroundTransparent(isBackgroundTransparent: Boolean) { fun setIsBackgroundTransparent(isBackgroundTransparent: Boolean) {
this.isBackgroundTransparent = isBackgroundTransparent this.isBackgroundTransparent = isBackgroundTransparent
initColors() initColors()
@@ -230,7 +213,7 @@ class FrequencyChart : ScrollableChart {
canvas.drawLine(rGrid.left, rGrid.top, rGrid.right, rGrid.top, pGrid!!) canvas.drawLine(rGrid.left, rGrid.top, rGrid.right, rGrid.top, pGrid!!)
} }
private fun drawMarker(canvas: Canvas, rect: RectF?, value: Int?, weekdayFrequency: Int) { private fun drawMarker(canvas: Canvas, rect: RectF?, value: Int?, frequency: Int) {
// value can be negative when the entry is skipped // value can be negative when the entry is skipped
val valueCopy = value?.let { max(0, it) } val valueCopy = value?.let { max(0, it) }
@@ -238,8 +221,8 @@ class FrequencyChart : ScrollableChart {
// maximal allowed mark radius // maximal allowed mark radius
val maxRadius = (rect.height() - 2 * padding) / 2.0f val maxRadius = (rect.height() - 2 * padding) / 2.0f
// the real mark radius is scaled down by a factor depending on the maximal frequency // the real mark radius is scaled down by a factor depending on the maximal frequency
val scalingFactor = if (isNumerical) maxFreq else weekdayFrequency
val scale = 1.0f / scalingFactor * valueCopy!! val scale = 1.0f / frequency * valueCopy!!
val radius = maxRadius * scale val radius = maxRadius * scale
val colorIndex = min((colors.size - 1), ((colors.size - 1) * scale).roundToInt()) val colorIndex = min((colors.size - 1), ((colors.size - 1) * scale).roundToInt())
pGraph!!.color = colors[colorIndex] pGraph!!.color = colors[colorIndex]
@@ -302,6 +285,5 @@ class FrequencyChart : ScrollableChart {
frequency[Timestamp(date)] = values frequency[Timestamp(date)] = values
date.add(Calendar.MONTH, -1) date.add(Calendar.MONTH, -1)
} }
maxFreq = getMaxFreq(frequency)
} }
} }

View File

@@ -40,13 +40,13 @@ class HabitTypeDialog : AppCompatDialogFragment() {
val binding = SelectHabitTypeBinding.inflate(inflater, container, false) val binding = SelectHabitTypeBinding.inflate(inflater, container, false)
binding.buttonYesNo.setOnClickListener { binding.buttonYesNo.setOnClickListener {
val intent = IntentFactory().startEditActivity(activity!!, HabitType.YES_NO.value) val intent = IntentFactory().startEditActivity(requireActivity(), HabitType.YES_NO.value)
startActivity(intent) startActivity(intent)
dismiss() dismiss()
} }
binding.buttonMeasurable.setOnClickListener { binding.buttonMeasurable.setOnClickListener {
val intent = IntentFactory().startEditActivity(activity!!, HabitType.NUMERICAL.value) val intent = IntentFactory().startEditActivity(requireActivity(), HabitType.NUMERICAL.value)
startActivity(intent) startActivity(intent)
dismiss() dismiss()
} }

View File

@@ -40,7 +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.dismissCurrentDialog
import org.isoron.uhabits.utils.restartWithFade import org.isoron.uhabits.utils.restartWithFade
class ListHabitsActivity : AppCompatActivity(), Preferences.Listener { class ListHabitsActivity : AppCompatActivity(), Preferences.Listener {
@@ -86,13 +85,13 @@ class ListHabitsActivity : AppCompatActivity(), Preferences.Listener {
Thread.setDefaultUncaughtExceptionHandler(BaseExceptionHandler(this)) Thread.setDefaultUncaughtExceptionHandler(BaseExceptionHandler(this))
component.listHabitsBehavior.onStartup() component.listHabitsBehavior.onStartup()
setContentView(rootView) setContentView(rootView)
parseIntents()
} }
override fun onPause() { override fun onPause() {
midnightTimer.onPause() midnightTimer.onPause()
screen.onDetached() screen.onDetached()
adapter.cancelRefresh() adapter.cancelRefresh()
dismissCurrentDialog()
super.onPause() super.onPause()
} }
@@ -101,19 +100,16 @@ class ListHabitsActivity : AppCompatActivity(), Preferences.Listener {
screen.onAttached() screen.onAttached()
rootView.postInvalidate() rootView.postInvalidate()
midnightTimer.onResume() midnightTimer.onResume()
appComponent.reminderScheduler.scheduleAll()
taskRunner.run { taskRunner.run {
try { try {
AutoBackup(this@ListHabitsActivity).run() AutoBackup(this@ListHabitsActivity).run()
appComponent.widgetUpdater.updateWidgets()
} catch (e: Exception) { } catch (e: Exception) {
Log.e("ListHabitActivity", "TaskRunner failed", e) Log.e("ListHabitActivity", "AutoBackup task failed", e)
} }
} }
if (prefs.theme == THEME_DARK && prefs.isPureBlackEnabled != pureBlack) { if (prefs.theme == THEME_DARK && prefs.isPureBlackEnabled != pureBlack) {
restartWithFade(ListHabitsActivity::class.java) restartWithFade(ListHabitsActivity::class.java)
} }
parseIntents()
super.onResume() super.onResume()
} }
@@ -133,7 +129,6 @@ class ListHabitsActivity : AppCompatActivity(), Preferences.Listener {
} }
private fun parseIntents() { private fun parseIntents() {
if (intent == null) return
if (intent.action == ACTION_EDIT) { if (intent.action == ACTION_EDIT) {
val habitId = intent.extras?.getLong("habit") val habitId = intent.extras?.getLong("habit")
val timestamp = intent.extras?.getLong("timestamp") val timestamp = intent.extras?.getLong("timestamp")
@@ -142,12 +137,6 @@ class ListHabitsActivity : AppCompatActivity(), Preferences.Listener {
component.listHabitsBehavior.onEdit(habit, Timestamp(timestamp)) component.listHabitsBehavior.onEdit(habit, Timestamp(timestamp))
} }
} }
intent = null
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
setIntent(intent)
} }
companion object { companion object {

View File

@@ -22,15 +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 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
@@ -226,7 +225,7 @@ class ListHabitsScreen
override fun showColorPicker(defaultColor: PaletteColor, callback: OnColorPickedCallback) { override fun showColorPicker(defaultColor: PaletteColor, callback: OnColorPickedCallback) {
val picker = colorPickerFactory.create(defaultColor, themeSwitcher.currentTheme!!) val picker = colorPickerFactory.create(defaultColor, themeSwitcher.currentTheme!!)
picker.setListener(callback) picker.setListener(callback)
picker.dismissCurrentAndShow(activity.supportFragmentManager, "picker") picker.dialog?.dismissCurrentAndShow()
} }
override fun showNumberPopup( override fun showNumberPopup(
@@ -234,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(
@@ -250,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? {

View File

@@ -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,25 +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) performToggle() if (preferences.isShortToggleEnabled) performToggle(TOGGLE_DELAY_MILLIS)
else onEdit() else onEdit()
} }
override fun onLongClick(v: View): Boolean { override fun onLongClick(v: View): Boolean {
if (preferences.isShortToggleEnabled) onEdit() if (preferences.isShortToggleEnabled) onEdit()
else performToggle() else performToggle(TOGGLE_DELAY_MILLIS)
return true return true
} }

View File

@@ -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) }
} }
} }

View File

@@ -57,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,
@@ -129,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 {
@@ -152,9 +160,12 @@ 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)
habit?.let { behavior.onToggle(it, timestamp, value, notes) } habit?.let {
val taskId = queueToggle(it, timestamp, value, notes);
{ runPendingToggles(taskId) }.delay(delay)
}
} }
onEdit = { timestamp -> onEdit = { timestamp ->
triggerRipple(timestamp) triggerRipple(timestamp)
@@ -194,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) }

View File

@@ -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
@@ -51,7 +51,6 @@ 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.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
@@ -130,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()
} }
@@ -172,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? {
@@ -214,6 +221,7 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
ShowHabitMenuPresenter.Message.COULD_NOT_EXPORT -> { ShowHabitMenuPresenter.Message.COULD_NOT_EXPORT -> {
showMessage(resources.getString(R.string.could_not_export)) showMessage(resources.getString(R.string.could_not_export))
} }
else -> {}
} }
} }

View File

@@ -33,7 +33,6 @@ class FrequencyCardView(context: Context, attrs: AttributeSet) : LinearLayout(co
fun setState(state: FrequencyCardState) { fun setState(state: FrequencyCardState) {
val androidColor = state.theme.color(state.color).toInt() val androidColor = state.theme.color(state.color).toInt()
binding.frequencyChart.setFrequency(state.frequency) binding.frequencyChart.setFrequency(state.frequency)
binding.frequencyChart.setIsNumerical(state.isNumerical)
binding.frequencyChart.setFirstWeekday(state.firstWeekday) binding.frequencyChart.setFirstWeekday(state.firstWeekday)
binding.title.setTextColor(androidColor) binding.title.setTextColor(androidColor)
binding.frequencyChart.setColor(androidColor) binding.frequencyChart.setColor(androidColor)

View File

@@ -65,7 +65,7 @@ class SettingsFragment : PreferenceFragmentCompat(), OnSharedPreferenceChangeLis
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
addPreferencesFromResource(R.xml.preferences) addPreferencesFromResource(R.xml.preferences)
val appContext = context!!.applicationContext val appContext = requireContext().applicationContext
if (appContext is HabitsApplication) { if (appContext is HabitsApplication) {
prefs = appContext.component.preferences prefs = appContext.component.preferences
widgetUpdater = appContext.component.widgetUpdater widgetUpdater = appContext.component.widgetUpdater
@@ -99,9 +99,9 @@ class SettingsFragment : PreferenceFragmentCompat(), OnSharedPreferenceChangeLis
return true return true
} else if (key == "reminderCustomize") { } else if (key == "reminderCustomize") {
if (SDK_INT < Build.VERSION_CODES.O) return true if (SDK_INT < Build.VERSION_CODES.O) return true
createAndroidNotificationChannel(context!!) createAndroidNotificationChannel(requireContext())
val intent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS) val intent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
intent.putExtra(Settings.EXTRA_APP_PACKAGE, context!!.packageName) intent.putExtra(Settings.EXTRA_APP_PACKAGE, requireContext().packageName)
intent.putExtra(Settings.EXTRA_CHANNEL_ID, NotificationTray.REMINDERS_CHANNEL_ID) intent.putExtra(Settings.EXTRA_CHANNEL_ID, NotificationTray.REMINDERS_CHANNEL_ID)
startActivity(intent) startActivity(intent)
return true return true
@@ -111,7 +111,7 @@ class SettingsFragment : PreferenceFragmentCompat(), OnSharedPreferenceChangeLis
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
ringtoneManager = RingtoneManager(activity!!) ringtoneManager = RingtoneManager(requireActivity())
sharedPrefs = preferenceManager.sharedPreferences sharedPrefs = preferenceManager.sharedPreferences
sharedPrefs!!.registerOnSharedPreferenceChangeListener(this) sharedPrefs!!.registerOnSharedPreferenceChangeListener(this)
if (!prefs.isDeveloper) { if (!prefs.isDeveloper) {
@@ -154,8 +154,8 @@ class SettingsFragment : PreferenceFragmentCompat(), OnSharedPreferenceChangeLis
val pref = findPreference(key) val pref = findPreference(key)
pref.onPreferenceClickListener = pref.onPreferenceClickListener =
Preference.OnPreferenceClickListener { Preference.OnPreferenceClickListener {
activity!!.setResult(result) requireActivity().setResult(result)
activity!!.finish() requireActivity().finish()
true true
} }
} }

View File

@@ -21,16 +21,13 @@ package org.isoron.uhabits.intents
import android.app.PendingIntent import android.app.PendingIntent
import android.app.PendingIntent.FLAG_IMMUTABLE import android.app.PendingIntent.FLAG_IMMUTABLE
import android.app.PendingIntent.FLAG_MUTABLE
import android.app.PendingIntent.FLAG_UPDATE_CURRENT import android.app.PendingIntent.FLAG_UPDATE_CURRENT
import android.app.PendingIntent.getActivity import android.app.PendingIntent.getActivity
import android.app.PendingIntent.getBroadcast import android.app.PendingIntent.getBroadcast
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Build
import org.isoron.uhabits.activities.habits.list.ListHabitsActivity import org.isoron.uhabits.activities.habits.list.ListHabitsActivity
import org.isoron.uhabits.activities.habits.show.ShowHabitActivity
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
import org.isoron.uhabits.core.models.Timestamp import org.isoron.uhabits.core.models.Timestamp
@@ -92,20 +89,6 @@ class PendingIntentFactory
) )
.getPendingIntent(0, FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT)!! .getPendingIntent(0, FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT)!!
fun showHabitTemplate(): PendingIntent {
return getActivity(
context,
0,
Intent(context, ShowHabitActivity::class.java),
getIntentTemplateFlags()
)
}
fun showHabitFillIn(habit: Habit) =
Intent().apply {
data = Uri.parse(habit.uriString)
}
fun showReminder( fun showReminder(
habit: Habit, habit: Habit,
reminderTime: Long?, reminderTime: Long?,
@@ -159,7 +142,7 @@ class PendingIntentFactory
fun showNumberPicker(habit: Habit, timestamp: Timestamp): PendingIntent? { fun showNumberPicker(habit: Habit, timestamp: Timestamp): PendingIntent? {
return getActivity( return getActivity(
context, context,
(habit.id!! % Integer.MAX_VALUE).toInt() + 1, 0,
Intent(context, ListHabitsActivity::class.java).apply { Intent(context, ListHabitsActivity::class.java).apply {
action = ListHabitsActivity.ACTION_EDIT action = ListHabitsActivity.ACTION_EDIT
putExtra("habit", habit.id) putExtra("habit", habit.id)
@@ -168,43 +151,4 @@ class PendingIntentFactory
FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT
) )
} }
fun showNumberPickerTemplate(): PendingIntent {
return getActivity(
context,
1,
Intent(context, ListHabitsActivity::class.java).apply {
action = ListHabitsActivity.ACTION_EDIT
},
getIntentTemplateFlags()
)
}
fun showNumberPickerFillIn(habit: Habit, timestamp: Timestamp) = Intent().apply {
putExtra("habit", habit.id)
putExtra("timestamp", timestamp.unixTime)
}
private fun getIntentTemplateFlags(): Int {
var flags = 0
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
flags = flags or FLAG_MUTABLE
}
return flags
}
fun toggleCheckmarkTemplate(): PendingIntent =
getBroadcast(
context,
2,
Intent(context, WidgetReceiver::class.java).apply {
action = WidgetReceiver.ACTION_TOGGLE_REPETITION
},
getIntentTemplateFlags()
)
fun toggleCheckmarkFillIn(habit: Habit, timestamp: Timestamp) = Intent().apply {
data = Uri.parse(habit.uriString)
putExtra("timestamp", timestamp.unixTime)
}
} }

View File

@@ -6,24 +6,16 @@ import androidx.fragment.app.FragmentManager
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
var currentDialog: WeakReference<Dialog> = WeakReference(null) var currentDialog: WeakReference<Dialog> = WeakReference(null)
var currentDialogFragment: WeakReference<DialogFragment> = WeakReference(null)
fun dismissCurrentDialog() {
currentDialog.get()?.dismiss()
currentDialog = WeakReference(null)
currentDialogFragment.get()?.dismiss()
currentDialogFragment = WeakReference(null)
}
fun Dialog.dismissCurrentAndShow() { fun Dialog.dismissCurrentAndShow() {
dismissCurrentDialog() currentDialog.get()?.dismiss()
currentDialog = WeakReference(this) currentDialog = WeakReference(this)
show() show()
} }
fun DialogFragment.dismissCurrentAndShow(fragmentManager: FragmentManager, tag: String) { fun DialogFragment.dismissCurrentAndShow(fragmentManager: FragmentManager, tag: String) {
dismissCurrentDialog() currentDialog.get()?.dismiss()
currentDialogFragment = WeakReference(this)
show(fragmentManager, tag) show(fragmentManager, tag)
fragmentManager.executePendingTransactions() fragmentManager.executePendingTransactions()
currentDialog = WeakReference(this.dialog)
} }

View File

@@ -43,7 +43,7 @@ open class CheckmarkWidget(
override fun getOnClickPendingIntent(context: Context): PendingIntent? { override fun getOnClickPendingIntent(context: Context): PendingIntent? {
return if (habit.isNumerical) { return if (habit.isNumerical) {
pendingIntentFactory.showNumberPicker(habit, DateUtils.getTodayWithOffset()) pendingIntentFactory.showNumberPicker(habit, DateUtils.getToday())
} else { } else {
pendingIntentFactory.toggleCheckmark(habit, null) pendingIntentFactory.toggleCheckmark(habit, null)
} }

View File

@@ -49,7 +49,6 @@ class FrequencyWidget(
(widgetView.dataView as FrequencyChart).apply { (widgetView.dataView as FrequencyChart).apply {
setFirstWeekday(firstWeekday) setFirstWeekday(firstWeekday)
setColor(WidgetTheme().color(habit.color).toInt()) setColor(WidgetTheme().color(habit.color).toInt())
setIsNumerical(habit.isNumerical)
setFrequency(habit.originalEntries.computeWeekdayFrequency(habit.isNumerical)) setFrequency(habit.originalEntries.computeWeekdayFrequency(habit.isNumerical))
} }
} }

View File

@@ -73,10 +73,6 @@ class StackWidget(
StackWidgetType.getStackWidgetAdapterViewId(widgetType), StackWidgetType.getStackWidgetAdapterViewId(widgetType),
StackWidgetType.getStackWidgetEmptyViewId(widgetType) StackWidgetType.getStackWidgetEmptyViewId(widgetType)
) )
remoteViews.setPendingIntentTemplate(
StackWidgetType.getStackWidgetAdapterViewId(widgetType),
StackWidgetType.getPendingIntentTemplate(pendingIntentFactory, widgetType, habits)
)
return remoteViews return remoteViews
} }
} }

View File

@@ -29,14 +29,11 @@ import android.widget.RemoteViewsService
import android.widget.RemoteViewsService.RemoteViewsFactory import android.widget.RemoteViewsService.RemoteViewsFactory
import org.isoron.platform.utils.StringUtils.Companion.splitLongs import org.isoron.platform.utils.StringUtils.Companion.splitLongs
import org.isoron.uhabits.HabitsApplication import org.isoron.uhabits.HabitsApplication
import org.isoron.uhabits.R
import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.HabitNotFoundException import org.isoron.uhabits.core.models.HabitNotFoundException
import org.isoron.uhabits.core.preferences.Preferences import org.isoron.uhabits.core.preferences.Preferences
import org.isoron.uhabits.core.utils.DateUtils.Companion.getTodayWithOffset
import org.isoron.uhabits.intents.IntentFactory
import org.isoron.uhabits.intents.PendingIntentFactory
import org.isoron.uhabits.utils.InterfaceUtils.dpToPixels import org.isoron.uhabits.utils.InterfaceUtils.dpToPixels
import java.util.ArrayList
class StackWidgetService : RemoteViewsService() { class StackWidgetService : RemoteViewsService() {
override fun onGetViewFactory(intent: Intent): RemoteViewsFactory { override fun onGetViewFactory(intent: Intent): RemoteViewsFactory {
@@ -57,6 +54,7 @@ internal class StackRemoteViewsFactory(private val context: Context, intent: Int
) )
private val habitIds: LongArray private val habitIds: LongArray
private val widgetType: StackWidgetType private val widgetType: StackWidgetType
private var remoteViews = ArrayList<RemoteViews>()
override fun onCreate() {} override fun onCreate() {}
override fun onDestroy() {} override fun onDestroy() {}
override fun getCount(): Int { override fun getCount(): Int {
@@ -87,26 +85,8 @@ internal class StackRemoteViewsFactory(private val context: Context, intent: Int
} }
override fun getViewAt(position: Int): RemoteViews? { override fun getViewAt(position: Int): RemoteViews? {
Log.i("StackRemoteViewsFactory", "getViewAt $position started") Log.i("StackRemoteViewsFactory", "getViewAt $position")
if (position < 0 || position >= habitIds.size) return null return if (0 <= position && position < remoteViews.size) remoteViews[position] else null
val app = context.applicationContext as HabitsApplication
val prefs = app.component.preferences
val habitList = app.component.habitList
val options = AppWidgetManager.getInstance(context).getAppWidgetOptions(widgetId)
if (Looper.myLooper() == null) Looper.prepare()
val habits = habitIds.map { habitList.getById(it) ?: throw HabitNotFoundException() }
val h = habits[position]
val widget = constructWidget(h, prefs)
widget.setDimensions(getDimensionsFromOptions(context, options))
val landscapeViews = widget.landscapeRemoteViews
val portraitViews = widget.portraitRemoteViews
val factory = PendingIntentFactory(context, IntentFactory())
val intent = StackWidgetType.getIntentFillIn(factory, widgetType, h, habits, getTodayWithOffset())
landscapeViews.setOnClickFillInIntent(R.id.button, intent)
portraitViews.setOnClickFillInIntent(R.id.button, intent)
val remoteViews = RemoteViews(landscapeViews, portraitViews)
Log.i("StackRemoteViewsFactory", "getViewAt $position ended")
return remoteViews
} }
private fun constructWidget( private fun constructWidget(
@@ -151,6 +131,24 @@ internal class StackRemoteViewsFactory(private val context: Context, intent: Int
} }
override fun onDataSetChanged() { override fun onDataSetChanged() {
Log.i("StackRemoteViewsFactory", "onDataSetChanged started")
val app = context.applicationContext as HabitsApplication
val prefs = app.component.preferences
val habitList = app.component.habitList
val options = AppWidgetManager.getInstance(context).getAppWidgetOptions(widgetId)
val newRemoteViews = ArrayList<RemoteViews>()
if (Looper.myLooper() == null) Looper.prepare()
for (id in habitIds) {
val h = habitList.getById(id) ?: throw HabitNotFoundException()
val widget = constructWidget(h, prefs)
widget.setDimensions(getDimensionsFromOptions(context, options))
val landscapeViews = widget.landscapeRemoteViews
val portraitViews = widget.portraitRemoteViews
newRemoteViews.add(RemoteViews(landscapeViews, portraitViews))
Log.i("StackRemoteViewsFactory", "onDataSetChanged constructed widget $id")
}
remoteViews = newRemoteViews
Log.i("StackRemoteViewsFactory", "onDataSetChanged ended")
} }
init { init {

View File

@@ -18,12 +18,7 @@
*/ */
package org.isoron.uhabits.widgets package org.isoron.uhabits.widgets
import android.app.PendingIntent
import android.content.Intent
import org.isoron.uhabits.R import org.isoron.uhabits.R
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.Timestamp
import org.isoron.uhabits.intents.PendingIntentFactory
import java.lang.IllegalStateException import java.lang.IllegalStateException
enum class StackWidgetType(val value: Int) { enum class StackWidgetType(val value: Int) {
@@ -78,39 +73,5 @@ enum class StackWidgetType(val value: Int) {
else -> throw IllegalStateException() else -> throw IllegalStateException()
} }
} }
fun getPendingIntentTemplate(
factory: PendingIntentFactory,
widgetType: StackWidgetType,
habits: List<Habit>
): PendingIntent {
val containsNumerical = habits.any { it.isNumerical }
return when (widgetType) {
CHECKMARK -> if (containsNumerical) {
factory.showNumberPickerTemplate()
} else {
factory.toggleCheckmarkTemplate()
}
FREQUENCY, SCORE, HISTORY, STREAKS, TARGET -> factory.showHabitTemplate()
}
}
fun getIntentFillIn(
factory: PendingIntentFactory,
widgetType: StackWidgetType,
habit: Habit,
allHabitsInStackWidget: List<Habit>,
timestamp: Timestamp
): Intent {
val containsNumerical = allHabitsInStackWidget.any { it.isNumerical }
return when (widgetType) {
CHECKMARK -> if (containsNumerical) {
factory.showNumberPickerFillIn(habit, timestamp)
} else {
factory.toggleCheckmarkFillIn(habit, timestamp)
}
FREQUENCY, SCORE, HISTORY, STREAKS, TARGET -> factory.showHabitFillIn(habit)
}
}
} }
} }

View File

@@ -24,6 +24,7 @@ import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID
import android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID import android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.widget.AbsListView.CHOICE_MODE_MULTIPLE
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import android.widget.Button import android.widget.Button
import android.widget.ListView import android.widget.ListView
@@ -33,6 +34,7 @@ import org.isoron.uhabits.R
import org.isoron.uhabits.activities.AndroidThemeSwitcher import org.isoron.uhabits.activities.AndroidThemeSwitcher
import org.isoron.uhabits.core.preferences.WidgetPreferences import org.isoron.uhabits.core.preferences.WidgetPreferences
import org.isoron.uhabits.widgets.WidgetUpdater import org.isoron.uhabits.widgets.WidgetUpdater
import java.util.ArrayList
class BooleanHabitPickerDialog : HabitPickerDialog() { class BooleanHabitPickerDialog : HabitPickerDialog() {
override fun shouldHideNumerical() = true override fun shouldHideNumerical() = true
@@ -86,12 +88,20 @@ open class HabitPickerDialog : Activity() {
with(listView) { with(listView) {
adapter = ArrayAdapter( adapter = ArrayAdapter(
context, context,
android.R.layout.simple_list_item_1, android.R.layout.simple_list_item_multiple_choice,
habitNames habitNames
) )
setOnItemClickListener { parent, view, position, id -> choiceMode = CHOICE_MODE_MULTIPLE
confirm(mutableListOf(habitIds[position])) itemsCanFocus = false
}
saveButton.setOnClickListener {
val selectedIds = mutableListOf<Long>()
for (i in 0..listView.count) {
if (listView.isItemChecked(i)) {
selectedIds.add(habitIds[i])
}
} }
confirm(selectedIds)
} }
} }

View File

@@ -66,21 +66,23 @@ class CheckmarkWidgetView : HabitWidgetView {
val res = StyledResources(context) val res = StyledResources(context)
val bgColor: Int val bgColor: Int
val fgColor: Int val fgColor: Int
setShadowAlpha(0x4f)
when (entryState) { when (entryState) {
YES_MANUAL, SKIP -> { YES_MANUAL, SKIP -> {
bgColor = activeColor bgColor = activeColor
fgColor = res.getColor(R.attr.contrast0) fgColor = res.getColor(R.attr.contrast0)
setShadowAlpha(0x4f)
backgroundPaint!!.color = bgColor backgroundPaint!!.color = bgColor
frame!!.setBackgroundDrawable(background) frame!!.setBackgroundDrawable(background)
} }
YES_AUTO, NO, UNKNOWN -> { YES_AUTO, NO, UNKNOWN -> {
bgColor = res.getColor(R.attr.cardBgColor) bgColor = res.getColor(R.attr.cardBgColor)
fgColor = res.getColor(R.attr.contrast60) fgColor = res.getColor(R.attr.contrast60)
setShadowAlpha(0x00)
} }
else -> { else -> {
bgColor = res.getColor(R.attr.cardBgColor) bgColor = res.getColor(R.attr.cardBgColor)
fgColor = res.getColor(R.attr.contrast60) fgColor = res.getColor(R.attr.contrast60)
setShadowAlpha(0x00)
} }
} }
ring.setPercentage(percentage) ring.setPercentage(percentage)
@@ -124,7 +126,7 @@ class CheckmarkWidgetView : HabitWidgetView {
} else { } else {
width = min(width, height) width = min(width, height)
} }
val textSize = min(0.175f * width, getDimension(context, R.dimen.smallTextSize)) val textSize = min(0.2f * width, getDimension(context, R.dimen.smallerTextSize))
label.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize) label.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
if (isNumerical) { if (isNumerical) {
ring.setTextSize(textSize * 0.9f) ring.setTextSize(textSize * 0.9f)
@@ -139,8 +141,7 @@ class CheckmarkWidgetView : HabitWidgetView {
} }
private fun init() { private fun init() {
val appComponent: HabitsApplicationComponent = val appComponent: HabitsApplicationComponent = (context.applicationContext as HabitsApplication).component
(context.applicationContext as HabitsApplication).component
preferences = appComponent.preferences preferences = appComponent.preferences
ring = findViewById<View>(R.id.scoreRing) as RingView ring = findViewById<View>(R.id.scoreRing) as RingView
label = findViewById<View>(R.id.label) as TextView label = findViewById<View>(R.id.label) as TextView

View File

@@ -69,7 +69,7 @@ abstract class HabitWidgetView : FrameLayout {
val shadowRadius = dpToPixels(context, 2f).toInt() val shadowRadius = dpToPixels(context, 2f).toInt()
val shadowOffset = dpToPixels(context, 1f).toInt() val shadowOffset = dpToPixels(context, 1f).toInt()
val shadowColor = Color.argb(shadowAlpha, 0, 0, 0) val shadowColor = Color.argb(shadowAlpha, 0, 0, 0)
val cornerRadius = dpToPixels(context, 12f) val cornerRadius = dpToPixels(context, 5f)
val radii = FloatArray(8) val radii = FloatArray(8)
Arrays.fill(radii, cornerRadius) Arrays.fill(radii, cornerRadius)
val shape = RoundRectShape(radii, null, null) val shape = RoundRectShape(radii, null, null)

View File

@@ -1,6 +1,3 @@
2.1.1:
* Fix Tasker plugin
2.1: 2.1:
* Add notes to specific dates * Add notes to specific dates
* Track at-most measurable habits * Track at-most measurable habits

View File

@@ -28,7 +28,7 @@
<item android:id="@android:id/mask"> <item android:id="@android:id/mask">
<shape android:shape="rectangle"> <shape android:shape="rectangle">
<corners android:radius="12dp"/> <corners android:radius="5dp"/>
<solid android:color="?android:colorPrimary"/> <solid android:color="?android:colorPrimary"/>
</shape> </shape>
<color android:color="@color/white"/> <color android:color="@color/white"/>

View File

@@ -21,10 +21,8 @@
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android" <androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/container" android:id="@+id/container"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:minHeight="128dp"
android:minWidth="208dp"
app:divider="@drawable/checkmark_dialog_divider" app:divider="@drawable/checkmark_dialog_divider"
app:showDividers="middle" app:showDividers="middle"
android:orientation="vertical" android:orientation="vertical"
@@ -36,7 +34,7 @@
android:layout_height="0dp" android:layout_height="0dp"
android:layout_weight="1" android:layout_weight="1"
android:gravity="center" android:gravity="center"
android:inputType="textCapSentences" android:inputType="textCapSentences|textMultiLine"
android:textSize="@dimen/smallTextSize" android:textSize="@dimen/smallTextSize"
android:padding="4dp" android:padding="4dp"
android:background="@color/transparent" android:background="@color/transparent"

View File

@@ -30,4 +30,10 @@
android:layout_weight="1"> android:layout_weight="1">
</ListView> </ListView>
<Button
android:id="@+id/buttonSave"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:text="@string/save"/>
</LinearLayout> </LinearLayout>

View File

@@ -35,7 +35,7 @@
android:paddingTop="4dp" android:paddingTop="4dp"
android:paddingLeft="8dp" android:paddingLeft="8dp"
android:paddingRight="8dp" android:paddingRight="8dp"
android:paddingBottom="8dp" android:paddingBottom="4dp"
tools:ignore="UselessParent"> tools:ignore="UselessParent">
<TextView <TextView

View File

@@ -21,5 +21,4 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/> <background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/> <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<monochrome android:drawable="@mipmap/ic_launcher_monochrome"/>
</adaptive-icon> </adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

View File

@@ -43,11 +43,11 @@ kotlin {
val jvmMain by getting { val jvmMain by getting {
dependencies { dependencies {
implementation(kotlin("stdlib-jdk8")) implementation(kotlin("stdlib-jdk8"))
compileOnly("com.google.dagger:dagger:2.41") compileOnly("com.google.dagger:dagger:2.43.2")
implementation("com.google.guava:guava:31.1-android") implementation("com.google.guava:guava:31.1-android")
implementation("org.jetbrains.kotlin:kotlin-stdlib:1.6.21") implementation("org.jetbrains.kotlin:kotlin-stdlib:1.7.10")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.6.1") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.6.4")
implementation("androidx.annotation:annotation:1.3.0") implementation("androidx.annotation:annotation:1.4.0")
implementation("com.google.code.findbugs:jsr305:3.0.2") implementation("com.google.code.findbugs:jsr305:3.0.2")
implementation("com.opencsv:opencsv:5.6") implementation("com.opencsv:opencsv:5.6")
implementation("commons-codec:commons-codec:1.15") implementation("commons-codec:commons-codec:1.15")
@@ -59,7 +59,7 @@ kotlin {
dependencies { dependencies {
implementation(kotlin("test")) implementation(kotlin("test"))
implementation(kotlin("test-junit")) implementation(kotlin("test-junit"))
implementation("org.xerial:sqlite-jdbc:3.36.0.3") implementation("org.xerial:sqlite-jdbc:3.39.2.0")
implementation("org.hamcrest:hamcrest:2.2") implementation("org.hamcrest:hamcrest:2.2")
implementation("org.apache.commons:commons-io:1.3.2") implementation("org.apache.commons:commons-io:1.3.2")
implementation("com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0") implementation("com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0")

View File

@@ -25,12 +25,6 @@ class StringUtils {
fun joinLongs(values: LongArray): String = values.joinToString(separator = ",") fun joinLongs(values: LongArray): String = values.joinToString(separator = ",")
fun splitLongs(str: String): LongArray { fun splitLongs(str: String): LongArray = str.split(",").map { it.toLong() }.toLongArray()
return try {
str.split(",").map { it.toLong() }.toLongArray()
} catch (e: NumberFormatException) {
LongArray(0)
}
}
} }
} }

View File

@@ -149,7 +149,7 @@ class HabitsCSVExporter(
val timeframe = getTimeframe() val timeframe = getTimeframe()
val oldest = timeframe[0] val oldest = timeframe[0]
val newest = DateUtils.getTodayWithOffset() val newest = DateUtils.getToday()
val checkmarks: MutableList<ArrayList<Entry>> = ArrayList() val checkmarks: MutableList<ArrayList<Entry>> = ArrayList()
val scores: MutableList<ArrayList<Score>> = ArrayList() val scores: MutableList<ArrayList<Score>> = ArrayList()
for (habit in selectedHabits) { for (habit in selectedHabits) {

View File

@@ -204,16 +204,9 @@ open class EntryList {
// Copy original entries // Copy original entries
original.forEach { entry -> original.forEach { entry ->
val offset = entry.timestamp.daysUntil(to) val offset = entry.timestamp.daysUntil(to)
val value = if ( if (result[offset].value == UNKNOWN || entry.value == SKIP || entry.value == YES_MANUAL) {
result[offset].value == UNKNOWN || result[offset] = entry
entry.value == SKIP ||
entry.value == YES_MANUAL
) {
entry.value
} else {
YES_AUTO
} }
result[offset] = Entry(entry.timestamp, value, entry.notes)
} }
return result return result

View File

@@ -57,7 +57,7 @@ class BarCardPresenter(
} else { } else {
boolBucketSizes[boolSpinnerPosition] boolBucketSizes[boolSpinnerPosition]
} }
val today = DateUtils.getTodayWithOffset() val today = DateUtils.getToday()
val oldest = habit.computedEntries.getKnown().lastOrNull()?.timestamp ?: today val oldest = habit.computedEntries.getKnown().lastOrNull()?.timestamp ?: today
val entries = habit.computedEntries.getByInterval(oldest, today).groupedSum( val entries = habit.computedEntries.getByInterval(oldest, today).groupedSum(
truncateField = ScoreCardPresenter.getTruncateField(bucketSize), truncateField = ScoreCardPresenter.getTruncateField(bucketSize),

View File

@@ -30,7 +30,6 @@ data class FrequencyCardState(
val firstWeekday: Int, val firstWeekday: Int,
val frequency: HashMap<Timestamp, Array<Int>>, val frequency: HashMap<Timestamp, Array<Int>>,
val theme: Theme, val theme: Theme,
val isNumerical: Boolean
) )
class FrequencyCardPresenter { class FrequencyCardPresenter {
@@ -41,7 +40,6 @@ class FrequencyCardPresenter {
theme: Theme theme: Theme
) = FrequencyCardState( ) = FrequencyCardState(
color = habit.color, color = habit.color,
isNumerical = habit.isNumerical,
frequency = habit.originalEntries.computeWeekdayFrequency( frequency = habit.originalEntries.computeWeekdayFrequency(
isNumerical = habit.isNumerical isNumerical = habit.isNumerical
), ),

View File

@@ -91,6 +91,7 @@ class HistoryCardPresenter(
screen.showCheckmarkPopup( screen.showCheckmarkPopup(
entry.value, entry.value,
entry.notes, entry.notes,
preferences,
habit.color, habit.color,
) { newValue, newNotes -> ) { newValue, newNotes ->
commandRunner.run( commandRunner.run(
@@ -129,6 +130,7 @@ class HistoryCardPresenter(
screen.showNumberPopup( screen.showNumberPopup(
value = oldValue / 1000.0, value = oldValue / 1000.0,
notes = entry.notes, notes = entry.notes,
preferences = preferences,
) { newValue: Double, newNotes: String -> ) { newValue: Double, newNotes: String ->
val thousands = (newValue * 1000).roundToInt() val thousands = (newValue * 1000).roundToInt()
commandRunner.run( commandRunner.run(
@@ -201,11 +203,13 @@ class HistoryCardPresenter(
fun showNumberPopup( fun showNumberPopup(
value: Double, value: Double,
notes: String, notes: String,
preferences: Preferences,
callback: ListHabitsBehavior.NumberPickerCallback, callback: ListHabitsBehavior.NumberPickerCallback,
) )
fun showCheckmarkPopup( fun showCheckmarkPopup(
selectedValue: Int, selectedValue: Int,
notes: String, notes: String,
preferences: Preferences,
color: PaletteColor, color: PaletteColor,
callback: ListHabitsBehavior.CheckMarkDialogCallback, callback: ListHabitsBehavior.CheckMarkDialogCallback,
) )

View File

@@ -92,7 +92,7 @@ class BarChart(
val r = round(barWidth * 0.15) val r = round(barWidth * 0.15)
if (2 * r < barHeight) { if (2 * r < barHeight) {
canvas.fillRect(x, y + r, barWidth, barHeight - r) canvas.fillRect(x, y + r, barWidth, barHeight - r)
canvas.fillRect(x + r, y, barWidth - 2 * r, r + 1) canvas.fillRect(x + r, y, barWidth - 2 * r, r)
canvas.fillCircle(x + r, y + r, r) canvas.fillCircle(x + r, y + r, r)
canvas.fillCircle(x + barWidth - r, y + r, r) canvas.fillCircle(x + barWidth - r, y + r, r)
} else { } else {

View File

@@ -217,7 +217,7 @@ class EntryListTest {
fun testAddFromInterval() { fun testAddFromInterval() {
val entries = listOf( val entries = listOf(
Entry(day(1), YES_MANUAL), Entry(day(1), YES_MANUAL),
Entry(day(2), NO, "Test"), Entry(day(2), NO),
Entry(day(4), NO), Entry(day(4), NO),
Entry(day(5), YES_MANUAL), Entry(day(5), YES_MANUAL),
Entry(day(10), YES_MANUAL), Entry(day(10), YES_MANUAL),
@@ -230,7 +230,7 @@ class EntryListTest {
) )
val expected = listOf( val expected = listOf(
Entry(day(1), YES_MANUAL), Entry(day(1), YES_MANUAL),
Entry(day(2), YES_AUTO, "Test"), Entry(day(2), YES_AUTO),
Entry(day(3), UNKNOWN), Entry(day(3), UNKNOWN),
Entry(day(4), YES_AUTO), Entry(day(4), YES_AUTO),
Entry(day(5), YES_MANUAL), Entry(day(5), YES_MANUAL),

View File

@@ -34,7 +34,7 @@ application {
dependencies { dependencies {
val ktorVersion = "1.6.8" val ktorVersion = "1.6.8"
val kotlinVersion = "1.6.21" val kotlinVersion = "1.7.10"
val logbackVersion = "1.2.11" val logbackVersion = "1.2.11"
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion")
implementation("io.ktor:ktor-server-netty:$ktorVersion") implementation("io.ktor:ktor-server-netty:$ktorVersion")
@@ -43,9 +43,9 @@ dependencies {
implementation("io.ktor:ktor-html-builder:$ktorVersion") implementation("io.ktor:ktor-html-builder:$ktorVersion")
implementation("io.ktor:ktor-jackson:$ktorVersion") implementation("io.ktor:ktor-jackson:$ktorVersion")
implementation("org.jetbrains:kotlin-css-jvm:1.0.0-pre.148-kotlin-1.4.30") implementation("org.jetbrains:kotlin-css-jvm:1.0.0-pre.148-kotlin-1.4.30")
implementation("io.prometheus:simpleclient:0.15.0") implementation("io.prometheus:simpleclient:0.16.0")
implementation("io.prometheus:simpleclient_httpserver:0.15.0") implementation("io.prometheus:simpleclient_httpserver:0.16.0")
implementation("io.prometheus:simpleclient_hotspot:0.15.0") implementation("io.prometheus:simpleclient_hotspot:0.16.0")
testImplementation("io.ktor:ktor-server-tests:$ktorVersion") testImplementation("io.ktor:ktor-server-tests:$ktorVersion")
testImplementation("com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0") testImplementation("com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0")
testImplementation(kotlin("test")) testImplementation(kotlin("test"))