Compare commits

..

102 Commits

Author SHA1 Message Date
Quentin Hibon
e65c7de086 Don't accept null urls 2024-04-04 23:55:28 +02:00
06090e238a Merge branch 'feature/konfetti' into dev 2024-04-02 19:51:59 -05:00
e48452f724 Move showConfetti to ListHabitsScreen; use button location; other tweaks 2024-04-02 19:34:00 -05:00
dependabot[bot]
936986e110 Bump daggerVersion from 2.50 to 2.51.1
Bumps `daggerVersion` from 2.50 to 2.51.1.

Updates `com.google.dagger:dagger` from 2.50 to 2.51.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.50...dagger-2.51.1)

Updates `com.google.dagger:dagger-compiler` from 2.50 to 2.51.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.50...dagger-2.51.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>
2024-04-02 22:04:24 +02:00
dependabot[bot]
4b3910aea8 Bump com.google.guava:guava from 33.0.0-android to 33.1.0-android
Bumps [com.google.guava:guava](https://github.com/google/guava) from 33.0.0-android to 33.1.0-android.
- [Release notes](https://github.com/google/guava/releases)
- [Commits](https://github.com/google/guava/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-01 18:11:48 +02:00
Gokul K
1280e798d2 rendering only if layout and konfetti view exists - to avoid null reference errors 2024-02-03 23:14:58 -06:00
Gokul K
b09306e793 fixing another lint error 2024-02-03 23:14:58 -06:00
Gokul K
e30636a447 fixed ktlint erorrs and is building successfully now 2024-02-03 23:14:58 -06:00
Gokul K
ad8738180c fixing some comments from @hiqua 2024-02-03 23:14:58 -06:00
Gokul K
08410c59d0 introducing confetti animation using konfetti library 2024-02-03 23:14:58 -06:00
Manish Kumar Verma
ab86cee70b Fix: No notification when goal type is set to "maximum" (#1931) 2024-02-02 19:21:40 -06:00
dependabot[bot]
3a0603605b Bump androidx.annotation:annotation from 1.7.0 to 1.7.1
Bumps androidx.annotation:annotation from 1.7.0 to 1.7.1.

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-01 04:43:18 -06:00
dependabot[bot]
6a78b4d853 Bump daggerVersion from 2.48.1 to 2.50 (#1907)
Bumps `daggerVersion` from 2.48.1 to 2.50.

Updates `com.google.dagger:dagger` from 2.48.1 to 2.50
- [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.48.1...dagger-2.50)

Updates `com.google.dagger:dagger-compiler` from 2.48.1 to 2.50
- [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.48.1...dagger-2.50)

---
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>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-01 04:34:40 -06:00
dependabot[bot]
fe43b1435d Bump ch.qos.logback:logback-classic from 1.4.13 to 1.4.14 (#1905)
Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.4.13 to 1.4.14.
- [Commits](https://github.com/qos-ch/logback/compare/v_1.4.13...v_1.4.14)

---
updated-dependencies:
- dependency-name: ch.qos.logback:logback-classic
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-01 04:33:18 -06:00
dependabot[bot]
12503b8a6d Bump com.google.guava:guava from 32.1.3-android to 33.0.0-android (#1902)
Bumps [com.google.guava:guava](https://github.com/google/guava) from 32.1.3-android to 33.0.0-android.
- [Release notes](https://github.com/google/guava/releases)
- [Commits](https://github.com/google/guava/commits)

---
updated-dependencies:
- dependency-name: com.google.guava:guava
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-01 04:33:03 -06:00
dependabot[bot]
ef7f78bff0 Bump org.xerial:sqlite-jdbc from 3.42.0.0 to 3.45.1.0 (#1929)
Bumps [org.xerial:sqlite-jdbc](https://github.com/xerial/sqlite-jdbc) from 3.42.0.0 to 3.45.1.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.42.0.0...3.45.1.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>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-01 04:32:43 -06:00
dependabot[bot]
53c208ded5 Bump com.google.android.material:material from 1.10.0 to 1.11.0 (#1908)
Bumps [com.google.android.material:material](https://github.com/material-components/material-components-android) from 1.10.0 to 1.11.0.
- [Release notes](https://github.com/material-components/material-components-android/releases)
- [Commits](https://github.com/material-components/material-components-android/compare/1.10.0...1.11.0)

---
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>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-01 04:32:23 -06:00
dependabot[bot]
1bdc83e92f Bump org.mockito.kotlin:mockito-kotlin from 5.1.0 to 5.2.1 (#1906)
Bumps [org.mockito.kotlin:mockito-kotlin](https://github.com/mockito/mockito-kotlin) from 5.1.0 to 5.2.1.
- [Release notes](https://github.com/mockito/mockito-kotlin/releases)
- [Commits](https://github.com/mockito/mockito-kotlin/compare/5.1.0...5.2.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-01 04:32:08 -06:00
dependabot[bot]
680c1cdc76 Bump kotlinVersion from 1.9.21 to 1.9.22 (#1904)
Bumps `kotlinVersion` from 1.9.21 to 1.9.22.

Updates `org.jetbrains.kotlin.android` from 1.9.21 to 1.9.22

Updates `org.jetbrains.kotlin.kapt` from 1.9.21 to 1.9.22

Updates `org.jetbrains.kotlin.multiplatform` from 1.9.21 to 1.9.22

Updates `org.jetbrains.kotlin:kotlin-stdlib-jdk8` from 1.9.21 to 1.9.22
- [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.9.21...v1.9.22)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlin.android
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.jetbrains.kotlin.kapt
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.jetbrains.kotlin.multiplatform
  dependency-type: direct:production
  update-type: version-update:semver-patch
- 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>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-01 04:31:50 -06:00
dependabot[bot]
80916bac50 Bump actions/upload-artifact from 3 to 4 (#1901)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 3 to 4.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-01 04:31:37 -06:00
Leon Todd
a5e3e9b3cf Unset measurable habits (#1899) 2024-01-30 19:42:53 -06:00
248ba50a8e Reformat source code 2024-01-30 19:38:32 -06:00
Andreas Gebhardt
45a82b3c2d Changed Checkmark Widget Color on implicit check
As proposed in #615, the background is now solid color on implicit check but has a stroked checkmark instead of a full one.
2024-01-30 19:25:57 -06:00
770d1293dc Merge branch 'master' into dev 2024-01-30 19:15:48 -06:00
d10538e720 Merge branch 'release/2.2.0' 2024-01-30 19:15:12 -06:00
02e9e2384e Update CHANGELOG 2024-01-30 19:09:22 -06:00
Serhii K
b627ff4413 Use colorBackground property instead of splash screen lib 2024-01-30 19:06:43 -06:00
Serhii K
0683ea43f4 Fix splash screen background color in dark mode 2024-01-30 19:06:43 -06:00
08f77a5cae Merge branch 'master' into release/2.2.0 2023-12-01 18:31:53 -06:00
27df792775 Update CHANGELOG 2023-12-01 18:29:34 -06:00
Leonard Dizon
800f92f255 Create locales_config.xml for per-app language 2023-12-01 18:14:06 -06:00
dependabot[bot]
e06ed3ed7d Bump ch.qos.logback:logback-classic from 1.4.11 to 1.4.13 (#1882)
Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.4.11 to 1.4.13.
- [Commits](https://github.com/qos-ch/logback/compare/v_1.4.11...v_1.4.13)

---
updated-dependencies:
- dependency-name: ch.qos.logback:logback-classic
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-01 18:08:31 -06:00
dependabot[bot]
3bb119c6ed Bump com.github.triplet.play from 3.8.4 to 3.8.6 (#1879)
Bumps com.github.triplet.play from 3.8.4 to 3.8.6.

---
updated-dependencies:
- dependency-name: com.github.triplet.play
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-01 18:08:05 -06:00
dependabot[bot]
0762699a86 Bump org.apache.commons:commons-lang3 from 3.13.0 to 3.14.0 (#1881)
Bumps org.apache.commons:commons-lang3 from 3.13.0 to 3.14.0.

---
updated-dependencies:
- dependency-name: org.apache.commons:commons-lang3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-01 18:07:26 -06:00
dependabot[bot]
8837326d44 Bump com.opencsv:opencsv from 5.8 to 5.9 (#1880)
Bumps com.opencsv:opencsv from 5.8 to 5.9.

---
updated-dependencies:
- dependency-name: com.opencsv:opencsv
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-01 18:07:17 -06:00
dependabot[bot]
481a3d5784 Bump org.junit.jupiter:junit-jupiter from 5.10.0 to 5.10.1 (#1878)
Bumps [org.junit.jupiter:junit-jupiter](https://github.com/junit-team/junit5) from 5.10.0 to 5.10.1.
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.10.0...r5.10.1)

---
updated-dependencies:
- dependency-name: org.junit.jupiter:junit-jupiter
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-01 18:07:03 -06:00
8eff782f54 Update dependencies 2023-11-29 20:07:45 -06:00
c590734c42 Update dependencies 2023-11-29 19:54:00 -06:00
7e7f68282b Update Kotlin 2023-11-29 19:38:21 -06:00
4bd5cee17b Update libraries 2023-11-29 19:30:32 -06:00
b2421dc8b1 GH Actions: Increase number of attempts 2023-11-27 20:57:43 -06:00
93363bff96 GH Actions: Add API 34 2023-11-27 20:57:14 -06:00
d01044b203 GH Actions: Add API 34 2023-11-27 20:47:48 -06:00
70fe513e52 Bump targetSdk to 34 2023-11-27 20:43:34 -06:00
dee93fde8f Upgrade to AGP 8.1.4 2023-11-27 19:46:54 -06:00
f0ce05e06e API 34: Implement workaround to keep sticky notifications non-dismissible 2023-11-25 15:42:04 -06:00
4975ba2752 Add permission: USE_EXACT_ALARM 2023-11-25 15:30:29 -06:00
ed8c60e52f Implement runtime notification permission; bump targetSdk to 33 2023-11-25 07:12:16 -06:00
dependabot[bot]
7735247521 Bump daggerVersion from 2.48 to 2.48.1
Bumps `daggerVersion` from 2.48 to 2.48.1.

Updates `com.google.dagger:dagger` from 2.48 to 2.48.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.48...dagger-2.48.1)

Updates `com.google.dagger:dagger-compiler` from 2.48 to 2.48.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.48...dagger-2.48.1)

---
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>
2023-11-06 16:07:22 +01:00
dependabot[bot]
b32a6ad1b8 Bump org.jlleitschuh.gradle.ktlint from 11.5.1 to 11.6.0
Bumps org.jlleitschuh.gradle.ktlint from 11.5.1 to 11.6.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>
2023-10-01 14:18:03 +02:00
dependabot[bot]
21a512ae71 Bump androidx.annotation:annotation from 1.6.0 to 1.7.0
Bumps androidx.annotation:annotation from 1.6.0 to 1.7.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>
2023-10-01 14:17:41 +02:00
dependabot[bot]
b0944d3f1c Bump actions/checkout from 3 to 4
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-01 14:17:26 +02:00
dependabot[bot]
8a9b719c50 Bump org.mockito.kotlin:mockito-kotlin from 2.2.11 to 5.1.0
Bumps [org.mockito.kotlin:mockito-kotlin](https://github.com/mockito/mockito-kotlin) from 2.2.11 to 5.1.0.
- [Release notes](https://github.com/mockito/mockito-kotlin/releases)
- [Commits](https://github.com/mockito/mockito-kotlin/compare/2.2.11...5.1.0)

---
updated-dependencies:
- dependency-name: org.mockito.kotlin:mockito-kotlin
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-20 22:04:01 +02:00
Quentin Hibon
ef7454ae75 Bump kotlin from 1.7.21 to 1.8.20 2023-09-20 21:04:38 +02:00
Quentin Hibon
b51f6abfce Target JVM 17 in uhabits-server 2023-09-20 21:04:38 +02:00
Quentin Hibon
a013635224 Bump mockito-kotlin from 4.1.0 to 5.0.0 2023-09-20 21:04:38 +02:00
Quentin Hibon
a9f028a34b Target JVM 11 2023-09-20 21:04:38 +02:00
Quentin Hibon
3b0fba12f5 Remove unused imports in datetimepicker 2023-09-20 21:04:38 +02:00
Quentin Hibon
4139f09fb7 Add @Deprecated when overriding @Deprecated methods 2023-09-20 21:04:38 +02:00
Quentin Hibon
248ff2ec62 Bump mockito-kotlin from 2.2.11 to 4.1.0 in -server 2023-09-20 21:04:38 +02:00
dependabot[bot]
cc7178eb21 Bump daggerVersion from 2.47 to 2.48
Bumps `daggerVersion` from 2.47 to 2.48.

Updates `com.google.dagger:dagger` from 2.47 to 2.48
- [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.47...dagger-2.48)

Updates `com.google.dagger:dagger-compiler` from 2.47 to 2.48
- [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.47...dagger-2.48)

---
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>
2023-09-04 14:47:47 +02:00
6116ef9450 Merge branch 'hotfix/2.1.3' 2023-08-28 05:51:08 -05:00
8801960615 Update CHANGELOG 2023-08-28 05:43:12 -05:00
b0a4284b66 NumberDialog: Use text input on Samsung devices
Fixes #1719
2023-08-28 05:36:59 -05:00
Felix Schlegel
334dabb407 Use default locale instead of hardcoded US locale (#1763) 2023-08-18 18:35:54 +02:00
dependabot[bot]
b2fc79a3ab Bump com.github.triplet.play from 3.7.0 to 3.8.4
Bumps com.github.triplet.play from 3.7.0 to 3.8.4.

---
updated-dependencies:
- dependency-name: com.github.triplet.play
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-08 22:56:02 +02:00
dependabot[bot]
f0e8643e6b Bump com.google.android.material:material from 1.8.0 to 1.9.0
Bumps [com.google.android.material:material](https://github.com/material-components/material-components-android) from 1.8.0 to 1.9.0.
- [Release notes](https://github.com/material-components/material-components-android/releases)
- [Commits](https://github.com/material-components/material-components-android/compare/1.8.0...1.9.0)

---
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>
2023-08-08 21:30:23 +02:00
dependabot[bot]
5cd616f967 Bump org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm
Bumps [org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm](https://github.com/Kotlin/kotlinx.coroutines) from 1.6.4 to 1.7.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.4...1.7.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-08 21:14:34 +02:00
dependabot[bot]
b53ef758ec Bump ch.qos.logback:logback-classic from 1.4.5 to 1.4.9
Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.4.5 to 1.4.9.
- [Commits](https://github.com/qos-ch/logback/compare/v_1.4.5...v_1.4.9)

---
updated-dependencies:
- dependency-name: ch.qos.logback:logback-classic
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-08 21:00:20 +02:00
dependabot[bot]
5add03bf23 Bump org.junit.jupiter:junit-jupiter from 5.8.1 to 5.10.0
Bumps [org.junit.jupiter:junit-jupiter](https://github.com/junit-team/junit5) from 5.8.1 to 5.10.0.
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.8.1...r5.10.0)

---
updated-dependencies:
- dependency-name: org.junit.jupiter:junit-jupiter
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-08 20:04:53 +02:00
Quentin Hibon
b465ee588b Update dependabot.yml
Increase PR limit from 5 (default) to 10 for gradle
2023-08-08 19:48:04 +02:00
dependabot[bot]
b9253d41ea Bump daggerVersion from 2.46 to 2.47
Bumps `daggerVersion` from 2.46 to 2.47.

Updates `com.google.dagger:dagger` from 2.46 to 2.47
- [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.46...dagger-2.47)

Updates `com.google.dagger:dagger-compiler` from 2.46 to 2.47
- [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.46...dagger-2.47)

---
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>
2023-08-08 19:45:56 +02:00
dependabot[bot]
ca0a9dd85f Bump org.xerial:sqlite-jdbc from 3.40.0.0 to 3.42.0.0
Bumps [org.xerial:sqlite-jdbc](https://github.com/xerial/sqlite-jdbc) from 3.40.0.0 to 3.42.0.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.40.0.0...3.42.0.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>
2023-08-08 14:59:14 +02:00
dependabot[bot]
9951525cbe Bump com.opencsv:opencsv from 5.7.1 to 5.8
Bumps com.opencsv:opencsv from 5.7.1 to 5.8.

---
updated-dependencies:
- dependency-name: com.opencsv:opencsv
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-08 14:19:54 +02:00
dependabot[bot]
98b2c9cce2 Bump com.google.guava:guava from 31.1-android to 32.1.2-android
Bumps [com.google.guava:guava](https://github.com/google/guava) from 31.1-android to 32.1.2-android.
- [Release notes](https://github.com/google/guava/releases)
- [Commits](https://github.com/google/guava/commits)

---
updated-dependencies:
- dependency-name: com.google.guava:guava
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-08 14:19:42 +02:00
dependabot[bot]
19de2a2d1c Bump androidx.annotation:annotation from 1.5.0 to 1.6.0
Bumps androidx.annotation:annotation from 1.5.0 to 1.6.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>
2023-08-08 09:31:17 +02:00
Quentin Hibon
80f783b669 Bump mockito-kotlin from 2.2.11 to 4.1.0 2023-08-08 02:44:41 +02:00
dependabot[bot]
761fe59c5e Bump commons-codec:commons-codec from 1.15 to 1.16.0
Bumps [commons-codec:commons-codec](https://github.com/apache/commons-codec) from 1.15 to 1.16.0.
- [Changelog](https://github.com/apache/commons-codec/blob/master/RELEASE-NOTES.txt)
- [Commits](https://github.com/apache/commons-codec/compare/rel/commons-codec-1.15...rel/commons-codec-1.16.0)

---
updated-dependencies:
- dependency-name: commons-codec:commons-codec
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-08 01:35:57 +02:00
dependabot[bot]
f58d8a52ff Bump kxCoroutinesVersion from 1.6.4 to 1.7.3
Bumps `kxCoroutinesVersion` from 1.6.4 to 1.7.3.

Updates `org.jetbrains.kotlinx:kotlinx-coroutines-android` from 1.6.4 to 1.7.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.4...1.7.3)

Updates `org.jetbrains.kotlinx:kotlinx-coroutines-core` from 1.6.4 to 1.7.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.4...1.7.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-08 00:56:48 +02:00
dependabot[bot]
f4e5b68258 Bump org.apache.commons:commons-lang3 from 3.12.0 to 3.13.0
Bumps org.apache.commons:commons-lang3 from 3.12.0 to 3.13.0.

---
updated-dependencies:
- dependency-name: org.apache.commons:commons-lang3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-08 00:26:32 +02:00
dependabot[bot]
5df3ee0d61 Bump org.jlleitschuh.gradle.ktlint from 11.4.2 to 11.5.1
Bumps org.jlleitschuh.gradle.ktlint from 11.4.2 to 11.5.1.

---
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>
2023-08-07 23:32:18 +02:00
dependabot[bot]
9f66a27b82 Bump com.android.tools:desugar_jdk_libs from 1.2.2 to 2.0.3
Bumps [com.android.tools:desugar_jdk_libs](https://github.com/google/desugar_jdk_libs) from 1.2.2 to 2.0.3.
- [Release notes](https://github.com/google/desugar_jdk_libs/releases)
- [Changelog](https://github.com/google/desugar_jdk_libs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/desugar_jdk_libs/commits)

---
updated-dependencies:
- dependency-name: com.android.tools:desugar_jdk_libs
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-06 22:46:51 +02:00
Quentin Hibon
4bbb20e18e Remove android extensions (deprecated) 2023-08-03 22:55:05 +02:00
Quentin Hibon
4f9ab6d263 Migrate EditHabitActivity to view bindings 2023-08-03 22:55:05 +02:00
Quentin Hibon
af21fd25db Migrate FrequencyPickerDialog to view bindings 2023-08-03 22:55:05 +02:00
Quentin Hibon
35097e1263 Bump targetSdk to 32 2023-08-03 19:07:34 +02:00
Quentin Hibon
8e4274d923 Use assertThrows instead of ExpectedException 2023-08-03 19:07:04 +02:00
Quentin Hibon
12649141b1 Migrate junit.framework.Assert.assertEquals to kotlin.test.assertEquals
Also fix some warnings, e.g. shadowed variables.
2023-08-03 19:07:04 +02:00
Quentin Hibon
0526d37fbd Use official version of mockito-kotlin 2023-07-26 10:01:41 +02:00
Quentin Hibon
b083899ec8 Make AGP version explicit
This makes it possible to run the AGP upgrade assistant from Android Studio.
2023-07-26 09:47:02 +02:00
Quentin Hibon
6b793c7c16 Bump com.android.application from 7.3.1 to 7.4.2 2023-07-25 20:25:34 +02:00
Quentin Hibon
11ca993a75 Bump org.jlleitschuh.gradle.ktlint from 11.0.0 to 11.4.2
Also run ./gradlew ktlintFormat to follow new format.
2023-07-25 17:37:59 +02:00
Quentin Hibon
7348ddeffa Bump minSdk back to 28 from 23 (#1759)
Fixes #1758.
2023-07-10 18:06:59 -05:00
88df8d2552 Format source code 2023-07-08 17:13:45 -05:00
d4f4f8b4a9 Prevent crash if exact alarm permission is revoked 2023-06-05 20:25:00 -05:00
9ca1aa911a Minor layout fixes 2023-06-05 20:11:50 -05:00
ba57ebad31 Fix an invisible color in widgets; adjust another color 2023-06-05 20:01:41 -05:00
dependabot[bot]
e4d2c93a1d Bump actions/upload-artifact from 2 to 3 (#1724)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 2 to 3.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-04 18:40:58 -05:00
Ezio Melotti
07065a60ad Add "github-actions" ecosystem to dependabot.yml (#1716) 2023-06-04 18:37:35 -05:00
Jean-Baptiste
916cd76be1 Update checkout actions (#1715) 2023-06-04 18:35:08 -05:00
8b55ffb147 Fix timezone bug in MidnightTimer; increase logging 2023-06-04 18:25:33 -05:00
727e88b7b1 Fix skip button in locales that use comma instead of dot
Fixes #1721
2023-06-04 17:30:38 -05:00
209 changed files with 1223 additions and 730 deletions

View File

@@ -4,3 +4,8 @@ updates:
directory: "/"
schedule:
interval: "monthly"
open-pull-requests-limit: 10
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"

View File

@@ -12,17 +12,17 @@ jobs:
timeout-minutes: 30
steps:
- name: Check out source code
uses: actions/checkout@v1
uses: actions/checkout@v4
- name: Build project
run: ./build.sh build
- name: Run Android tests
run: ./build.sh android-tests-parallel 28 29 30 31 32 33
run: ./build.sh android-tests-parallel 28 29 30 32 33 34
- name: Upload artifacts
if: always()
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: build
path: |

1
.gitignore vendored
View File

@@ -17,3 +17,4 @@ node_modules
*xcuserdata*
*.sketch
crowdin.yml
kotlin-js-store

View File

@@ -1,5 +1,22 @@
# Changelog
## [2.2.0] -- 2024-01-30
### Added
- Add support for Android 14 (@iSoron, @hiqua)
- Allow user to change app language (@leondzn)
### Fixed
- Implement workaround to make notifications non-dismissible in Android 14 (@iSoron, #1872)
- Fix splash screen background color in dark mode (@SIKV, #1888)
## [2.1.3] -- 2023-08-28
### Fixed
- Use text input on Samsung devices (@iSoron, #1719)
- Prevent crash if alarm permission is revoked (@iSoron)
- Adjust widget colors (@iSoron)
- Fix bug preventing screens from updating at midnight (@iSoron)
- Fix skip button in locales that use comma instead of dot (@iSoron, #1721)
## [2.1.2] -- 2023-05-26
### Fixed
- Fix bug that caused widget to enter checkmark on wrong date (@iSoron, #1541)

View File

@@ -1,11 +1,10 @@
plugins {
val kotlinVersion = "1.7.21"
id("com.android.application") version ("7.3.1") apply (false)
val kotlinVersion = "1.9.22"
id("com.android.application") version "8.1.4" apply (false)
id("org.jetbrains.kotlin.android") 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.multiplatform") version kotlinVersion apply (false)
id("org.jlleitschuh.gradle.ktlint") version "11.0.0"
id("org.jlleitschuh.gradle.ktlint") version "11.6.1"
}
apply {

View File

@@ -181,7 +181,7 @@ android_test() {
OUT_INSTRUMENT=${ANDROID_OUTPUTS_DIR}/instrument-${API}.txt
OUT_LOGCAT=${ANDROID_OUTPUTS_DIR}/logcat-${API}.txt
FAILED_TESTS=""
for i in {1..5}; do
for i in {1..10}; do
log_info "Running $size instrumented tests (attempt $i)..."
$ADB shell am instrument \
-r -e coverage true -e size "$size" $FAILED_TESTS \

View File

@@ -3,3 +3,6 @@ org.gradle.daemon=true
org.gradle.jvmargs=-Xms2048m -Xmx2048m
android.useAndroidX=true
android.enableJetifier=true
android.defaults.buildfeatures.buildconfig=true
android.nonTransitiveRClass=false
android.nonFinalResIds=false

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -18,11 +18,10 @@
*/
plugins {
id("com.github.triplet.play") version "3.7.0"
id("com.android.application")
id("com.github.triplet.play") version "3.8.6"
id("com.android.application") version "8.1.4"
id("org.jetbrains.kotlin.android")
id("org.jetbrains.kotlin.kapt")
id("org.jetbrains.kotlin.android.extensions")
id("org.jlleitschuh.gradle.ktlint")
}
@@ -30,15 +29,27 @@ tasks.compileLint {
dependsOn("updateTranslators")
}
/*
Added on top of kotlinOptions to work around this issue:
https://youtrack.jetbrains.com/issue/KTIJ-24311/task-current-target-is-17-and-kaptGenerateStubsProductionDebugKotlin-task-current-target-is-1.8-jvm-target-compatibility-should#focus=Comments-27-6798448.0-0
Updating gradle might fix this, so try again in the future to remove this and run:
./gradlew --rerun-tasks :uhabits-android:kaptGenerateStubsReleaseKotlin
If this doesn't produce any warning, try to remove it.
*/
kotlin {
jvmToolchain(11)
}
android {
compileSdk = 32
namespace = "org.isoron.uhabits"
compileSdk = 34
defaultConfig {
versionCode = 20200
versionName = "2.2.0"
minSdk = 23
targetSdk = 31
minSdk = 28
targetSdk = 34
applicationId = "org.isoron.uhabits"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
@@ -70,8 +81,11 @@ android {
compileOptions {
isCoreLibraryDesugaringEnabled = true
targetCompatibility(JavaVersion.VERSION_1_8)
sourceCompatibility(JavaVersion.VERSION_1_8)
targetCompatibility(JavaVersion.VERSION_11)
sourceCompatibility(JavaVersion.VERSION_11)
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
}
buildFeatures {
@@ -80,9 +94,9 @@ android {
}
dependencies {
val daggerVersion = "2.46"
val kotlinVersion = "1.7.21"
val kxCoroutinesVersion = "1.6.4"
val daggerVersion = "2.51.1"
val kotlinVersion = "1.9.22"
val kxCoroutinesVersion = "1.7.3"
val ktorVersion = "1.6.8"
val espressoVersion = "3.5.1"
@@ -92,17 +106,17 @@ dependencies {
androidTestImplementation("com.linkedin.dexmaker:dexmaker-mockito:2.28.3")
androidTestImplementation("io.ktor:ktor-client-mock:$ktorVersion")
androidTestImplementation("io.ktor:ktor-jackson:$ktorVersion")
androidTestImplementation("androidx.annotation:annotation:1.5.0")
androidTestImplementation("androidx.annotation:annotation:1.7.1")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.uiautomator:uiautomator:2.2.0")
androidTestImplementation("androidx.test:rules:1.5.0")
androidTestImplementation("com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0")
androidTestImplementation("org.mockito.kotlin:mockito-kotlin:5.2.1")
compileOnly("javax.annotation:jsr250-api:1.0")
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.2.2")
implementation("com.github.AppIntro:AppIntro:6.2.0")
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.4")
implementation("com.github.AppIntro:AppIntro:6.3.1")
implementation("com.google.code.findbugs:jsr305:3.0.2")
implementation("com.google.dagger:dagger:$daggerVersion")
implementation("com.google.guava:guava:31.1-android")
implementation("com.google.guava:guava:33.1.0-android")
implementation("io.ktor:ktor-client-android:$ktorVersion")
implementation("io.ktor:ktor-client-core:$ktorVersion")
implementation("io.ktor:ktor-client-jackson:$ktorVersion")
@@ -110,17 +124,18 @@ dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$kxCoroutinesVersion")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kxCoroutinesVersion")
implementation("androidx.appcompat:appcompat:1.5.1")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("androidx.legacy:legacy-preference-v14:1.0.0")
implementation("androidx.legacy:legacy-support-v4:1.0.0")
implementation("com.google.android.material:material:1.8.0")
implementation("com.opencsv:opencsv:5.7.1")
implementation("com.google.android.material:material:1.11.0")
implementation("com.opencsv:opencsv:5.9")
implementation("nl.dionsegijn:konfetti-xml:2.0.2")
implementation(project(":uhabits-core"))
kapt("com.google.dagger:dagger-compiler:$daggerVersion")
kaptAndroidTest("com.google.dagger:dagger-compiler:$daggerVersion")
testImplementation("com.google.dagger:dagger:$daggerVersion")
testImplementation("junit:junit:4.13.2")
testImplementation("com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0")
testImplementation("org.mockito.kotlin:mockito-kotlin:5.2.1")
}
kapt {

View File

@@ -39,6 +39,7 @@ open class BaseUserInterfaceTest {
private lateinit var prefs: Preferences
private lateinit var fixtures: HabitFixtures
private lateinit var cache: HabitCardListCache
@Before
@Throws(Exception::class)
fun setUp() {

View File

@@ -153,18 +153,22 @@ open class BaseViewTest : BaseAndroidTest() {
var filename = filename
var dir = getSDCardDir("test-screenshots")
if (dir == null) dir = AndroidDirFinder(targetContext).getFilesDir("test-screenshots")
if (dir == null) throw RuntimeException(
"Could not find suitable dir for screenshots"
)
if (dir == null) {
throw RuntimeException(
"Could not find suitable dir for screenshots"
)
}
filename = filename.replace("\\.png$".toRegex(), "$suffix.png")
val absolutePath = String.format("%s/%s", dir.absolutePath, filename)
val parent = File(absolutePath).parentFile
if (!parent.exists() && !parent.mkdirs()) throw RuntimeException(
String.format(
"Could not create dir: %s",
parent.absolutePath
if (!parent.exists() && !parent.mkdirs()) {
throw RuntimeException(
String.format(
"Could not create dir: %s",
parent.absolutePath
)
)
)
}
val out = FileOutputStream(absolutePath)
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out)
return absolutePath

View File

@@ -19,7 +19,6 @@
package org.isoron.uhabits
import com.nhaarman.mockitokotlin2.mock
import dagger.Component
import dagger.Module
import dagger.Provides
@@ -35,6 +34,7 @@ import org.isoron.uhabits.inject.ActivityScope
import org.isoron.uhabits.inject.HabitModule
import org.isoron.uhabits.inject.HabitsActivityModule
import org.isoron.uhabits.inject.HabitsApplicationComponent
import org.mockito.kotlin.mock
@Module
class TestModule {

View File

@@ -20,7 +20,6 @@ package org.isoron.uhabits.acceptance.steps
import android.os.Build
import android.os.Build.VERSION.SDK_INT
import android.os.Build.VERSION_CODES
import androidx.annotation.StringRes
import androidx.recyclerview.widget.RecyclerView
import androidx.test.espresso.Espresso
@@ -33,11 +32,11 @@ import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiSelector
import androidx.test.uiautomator.Until
import junit.framework.Assert.assertTrue
import org.hamcrest.CoreMatchers
import org.isoron.uhabits.BaseUserInterfaceTest
import org.isoron.uhabits.R
import org.isoron.uhabits.activities.habits.list.ListHabitsActivity
import org.junit.Assert.assertTrue
object CommonSteps : BaseUserInterfaceTest() {
fun pressBack() {
@@ -133,7 +132,7 @@ object CommonSteps : BaseUserInterfaceTest() {
}
@Throws(Exception::class)
fun verifyOpensWebsite(url: String?) {
fun verifyOpensWebsite(url: String) {
var browserPkg = "org.chromium.webview_shell"
if (SDK_INT <= Build.VERSION_CODES.M) {
browserPkg = "com.android.browser"
@@ -148,15 +147,19 @@ object CommonSteps : BaseUserInterfaceTest() {
Screen.LIST_HABITS ->
Espresso.onView(ViewMatchers.withClassName(CoreMatchers.endsWith("ListHabitsRootView")))
.check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
Screen.SHOW_HABIT ->
Espresso.onView(ViewMatchers.withId(R.id.subtitleCard))
.check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
Screen.EDIT_HABIT ->
Espresso.onView(ViewMatchers.withId(R.id.questionInput))
.check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
Screen.SELECT_HABIT_TYPE ->
Espresso.onView(ViewMatchers.withText(R.string.yes_or_no_example))
.check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
else -> throw IllegalStateException()
}
}

View File

@@ -21,9 +21,9 @@ package org.isoron.uhabits.acceptance.steps
import android.os.Build.VERSION.SDK_INT
import androidx.test.uiautomator.UiScrollable
import androidx.test.uiautomator.UiSelector
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
import org.isoron.uhabits.BaseUserInterfaceTest
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
object WidgetSteps {
@Throws(Exception::class)

View File

@@ -18,6 +18,7 @@
*/
package org.isoron.uhabits.activities.common.views
import android.view.MotionEvent
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import org.isoron.uhabits.BaseViewTest
@@ -52,7 +53,8 @@ class FrequencyChartTest : BaseViewTest() {
@Test
@Throws(Throwable::class)
fun testRender_withDataOffset() {
view.onScroll(null, null, -dpToPixels(150), 0f)
val e = MotionEvent.obtain(0, 0, 0, 0f, 0f, 0)
view.onScroll(e, e, -dpToPixels(150), 0f)
view.invalidate()
assertRenders(view, BASE_PATH + "renderDataOffset.png")
}

View File

@@ -18,6 +18,7 @@
*/
package org.isoron.uhabits.activities.common.views
import android.view.MotionEvent
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import org.isoron.uhabits.BaseViewTest
@@ -44,7 +45,7 @@ class ScoreChartTest : BaseViewTest() {
habit = habit,
firstWeekday = prefs.firstWeekdayInt,
spinnerPosition = 0,
theme = LightTheme(),
theme = LightTheme()
)
view = ScoreChart(targetContext).apply {
setScores(state.scores)
@@ -63,7 +64,8 @@ class ScoreChartTest : BaseViewTest() {
@Test
@Throws(Throwable::class)
fun testRender_withDataOffset() {
view.onScroll(null, null, -dpToPixels(150), 0f)
val e = MotionEvent.obtain(0, 0, 0, 0f, 0f, 0)
view.onScroll(e, e, -dpToPixels(150), 0f)
view.invalidate()
assertRenders(view, BASE_PATH + "renderDataOffset.png")
}

View File

@@ -30,6 +30,7 @@ import org.junit.runner.RunWith
@MediumTest
class StreakChartTest : BaseViewTest() {
private lateinit var view: StreakChart
@Before
override fun setUp() {
super.setUp()

View File

@@ -20,15 +20,15 @@ package org.isoron.uhabits.activities.habits.list.views
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import com.nhaarman.mockitokotlin2.doReturn
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions
import com.nhaarman.mockitokotlin2.whenever
import org.isoron.uhabits.BaseViewTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyNoMoreInteractions
import org.mockito.kotlin.whenever
@RunWith(AndroidJUnit4::class)
@MediumTest

View File

@@ -20,9 +20,6 @@ package org.isoron.uhabits.activities.habits.list.views
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import com.nhaarman.mockitokotlin2.doReturn
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.whenever
import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.MatcherAssert.assertThat
import org.isoron.uhabits.BaseViewTest
@@ -30,6 +27,9 @@ import org.isoron.uhabits.core.ui.screens.habits.list.HintList
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
@RunWith(AndroidJUnit4::class)
@MediumTest

View File

@@ -48,7 +48,7 @@ class FrequencyCardViewTest : BaseViewTest() {
FrequencyCardPresenter.buildState(
habit = habit,
firstWeekday = 0,
theme = LightTheme(),
theme = LightTheme()
)
)
measureView(view, 800f, 600f)

View File

@@ -49,7 +49,7 @@ class HistoryCardViewTest : BaseViewTest() {
HistoryCardPresenter.buildState(
habit = habit,
firstWeekday = SUNDAY,
theme = LightTheme(),
theme = LightTheme()
)
)
measureView(view, 800f, 600f)

View File

@@ -51,7 +51,7 @@ class OverviewCardViewTest : BaseViewTest() {
scoreYearDiff = 0.74f,
totalCount = 44,
color = PaletteColor(7),
theme = LightTheme(),
theme = LightTheme()
)
)
measureView(view, 800f, 300f)

View File

@@ -49,7 +49,7 @@ class ScoreCardViewTest : BaseViewTest() {
habit = habit,
firstWeekday = 0,
spinnerPosition = 0,
theme = LightTheme(),
theme = LightTheme()
)
)
measureView(view, 800f, 600f)

View File

@@ -48,7 +48,7 @@ class StreakCardViewTest : BaseViewTest() {
StreakCardState(
bestStreaks = habit.streaks.getBest(10),
color = habit.color,
theme = LightTheme(),
theme = LightTheme()
)
)
measureView(view, 800f, 600f)

View File

@@ -53,7 +53,7 @@ class SubtitleCardViewTest : BaseViewTest() {
isNumerical = false,
question = "Did you meditate this morning?",
reminder = Reminder(8, 30, EVERY_DAY),
theme = LightTheme(),
theme = LightTheme()
)
)
measureView(view, 800f, 200f)

View File

@@ -119,7 +119,7 @@ class ListHabitsRegressionTest : BaseUserInterfaceTest() {
"Wake up early",
"Meditate",
"Read books",
"Track time",
"Track time"
)
}
}

View File

@@ -33,6 +33,7 @@ import java.io.IOException
@MediumTest
class CheckmarkWidgetViewTest : BaseViewTest() {
private lateinit var view: CheckmarkWidgetView
@Before
override fun setUp() {
super.setUp()

View File

@@ -16,12 +16,13 @@
~ You should have received a copy of the GNU General Public License along
~ with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.isoron.uhabits">
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
<uses-permission android:name="android.permission.VIBRATE" />
<application
android:name=".HabitsApplication"
@@ -29,6 +30,7 @@
android:backupAgent=".HabitsBackupAgent"
android:icon="@mipmap/ic_launcher"
android:label="@string/main_activity_title"
android:localeConfig="@xml/locales_config"
android:supportsRtl="true"
android:theme="@style/AppBaseTheme">

View File

@@ -22,7 +22,6 @@ import java.util.Locale;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.util.AttributeSet;

View File

@@ -23,14 +23,13 @@ import android.graphics.Paint.*;
import android.os.*;
import androidx.core.view.*;
import androidx.core.view.accessibility.*;
import androidx.core.widget.*;
import android.text.format.*;
import android.view.*;
import android.view.accessibility.*;
import androidx.customview.widget.ExploreByTouchHelper;
import com.android.*;
import com.android.datetimepicker.*;
import com.android.datetimepicker.date.MonthAdapter.*;

View File

@@ -23,7 +23,6 @@ import android.graphics.Paint.*;
import android.util.*;
import android.view.*;
import com.android.*;
import com.android.datetimepicker.*;
import org.isoron.uhabits.R;

View File

@@ -28,7 +28,6 @@ import android.view.View.*;
import android.view.accessibility.*;
import android.widget.*;
import com.android.*;
import com.android.datetimepicker.*;
import org.isoron.uhabits.R;

View File

@@ -60,7 +60,7 @@ class AndroidCanvas : Canvas {
y1.toDp(),
x2.toDp(),
y2.toDp(),
paint,
paint
)
}
@@ -69,7 +69,7 @@ class AndroidCanvas : Canvas {
text,
x.toDp(),
y.toDp() + 0.6f * mHeight,
textPaint,
textPaint
)
}
@@ -83,7 +83,7 @@ class AndroidCanvas : Canvas {
y: Double,
width: Double,
height: Double,
cornerRadius: Double,
cornerRadius: Double
) {
paint.style = Paint.Style.FILL
innerCanvas.drawRoundRect(
@@ -93,7 +93,7 @@ class AndroidCanvas : Canvas {
(y + height).toDp(),
cornerRadius.toDp(),
cornerRadius.toDp(),
paint,
paint
)
}
@@ -108,7 +108,7 @@ class AndroidCanvas : Canvas {
y.toDp(),
(x + width).toDp(),
(y + height).toDp(),
paint,
paint
)
}
@@ -148,7 +148,7 @@ class AndroidCanvas : Canvas {
centerY: Double,
radius: Double,
startAngle: Double,
swipeAngle: Double,
swipeAngle: Double
) {
paint.style = Paint.Style.FILL
innerCanvas.drawArc(
@@ -159,14 +159,14 @@ class AndroidCanvas : Canvas {
-startAngle.toFloat(),
-swipeAngle.toFloat(),
true,
paint,
paint
)
}
override fun fillCircle(
centerX: Double,
centerY: Double,
radius: Double,
radius: Double
) {
paint.style = Paint.Style.FILL
innerCanvas.drawCircle(centerX.toDp(), centerY.toDp(), radius.toDp(), paint)

View File

@@ -33,7 +33,7 @@ import kotlin.math.max
*/
class AndroidDataView(
context: Context,
attrs: AttributeSet? = null,
attrs: AttributeSet? = null
) : AndroidView<DataView>(context, attrs),
GestureDetector.OnGestureListener,
ValueAnimator.AnimatorUpdateListener {
@@ -44,23 +44,23 @@ class AndroidDataView(
addUpdateListener(this@AndroidDataView)
}
override fun onTouchEvent(event: MotionEvent?) = detector.onTouchEvent(event)
override fun onDown(e: MotionEvent?) = true
override fun onShowPress(e: MotionEvent?) = Unit
override fun onTouchEvent(event: MotionEvent) = detector.onTouchEvent(event)
override fun onDown(e: MotionEvent) = true
override fun onShowPress(e: MotionEvent) = Unit
override fun onSingleTapUp(e: MotionEvent?): Boolean {
override fun onSingleTapUp(e: MotionEvent): Boolean {
return handleClick(e, true)
}
override fun onLongPress(e: MotionEvent?) {
override fun onLongPress(e: MotionEvent) {
handleClick(e)
}
override fun onScroll(
e1: MotionEvent?,
e2: MotionEvent?,
e2: MotionEvent,
dx: Float,
dy: Float,
dy: Float
): Boolean {
if (abs(dx) > abs(dy)) {
val parent = parent
@@ -80,9 +80,9 @@ class AndroidDataView(
override fun onFling(
e1: MotionEvent?,
e2: MotionEvent?,
e2: MotionEvent,
velocityX: Float,
velocityY: Float,
velocityY: Float
): Boolean {
scroller.fling(
scroller.currX,
@@ -100,7 +100,7 @@ class AndroidDataView(
return false
}
override fun onAnimationUpdate(animation: ValueAnimator?) {
override fun onAnimationUpdate(animation: ValueAnimator) {
if (!scroller.isFinished) {
scroller.computeScrollOffset()
updateDataOffset()
@@ -127,11 +127,11 @@ class AndroidDataView(
}
}
private fun handleClick(e: MotionEvent?, isSingleTap: Boolean = false): Boolean {
private fun handleClick(e: MotionEvent, isSingleTap: Boolean = false): Boolean {
val x: Float
val y: Float
try {
val pointerId = e!!.getPointerId(0)
val pointerId = e.getPointerId(0)
x = e.getX(pointerId)
y = e.getY(pointerId)
} catch (ex: RuntimeException) {
@@ -140,8 +140,11 @@ class AndroidDataView(
// e.getPointerId.
return false
}
if (isSingleTap) view?.onClick(x / canvas.innerDensity, y / canvas.innerDensity)
else view?.onLongClick(x / canvas.innerDensity, y / canvas.innerDensity)
if (isSingleTap) {
view?.onClick(x / canvas.innerDensity, y / canvas.innerDensity)
} else {
view?.onLongClick(x / canvas.innerDensity, y / canvas.innerDensity)
}
return true
}
}

View File

@@ -47,6 +47,6 @@ fun Color.toInt(): Int {
(255 * this.alpha).roundToInt(),
(255 * this.red).roundToInt(),
(255 * this.green).roundToInt(),
(255 * this.blue).roundToInt(),
(255 * this.blue).roundToInt()
)
}

View File

@@ -24,7 +24,7 @@ import android.util.AttributeSet
open class AndroidView<T : View>(
context: Context,
attrs: AttributeSet? = null,
attrs: AttributeSet? = null
) : android.view.View(context, attrs) {
var view: T? = null

View File

@@ -39,7 +39,7 @@ import org.isoron.uhabits.inject.ActivityScope
class AndroidThemeSwitcher
constructor(
@ActivityContext val context: Context,
preferences: Preferences,
preferences: Preferences
) : ThemeSwitcher(preferences) {
override var currentTheme: Theme = LightTheme()

View File

@@ -27,7 +27,7 @@ import org.isoron.uhabits.utils.startActivitySafely
class AboutScreen(
private val activity: AboutActivity,
private val intents: IntentFactory,
private val prefs: Preferences,
private val prefs: Preferences
) {
private var developerCountdown = 5

View File

@@ -32,7 +32,7 @@ import org.isoron.uhabits.utils.setupToolbar
@SuppressLint("ViewConstructor")
class AboutView(
context: Context,
private val screen: AboutScreen,
private val screen: AboutScreen
) : FrameLayout(context) {
private var binding = AboutBinding.inflate(LayoutInflater.from(context))
@@ -43,7 +43,7 @@ class AboutView(
toolbar = binding.toolbar,
color = PaletteColor(11),
title = resources.getString(R.string.about),
theme = currentTheme(),
theme = currentTheme()
)
val version = resources.getString(R.string.version_n)
binding.tvContributors.setOnClickListener { screen.showCodeContributorsWebsite() }

View File

@@ -33,17 +33,19 @@ import org.isoron.uhabits.core.models.Entry.Companion.UNKNOWN
import org.isoron.uhabits.core.models.Entry.Companion.YES_MANUAL
import org.isoron.uhabits.databinding.CheckmarkPopupBinding
import org.isoron.uhabits.utils.InterfaceUtils.getFontAwesome
import org.isoron.uhabits.utils.getCenter
import org.isoron.uhabits.utils.sres
class CheckmarkDialog : AppCompatDialogFragment() {
var onToggle: (Int, String) -> Unit = { _, _ -> }
var onToggle: (Int, String, Float, Float) -> Unit = { _, _, _, _ -> }
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val appComponent = (requireActivity().application as HabitsApplication).component
val prefs = appComponent.preferences
val view = CheckmarkPopupBinding.inflate(LayoutInflater.from(context))
val color = requireArguments().getInt("color")
arrayOf(view.yesBtn, view.skipBtn).forEach {
it.setTextColor(requireArguments().getInt("color"))
it.setTextColor(color)
}
arrayOf(view.noBtn, view.unknownBtn).forEach {
it.setTextColor(view.root.sres.getColor(R.attr.contrast60))
@@ -62,7 +64,8 @@ class CheckmarkDialog : AppCompatDialogFragment() {
}
fun onClick(v: Int) {
val notes = view.notes.text.toString().trim()
onToggle(v, notes)
val location = view.yesBtn.getCenter()
onToggle(v, notes, location.x, location.y)
requireDialog().dismiss()
}
view.yesBtn.setOnClickListener { onClick(YES_MANUAL) }

View File

@@ -22,97 +22,101 @@ package org.isoron.uhabits.activities.common.dialogs
import android.app.Dialog
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.widget.EditText
import android.widget.LinearLayout
import android.widget.RadioButton
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatDialogFragment
import kotlinx.android.synthetic.main.frequency_picker_dialog.view.*
import org.isoron.uhabits.R
import org.isoron.uhabits.databinding.FrequencyPickerDialogBinding
class FrequencyPickerDialog(
var freqNumerator: Int,
var freqDenominator: Int
) : AppCompatDialogFragment() {
private var _binding: FrequencyPickerDialogBinding? = null
private val binding get() = _binding!!
lateinit var contentView: View
var onFrequencyPicked: (num: Int, den: Int) -> Unit = { _, _ -> }
constructor() : this(1, 1)
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val inflater = LayoutInflater.from(requireActivity())
contentView = inflater.inflate(R.layout.frequency_picker_dialog, null)
_binding = FrequencyPickerDialogBinding.inflate(LayoutInflater.from(requireActivity()))
addBeforeAfterText(
this.getString(R.string.every_x_days),
contentView.everyXDaysContainer,
binding.everyXDaysContainer
)
addBeforeAfterText(
this.getString(R.string.x_times_per_week),
contentView.xTimesPerWeekContainer,
binding.xTimesPerWeekContainer
)
addBeforeAfterText(
this.getString(R.string.x_times_per_month),
contentView.xTimesPerMonthContainer,
binding.xTimesPerMonthContainer
)
addBeforeAfterText(
this.getString(R.string.x_times_per_y_days),
contentView.xTimesPerYDaysContainer,
binding.xTimesPerYDaysContainer
)
contentView.everyDayRadioButton.setOnClickListener {
check(contentView.everyDayRadioButton)
binding.everyDayRadioButton.setOnClickListener {
check(binding.everyDayRadioButton)
}
contentView.everyXDaysRadioButton.setOnClickListener {
check(contentView.everyXDaysRadioButton)
val everyXDaysTextView = contentView.everyXDaysTextView
binding.everyXDaysRadioButton.setOnClickListener {
check(binding.everyXDaysRadioButton)
val everyXDaysTextView = binding.everyXDaysTextView
selectInputField(everyXDaysTextView)
}
contentView.everyXDaysTextView.setOnFocusChangeListener { v, hasFocus ->
if (hasFocus) check(contentView.everyXDaysRadioButton)
binding.everyXDaysTextView.setOnFocusChangeListener { v, hasFocus ->
if (hasFocus) check(binding.everyXDaysRadioButton)
}
contentView.xTimesPerWeekRadioButton.setOnClickListener {
check(contentView.xTimesPerWeekRadioButton)
selectInputField(contentView.xTimesPerWeekTextView)
binding.xTimesPerWeekRadioButton.setOnClickListener {
check(binding.xTimesPerWeekRadioButton)
selectInputField(binding.xTimesPerWeekTextView)
}
contentView.xTimesPerWeekTextView.setOnFocusChangeListener { v, hasFocus ->
if (hasFocus) check(contentView.xTimesPerWeekRadioButton)
binding.xTimesPerWeekTextView.setOnFocusChangeListener { v, hasFocus ->
if (hasFocus) check(binding.xTimesPerWeekRadioButton)
}
contentView.xTimesPerMonthRadioButton.setOnClickListener {
check(contentView.xTimesPerMonthRadioButton)
selectInputField(contentView.xTimesPerMonthTextView)
binding.xTimesPerMonthRadioButton.setOnClickListener {
check(binding.xTimesPerMonthRadioButton)
selectInputField(binding.xTimesPerMonthTextView)
}
contentView.xTimesPerMonthTextView.setOnFocusChangeListener { v, hasFocus ->
if (hasFocus) check(contentView.xTimesPerMonthRadioButton)
binding.xTimesPerMonthTextView.setOnFocusChangeListener { v, hasFocus ->
if (hasFocus) check(binding.xTimesPerMonthRadioButton)
}
contentView.xTimesPerYDaysRadioButton.setOnClickListener {
check(contentView.xTimesPerYDaysRadioButton)
selectInputField(contentView.xTimesPerYDaysXTextView)
binding.xTimesPerYDaysRadioButton.setOnClickListener {
check(binding.xTimesPerYDaysRadioButton)
selectInputField(binding.xTimesPerYDaysXTextView)
}
contentView.xTimesPerYDaysXTextView.setOnFocusChangeListener { v, hasFocus ->
if (hasFocus) check(contentView.xTimesPerYDaysRadioButton)
binding.xTimesPerYDaysXTextView.setOnFocusChangeListener { v, hasFocus ->
if (hasFocus) check(binding.xTimesPerYDaysRadioButton)
}
contentView.xTimesPerYDaysYTextView.setOnFocusChangeListener { v, hasFocus ->
if (hasFocus) check(contentView.xTimesPerYDaysRadioButton)
binding.xTimesPerYDaysYTextView.setOnFocusChangeListener { v, hasFocus ->
if (hasFocus) check(binding.xTimesPerYDaysRadioButton)
}
return AlertDialog.Builder(requireActivity())
.setView(contentView)
.setView(binding.root)
.setPositiveButton(R.string.save) { _, _ -> onSaveClicked() }
.create()
}
@@ -124,7 +128,8 @@ class FrequencyPickerDialog(
val parts = str.split("%d")
for (i in parts.indices) {
container.addView(
TextView(activity).apply { text = parts[i].trim() }, 2 * i + 1,
TextView(activity).apply { text = parts[i].trim() },
2 * i + 1
)
}
}
@@ -133,31 +138,35 @@ class FrequencyPickerDialog(
var numerator = 1
var denominator = 1
when {
contentView.everyDayRadioButton.isChecked -> {
binding.everyDayRadioButton.isChecked -> {
// NOP
}
contentView.everyXDaysRadioButton.isChecked -> {
if (contentView.everyXDaysTextView.text.isNotEmpty()) {
denominator = Integer.parseInt(contentView.everyXDaysTextView.text.toString())
binding.everyXDaysRadioButton.isChecked -> {
if (binding.everyXDaysTextView.text.isNotEmpty()) {
denominator = Integer.parseInt(binding.everyXDaysTextView.text.toString())
}
}
contentView.xTimesPerWeekRadioButton.isChecked -> {
if (contentView.xTimesPerWeekTextView.text.isNotEmpty()) {
numerator = Integer.parseInt(contentView.xTimesPerWeekTextView.text.toString())
binding.xTimesPerWeekRadioButton.isChecked -> {
if (binding.xTimesPerWeekTextView.text.isNotEmpty()) {
numerator = Integer.parseInt(binding.xTimesPerWeekTextView.text.toString())
denominator = 7
}
}
contentView.xTimesPerYDaysRadioButton.isChecked -> {
if (contentView.xTimesPerYDaysXTextView.text.isNotEmpty() && contentView.xTimesPerYDaysYTextView.text.isNotEmpty()) {
binding.xTimesPerYDaysRadioButton.isChecked -> {
if (binding.xTimesPerYDaysXTextView.text.isNotEmpty() && binding.xTimesPerYDaysYTextView.text.isNotEmpty()) {
numerator =
Integer.parseInt(contentView.xTimesPerYDaysXTextView.text.toString())
Integer.parseInt(binding.xTimesPerYDaysXTextView.text.toString())
denominator =
Integer.parseInt(contentView.xTimesPerYDaysYTextView.text.toString())
Integer.parseInt(binding.xTimesPerYDaysYTextView.text.toString())
}
}
else -> {
if (contentView.xTimesPerMonthTextView.text.isNotEmpty()) {
numerator = Integer.parseInt(contentView.xTimesPerMonthTextView.text.toString())
if (binding.xTimesPerMonthTextView.text.isNotEmpty()) {
numerator = Integer.parseInt(binding.xTimesPerMonthTextView.text.toString())
denominator = 30
}
}
@@ -184,27 +193,27 @@ class FrequencyPickerDialog(
private fun populateViews() {
uncheckAll()
if (freqDenominator == 30 || freqDenominator == 31) {
contentView.xTimesPerMonthRadioButton.isChecked = true
contentView.xTimesPerMonthTextView.setText(freqNumerator.toString())
selectInputField(contentView.xTimesPerMonthTextView)
binding.xTimesPerMonthRadioButton.isChecked = true
binding.xTimesPerMonthTextView.setText(freqNumerator.toString())
selectInputField(binding.xTimesPerMonthTextView)
} else {
if (freqNumerator == 1) {
if (freqDenominator == 1) {
contentView.everyDayRadioButton.isChecked = true
binding.everyDayRadioButton.isChecked = true
} else {
contentView.everyXDaysRadioButton.isChecked = true
contentView.everyXDaysTextView.setText(freqDenominator.toString())
selectInputField(contentView.everyXDaysTextView)
binding.everyXDaysRadioButton.isChecked = true
binding.everyXDaysTextView.setText(freqDenominator.toString())
selectInputField(binding.everyXDaysTextView)
}
} else {
if (freqDenominator == 7) {
contentView.xTimesPerWeekRadioButton.isChecked = true
contentView.xTimesPerWeekTextView.setText(freqNumerator.toString())
selectInputField(contentView.xTimesPerWeekTextView)
binding.xTimesPerWeekRadioButton.isChecked = true
binding.xTimesPerWeekTextView.setText(freqNumerator.toString())
selectInputField(binding.xTimesPerWeekTextView)
} else {
contentView.xTimesPerYDaysRadioButton.isChecked = true
contentView.xTimesPerYDaysXTextView.setText(freqNumerator.toString())
contentView.xTimesPerYDaysYTextView.setText(freqDenominator.toString())
binding.xTimesPerYDaysRadioButton.isChecked = true
binding.xTimesPerYDaysXTextView.setText(freqNumerator.toString())
binding.xTimesPerYDaysYTextView.setText(freqDenominator.toString())
}
}
}
@@ -215,10 +224,10 @@ class FrequencyPickerDialog(
}
private fun uncheckAll() {
contentView.everyDayRadioButton.isChecked = false
contentView.everyXDaysRadioButton.isChecked = false
contentView.xTimesPerWeekRadioButton.isChecked = false
contentView.xTimesPerMonthRadioButton.isChecked = false
contentView.xTimesPerYDaysRadioButton.isChecked = false
binding.everyDayRadioButton.isChecked = false
binding.everyXDaysRadioButton.isChecked = false
binding.xTimesPerWeekRadioButton.isChecked = false
binding.xTimesPerMonthRadioButton.isChecked = false
binding.xTimesPerYDaysRadioButton.isChecked = false
}
}

View File

@@ -69,7 +69,7 @@ class HistoryEditorDialog : AppCompatDialogFragment(), CommandRunner.Listener {
theme = themeSwitcher.currentTheme,
today = DateUtils.getTodayWithOffset().toLocalDate(),
onDateClickedListener = onDateClickedListener ?: object : OnDateClickedListener {},
padding = 10.0,
padding = 10.0
)
dataView = AndroidDataView(requireContext(), null)
dataView.view = chart!!

View File

@@ -2,17 +2,20 @@ package org.isoron.uhabits.activities.common.dialogs
import android.app.Dialog
import android.os.Bundle
import android.provider.Settings
import android.text.method.DigitsKeyListener
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.inputmethod.EditorInfo
import androidx.appcompat.app.AppCompatDialogFragment
import org.isoron.uhabits.HabitsApplication
import org.isoron.uhabits.R
import org.isoron.uhabits.core.models.Entry
import org.isoron.uhabits.databinding.CheckmarkPopupBinding
import org.isoron.uhabits.utils.InterfaceUtils
import org.isoron.uhabits.utils.getCenter
import org.isoron.uhabits.utils.requestFocusWithKeyboard
import org.isoron.uhabits.utils.sres
import java.text.DecimalFormat
@@ -22,7 +25,7 @@ import java.text.ParseException
class NumberDialog : AppCompatDialogFragment() {
var onToggle: (Double, String) -> Unit = { _, _ -> }
var onToggle: (Double, String, Float, Float) -> Unit = { _, _, _, _ -> }
var onDismiss: () -> Unit = {}
private var originalNotes: String = ""
@@ -65,7 +68,7 @@ class NumberDialog : AppCompatDialogFragment() {
save()
}
view.skipBtnNumber.setOnClickListener {
view.value.setText((Entry.SKIP.toDouble() / 1000).toString())
view.value.setText(DecimalFormat("#.###").format((Entry.SKIP.toDouble() / 1000)))
save()
}
view.notes.setOnEditorActionListener { v, actionId, event ->
@@ -86,6 +89,15 @@ class NumberDialog : AppCompatDialogFragment() {
// https://stackoverflow.com/a/34256139
val separator = DecimalFormatSymbols.getInstance().decimalSeparator
view.value.keyListener = DigitsKeyListener.getInstance("0123456789$separator")
// https://github.com/flutter/flutter/issues/61175
val currKeyboard = Settings.Secure.getString(
requireContext().contentResolver,
Settings.Secure.DEFAULT_INPUT_METHOD
)
if (currKeyboard.contains("swiftkey") || currKeyboard.contains("samsung")) {
view.value.inputType = EditorInfo.TYPE_CLASS_TEXT
}
}
fun save() {
@@ -93,12 +105,17 @@ class NumberDialog : AppCompatDialogFragment() {
try {
val numberFormat = NumberFormat.getInstance()
val valueStr = view.value.text.toString()
value = numberFormat.parse(valueStr)!!.toDouble()
value = if (valueStr.isNotEmpty()) {
numberFormat.parse(valueStr)!!.toDouble()
} else {
Entry.UNKNOWN.toDouble() / 1000
}
} catch (e: ParseException) {
// NOP
}
val notes = view.notes.text.toString()
onToggle(value, notes)
val location = view.saveBtn.getCenter()
onToggle(value, notes, location.x, location.y)
requireDialog().dismiss()
}
}

View File

@@ -181,8 +181,9 @@ class FrequencyChart : ScrollableChart {
rect[0f, 0f, baseSize.toFloat()] = baseSize.toFloat()
rect.offset(prevRect!!.left, prevRect!!.top + baseSize * j)
val i = localeWeekdayList[j] % 7
if (values != null)
if (values != null) {
drawMarker(canvas, rect, values[i], weekDaysInMonth[i])
}
rect.offset(0f, rowHeight)
}
drawFooter(canvas, rect, date)
@@ -196,12 +197,14 @@ class FrequencyChart : ScrollableChart {
rect.centerY() - 0.1f * em,
pText!!
)
if (date[Calendar.MONTH] == 1) canvas.drawText(
dfYear!!.format(time),
rect.centerX(),
rect.centerY() + 0.9f * em,
pText!!
)
if (date[Calendar.MONTH] == 1) {
canvas.drawText(
dfYear!!.format(time),
rect.centerX(),
rect.centerY() + 0.9f * em,
pText!!
)
}
}
private fun drawGrid(canvas: Canvas, rGrid: RectF?) {

View File

@@ -58,6 +58,7 @@ class RingView : View {
private var em = 0f
private var text: String?
private var textSize: Float
private var isStrokedTextEnabled: Boolean = false
private var enableFontAwesome = false
private var internalDrawingCache: Bitmap? = null
private var cacheCanvas: Canvas? = null
@@ -131,6 +132,10 @@ class RingView : View {
invalidate()
}
fun setIsStrokedTextEnabled(isStroked: Boolean) {
this.isStrokedTextEnabled = isStroked
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val activeCanvas: Canvas?
@@ -148,13 +153,23 @@ class RingView : View {
pRing!!.color = inactiveColor!!
activeCanvas.drawArc(rect!!, angle - 90, 360 - angle, true, pRing!!)
if (thickness > 0) {
if (isTransparencyEnabled) pRing!!.xfermode = XFERMODE_CLEAR else pRing!!.color =
backgroundColor!!
if (isTransparencyEnabled) {
pRing!!.xfermode = XFERMODE_CLEAR
} else {
pRing!!.color =
backgroundColor!!
}
rect!!.inset(thickness, thickness)
activeCanvas.drawArc(rect!!, 0f, 360f, true, pRing!!)
pRing!!.xfermode = null
pRing!!.color = color
pRing!!.textSize = textSize
if (isStrokedTextEnabled) {
pRing!!.style = Paint.Style.STROKE
pRing!!.strokeWidth = textSize / 15f
}
if (enableFontAwesome) pRing!!.typeface = getFontAwesome(context)
activeCanvas.drawText(
text!!,

View File

@@ -65,7 +65,7 @@ abstract class ScrollableChart : View, GestureDetector.OnGestureListener, Animat
}
override fun onFling(
e1: MotionEvent,
e1: MotionEvent?,
e2: MotionEvent,
velocityX: Float,
velocityY: Float
@@ -116,7 +116,7 @@ abstract class ScrollableChart : View, GestureDetector.OnGestureListener, Animat
return BundleSavedState(superState, bundle)
}
override fun onScroll(e1: MotionEvent?, e2: MotionEvent?, dx: Float, dy: Float): Boolean {
override fun onScroll(e1: MotionEvent?, e2: MotionEvent, dx: Float, dy: Float): Boolean {
var dx = dx
if (scrollerBucketSize == 0) return false
if (abs(dx) > abs(dy)) {

View File

@@ -35,11 +35,6 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.DialogFragment
import com.android.datetimepicker.time.RadialPickerLayout
import com.android.datetimepicker.time.TimePickerDialog
import kotlinx.android.synthetic.main.activity_edit_habit.nameInput
import kotlinx.android.synthetic.main.activity_edit_habit.notesInput
import kotlinx.android.synthetic.main.activity_edit_habit.questionInput
import kotlinx.android.synthetic.main.activity_edit_habit.targetInput
import kotlinx.android.synthetic.main.activity_edit_habit.unitInput
import org.isoron.platform.gui.toInt
import org.isoron.uhabits.HabitsApplication
import org.isoron.uhabits.R
@@ -271,9 +266,9 @@ class EditHabitActivity : AppCompatActivity() {
habit.copyFrom(original)
}
habit.name = nameInput.text.trim().toString()
habit.question = questionInput.text.trim().toString()
habit.description = notesInput.text.trim().toString()
habit.name = binding.nameInput.text.trim().toString()
habit.question = binding.questionInput.text.trim().toString()
habit.description = binding.notesInput.text.trim().toString()
habit.color = color
if (reminderHour >= 0) {
habit.reminder = Reminder(reminderHour, reminderMin, reminderDays)
@@ -283,9 +278,9 @@ class EditHabitActivity : AppCompatActivity() {
habit.frequency = Frequency(freqNum, freqDen)
if (habitType == HabitType.NUMERICAL) {
habit.targetValue = targetInput.text.toString().toDouble()
habit.targetValue = binding.targetInput.text.toString().toDouble()
habit.targetType = targetType
habit.unit = unitInput.text.trim().toString()
habit.unit = binding.unitInput.text.trim().toString()
}
habit.type = habitType
@@ -308,13 +303,13 @@ class EditHabitActivity : AppCompatActivity() {
private fun validate(): Boolean {
var isValid = true
if (nameInput.text.isEmpty()) {
nameInput.error = getFormattedValidationError(R.string.validation_cannot_be_blank)
if (binding.nameInput.text.isEmpty()) {
binding.nameInput.error = getFormattedValidationError(R.string.validation_cannot_be_blank)
isValid = false
}
if (habitType == HabitType.NUMERICAL) {
if (targetInput.text.isEmpty()) {
targetInput.error = getString(R.string.validation_cannot_be_blank)
if (binding.targetInput.text.isEmpty()) {
binding.targetInput.error = getString(R.string.validation_cannot_be_blank)
isValid = false
}
}

View File

@@ -19,12 +19,17 @@
package org.isoron.uhabits.activities.habits.list
import android.Manifest.permission.POST_NOTIFICATIONS
import android.content.Intent
import android.content.pm.PackageManager.PERMISSION_GRANTED
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat.checkSelfPermission
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import org.isoron.uhabits.BaseExceptionHandler
@@ -56,6 +61,16 @@ class ListHabitsActivity : AppCompatActivity(), Preferences.Listener {
lateinit var midnightTimer: MidnightTimer
private val scope = CoroutineScope(Dispatchers.Main)
private var permissionAlreadyRequested = false
private val permissionLauncher =
registerForActivityResult(RequestPermission()) { isGranted: Boolean ->
if (isGranted) {
scheduleReminders()
} else {
Log.i("ListHabitsActivity", "POST_NOTIFICATIONS denied")
}
}
private lateinit var menu: ListHabitsMenu
override fun onQuestionMarksChanged() {
@@ -101,7 +116,26 @@ class ListHabitsActivity : AppCompatActivity(), Preferences.Listener {
screen.onAttached()
rootView.postInvalidate()
midnightTimer.onResume()
appComponent.reminderScheduler.scheduleAll()
if (appComponent.reminderScheduler.hasHabitsWithReminders()) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
scheduleReminders()
} else {
if (checkSelfPermission(this, POST_NOTIFICATIONS) == PERMISSION_GRANTED) {
scheduleReminders()
} else {
// If we have not requested the permission yet, request it. Otherwide do
// nothing. This check is necessary to avoid an infinite onResume loop in case
// the user denies the permission.
if (!permissionAlreadyRequested) {
Log.i("ListHabitsActivity", "Requestion permission: POST_NOTIFICATIONS")
permissionLauncher.launch(POST_NOTIFICATIONS)
permissionAlreadyRequested = true
}
}
}
}
taskRunner.run {
try {
AutoBackup(this@ListHabitsActivity).run()
@@ -117,6 +151,10 @@ class ListHabitsActivity : AppCompatActivity(), Preferences.Listener {
super.onResume()
}
private fun scheduleReminders() {
appComponent.reminderScheduler.scheduleAll()
}
override fun onCreateOptionsMenu(m: Menu): Boolean {
menu.onCreate(menuInflater, m)
return true
@@ -127,6 +165,7 @@ class ListHabitsActivity : AppCompatActivity(), Preferences.Listener {
return menu.onItemSelected(item)
}
@Deprecated("Deprecated in Java")
override fun onActivityResult(request: Int, result: Int, data: Intent?) {
super.onActivityResult(request, result, data)
screen.onResult(request, result, data)

View File

@@ -23,6 +23,7 @@ import android.content.Context
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.widget.FrameLayout
import android.widget.RelativeLayout
import nl.dionsegijn.konfetti.xml.KonfettiView
import org.isoron.uhabits.R
import org.isoron.uhabits.activities.common.views.ScrollableChart
import org.isoron.uhabits.activities.common.views.TaskProgressBar
@@ -69,6 +70,9 @@ class ListHabitsRootView @Inject constructor(
val listView: HabitCardListView = habitCardListViewFactory.create()
val llEmpty = EmptyListView(context)
val tbar = buildToolbar()
val konfettiView = KonfettiView(context).apply {
translationZ = 10f
}
val progressBar = TaskProgressBar(context, runner)
val hintView: HintView
val header = HeaderView(context, preferences, midnightTimer)
@@ -80,6 +84,7 @@ class ListHabitsRootView @Inject constructor(
val rootView = RelativeLayout(context).apply {
background = sres.getDrawable(R.attr.windowBackgroundColor)
addAtTop(konfettiView)
addAtTop(tbar)
addBelow(header, tbar)
addBelow(listView, header, height = MATCH_PARENT)
@@ -94,7 +99,7 @@ class ListHabitsRootView @Inject constructor(
title = resources.getString(R.string.main_activity_title),
color = PaletteColor(17),
displayHomeAsUpEnabled = false,
theme = currentTheme(),
theme = currentTheme()
)
addView(rootView, MATCH_PARENT, MATCH_PARENT)
listAdapter.setListView(listView)

View File

@@ -25,6 +25,9 @@ import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import dagger.Lazy
import nl.dionsegijn.konfetti.core.Party
import nl.dionsegijn.konfetti.core.Position
import nl.dionsegijn.konfetti.core.emitter.Emitter
import org.isoron.platform.gui.toInt
import org.isoron.uhabits.R
import org.isoron.uhabits.activities.common.dialogs.CheckmarkDialog
@@ -63,6 +66,7 @@ import org.isoron.uhabits.intents.IntentFactory
import org.isoron.uhabits.tasks.ExportDBTaskFactory
import org.isoron.uhabits.tasks.ImportDataTask
import org.isoron.uhabits.tasks.ImportDataTaskFactory
import org.isoron.uhabits.utils.ColorUtils
import org.isoron.uhabits.utils.copyTo
import org.isoron.uhabits.utils.currentTheme
import org.isoron.uhabits.utils.dismissCurrentAndShow
@@ -72,6 +76,7 @@ import org.isoron.uhabits.utils.showSendEmailScreen
import org.isoron.uhabits.utils.showSendFileScreen
import java.io.File
import java.io.IOException
import java.util.concurrent.TimeUnit
import javax.inject.Inject
const val RESULT_IMPORT_DATA = 101
@@ -96,7 +101,7 @@ class ListHabitsScreen
private val colorPickerFactory: ColorPickerDialogFactory,
private val behavior: Lazy<ListHabitsBehavior>,
private val preferences: Preferences,
private val rootView: Lazy<ListHabitsRootView>,
private val rootView: Lazy<ListHabitsRootView>
) : CommandRunner.Listener,
ListHabitsBehavior.Screen,
ListHabitsMenuBehavior.Screen,
@@ -218,6 +223,28 @@ class ListHabitsScreen
activity.showSendFileScreen(filename)
}
override fun showConfetti(color: PaletteColor, x: Float, y: Float) {
val baseColor = themeSwitcher.currentTheme!!.color(color).toInt()
rootView.get().konfettiView.start(
Party(
speed = 0f,
maxSpeed = 16f,
damping = 0.9f,
spread = 360,
angle = 0,
colors = listOf(
ColorUtils.changeHue(baseColor, 180f),
ColorUtils.changeHue(baseColor, 20f),
ColorUtils.changeHue(baseColor, -20f),
baseColor
),
position = Position.Absolute(x, y),
emitter = Emitter(duration = 25, TimeUnit.MILLISECONDS).max(25),
timeToLive = 0
)
)
}
override fun showSettingsScreen() {
val intent = intentFactory.startSettingsActivity(activity)
activity.startActivityForResult(intent, REQUEST_SETTINGS)
@@ -240,7 +267,7 @@ class ListHabitsScreen
putDouble("value", value)
putString("notes", notes)
}
dialog.onToggle = { v, n -> callback.onNumberPicked(v, n) }
dialog.onToggle = { v, n, x, y -> callback.onNumberPicked(v, n, x, y) }
dialog.dismissCurrentAndShow(fm, "numberDialog")
}
@@ -258,7 +285,7 @@ class ListHabitsScreen
putInt("value", selectedValue)
putString("notes", notes)
}
dialog.onToggle = { v, n -> callback.onNotesSaved(v, n) }
dialog.onToggle = { v, n, x, y -> callback.onNotesSaved(v, n, x, y) }
dialog.dismissCurrentAndShow(fm, "checkmarkDialog")
}
@@ -321,8 +348,11 @@ class ListHabitsScreen
private fun onExportDB() {
taskRunner.execute(
exportDBFactory.create { filename ->
if (filename != null) activity.showSendFileScreen(filename)
else activity.showMessage(activity.resources.getString(R.string.could_not_export))
if (filename != null) {
activity.showSendFileScreen(filename)
} else {
activity.showMessage(activity.resources.getString(R.string.could_not_export))
}
}
)
}

View File

@@ -60,8 +60,11 @@ abstract class ButtonPanelView<T : View>(
repeat(buttonCount) { buttons.add(createButton()) }
removeAllViews()
if (reverse) buttons.reversed().forEach { addView(it) }
else buttons.forEach { addView(it) }
if (reverse) {
buttons.reversed().forEach { addView(it) }
} else {
buttons.forEach { addView(it) }
}
setupButtons()
requestLayout()
}

View File

@@ -100,13 +100,19 @@ class CheckmarkButtonView(
}
override fun onClick(v: View) {
if (preferences.isShortToggleEnabled) performToggle()
else onEdit()
if (preferences.isShortToggleEnabled) {
performToggle()
} else {
onEdit()
}
}
override fun onLongClick(v: View): Boolean {
if (preferences.isShortToggleEnabled) onEdit()
else performToggle()
if (preferences.isShortToggleEnabled) {
onEdit()
} else {
performToggle()
}
return true
}
@@ -140,8 +146,11 @@ class CheckmarkButtonView(
paint.color = when (value) {
YES_MANUAL, YES_AUTO, SKIP -> color
NO -> {
if (preferences.areQuestionMarksEnabled) mediumContrastColor
else lowContrastColor
if (preferences.areQuestionMarksEnabled) {
mediumContrastColor
} else {
lowContrastColor
}
}
else -> lowContrastColor
}
@@ -149,8 +158,11 @@ class CheckmarkButtonView(
SKIP -> R.string.fa_skipped
NO -> R.string.fa_times
UNKNOWN -> {
if (preferences.areQuestionMarksEnabled) R.string.fa_question
else R.string.fa_times
if (preferences.areQuestionMarksEnabled) {
R.string.fa_question
} else {
R.string.fa_times
}
}
else -> R.string.fa_check
}

View File

@@ -156,10 +156,11 @@ class HabitCardListController @Inject constructor(
}
private fun notifyListener() {
if (activeMode is SelectionMode)
if (activeMode is SelectionMode) {
selectionMenu.get().onSelectionChange()
else
} else {
selectionMenu.get().onSelectionFinish()
}
}
}
}

View File

@@ -20,6 +20,7 @@
package org.isoron.uhabits.activities.habits.list.views
import android.content.Context
import android.graphics.PointF
import android.graphics.text.LineBreaker.BREAK_STRATEGY_BALANCED
import android.os.Build
import android.os.Build.VERSION.SDK_INT
@@ -154,7 +155,17 @@ class HabitCardView(
checkmarkPanel = checkmarkPanelFactory.create().apply {
onToggle = { timestamp, value, notes ->
triggerRipple(timestamp)
habit?.let { behavior.onToggle(it, timestamp, value, notes) }
val location = getAbsoluteButtonLocation(timestamp)
habit?.let {
behavior.onToggle(
it,
timestamp,
value,
notes,
location.x,
location.y
)
}
}
onEdit = { timestamp ->
triggerRipple(timestamp)
@@ -206,12 +217,27 @@ class HabitCardView(
}
fun triggerRipple(timestamp: Timestamp) {
val location = getRelativeButtonLocation(timestamp)
triggerRipple(location.x, location.y)
}
private fun getRelativeButtonLocation(timestamp: Timestamp): PointF {
val today = DateUtils.getTodayWithOffset()
val offset = timestamp.daysUntil(today) - dataOffset
val button = checkmarkPanel.buttons[offset]
val y = button.height / 2.0f
val x = checkmarkPanel.x + button.x + (button.width / 2).toFloat()
triggerRipple(x, y)
return PointF(x, y)
}
private fun getAbsoluteButtonLocation(timestamp: Timestamp): PointF {
val containerLocation = IntArray(2)
this.getLocationOnScreen(containerLocation)
val relButtonLocation = getRelativeButtonLocation(timestamp)
return PointF(
containerLocation[0].toFloat() + relButtonLocation.x,
containerLocation[1].toFloat() - relButtonLocation.y
)
}
override fun onAttachedToWindow() {
@@ -225,7 +251,6 @@ class HabitCardView(
}
private fun copyAttributesFrom(h: Habit) {
fun getActiveColor(habit: Habit): Int {
return when (habit.isArchived) {
true -> sres.getColor(R.attr.contrast60)
@@ -271,7 +296,6 @@ class HabitCardView(
}
private fun updateBackground(isSelected: Boolean) {
val background = when (isSelected) {
true -> R.drawable.selected_box
false -> R.drawable.ripple

View File

@@ -124,15 +124,20 @@ class HeaderView(
rect.set(0f, 0f, width, height)
rect.offset(canvas.width.toFloat() - dp(3.0f), 0f)
if (isReversed) rect.offset(-(index + 1) * width, 0f)
else rect.offset((index - buttonCount) * width, 0f)
if (isReversed) {
rect.offset(-(index + 1) * width, 0f)
} else {
rect.offset((index - buttonCount) * width, 0f)
}
if (isRTL()) rect.set(
canvas.width - rect.right,
rect.top,
canvas.width - rect.left,
rect.bottom
)
if (isRTL()) {
rect.set(
canvas.width - rect.right,
rect.top,
canvas.width - rect.left,
rect.bottom
)
}
val y1 = rect.centerY() - 0.25 * em
val y2 = rect.centerY() + 1.25 * em

View File

@@ -88,7 +88,7 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
habit = habit,
habitList = habitList,
preferences = preferences,
screen = screen,
screen = screen
)
view = ShowHabitView(this)
@@ -99,13 +99,13 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
habitList = habitList,
screen = screen,
system = HabitsDirFinder(AndroidDirFinder(this)),
taskRunner = appComponent.taskRunner,
taskRunner = appComponent.taskRunner
)
menu = ShowHabitMenu(
activity = this,
presenter = menuPresenter,
preferences = preferences,
preferences = preferences
)
view.setListener(presenter)
@@ -150,7 +150,7 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
ShowHabitPresenter.buildState(
habit = habit,
preferences = preferences,
theme = themeSwitcher.currentTheme,
theme = themeSwitcher.currentTheme
)
)
}
@@ -179,7 +179,7 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
putDouble("value", value)
putString("notes", notes)
}
dialog.onToggle = { v, n -> callback.onNumberPicked(v, n) }
dialog.onToggle = { v, n, x, y -> callback.onNumberPicked(v, n, x, y) }
dialog.dismissCurrentAndShow(supportFragmentManager, "numberDialog")
}
@@ -196,7 +196,7 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
putInt("value", selectedValue)
putString("notes", notes)
}
dialog.onToggle = { v, n -> callback.onNotesSaved(v, n) }
dialog.onToggle = { v, n, x, y -> callback.onNotesSaved(v, n, x, y) }
dialog.dismissCurrentAndShow(supportFragmentManager, "checkmarkDialog")
}

View File

@@ -28,7 +28,7 @@ import org.isoron.uhabits.core.ui.screens.habits.show.ShowHabitMenuPresenter
class ShowHabitMenu(
val activity: ShowHabitActivity,
val presenter: ShowHabitMenuPresenter,
val preferences: Preferences,
val preferences: Preferences
) {
fun onCreateOptionsMenu(menu: Menu): Boolean {
activity.menuInflater.inflate(R.menu.show_habit, menu)

View File

@@ -39,7 +39,7 @@ class ShowHabitView(context: Context) : FrameLayout(context) {
binding.toolbar,
title = data.title,
color = data.color,
theme = data.theme,
theme = data.theme
)
binding.subtitleCard.setState(data.subtitle)
binding.overviewCard.setState(data.overview)

View File

@@ -38,7 +38,7 @@ class BarCardView(context: Context, attrs: AttributeSet) : LinearLayout(context,
fun setState(state: BarCardState) {
val androidColor = state.theme.color(state.color).toInt()
binding.chart.view = BarChart(state.theme, JavaLocalDateFormatter(Locale.US)).apply {
binding.chart.view = BarChart(state.theme, JavaLocalDateFormatter(Locale.getDefault())).apply {
series = mutableListOf(state.entries.map { it.value / 1000.0 })
colors = mutableListOf(theme.color(state.color.paletteIndex))
axis = state.entries.map { it.timestamp.toLocalDate() }
@@ -63,7 +63,7 @@ class BarCardView(context: Context, attrs: AttributeSet) : LinearLayout(context,
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long,
id: Long
) {
presenter.onBoolSpinnerPosition(position)
}
@@ -77,7 +77,7 @@ class BarCardView(context: Context, attrs: AttributeSet) : LinearLayout(context,
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long,
id: Long
) {
presenter.onNumericalSpinnerPosition(position)
}

View File

@@ -45,7 +45,7 @@ class HistoryCardView(context: Context, attrs: AttributeSet) : LinearLayout(cont
series = state.series,
defaultSquare = state.defaultSquare,
notesIndicators = state.notesIndicators,
firstWeekday = state.firstWeekday,
firstWeekday = state.firstWeekday
)
binding.chart.postInvalidate()
}

View File

@@ -52,7 +52,7 @@ class SubtitleCardView(context: Context, attrs: AttributeSet) : LinearLayout(con
binding.frequencyLabel.text = formatFrequency(
state.frequency.numerator,
state.frequency.denominator,
resources,
resources
)
binding.questionLabel.setTextColor(color)
binding.questionLabel.text = state.question

View File

@@ -40,7 +40,7 @@ class SettingsActivity : AppCompatActivity() {
toolbar = binding.toolbar,
title = resources.getString(R.string.settings),
color = PaletteColor(11),
theme = themeSwitcher.currentTheme,
theme = themeSwitcher.currentTheme
)
setContentView(binding.root)
}

View File

@@ -53,6 +53,8 @@ class SettingsFragment : PreferenceFragmentCompat(), OnSharedPreferenceChangeLis
private var ringtoneManager: RingtoneManager? = null
private lateinit var prefs: Preferences
private var widgetUpdater: WidgetUpdater? = null
@Deprecated("Deprecated in Java")
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == RINGTONE_REQUEST_CODE) {
ringtoneManager!!.update(data)

View File

@@ -32,7 +32,7 @@ class EditSettingActivity : AppCompatActivity() {
val habits = app.component.habitList.getFiltered(
HabitMatcher(
isArchivedAllowed = false,
isCompletedAllowed = true,
isCompletedAllowed = true
)
)
AndroidThemeSwitcher(this, app.component.preferences).apply()
@@ -43,7 +43,7 @@ class EditSettingActivity : AppCompatActivity() {
context = this,
habitList = app.component.habitList,
onSave = controller::onSave,
args = args,
args = args
)
setContentView(view)
}

View File

@@ -54,7 +54,7 @@ class EditSettingRootView(
title = resources.getString(R.string.app_name),
color = PaletteColor(11),
displayHomeAsUpEnabled = false,
theme = currentTheme(),
theme = currentTheme()
)
populateHabitSpinner()
binding.habitSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
@@ -69,7 +69,7 @@ class EditSettingRootView(
val habit = habitList.getByPosition(binding.habitSpinner.selectedItemPosition)
val action = mapSpinnerPositionToAction(
isNumerical = habit.isNumerical,
itemPosition = binding.actionSpinner.selectedItemPosition,
itemPosition = binding.actionSpinner.selectedItemPosition
)
onSave(habit, action)
}

View File

@@ -27,22 +27,34 @@ class AndroidCursor(private val cursor: android.database.Cursor) : Cursor {
override fun moveToNext() = cursor.moveToNext()
override fun getInt(index: Int): Int? {
return if (cursor.isNull(index)) null
else cursor.getInt(index)
return if (cursor.isNull(index)) {
null
} else {
cursor.getInt(index)
}
}
override fun getLong(index: Int): Long? {
return if (cursor.isNull(index)) null
else cursor.getLong(index)
return if (cursor.isNull(index)) {
null
} else {
cursor.getLong(index)
}
}
override fun getDouble(index: Int): Double? {
return if (cursor.isNull(index)) null
else cursor.getDouble(index)
return if (cursor.isNull(index)) {
null
} else {
cursor.getDouble(index)
}
}
override fun getString(index: Int): String? {
return if (cursor.isNull(index)) null
else cursor.getString(index)
return if (cursor.isNull(index)) {
null
} else {
cursor.getString(index)
}
}
}

View File

@@ -26,7 +26,7 @@ import java.io.File
class AndroidDatabase(
private val db: SQLiteDatabase,
override val file: File?,
override val file: File?
) : Database {
override fun beginTransaction() = db.beginTransaction()
@@ -45,7 +45,7 @@ class AndroidDatabase(
tableName: String,
values: Map<String, Any?>,
where: String,
vararg params: String,
vararg params: String
): Int {
val contValues = mapToContentValues(values)
return db.update(tableName, contValues, where, params)
@@ -59,7 +59,7 @@ class AndroidDatabase(
override fun delete(
tableName: String,
where: String,
vararg params: String,
vararg params: String
) {
db.delete(tableName, where, params)
}

View File

@@ -30,7 +30,7 @@ class AndroidDatabaseOpener @Inject constructor() : DatabaseOpener {
db = SQLiteDatabase.openDatabase(
file.absolutePath,
null,
SQLiteDatabase.OPEN_READWRITE,
SQLiteDatabase.OPEN_READWRITE
),
file = file
)

View File

@@ -25,5 +25,6 @@ import dagger.Provides
@Module
class ActivityContextModule(
@get:Provides
@get:ActivityContext val context: Context
@get:ActivityContext
val context: Context
)

View File

@@ -26,5 +26,6 @@ import dagger.Provides
class AppContextModule(
@get:Provides
@get:AppContext
@param:AppContext val context: Context
@param:AppContext
val context: Context
)

View File

@@ -53,8 +53,9 @@ class IntentParser
var timestamp = intent.getLongExtra("timestamp", today)
timestamp = DateUtils.getStartOfDay(timestamp)
if (timestamp < 0 || timestamp > today)
if (timestamp < 0 || timestamp > today) {
throw IllegalArgumentException("timestamp is not valid")
}
return Timestamp(timestamp)
}

View File

@@ -25,6 +25,7 @@ import android.app.AlarmManager.RTC_WAKEUP
import android.app.PendingIntent
import android.content.Context
import android.content.Context.ALARM_SERVICE
import android.os.Build
import android.util.Log
import org.isoron.uhabits.core.AppScope
import org.isoron.uhabits.core.models.Habit
@@ -56,6 +57,10 @@ class IntentScheduler
)
return SchedulerResult.IGNORED
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !manager.canScheduleExactAlarms()) {
Log.e("IntentScheduler", "No permission to schedule exact alarms")
return SchedulerResult.IGNORED
}
manager.setExactAndAllowWhileIdle(alarmType, timestamp, intent)
return SchedulerResult.OK
}

View File

@@ -96,7 +96,6 @@ class AndroidNotificationTray
timestamp: Timestamp,
disableSound: Boolean = false
): Notification {
val addRepetitionAction = Action(
R.drawable.ic_action_check,
context.getString(R.string.yes),
@@ -146,8 +145,9 @@ class AndroidNotificationTray
.addAction(removeRepetitionAction)
}
if (!disableSound)
if (!disableSound) {
builder.setSound(ringtoneManager.getURI())
}
if (SDK_INT < Build.VERSION_CODES.S) {
val snoozeAction = Action(

View File

@@ -61,8 +61,9 @@ class RingtoneManager
"pref_ringtone_uri",
defaultRingtoneUri.toString()
)!!
if (prefRingtoneUri.isNotEmpty())
if (prefRingtoneUri.isNotEmpty()) {
ringtoneUri = Uri.parse(prefRingtoneUri)
}
return ringtoneUri
}

View File

@@ -103,7 +103,9 @@ class SnoozeDelayPickerActivity : FragmentActivity(), OnItemClickListener {
if (snoozeValues[position] >= 0) {
reminderController!!.onSnoozeDelayPicked(habit!!, snoozeValues[position])
finish()
} else showTimePicker()
} else {
showTimePicker()
}
}
override fun finish() {

View File

@@ -66,7 +66,13 @@ class ReminderController @Inject constructor(
}
fun onDismiss(habit: Habit) {
notificationTray.cancel(habit)
if (preferences.shouldMakeNotificationsSticky()) {
// This is a workaround to keep sticky notifications non-dismissible in Android 14+.
// If the notification is dismissed, we immediately reshow it.
notificationTray.reshow(habit)
} else {
notificationTray.cancel(habit)
}
}
private fun showSnoozeDelayPicker(habit: Habit, context: Context) {

View File

@@ -60,12 +60,14 @@ class AndroidTaskRunner : TaskRunner {
publishProgress(progress)
}
@Deprecated("Deprecated in Java")
override fun doInBackground(vararg params: Void?): Void? {
if (isCancelled) return null
task.doInBackground()
return null
}
@Deprecated("Deprecated in Java")
override fun onPostExecute(aVoid: Void?) {
if (isCancelled) return
task.onPostExecute()
@@ -74,6 +76,7 @@ class AndroidTaskRunner : TaskRunner {
for (l in listeners) l.onTaskFinished(task)
}
@Deprecated("Deprecated in Java")
override fun onPreExecute() {
if (isCancelled) return
for (l in listeners) l.onTaskStarted(task)
@@ -82,6 +85,7 @@ class AndroidTaskRunner : TaskRunner {
task.onPreExecute()
}
@Deprecated("Deprecated in Java")
override fun onProgressUpdate(vararg values: Int?) {
values[0]?.let { task.onProgressUpdate(it) }
}

View File

@@ -27,7 +27,7 @@ import javax.inject.Inject
class ExportDBTaskFactory
@Inject constructor(
@AppContext private val context: Context,
private val system: AndroidDirFinder,
private val system: AndroidDirFinder
) {
fun create(listener: ExportDBTask.Listener) = ExportDBTask(context, system, listener)
}

View File

@@ -27,7 +27,7 @@ import javax.inject.Inject
class ImportDataTaskFactory
@Inject constructor(
private val importer: GenericImporter,
private val modelFactory: ModelFactory,
private val modelFactory: ModelFactory
) {
fun create(file: File, listener: ImportDataTask.Listener) =
ImportDataTask(importer, modelFactory, file, listener)

View File

@@ -24,6 +24,7 @@ import org.jetbrains.annotations.Contract
object AttributeSetUtils {
const val ISORON_NAMESPACE = "http://isoron.org/android"
@JvmStatic
fun getAttribute(
context: Context,

View File

@@ -36,6 +36,13 @@ object ColorUtils {
return a or r or g or b
}
fun changeHue(color: Int, delta: Float): Int {
val hsv = FloatArray(3)
Color.colorToHSV(color, hsv)
hsv[0] = (hsv[0] + delta).mod(360f)
return Color.HSVToColor(hsv)
}
@JvmStatic
fun setAlpha(color: Int, newAlpha: Float): Int {
val intAlpha = (newAlpha * 255).toInt()

View File

@@ -33,6 +33,7 @@ import java.text.SimpleDateFormat
object DatabaseUtils {
private var opener: HabitsDatabaseOpener? = null
@JvmStatic
fun getDatabaseFile(context: Context): File {
val databaseFilename = databaseFilename

View File

@@ -26,6 +26,7 @@ import android.content.Intent
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.PointF
import android.graphics.drawable.ColorDrawable
import android.os.Handler
import android.os.SystemClock
@@ -61,7 +62,6 @@ fun RelativeLayout.addBelow(
height: Int = WRAP_CONTENT,
applyCustomRules: (params: RelativeLayout.LayoutParams) -> Unit = {}
) {
view.layoutParams = RelativeLayout.LayoutParams(width, height).apply {
addRule(BELOW, subject.id)
applyCustomRules(this)
@@ -75,7 +75,6 @@ fun RelativeLayout.addAtBottom(
width: Int = MATCH_PARENT,
height: Int = WRAP_CONTENT
) {
view.layoutParams = RelativeLayout.LayoutParams(width, height).apply {
addRule(ALIGN_PARENT_BOTTOM)
}
@@ -88,7 +87,6 @@ fun RelativeLayout.addAtTop(
width: Int = MATCH_PARENT,
height: Int = WRAP_CONTENT
) {
view.layoutParams = RelativeLayout.LayoutParams(width, height).apply {
addRule(ALIGN_PARENT_TOP)
}
@@ -138,7 +136,11 @@ fun Activity.startActivitySafely(intent: Intent) {
}
}
fun Activity.showSendEmailScreen(@StringRes toId: Int, @StringRes subjectId: Int, content: String?) {
fun Activity.showSendEmailScreen(
@StringRes toId: Int,
@StringRes subjectId: Int,
content: String?
) {
val to = this.getString(toId)
val subject = this.getString(subjectId)
this.startActivity(
@@ -159,7 +161,7 @@ fun Activity.restartWithFade(cls: Class<*>?) {
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
startActivity(Intent(this, cls))
},
500,
500
) // HACK: Let the menu disappear first
}
@@ -168,7 +170,7 @@ fun View.setupToolbar(
title: String,
color: PaletteColor,
theme: Theme,
displayHomeAsUpEnabled: Boolean = true,
displayHomeAsUpEnabled: Boolean = true
) {
toolbar.elevation = InterfaceUtils.dpToPixels(context, 2f)
val res = StyledResources(context)
@@ -235,3 +237,11 @@ fun View.requestFocusWithKeyboard() {
dispatchTouchEvent(MotionEvent.obtain(time, time, MotionEvent.ACTION_UP, 0f, 0f, 0))
}, 250)
}
fun View.getCenter(): PointF {
val viewLocation = IntArray(2)
this.getLocationOnScreen(viewLocation)
viewLocation[0] += this.width / 2
viewLocation[1] -= this.height / 2
return PointF(viewLocation[0].toFloat(), viewLocation[1].toFloat())
}

View File

@@ -147,10 +147,11 @@ abstract class BaseWidget(val context: Context, val id: Int, val stacked: Boolea
protected val preferedBackgroundAlpha: Int
get() {
return if (stacked)
return if (stacked) {
255
else
} else {
prefs.widgetOpacity
}
}
init {

View File

@@ -33,7 +33,7 @@ open class CheckmarkWidget(
context: Context,
widgetId: Int,
protected val habit: Habit,
stacked: Boolean = false,
stacked: Boolean = false
) : BaseWidget(context, widgetId, stacked) {
override val defaultHeight: Int = 125

View File

@@ -23,7 +23,10 @@ import android.content.Context
class CheckmarkWidgetProvider : BaseWidgetProvider() {
override fun getWidgetFromId(context: Context, id: Int): BaseWidget {
val habits = getHabitsFromWidgetId(id)
return if (habits.size == 1) CheckmarkWidget(context, id, habits[0])
else StackWidget(context, id, StackWidgetType.CHECKMARK, habits)
return if (habits.size == 1) {
CheckmarkWidget(context, id, habits[0])
} else {
StackWidget(context, id, StackWidgetType.CHECKMARK, habits)
}
}
}

View File

@@ -27,7 +27,7 @@ import org.isoron.uhabits.widgets.views.EmptyWidgetView
class EmptyWidget(
context: Context,
widgetId: Int,
stacked: Boolean = false,
stacked: Boolean = false
) : BaseWidget(context, widgetId, stacked) {
override val defaultHeight: Int = 200
override val defaultWidth: Int = 200

View File

@@ -33,7 +33,7 @@ class FrequencyWidget(
widgetId: Int,
private val habit: Habit,
private val firstWeekday: Int,
stacked: Boolean = false,
stacked: Boolean = false
) : BaseWidget(context, widgetId, stacked) {
override val defaultHeight: Int = 200
override val defaultWidth: Int = 200

View File

@@ -24,12 +24,15 @@ import android.content.Context
class FrequencyWidgetProvider : BaseWidgetProvider() {
override fun getWidgetFromId(context: Context, id: Int): BaseWidget {
val habits = getHabitsFromWidgetId(id)
return if (habits.size == 1) FrequencyWidget(
context,
id,
habits[0],
preferences.firstWeekdayInt
)
else StackWidget(context, id, StackWidgetType.FREQUENCY, habits)
return if (habits.size == 1) {
FrequencyWidget(
context,
id,
habits[0],
preferences.firstWeekdayInt
)
} else {
StackWidget(context, id, StackWidgetType.FREQUENCY, habits)
}
}
}

View File

@@ -36,7 +36,7 @@ class HistoryWidget(
context: Context,
id: Int,
private val habit: Habit,
stacked: Boolean = false,
stacked: Boolean = false
) : BaseWidget(context, id, stacked) {
override val defaultHeight: Int = 250
@@ -53,7 +53,7 @@ class HistoryWidget(
val model = HistoryCardPresenter.buildState(
habit = habit,
firstWeekday = prefs.firstWeekday,
theme = WidgetTheme(),
theme = WidgetTheme()
)
(widgetView.dataView as AndroidDataView).apply {
val historyChart = (this.view as HistoryChart)
@@ -75,7 +75,7 @@ class HistoryWidget(
firstWeekday = prefs.firstWeekday,
series = listOf(),
defaultSquare = HistoryChart.Square.OFF,
notesIndicators = listOf(),
notesIndicators = listOf()
)
}
).apply {

View File

@@ -23,11 +23,14 @@ import android.content.Context
class HistoryWidgetProvider : BaseWidgetProvider() {
override fun getWidgetFromId(context: Context, id: Int): BaseWidget {
val habits = getHabitsFromWidgetId(id)
return if (habits.size == 1) HistoryWidget(
context,
id,
habits[0]
)
else StackWidget(context, id, StackWidgetType.HISTORY, habits)
return if (habits.size == 1) {
HistoryWidget(
context,
id,
habits[0]
)
} else {
StackWidget(context, id, StackWidgetType.HISTORY, habits)
}
}
}

View File

@@ -33,7 +33,7 @@ class ScoreWidget(
context: Context,
id: Int,
private val habit: Habit,
stacked: Boolean = false,
stacked: Boolean = false
) : BaseWidget(context, id, stacked) {
override val defaultHeight: Int = 300
override val defaultWidth: Int = 300
@@ -46,7 +46,7 @@ class ScoreWidget(
habit = habit,
firstWeekday = prefs.firstWeekdayInt,
spinnerPosition = prefs.scoreCardSpinnerPosition,
theme = WidgetTheme(),
theme = WidgetTheme()
)
val widgetView = view as GraphWidgetView
widgetView.setBackgroundAlpha(preferedBackgroundAlpha)

View File

@@ -23,7 +23,10 @@ import android.content.Context
class ScoreWidgetProvider : BaseWidgetProvider() {
override fun getWidgetFromId(context: Context, id: Int): BaseWidget {
val habits = getHabitsFromWidgetId(id)
return if (habits.size == 1) ScoreWidget(context, id, habits[0])
else StackWidget(context, id, StackWidgetType.SCORE, habits)
return if (habits.size == 1) {
ScoreWidget(context, id, habits[0])
} else {
StackWidget(context, id, StackWidgetType.SCORE, habits)
}
}
}

View File

@@ -34,7 +34,7 @@ class StackWidget(
widgetId: Int,
private val widgetType: StackWidgetType,
private val habits: List<Habit>,
stacked: Boolean = true,
stacked: Boolean = true
) : BaseWidget(context, widgetId, stacked) {
override val defaultHeight: Int = 0
override val defaultWidth: Int = 0

View File

@@ -34,7 +34,7 @@ class StreakWidget(
context: Context,
id: Int,
private val habit: Habit,
stacked: Boolean = false,
stacked: Boolean = false
) : BaseWidget(context, id, stacked) {
override val defaultHeight: Int = 200
override val defaultWidth: Int = 200

View File

@@ -23,7 +23,10 @@ import android.content.Context
class StreakWidgetProvider : BaseWidgetProvider() {
override fun getWidgetFromId(context: Context, id: Int): BaseWidget {
val habits = getHabitsFromWidgetId(id)
return if (habits.size == 1) StreakWidget(context, id, habits[0])
else StackWidget(context, id, StackWidgetType.STREAKS, habits)
return if (habits.size == 1) {
StreakWidget(context, id, habits[0])
} else {
StackWidget(context, id, StackWidgetType.STREAKS, habits)
}
}
}

View File

@@ -37,7 +37,7 @@ class TargetWidget(
context: Context,
id: Int,
private val habit: Habit,
stacked: Boolean = false,
stacked: Boolean = false
) : BaseWidget(context, id, stacked) {
override val defaultHeight: Int = 200
override val defaultWidth: Int = 200
@@ -53,7 +53,7 @@ class TargetWidget(
val data = TargetCardPresenter.buildState(
habit = habit,
firstWeekday = prefs.firstWeekdayInt,
theme = WidgetTheme(),
theme = WidgetTheme()
)
chart.setColor(WidgetTheme().color(habit.color).toInt())
chart.setTargets(data.targets)

View File

@@ -23,7 +23,10 @@ import android.content.Context
class TargetWidgetProvider : BaseWidgetProvider() {
override fun getWidgetFromId(context: Context, id: Int): BaseWidget {
val habits = getHabitsFromWidgetId(id)
return if (habits.size == 1) TargetWidget(context, id, habits[0])
else StackWidget(context, id, StackWidgetType.TARGET, habits)
return if (habits.size == 1) {
TargetWidget(context, id, habits[0])
} else {
StackWidget(context, id, StackWidgetType.TARGET, habits)
}
}
}

View File

@@ -68,13 +68,13 @@ class CheckmarkWidgetView : HabitWidgetView {
val fgColor: Int
setShadowAlpha(0x4f)
when (entryState) {
YES_MANUAL, SKIP -> {
YES_MANUAL, SKIP, YES_AUTO -> {
bgColor = activeColor
fgColor = res.getColor(R.attr.contrast0)
backgroundPaint!!.color = bgColor
frame!!.setBackgroundDrawable(background)
}
YES_AUTO, NO, UNKNOWN -> {
NO, UNKNOWN -> {
bgColor = res.getColor(R.attr.cardBgColor)
fgColor = res.getColor(R.attr.contrast60)
}
@@ -87,30 +87,43 @@ class CheckmarkWidgetView : HabitWidgetView {
ring.setColor(fgColor)
ring.setBackgroundColor(bgColor)
ring.setText(text)
ring.setIsStrokedTextEnabled(strokedTextEnabled)
label.text = name
label.setTextColor(fgColor)
requestLayout()
postInvalidate()
}
private val strokedTextEnabled: Boolean
get() = if (isNumerical) {
false
} else {
when (entryState) {
YES_AUTO -> true
else -> false
}
}
private val text: String
get() = if (isNumerical) {
(max(0, entryValue) / 1000.0).toShortString()
} else when (entryState) {
YES_MANUAL, YES_AUTO -> resources.getString(R.string.fa_check)
SKIP -> resources.getString(R.string.fa_skipped)
UNKNOWN -> {
run {
if (preferences!!.areQuestionMarksEnabled) {
return resources.getString(R.string.fa_question)
} else {
resources.getString(R.string.fa_times)
} else {
when (entryState) {
YES_MANUAL, YES_AUTO -> resources.getString(R.string.fa_check)
SKIP -> resources.getString(R.string.fa_skipped)
UNKNOWN -> {
run {
if (preferences!!.areQuestionMarksEnabled) {
return resources.getString(R.string.fa_question)
} else {
resources.getString(R.string.fa_times)
}
}
resources.getString(R.string.fa_times)
}
resources.getString(R.string.fa_times)
NO -> resources.getString(R.string.fa_times)
else -> resources.getString(R.string.fa_times)
}
NO -> resources.getString(R.string.fa_times)
else -> resources.getString(R.string.fa_times)
}
override val innerLayoutId: Int

View File

@@ -36,7 +36,7 @@
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="center"
android:inputType="textCapSentences"
android:inputType="textCapSentences|textMultiLine"
android:textSize="@dimen/smallTextSize"
android:padding="4dp"
android:background="@color/transparent"

Some files were not shown because too many files have changed in this diff Show More