Compare commits

..

96 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
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
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
99 changed files with 726 additions and 358 deletions

View File

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

View File

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

1
.gitignore vendored
View File

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

View File

@@ -1,5 +1,22 @@
# Changelog # 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 ## [2.1.2] -- 2023-05-26
### Fixed ### Fixed
- Fix bug that caused widget to enter checkmark on wrong date (@iSoron, #1541) - Fix bug that caused widget to enter checkmark on wrong date (@iSoron, #1541)

View File

@@ -1,11 +1,10 @@
plugins { plugins {
val kotlinVersion = "1.7.21" val kotlinVersion = "1.9.22"
id("com.android.application") version ("7.4.2") apply (false) id("com.android.application") version "8.1.4" apply (false)
id("org.jetbrains.kotlin.android") version kotlinVersion apply (false) id("org.jetbrains.kotlin.android") version kotlinVersion apply (false)
id("org.jetbrains.kotlin.kapt") version kotlinVersion apply (false) id("org.jetbrains.kotlin.kapt") version kotlinVersion apply (false)
id("org.jetbrains.kotlin.android.extensions") version kotlinVersion apply (false)
id("org.jetbrains.kotlin.multiplatform") version kotlinVersion apply (false) id("org.jetbrains.kotlin.multiplatform") version kotlinVersion apply (false)
id("org.jlleitschuh.gradle.ktlint") version "11.4.2" id("org.jlleitschuh.gradle.ktlint") version "11.6.1"
} }
apply { apply {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -18,6 +18,7 @@
*/ */
package org.isoron.uhabits.activities.common.views package org.isoron.uhabits.activities.common.views
import android.view.MotionEvent
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest import androidx.test.filters.MediumTest
import org.isoron.uhabits.BaseViewTest import org.isoron.uhabits.BaseViewTest
@@ -52,7 +53,8 @@ class FrequencyChartTest : BaseViewTest() {
@Test @Test
@Throws(Throwable::class) @Throws(Throwable::class)
fun testRender_withDataOffset() { 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() view.invalidate()
assertRenders(view, BASE_PATH + "renderDataOffset.png") assertRenders(view, BASE_PATH + "renderDataOffset.png")
} }

View File

@@ -18,6 +18,7 @@
*/ */
package org.isoron.uhabits.activities.common.views package org.isoron.uhabits.activities.common.views
import android.view.MotionEvent
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest import androidx.test.filters.MediumTest
import org.isoron.uhabits.BaseViewTest import org.isoron.uhabits.BaseViewTest
@@ -63,7 +64,8 @@ class ScoreChartTest : BaseViewTest() {
@Test @Test
@Throws(Throwable::class) @Throws(Throwable::class)
fun testRender_withDataOffset() { 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() view.invalidate()
assertRenders(view, BASE_PATH + "renderDataOffset.png") assertRenders(view, BASE_PATH + "renderDataOffset.png")
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -44,21 +44,21 @@ class AndroidDataView(
addUpdateListener(this@AndroidDataView) addUpdateListener(this@AndroidDataView)
} }
override fun onTouchEvent(event: MotionEvent?) = detector.onTouchEvent(event) override fun onTouchEvent(event: MotionEvent) = detector.onTouchEvent(event)
override fun onDown(e: MotionEvent?) = true override fun onDown(e: MotionEvent) = true
override fun onShowPress(e: MotionEvent?) = Unit override fun onShowPress(e: MotionEvent) = Unit
override fun onSingleTapUp(e: MotionEvent?): Boolean { override fun onSingleTapUp(e: MotionEvent): Boolean {
return handleClick(e, true) return handleClick(e, true)
} }
override fun onLongPress(e: MotionEvent?) { override fun onLongPress(e: MotionEvent) {
handleClick(e) handleClick(e)
} }
override fun onScroll( override fun onScroll(
e1: MotionEvent?, e1: MotionEvent?,
e2: MotionEvent?, e2: MotionEvent,
dx: Float, dx: Float,
dy: Float dy: Float
): Boolean { ): Boolean {
@@ -80,7 +80,7 @@ class AndroidDataView(
override fun onFling( override fun onFling(
e1: MotionEvent?, e1: MotionEvent?,
e2: MotionEvent?, e2: MotionEvent,
velocityX: Float, velocityX: Float,
velocityY: Float velocityY: Float
): Boolean { ): Boolean {
@@ -100,7 +100,7 @@ class AndroidDataView(
return false return false
} }
override fun onAnimationUpdate(animation: ValueAnimator?) { override fun onAnimationUpdate(animation: ValueAnimator) {
if (!scroller.isFinished) { if (!scroller.isFinished) {
scroller.computeScrollOffset() scroller.computeScrollOffset()
updateDataOffset() updateDataOffset()
@@ -127,11 +127,11 @@ class AndroidDataView(
} }
} }
private fun handleClick(e: MotionEvent?, isSingleTap: Boolean = false): Boolean { private fun handleClick(e: MotionEvent, isSingleTap: Boolean = false): Boolean {
val x: Float val x: Float
val y: Float val y: Float
try { try {
val pointerId = e!!.getPointerId(0) val pointerId = e.getPointerId(0)
x = e.getX(pointerId) x = e.getX(pointerId)
y = e.getY(pointerId) y = e.getY(pointerId)
} catch (ex: RuntimeException) { } catch (ex: RuntimeException) {

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.core.models.Entry.Companion.YES_MANUAL
import org.isoron.uhabits.databinding.CheckmarkPopupBinding import org.isoron.uhabits.databinding.CheckmarkPopupBinding
import org.isoron.uhabits.utils.InterfaceUtils.getFontAwesome import org.isoron.uhabits.utils.InterfaceUtils.getFontAwesome
import org.isoron.uhabits.utils.getCenter
import org.isoron.uhabits.utils.sres import org.isoron.uhabits.utils.sres
class CheckmarkDialog : AppCompatDialogFragment() { class CheckmarkDialog : AppCompatDialogFragment() {
var onToggle: (Int, String) -> Unit = { _, _ -> } var onToggle: (Int, String, Float, Float) -> Unit = { _, _, _, _ -> }
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val appComponent = (requireActivity().application as HabitsApplication).component val appComponent = (requireActivity().application as HabitsApplication).component
val prefs = appComponent.preferences val prefs = appComponent.preferences
val view = CheckmarkPopupBinding.inflate(LayoutInflater.from(context)) val view = CheckmarkPopupBinding.inflate(LayoutInflater.from(context))
val color = requireArguments().getInt("color")
arrayOf(view.yesBtn, view.skipBtn).forEach { arrayOf(view.yesBtn, view.skipBtn).forEach {
it.setTextColor(requireArguments().getInt("color")) it.setTextColor(color)
} }
arrayOf(view.noBtn, view.unknownBtn).forEach { arrayOf(view.noBtn, view.unknownBtn).forEach {
it.setTextColor(view.root.sres.getColor(R.attr.contrast60)) it.setTextColor(view.root.sres.getColor(R.attr.contrast60))
@@ -62,7 +64,8 @@ class CheckmarkDialog : AppCompatDialogFragment() {
} }
fun onClick(v: Int) { fun onClick(v: Int) {
val notes = view.notes.text.toString().trim() val notes = view.notes.text.toString().trim()
onToggle(v, notes) val location = view.yesBtn.getCenter()
onToggle(v, notes, location.x, location.y)
requireDialog().dismiss() requireDialog().dismiss()
} }
view.yesBtn.setOnClickListener { onClick(YES_MANUAL) } 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.app.Dialog
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.widget.EditText import android.widget.EditText
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.RadioButton import android.widget.RadioButton
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatDialogFragment import androidx.appcompat.app.AppCompatDialogFragment
import kotlinx.android.synthetic.main.frequency_picker_dialog.view.*
import org.isoron.uhabits.R import org.isoron.uhabits.R
import org.isoron.uhabits.databinding.FrequencyPickerDialogBinding
class FrequencyPickerDialog( class FrequencyPickerDialog(
var freqNumerator: Int, var freqNumerator: Int,
var freqDenominator: Int var freqDenominator: Int
) : AppCompatDialogFragment() { ) : AppCompatDialogFragment() {
private var _binding: FrequencyPickerDialogBinding? = null
private val binding get() = _binding!!
lateinit var contentView: View
var onFrequencyPicked: (num: Int, den: Int) -> Unit = { _, _ -> } var onFrequencyPicked: (num: Int, den: Int) -> Unit = { _, _ -> }
constructor() : this(1, 1) constructor() : this(1, 1)
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val inflater = LayoutInflater.from(requireActivity()) _binding = FrequencyPickerDialogBinding.inflate(LayoutInflater.from(requireActivity()))
contentView = inflater.inflate(R.layout.frequency_picker_dialog, null)
addBeforeAfterText( addBeforeAfterText(
this.getString(R.string.every_x_days), this.getString(R.string.every_x_days),
contentView.everyXDaysContainer binding.everyXDaysContainer
) )
addBeforeAfterText( addBeforeAfterText(
this.getString(R.string.x_times_per_week), this.getString(R.string.x_times_per_week),
contentView.xTimesPerWeekContainer binding.xTimesPerWeekContainer
) )
addBeforeAfterText( addBeforeAfterText(
this.getString(R.string.x_times_per_month), this.getString(R.string.x_times_per_month),
contentView.xTimesPerMonthContainer binding.xTimesPerMonthContainer
) )
addBeforeAfterText( addBeforeAfterText(
this.getString(R.string.x_times_per_y_days), this.getString(R.string.x_times_per_y_days),
contentView.xTimesPerYDaysContainer binding.xTimesPerYDaysContainer
) )
contentView.everyDayRadioButton.setOnClickListener { binding.everyDayRadioButton.setOnClickListener {
check(contentView.everyDayRadioButton) check(binding.everyDayRadioButton)
} }
contentView.everyXDaysRadioButton.setOnClickListener { binding.everyXDaysRadioButton.setOnClickListener {
check(contentView.everyXDaysRadioButton) check(binding.everyXDaysRadioButton)
val everyXDaysTextView = contentView.everyXDaysTextView val everyXDaysTextView = binding.everyXDaysTextView
selectInputField(everyXDaysTextView) selectInputField(everyXDaysTextView)
} }
contentView.everyXDaysTextView.setOnFocusChangeListener { v, hasFocus -> binding.everyXDaysTextView.setOnFocusChangeListener { v, hasFocus ->
if (hasFocus) check(contentView.everyXDaysRadioButton) if (hasFocus) check(binding.everyXDaysRadioButton)
} }
contentView.xTimesPerWeekRadioButton.setOnClickListener { binding.xTimesPerWeekRadioButton.setOnClickListener {
check(contentView.xTimesPerWeekRadioButton) check(binding.xTimesPerWeekRadioButton)
selectInputField(contentView.xTimesPerWeekTextView) selectInputField(binding.xTimesPerWeekTextView)
} }
contentView.xTimesPerWeekTextView.setOnFocusChangeListener { v, hasFocus -> binding.xTimesPerWeekTextView.setOnFocusChangeListener { v, hasFocus ->
if (hasFocus) check(contentView.xTimesPerWeekRadioButton) if (hasFocus) check(binding.xTimesPerWeekRadioButton)
} }
contentView.xTimesPerMonthRadioButton.setOnClickListener { binding.xTimesPerMonthRadioButton.setOnClickListener {
check(contentView.xTimesPerMonthRadioButton) check(binding.xTimesPerMonthRadioButton)
selectInputField(contentView.xTimesPerMonthTextView) selectInputField(binding.xTimesPerMonthTextView)
} }
contentView.xTimesPerMonthTextView.setOnFocusChangeListener { v, hasFocus -> binding.xTimesPerMonthTextView.setOnFocusChangeListener { v, hasFocus ->
if (hasFocus) check(contentView.xTimesPerMonthRadioButton) if (hasFocus) check(binding.xTimesPerMonthRadioButton)
} }
contentView.xTimesPerYDaysRadioButton.setOnClickListener { binding.xTimesPerYDaysRadioButton.setOnClickListener {
check(contentView.xTimesPerYDaysRadioButton) check(binding.xTimesPerYDaysRadioButton)
selectInputField(contentView.xTimesPerYDaysXTextView) selectInputField(binding.xTimesPerYDaysXTextView)
} }
contentView.xTimesPerYDaysXTextView.setOnFocusChangeListener { v, hasFocus -> binding.xTimesPerYDaysXTextView.setOnFocusChangeListener { v, hasFocus ->
if (hasFocus) check(contentView.xTimesPerYDaysRadioButton) if (hasFocus) check(binding.xTimesPerYDaysRadioButton)
} }
contentView.xTimesPerYDaysYTextView.setOnFocusChangeListener { v, hasFocus -> binding.xTimesPerYDaysYTextView.setOnFocusChangeListener { v, hasFocus ->
if (hasFocus) check(contentView.xTimesPerYDaysRadioButton) if (hasFocus) check(binding.xTimesPerYDaysRadioButton)
} }
return AlertDialog.Builder(requireActivity()) return AlertDialog.Builder(requireActivity())
.setView(contentView) .setView(binding.root)
.setPositiveButton(R.string.save) { _, _ -> onSaveClicked() } .setPositiveButton(R.string.save) { _, _ -> onSaveClicked() }
.create() .create()
} }
@@ -134,31 +138,35 @@ class FrequencyPickerDialog(
var numerator = 1 var numerator = 1
var denominator = 1 var denominator = 1
when { when {
contentView.everyDayRadioButton.isChecked -> { binding.everyDayRadioButton.isChecked -> {
// NOP // NOP
} }
contentView.everyXDaysRadioButton.isChecked -> {
if (contentView.everyXDaysTextView.text.isNotEmpty()) { binding.everyXDaysRadioButton.isChecked -> {
denominator = Integer.parseInt(contentView.everyXDaysTextView.text.toString()) if (binding.everyXDaysTextView.text.isNotEmpty()) {
denominator = Integer.parseInt(binding.everyXDaysTextView.text.toString())
} }
} }
contentView.xTimesPerWeekRadioButton.isChecked -> {
if (contentView.xTimesPerWeekTextView.text.isNotEmpty()) { binding.xTimesPerWeekRadioButton.isChecked -> {
numerator = Integer.parseInt(contentView.xTimesPerWeekTextView.text.toString()) if (binding.xTimesPerWeekTextView.text.isNotEmpty()) {
numerator = Integer.parseInt(binding.xTimesPerWeekTextView.text.toString())
denominator = 7 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 = numerator =
Integer.parseInt(contentView.xTimesPerYDaysXTextView.text.toString()) Integer.parseInt(binding.xTimesPerYDaysXTextView.text.toString())
denominator = denominator =
Integer.parseInt(contentView.xTimesPerYDaysYTextView.text.toString()) Integer.parseInt(binding.xTimesPerYDaysYTextView.text.toString())
} }
} }
else -> { else -> {
if (contentView.xTimesPerMonthTextView.text.isNotEmpty()) { if (binding.xTimesPerMonthTextView.text.isNotEmpty()) {
numerator = Integer.parseInt(contentView.xTimesPerMonthTextView.text.toString()) numerator = Integer.parseInt(binding.xTimesPerMonthTextView.text.toString())
denominator = 30 denominator = 30
} }
} }
@@ -185,27 +193,27 @@ class FrequencyPickerDialog(
private fun populateViews() { private fun populateViews() {
uncheckAll() uncheckAll()
if (freqDenominator == 30 || freqDenominator == 31) { if (freqDenominator == 30 || freqDenominator == 31) {
contentView.xTimesPerMonthRadioButton.isChecked = true binding.xTimesPerMonthRadioButton.isChecked = true
contentView.xTimesPerMonthTextView.setText(freqNumerator.toString()) binding.xTimesPerMonthTextView.setText(freqNumerator.toString())
selectInputField(contentView.xTimesPerMonthTextView) selectInputField(binding.xTimesPerMonthTextView)
} else { } else {
if (freqNumerator == 1) { if (freqNumerator == 1) {
if (freqDenominator == 1) { if (freqDenominator == 1) {
contentView.everyDayRadioButton.isChecked = true binding.everyDayRadioButton.isChecked = true
} else { } else {
contentView.everyXDaysRadioButton.isChecked = true binding.everyXDaysRadioButton.isChecked = true
contentView.everyXDaysTextView.setText(freqDenominator.toString()) binding.everyXDaysTextView.setText(freqDenominator.toString())
selectInputField(contentView.everyXDaysTextView) selectInputField(binding.everyXDaysTextView)
} }
} else { } else {
if (freqDenominator == 7) { if (freqDenominator == 7) {
contentView.xTimesPerWeekRadioButton.isChecked = true binding.xTimesPerWeekRadioButton.isChecked = true
contentView.xTimesPerWeekTextView.setText(freqNumerator.toString()) binding.xTimesPerWeekTextView.setText(freqNumerator.toString())
selectInputField(contentView.xTimesPerWeekTextView) selectInputField(binding.xTimesPerWeekTextView)
} else { } else {
contentView.xTimesPerYDaysRadioButton.isChecked = true binding.xTimesPerYDaysRadioButton.isChecked = true
contentView.xTimesPerYDaysXTextView.setText(freqNumerator.toString()) binding.xTimesPerYDaysXTextView.setText(freqNumerator.toString())
contentView.xTimesPerYDaysYTextView.setText(freqDenominator.toString()) binding.xTimesPerYDaysYTextView.setText(freqDenominator.toString())
} }
} }
} }
@@ -216,10 +224,10 @@ class FrequencyPickerDialog(
} }
private fun uncheckAll() { private fun uncheckAll() {
contentView.everyDayRadioButton.isChecked = false binding.everyDayRadioButton.isChecked = false
contentView.everyXDaysRadioButton.isChecked = false binding.everyXDaysRadioButton.isChecked = false
contentView.xTimesPerWeekRadioButton.isChecked = false binding.xTimesPerWeekRadioButton.isChecked = false
contentView.xTimesPerMonthRadioButton.isChecked = false binding.xTimesPerMonthRadioButton.isChecked = false
contentView.xTimesPerYDaysRadioButton.isChecked = false binding.xTimesPerYDaysRadioButton.isChecked = false
} }
} }

View File

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

View File

@@ -58,6 +58,7 @@ class RingView : View {
private var em = 0f private var em = 0f
private var text: String? private var text: String?
private var textSize: Float private var textSize: Float
private var isStrokedTextEnabled: Boolean = false
private var enableFontAwesome = false private var enableFontAwesome = false
private var internalDrawingCache: Bitmap? = null private var internalDrawingCache: Bitmap? = null
private var cacheCanvas: Canvas? = null private var cacheCanvas: Canvas? = null
@@ -131,6 +132,10 @@ class RingView : View {
invalidate() invalidate()
} }
fun setIsStrokedTextEnabled(isStroked: Boolean) {
this.isStrokedTextEnabled = isStroked
}
override fun onDraw(canvas: Canvas) { override fun onDraw(canvas: Canvas) {
super.onDraw(canvas) super.onDraw(canvas)
val activeCanvas: Canvas? val activeCanvas: Canvas?
@@ -159,6 +164,12 @@ class RingView : View {
pRing!!.xfermode = null pRing!!.xfermode = null
pRing!!.color = color pRing!!.color = color
pRing!!.textSize = textSize pRing!!.textSize = textSize
if (isStrokedTextEnabled) {
pRing!!.style = Paint.Style.STROKE
pRing!!.strokeWidth = textSize / 15f
}
if (enableFontAwesome) pRing!!.typeface = getFontAwesome(context) if (enableFontAwesome) pRing!!.typeface = getFontAwesome(context)
activeCanvas.drawText( activeCanvas.drawText(
text!!, text!!,

View File

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

View File

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

View File

@@ -19,12 +19,17 @@
package org.isoron.uhabits.activities.habits.list package org.isoron.uhabits.activities.habits.list
import android.Manifest.permission.POST_NOTIFICATIONS
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager.PERMISSION_GRANTED
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat.checkSelfPermission
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import org.isoron.uhabits.BaseExceptionHandler import org.isoron.uhabits.BaseExceptionHandler
@@ -56,6 +61,16 @@ class ListHabitsActivity : AppCompatActivity(), Preferences.Listener {
lateinit var midnightTimer: MidnightTimer lateinit var midnightTimer: MidnightTimer
private val scope = CoroutineScope(Dispatchers.Main) private val scope = CoroutineScope(Dispatchers.Main)
private var permissionAlreadyRequested = false
private val permissionLauncher =
registerForActivityResult(RequestPermission()) { isGranted: Boolean ->
if (isGranted) {
scheduleReminders()
} else {
Log.i("ListHabitsActivity", "POST_NOTIFICATIONS denied")
}
}
private lateinit var menu: ListHabitsMenu private lateinit var menu: ListHabitsMenu
override fun onQuestionMarksChanged() { override fun onQuestionMarksChanged() {
@@ -101,7 +116,26 @@ class ListHabitsActivity : AppCompatActivity(), Preferences.Listener {
screen.onAttached() screen.onAttached()
rootView.postInvalidate() rootView.postInvalidate()
midnightTimer.onResume() 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 { taskRunner.run {
try { try {
AutoBackup(this@ListHabitsActivity).run() AutoBackup(this@ListHabitsActivity).run()
@@ -117,6 +151,10 @@ class ListHabitsActivity : AppCompatActivity(), Preferences.Listener {
super.onResume() super.onResume()
} }
private fun scheduleReminders() {
appComponent.reminderScheduler.scheduleAll()
}
override fun onCreateOptionsMenu(m: Menu): Boolean { override fun onCreateOptionsMenu(m: Menu): Boolean {
menu.onCreate(menuInflater, m) menu.onCreate(menuInflater, m)
return true return true
@@ -127,6 +165,7 @@ class ListHabitsActivity : AppCompatActivity(), Preferences.Listener {
return menu.onItemSelected(item) return menu.onItemSelected(item)
} }
@Deprecated("Deprecated in Java")
override fun onActivityResult(request: Int, result: Int, data: Intent?) { override fun onActivityResult(request: Int, result: Int, data: Intent?) {
super.onActivityResult(request, result, data) super.onActivityResult(request, result, data)
screen.onResult(request, result, data) screen.onResult(request, result, data)

View File

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

View File

@@ -25,6 +25,9 @@ import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import dagger.Lazy import dagger.Lazy
import nl.dionsegijn.konfetti.core.Party
import nl.dionsegijn.konfetti.core.Position
import nl.dionsegijn.konfetti.core.emitter.Emitter
import org.isoron.platform.gui.toInt import org.isoron.platform.gui.toInt
import org.isoron.uhabits.R import org.isoron.uhabits.R
import org.isoron.uhabits.activities.common.dialogs.CheckmarkDialog import org.isoron.uhabits.activities.common.dialogs.CheckmarkDialog
@@ -63,6 +66,7 @@ import org.isoron.uhabits.intents.IntentFactory
import org.isoron.uhabits.tasks.ExportDBTaskFactory import org.isoron.uhabits.tasks.ExportDBTaskFactory
import org.isoron.uhabits.tasks.ImportDataTask import org.isoron.uhabits.tasks.ImportDataTask
import org.isoron.uhabits.tasks.ImportDataTaskFactory import org.isoron.uhabits.tasks.ImportDataTaskFactory
import org.isoron.uhabits.utils.ColorUtils
import org.isoron.uhabits.utils.copyTo import org.isoron.uhabits.utils.copyTo
import org.isoron.uhabits.utils.currentTheme import org.isoron.uhabits.utils.currentTheme
import org.isoron.uhabits.utils.dismissCurrentAndShow import org.isoron.uhabits.utils.dismissCurrentAndShow
@@ -72,6 +76,7 @@ import org.isoron.uhabits.utils.showSendEmailScreen
import org.isoron.uhabits.utils.showSendFileScreen import org.isoron.uhabits.utils.showSendFileScreen
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
const val RESULT_IMPORT_DATA = 101 const val RESULT_IMPORT_DATA = 101
@@ -218,6 +223,28 @@ class ListHabitsScreen
activity.showSendFileScreen(filename) 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() { override fun showSettingsScreen() {
val intent = intentFactory.startSettingsActivity(activity) val intent = intentFactory.startSettingsActivity(activity)
activity.startActivityForResult(intent, REQUEST_SETTINGS) activity.startActivityForResult(intent, REQUEST_SETTINGS)
@@ -240,7 +267,7 @@ class ListHabitsScreen
putDouble("value", value) putDouble("value", value)
putString("notes", notes) 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") dialog.dismissCurrentAndShow(fm, "numberDialog")
} }
@@ -258,7 +285,7 @@ class ListHabitsScreen
putInt("value", selectedValue) putInt("value", selectedValue)
putString("notes", notes) 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") dialog.dismissCurrentAndShow(fm, "checkmarkDialog")
} }

View File

@@ -20,6 +20,7 @@
package org.isoron.uhabits.activities.habits.list.views package org.isoron.uhabits.activities.habits.list.views
import android.content.Context import android.content.Context
import android.graphics.PointF
import android.graphics.text.LineBreaker.BREAK_STRATEGY_BALANCED import android.graphics.text.LineBreaker.BREAK_STRATEGY_BALANCED
import android.os.Build import android.os.Build
import android.os.Build.VERSION.SDK_INT import android.os.Build.VERSION.SDK_INT
@@ -154,7 +155,17 @@ class HabitCardView(
checkmarkPanel = checkmarkPanelFactory.create().apply { checkmarkPanel = checkmarkPanelFactory.create().apply {
onToggle = { timestamp, value, notes -> onToggle = { timestamp, value, notes ->
triggerRipple(timestamp) 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 -> onEdit = { timestamp ->
triggerRipple(timestamp) triggerRipple(timestamp)
@@ -206,12 +217,27 @@ class HabitCardView(
} }
fun triggerRipple(timestamp: Timestamp) { fun triggerRipple(timestamp: Timestamp) {
val location = getRelativeButtonLocation(timestamp)
triggerRipple(location.x, location.y)
}
private fun getRelativeButtonLocation(timestamp: Timestamp): PointF {
val today = DateUtils.getTodayWithOffset() val today = DateUtils.getTodayWithOffset()
val offset = timestamp.daysUntil(today) - dataOffset val offset = timestamp.daysUntil(today) - dataOffset
val button = checkmarkPanel.buttons[offset] val button = checkmarkPanel.buttons[offset]
val y = button.height / 2.0f val y = button.height / 2.0f
val x = checkmarkPanel.x + button.x + (button.width / 2).toFloat() 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() { override fun onAttachedToWindow() {

View File

@@ -179,7 +179,7 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
putDouble("value", value) putDouble("value", value)
putString("notes", notes) 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") dialog.dismissCurrentAndShow(supportFragmentManager, "numberDialog")
} }
@@ -196,7 +196,7 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
putInt("value", selectedValue) putInt("value", selectedValue)
putString("notes", notes) 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") dialog.dismissCurrentAndShow(supportFragmentManager, "checkmarkDialog")
} }

View File

@@ -38,7 +38,7 @@ class BarCardView(context: Context, attrs: AttributeSet) : LinearLayout(context,
fun setState(state: BarCardState) { fun setState(state: BarCardState) {
val androidColor = state.theme.color(state.color).toInt() val androidColor = state.theme.color(state.color).toInt()
binding.chart.view = BarChart(state.theme, JavaLocalDateFormatter(Locale.US)).apply { binding.chart.view = BarChart(state.theme, JavaLocalDateFormatter(Locale.getDefault())).apply {
series = mutableListOf(state.entries.map { it.value / 1000.0 }) series = mutableListOf(state.entries.map { it.value / 1000.0 })
colors = mutableListOf(theme.color(state.color.paletteIndex)) colors = mutableListOf(theme.color(state.color.paletteIndex))
axis = state.entries.map { it.timestamp.toLocalDate() } axis = state.entries.map { it.timestamp.toLocalDate() }

View File

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

View File

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

View File

@@ -66,8 +66,14 @@ class ReminderController @Inject constructor(
} }
fun onDismiss(habit: Habit) { fun onDismiss(habit: 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) notificationTray.cancel(habit)
} }
}
private fun showSnoozeDelayPicker(habit: Habit, context: Context) { private fun showSnoozeDelayPicker(habit: Habit, context: Context) {
context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))

View File

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

View File

@@ -36,6 +36,13 @@ object ColorUtils {
return a or r or g or b 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 @JvmStatic
fun setAlpha(color: Int, newAlpha: Float): Int { fun setAlpha(color: Int, newAlpha: Float): Int {
val intAlpha = (newAlpha * 255).toInt() val intAlpha = (newAlpha * 255).toInt()

View File

@@ -26,6 +26,7 @@ import android.content.Intent
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.Color import android.graphics.Color
import android.graphics.Paint import android.graphics.Paint
import android.graphics.PointF
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
import android.os.Handler import android.os.Handler
import android.os.SystemClock import android.os.SystemClock
@@ -135,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 to = this.getString(toId)
val subject = this.getString(subjectId) val subject = this.getString(subjectId)
this.startActivity( this.startActivity(
@@ -232,3 +237,11 @@ fun View.requestFocusWithKeyboard() {
dispatchTouchEvent(MotionEvent.obtain(time, time, MotionEvent.ACTION_UP, 0f, 0f, 0)) dispatchTouchEvent(MotionEvent.obtain(time, time, MotionEvent.ACTION_UP, 0f, 0f, 0))
}, 250) }, 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

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

View File

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

View File

@@ -44,6 +44,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center" android:gravity="center"
android:textSize="@dimen/smallTextSize" android:textSize="@dimen/smallTextSize"
android:maxLines="2"
android:textColor="@color/white"/> android:textColor="@color/white"/>
</LinearLayout> </LinearLayout>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2016-2021 Álinson Santos Xavier <git@axavier.org>
~
~ This file is part of Loop Habit Tracker.
~
~ Loop Habit Tracker is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by the
~ Free Software Foundation, either version 3 of the License, or (at your
~ option) any later version.
~
~ Loop Habit Tracker is distributed in the hope that it will be useful, but
~ WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
~ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
~ more details.
~
~ You should have received a copy of the GNU General Public License along
~ with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<resources>
<color name="color_background">@color/grey_900</color>
</resources>

View File

@@ -89,4 +89,5 @@
</array> </array>
<color name="ic_launcher_background">#1976D2</color> <color name="ic_launcher_background">#1976D2</color>
<color name="color_background">@color/grey_200</color>
</resources> </resources>

View File

@@ -61,6 +61,7 @@
<item name="widgetShadowAlpha">0.25</item> <item name="widgetShadowAlpha">0.25</item>
<item name="windowActionModeOverlay">true</item> <item name="windowActionModeOverlay">true</item>
<item name="windowBackgroundColor">@color/grey_200</item> <item name="windowBackgroundColor">@color/grey_200</item>
<item name="android:colorBackground">@color/color_background</item>
<item name="android:textColorAlertDialogListItem">@color/grey_800</item> <item name="android:textColorAlertDialogListItem">@color/grey_800</item>
<item name="singleLineTitle">false</item> <item name="singleLineTitle">false</item>
<item name="dialogFormLabelColor">@color/white</item> <item name="dialogFormLabelColor">@color/white</item>

View File

@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
<locale android:name="en" /> <!-- English -->
<locale android:name="af-ZA" /> <!-- Afrikaans -->
<locale android:name="ar-SA" /> <!-- Arabic -->
<locale android:name="bg-BG" /> <!-- Bulgarian -->
<locale android:name="ca-ES" /> <!-- Catalan -->
<locale android:name="cs-CZ" /> <!-- Czech -->
<locale android:name="da-DK" /> <!-- Danish -->
<locale android:name="de-DE" /> <!-- German -->
<locale android:name="el-GR" /> <!-- Greek -->
<locale android:name="eo-UY" /> <!-- Esperanto -->
<locale android:name="es-ES" /> <!-- Spanish -->
<locale android:name="eu-ES" /> <!-- Basque -->
<locale android:name="fa-IR" /> <!-- Farsi -->
<locale android:name="fi-FI" /> <!-- Finnish -->
<locale android:name="fr-FR" /> <!-- French -->
<locale android:name="hi-IN" /> <!-- Hindi -->
<locale android:name="hr-HR" /> <!-- Croatian -->
<locale android:name="hu-HU" /> <!-- Hungarian -->
<locale android:name="hy-AM" /> <!-- Armenian -->
<locale android:name="in-ID" /> <!-- Indonesian -->
<locale android:name="it-IT" /> <!-- Italian -->
<locale android:name="iw-IL" /> <!-- Hebrew -->
<locale android:name="ja-JP" /> <!-- Japanese -->
<locale android:name="ko-KR" /> <!-- Korean -->
<locale android:name="nl-NL" /> <!-- Dutch -->
<locale android:name="no-NO" /> <!-- Norwegian -->
<locale android:name="pl-PL" /> <!-- Polish -->
<locale android:name="pt-BR" /> <!-- Portuguese (Brazil) -->
<locale android:name="pt-PT" /> <!-- Portuguese (Portugal) -->
<locale android:name="ro-RO" /> <!-- Romanian -->
<locale android:name="ru-RU" /> <!-- Russian -->
<locale android:name="sk-SK" /> <!-- Slovak -->
<locale android:name="sl-SL" /> <!-- Slovenian -->
<locale android:name="sr-CS" /> <!-- Serbian (Latin) -->
<locale android:name="sr-SP" /> <!-- Serbian (Cyrillic) -->
<locale android:name="sv-SE" /> <!-- Swedish -->
<locale android:name="ta-IN" /> <!-- Tamil -->
<locale android:name="te-IN" /> <!-- Telugu -->
<locale android:name="tr-TR" /> <!-- Turkish -->
<locale android:name="ug-CN" /> <!-- Uyghur -->
<locale android:name="uk-UA" /> <!-- Ukrainian -->
<locale android:name="vi-VN" /> <!-- Vietnamese -->
<locale android:name="zh-CN" /> <!-- Chinese (Simplified) -->
<locale android:name="zh-TW" /> <!-- Chinese (Traditional) -->
</locale-config>

View File

@@ -18,7 +18,6 @@
*/ */
package org.isoron.uhabits package org.isoron.uhabits
import com.nhaarman.mockitokotlin2.spy
import org.isoron.uhabits.core.commands.CommandRunner import org.isoron.uhabits.core.commands.CommandRunner
import org.isoron.uhabits.core.models.HabitList import org.isoron.uhabits.core.models.HabitList
import org.isoron.uhabits.core.models.memory.MemoryModelFactory import org.isoron.uhabits.core.models.memory.MemoryModelFactory
@@ -31,6 +30,7 @@ import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mockito.junit.MockitoJUnitRunner import org.mockito.junit.MockitoJUnitRunner
import org.mockito.kotlin.spy
@RunWith(MockitoJUnitRunner::class) @RunWith(MockitoJUnitRunner::class)
open class BaseAndroidJVMTest { open class BaseAndroidJVMTest {

View File

@@ -18,9 +18,6 @@
*/ */
package org.isoron.uhabits.receivers package org.isoron.uhabits.receivers
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions
import org.isoron.uhabits.BaseAndroidJVMTest import org.isoron.uhabits.BaseAndroidJVMTest
import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.Timestamp import org.isoron.uhabits.core.models.Timestamp
@@ -28,6 +25,9 @@ import org.isoron.uhabits.core.preferences.Preferences
import org.isoron.uhabits.core.reminders.ReminderScheduler import org.isoron.uhabits.core.reminders.ReminderScheduler
import org.isoron.uhabits.core.ui.NotificationTray import org.isoron.uhabits.core.ui.NotificationTray
import org.junit.Test import org.junit.Test
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyNoMoreInteractions
class ReminderControllerTest : BaseAndroidJVMTest() { class ReminderControllerTest : BaseAndroidJVMTest() {
private lateinit var controller: ReminderController private lateinit var controller: ReminderController

View File

@@ -24,6 +24,7 @@ plugins {
kotlin { kotlin {
jvm().withJava() jvm().withJava()
jvmToolchain(11)
sourceSets { sourceSets {
val commonMain by getting { val commonMain by getting {
@@ -43,14 +44,14 @@ kotlin {
val jvmMain by getting { val jvmMain by getting {
dependencies { dependencies {
implementation(kotlin("stdlib-jdk8")) implementation(kotlin("stdlib-jdk8"))
compileOnly("com.google.dagger:dagger:2.46") compileOnly("com.google.dagger:dagger:2.51.1")
implementation("com.google.guava:guava:31.1-android") implementation("com.google.guava:guava:33.1.0-android")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.6.4") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.3")
implementation("androidx.annotation:annotation:1.5.0") implementation("androidx.annotation:annotation:1.7.1")
implementation("com.google.code.findbugs:jsr305:3.0.2") implementation("com.google.code.findbugs:jsr305:3.0.2")
implementation("com.opencsv:opencsv:5.7.1") implementation("com.opencsv:opencsv:5.9")
implementation("commons-codec:commons-codec:1.15") implementation("commons-codec:commons-codec:1.16.0")
implementation("org.apache.commons:commons-lang3:3.12.0") implementation("org.apache.commons:commons-lang3:3.14.0")
} }
} }
@@ -58,10 +59,11 @@ kotlin {
dependencies { dependencies {
implementation(kotlin("test")) implementation(kotlin("test"))
implementation(kotlin("test-junit")) implementation(kotlin("test-junit"))
implementation("org.xerial:sqlite-jdbc:3.40.0.0") implementation("org.xerial:sqlite-jdbc:3.45.1.0")
implementation("org.hamcrest:hamcrest:2.2") implementation("org.hamcrest:hamcrest:2.2")
implementation("org.apache.commons:commons-io:1.3.2") implementation("org.apache.commons:commons-io:1.3.2")
implementation("com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0") implementation("org.mockito.kotlin:mockito-kotlin:5.2.1")
implementation("org.junit.jupiter:junit-jupiter:5.10.1")
} }
} }
} }

View File

@@ -82,8 +82,8 @@ class JavaUserFile(val path: Path) : UserFile {
@Suppress("NewApi") @Suppress("NewApi")
class JavaFileOpener : FileOpener { class JavaFileOpener : FileOpener {
override fun openUserFile(path: String): UserFile { override fun openUserFile(path: String): UserFile {
val path = Paths.get("/tmp/$path") val resolvedPath = Paths.get("/tmp/$path")
return JavaUserFile(path) return JavaUserFile(resolvedPath)
} }
override fun openResourceFile(path: String): ResourceFile { override fun openResourceFile(path: String): ResourceFile {

View File

@@ -45,14 +45,14 @@ internal class Tokenizer(
if (s == null || s.isEmpty()) { if (s == null || s.isEmpty()) {
return false return false
} }
if (s[0].toInt() != mCurrent) { if (s[0].code != mCurrent) {
return false return false
} }
val len = s.length val len = s.length
mStream.mark(len - 1) mStream.mark(len - 1)
for (n in 1 until len) { for (n in 1 until len) {
val value = mStream.read() val value = mStream.read()
if (value != s[n].toInt()) { if (value != s[n].code) {
mStream.reset() mStream.reset()
return false return false
} }
@@ -68,10 +68,9 @@ object SQLParser {
private const val STATE_COMMENT_BLOCK = 3 private const val STATE_COMMENT_BLOCK = 3
fun parse(stream: InputStream): List<String> { fun parse(stream: InputStream): List<String> {
val buffer = BufferedInputStream(stream)
val commands: MutableList<String> = ArrayList() val commands: MutableList<String> = ArrayList()
val sb = StringBuffer() val sb = StringBuffer()
buffer.use { buffer -> BufferedInputStream(stream).use { buffer ->
val tokenizer = Tokenizer(buffer) val tokenizer = Tokenizer(buffer)
var state = STATE_NONE var state = STATE_NONE
while (tokenizer.hasNext()) { while (tokenizer.hasNext()) {

View File

@@ -61,7 +61,7 @@ data class Habit(
return if (isNumerical) { return if (isNumerical) {
when (targetType) { when (targetType) {
NumericalHabitType.AT_LEAST -> value / 1000.0 >= targetValue NumericalHabitType.AT_LEAST -> value / 1000.0 >= targetValue
NumericalHabitType.AT_MOST -> value / 1000.0 <= targetValue NumericalHabitType.AT_MOST -> value != Entry.UNKNOWN && value / 1000.0 <= targetValue
} }
} else { } else {
value != Entry.NO && value != Entry.UNKNOWN value != Entry.NO && value != Entry.UNKNOWN

View File

@@ -115,6 +115,11 @@ class ReminderScheduler @Inject constructor(
for (habit in reminderHabits) schedule(habit) for (habit in reminderHabits) schedule(habit)
} }
@Synchronized
fun hasHabitsWithReminders(): Boolean {
return !habitList.getFiltered(HabitMatcher.WITH_ALARM).isEmpty
}
@Synchronized @Synchronized
fun startListening() { fun startListening() {
commandRunner.addListener(this) commandRunner.addListener(this)

View File

@@ -25,5 +25,5 @@ fun interface Task {
fun onAttached(runner: TaskRunner) {} fun onAttached(runner: TaskRunner) {}
fun onPostExecute() {} fun onPostExecute() {}
fun onPreExecute() {} fun onPreExecute() {}
fun onProgressUpdate(value: Int) {} fun onProgressUpdate(currentPosition: Int) {}
} }

View File

@@ -89,6 +89,12 @@ class NotificationTray @Inject constructor(
} }
} }
fun reshow(habit: Habit) {
active[habit]?.let {
taskRunner.execute(ShowNotificationTask(habit, it))
}
}
interface SystemTray { interface SystemTray {
fun removeNotification(notificationId: Int) fun removeNotification(notificationId: Int)
fun showNotification( fun showNotification(

View File

@@ -20,9 +20,12 @@ package org.isoron.uhabits.core.ui.screens.habits.list
import org.isoron.uhabits.core.commands.CommandRunner import org.isoron.uhabits.core.commands.CommandRunner
import org.isoron.uhabits.core.commands.CreateRepetitionCommand import org.isoron.uhabits.core.commands.CreateRepetitionCommand
import org.isoron.uhabits.core.models.Entry.Companion.YES_MANUAL
import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.HabitList import org.isoron.uhabits.core.models.HabitList
import org.isoron.uhabits.core.models.HabitType import org.isoron.uhabits.core.models.HabitType
import org.isoron.uhabits.core.models.NumericalHabitType.AT_LEAST
import org.isoron.uhabits.core.models.NumericalHabitType.AT_MOST
import org.isoron.uhabits.core.models.PaletteColor import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.models.Timestamp import org.isoron.uhabits.core.models.Timestamp
import org.isoron.uhabits.core.preferences.Preferences import org.isoron.uhabits.core.preferences.Preferences
@@ -52,8 +55,16 @@ open class ListHabitsBehavior @Inject constructor(
val entry = habit.computedEntries.get(timestamp!!) val entry = habit.computedEntries.get(timestamp!!)
if (habit.type == HabitType.NUMERICAL) { if (habit.type == HabitType.NUMERICAL) {
val oldValue = entry.value.toDouble() / 1000 val oldValue = entry.value.toDouble() / 1000
screen.showNumberPopup(oldValue, entry.notes) { newValue: Double, newNotes: String -> screen.showNumberPopup(oldValue, entry.notes) { newValue: Double, newNotes: String, x: Float, y: Float ->
val value = (newValue * 1000).roundToInt() val value = (newValue * 1000).roundToInt()
if (newValue != oldValue) {
if (
(habit.targetType == AT_LEAST && newValue >= habit.targetValue) ||
(habit.targetType == AT_MOST && newValue <= habit.targetValue)
) {
screen.showConfetti(habit.color, x, y)
}
}
commandRunner.run(CreateRepetitionCommand(habitList, habit, timestamp, value, newNotes)) commandRunner.run(CreateRepetitionCommand(habitList, habit, timestamp, value, newNotes))
} }
} else { } else {
@@ -61,7 +72,8 @@ open class ListHabitsBehavior @Inject constructor(
entry.value, entry.value,
entry.notes, entry.notes,
habit.color habit.color
) { newValue, newNotes -> ) { newValue: Int, newNotes: String, x: Float, y: Float ->
if (newValue != entry.value && newValue == YES_MANUAL) screen.showConfetti(habit.color, x, y)
commandRunner.run(CreateRepetitionCommand(habitList, habit, timestamp, newValue, newNotes)) commandRunner.run(CreateRepetitionCommand(habitList, habit, timestamp, newValue, newNotes))
} }
} }
@@ -117,10 +129,11 @@ open class ListHabitsBehavior @Inject constructor(
if (prefs.isFirstRun) onFirstRun() if (prefs.isFirstRun) onFirstRun()
} }
fun onToggle(habit: Habit, timestamp: Timestamp, value: Int, notes: String) { fun onToggle(habit: Habit, timestamp: Timestamp, value: Int, notes: String, x: Float, y: Float) {
commandRunner.run( commandRunner.run(
CreateRepetitionCommand(habitList, habit, timestamp, value, notes) CreateRepetitionCommand(habitList, habit, timestamp, value, notes)
) )
if (value == YES_MANUAL) screen.showConfetti(habit.color, x, y)
} }
enum class Message { enum class Message {
@@ -144,12 +157,22 @@ open class ListHabitsBehavior @Inject constructor(
} }
fun interface NumberPickerCallback { fun interface NumberPickerCallback {
fun onNumberPicked(newValue: Double, notes: String) fun onNumberPicked(
newValue: Double,
notes: String,
x: Float,
y: Float
)
fun onNumberPickerDismissed() {} fun onNumberPickerDismissed() {}
} }
fun interface CheckMarkDialogCallback { fun interface CheckMarkDialogCallback {
fun onNotesSaved(value: Int, notes: String) fun onNotesSaved(
value: Int,
notes: String,
x: Float,
y: Float
)
fun onNotesDismissed() {} fun onNotesDismissed() {}
} }
@@ -170,5 +193,6 @@ open class ListHabitsBehavior @Inject constructor(
) )
fun showSendBugReportToDeveloperScreen(log: String) fun showSendBugReportToDeveloperScreen(log: String)
fun showSendFileScreen(filename: String) fun showSendFileScreen(filename: String)
fun showConfetti(color: PaletteColor, x: Float, y: Float)
} }
} }

View File

@@ -98,7 +98,7 @@ class HistoryCardPresenter(
entry.value, entry.value,
entry.notes, entry.notes,
habit.color habit.color
) { newValue, newNotes -> ) { newValue, newNotes, _: Float, _: Float ->
commandRunner.run( commandRunner.run(
CreateRepetitionCommand( CreateRepetitionCommand(
habitList, habitList,
@@ -135,7 +135,7 @@ class HistoryCardPresenter(
screen.showNumberPopup( screen.showNumberPopup(
value = oldValue / 1000.0, value = oldValue / 1000.0,
notes = entry.notes notes = entry.notes
) { newValue: Double, newNotes: String -> ) { newValue: Double, newNotes: String, _: Float, _: Float ->
val thousands = (newValue * 1000).roundToInt() val thousands = (newValue * 1000).roundToInt()
commandRunner.run( commandRunner.run(
CreateRepetitionCommand( CreateRepetitionCommand(

View File

@@ -49,7 +49,7 @@ class HabitListHeader(
repeat(nButtons) { index -> repeat(nButtons) { index ->
val date = today.minus(nButtons - index - 1) val date = today.minus(nButtons - index - 1)
val name = fmt.shortWeekdayName(date).toUpperCase() val name = fmt.shortWeekdayName(date).uppercase()
val number = date.day.toString() val number = date.day.toString()
val x = width - (index + 1) * buttonSize + buttonSize / 2 val x = width - (index + 1) * buttonSize + buttonSize / 2

View File

@@ -125,4 +125,30 @@ class WidgetTheme : LightTheme() {
override val highContrastTextColor = Color.WHITE override val highContrastTextColor = Color.WHITE
override val mediumContrastTextColor = Color.WHITE.withAlpha(0.50) override val mediumContrastTextColor = Color.WHITE.withAlpha(0.50)
override val lowContrastTextColor = Color.WHITE.withAlpha(0.10) override val lowContrastTextColor = Color.WHITE.withAlpha(0.10)
override fun color(paletteIndex: Int): Color {
return when (paletteIndex) {
0 -> Color(0xD32F2F)
1 -> Color(0xE64A19)
2 -> Color(0xF57C00)
3 -> Color(0xFF8F00)
4 -> Color(0xF9A825)
5 -> Color(0xAFB42B)
6 -> Color(0x7CB342)
7 -> Color(0x388E3C)
8 -> Color(0x00897B)
9 -> Color(0x00ACC1)
10 -> Color(0x039BE5)
11 -> Color(0x1976D2)
12 -> Color(0x6275f0)
13 -> Color(0x5E35B1)
14 -> Color(0x8E24AA)
15 -> Color(0xD81B60)
16 -> Color(0x5D4037)
17 -> Color(0x757575)
18 -> Color(0x757575)
19 -> Color(0x9E9E9E)
else -> Color(0x000000)
}
}
} }

View File

@@ -227,7 +227,7 @@ abstract class DateUtils {
fun getStartOfTodayWithOffset(): Long = getStartOfDayWithOffset(getLocalTime()) fun getStartOfTodayWithOffset(): Long = getStartOfDayWithOffset(getLocalTime())
@JvmStatic @JvmStatic
fun millisecondsUntilTomorrowWithOffset(): Long = getStartOfTomorrowWithOffset() - getLocalTime() fun millisecondsUntilTomorrowWithOffset(): Long = getStartOfTomorrowWithOffset() - applyTimezone(getLocalTime())
@JvmStatic @JvmStatic
fun getStartOfTodayCalendar(): GregorianCalendar = getCalendar(getStartOfToday()) fun getStartOfTodayCalendar(): GregorianCalendar = getCalendar(getStartOfToday())

View File

@@ -19,6 +19,7 @@
package org.isoron.uhabits.core.utils package org.isoron.uhabits.core.utils
import org.isoron.uhabits.core.AppScope import org.isoron.uhabits.core.AppScope
import org.isoron.uhabits.core.io.Logging
import java.util.LinkedList import java.util.LinkedList
import java.util.concurrent.Executors import java.util.concurrent.Executors
import java.util.concurrent.ScheduledExecutorService import java.util.concurrent.ScheduledExecutorService
@@ -29,9 +30,10 @@ import javax.inject.Inject
* A class that emits events when a new day starts. * A class that emits events when a new day starts.
*/ */
@AppScope @AppScope
open class MidnightTimer @Inject constructor() { open class MidnightTimer @Inject constructor(logging: Logging) {
private val listeners: MutableList<MidnightListener> = LinkedList() private val listeners: MutableList<MidnightListener> = LinkedList()
private lateinit var executor: ScheduledExecutorService private lateinit var executor: ScheduledExecutorService
private val logger = logging.getLogger("MidnightTimer")
@Synchronized @Synchronized
fun addListener(listener: MidnightListener) { fun addListener(listener: MidnightListener) {
@@ -39,7 +41,10 @@ open class MidnightTimer @Inject constructor() {
} }
@Synchronized @Synchronized
fun onPause(): MutableList<Runnable>? = executor.shutdownNow() fun onPause(): MutableList<Runnable>? {
logger.info("Pausing timer")
return executor.shutdownNow()
}
@Synchronized @Synchronized
fun onResume( fun onResume(
@@ -47,9 +52,11 @@ open class MidnightTimer @Inject constructor() {
testExecutor: ScheduledExecutorService? = null testExecutor: ScheduledExecutorService? = null
) { ) {
executor = testExecutor ?: Executors.newSingleThreadScheduledExecutor() executor = testExecutor ?: Executors.newSingleThreadScheduledExecutor()
val initialDelay = DateUtils.millisecondsUntilTomorrowWithOffset() + delayOffsetInMillis
logger.info("Scheduling refresh for $initialDelay ms from now")
executor.scheduleAtFixedRate( executor.scheduleAtFixedRate(
{ notifyListeners() }, { notifyListeners() },
DateUtils.millisecondsUntilTomorrowWithOffset() + delayOffsetInMillis, initialDelay,
DateUtils.DAY_LENGTH, DateUtils.DAY_LENGTH,
TimeUnit.MILLISECONDS TimeUnit.MILLISECONDS
) )
@@ -60,6 +67,7 @@ open class MidnightTimer @Inject constructor() {
@Synchronized @Synchronized
private fun notifyListeners() { private fun notifyListeners() {
logger.info("Midnight refresh")
for (l in listeners) { for (l in listeners) {
l.atMidnight() l.atMidnight()
} }

View File

@@ -19,9 +19,9 @@
package org.isoron.platform.gui package org.isoron.platform.gui
import junit.framework.Assert.fail
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.isoron.platform.io.JavaFileOpener import org.isoron.platform.io.JavaFileOpener
import org.junit.Assert.fail
import org.junit.Test import org.junit.Test
import java.awt.image.BufferedImage import java.awt.image.BufferedImage
import java.awt.image.BufferedImage.TYPE_INT_ARGB import java.awt.image.BufferedImage.TYPE_INT_ARGB

View File

@@ -18,8 +18,6 @@
*/ */
package org.isoron.uhabits.core package org.isoron.uhabits.core
import com.nhaarman.mockitokotlin2.spy
import com.nhaarman.mockitokotlin2.validateMockitoUsage
import org.apache.commons.io.IOUtils import org.apache.commons.io.IOUtils
import org.isoron.uhabits.core.commands.CommandRunner import org.isoron.uhabits.core.commands.CommandRunner
import org.isoron.uhabits.core.database.Database import org.isoron.uhabits.core.database.Database
@@ -40,6 +38,8 @@ import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mockito.junit.MockitoJUnitRunner import org.mockito.junit.MockitoJUnitRunner
import org.mockito.kotlin.spy
import org.mockito.kotlin.validateMockitoUsage
import java.io.File import java.io.File
import java.io.FileInputStream import java.io.FileInputStream
import java.io.FileOutputStream import java.io.FileOutputStream

View File

@@ -18,12 +18,12 @@
*/ */
package org.isoron.uhabits.core.commands package org.isoron.uhabits.core.commands
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
import org.isoron.uhabits.core.BaseUnitTest import org.isoron.uhabits.core.BaseUnitTest
import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.models.Habit
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class ArchiveHabitsCommandTest : BaseUnitTest() { class ArchiveHabitsCommandTest : BaseUnitTest() {
private lateinit var command: ArchiveHabitsCommand private lateinit var command: ArchiveHabitsCommand

View File

@@ -18,7 +18,6 @@
*/ */
package org.isoron.uhabits.core.commands package org.isoron.uhabits.core.commands
import junit.framework.Assert.assertTrue
import org.hamcrest.CoreMatchers.equalTo import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.MatcherAssert.assertThat
import org.isoron.uhabits.core.BaseUnitTest import org.isoron.uhabits.core.BaseUnitTest
@@ -27,6 +26,7 @@ import org.isoron.uhabits.core.models.Reminder
import org.isoron.uhabits.core.models.WeekdayList import org.isoron.uhabits.core.models.WeekdayList
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import kotlin.test.assertTrue
class CreateHabitCommandTest : BaseUnitTest() { class CreateHabitCommandTest : BaseUnitTest() {
private lateinit var command: CreateHabitCommand private lateinit var command: CreateHabitCommand

View File

@@ -18,7 +18,6 @@
*/ */
package org.isoron.uhabits.core.commands package org.isoron.uhabits.core.commands
import junit.framework.Assert.assertEquals
import org.isoron.uhabits.core.BaseUnitTest import org.isoron.uhabits.core.BaseUnitTest
import org.isoron.uhabits.core.models.Entry import org.isoron.uhabits.core.models.Entry
import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.models.Habit
@@ -26,6 +25,7 @@ import org.isoron.uhabits.core.models.Timestamp
import org.isoron.uhabits.core.utils.DateUtils.Companion.getToday import org.isoron.uhabits.core.utils.DateUtils.Companion.getToday
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import kotlin.test.assertEquals
class CreateRepetitionCommandTest : BaseUnitTest() { class CreateRepetitionCommandTest : BaseUnitTest() {
private lateinit var command: CreateRepetitionCommand private lateinit var command: CreateRepetitionCommand

View File

@@ -23,18 +23,13 @@ import org.hamcrest.MatcherAssert.assertThat
import org.isoron.uhabits.core.BaseUnitTest import org.isoron.uhabits.core.BaseUnitTest
import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.models.Habit
import org.junit.Before import org.junit.Before
import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.rules.ExpectedException import java.util.*
import java.util.LinkedList
class DeleteHabitsCommandTest : BaseUnitTest() { class DeleteHabitsCommandTest : BaseUnitTest() {
private lateinit var command: DeleteHabitsCommand private lateinit var command: DeleteHabitsCommand
private lateinit var selected: LinkedList<Habit> private lateinit var selected: LinkedList<Habit>
@get:Rule
var thrown = ExpectedException.none()!!
@Before @Before
@Throws(Exception::class) @Throws(Exception::class)
override fun setUp() { override fun setUp() {

View File

@@ -18,12 +18,12 @@
*/ */
package org.isoron.uhabits.core.commands package org.isoron.uhabits.core.commands
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
import org.isoron.uhabits.core.BaseUnitTest import org.isoron.uhabits.core.BaseUnitTest
import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.models.Habit
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class UnarchiveHabitsCommandTest : BaseUnitTest() { class UnarchiveHabitsCommandTest : BaseUnitTest() {
private lateinit var command: UnarchiveHabitsCommand private lateinit var command: UnarchiveHabitsCommand

View File

@@ -18,7 +18,6 @@
*/ */
package org.isoron.uhabits.core.database package org.isoron.uhabits.core.database
import junit.framework.Assert.assertNull
import org.apache.commons.lang3.builder.EqualsBuilder import org.apache.commons.lang3.builder.EqualsBuilder
import org.apache.commons.lang3.builder.HashCodeBuilder import org.apache.commons.lang3.builder.HashCodeBuilder
import org.apache.commons.lang3.builder.ToStringBuilder import org.apache.commons.lang3.builder.ToStringBuilder
@@ -27,6 +26,7 @@ import org.hamcrest.core.IsEqual.equalTo
import org.isoron.uhabits.core.BaseUnitTest import org.isoron.uhabits.core.BaseUnitTest
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import kotlin.test.assertNull
class RepositoryTest : BaseUnitTest() { class RepositoryTest : BaseUnitTest() {
private lateinit var repository: Repository<ThingRecord> private lateinit var repository: Repository<ThingRecord>

View File

@@ -27,13 +27,10 @@ import org.isoron.uhabits.core.database.Database
import org.isoron.uhabits.core.database.MigrationHelper import org.isoron.uhabits.core.database.MigrationHelper
import org.isoron.uhabits.core.models.sqlite.SQLModelFactory import org.isoron.uhabits.core.models.sqlite.SQLModelFactory
import org.isoron.uhabits.core.test.HabitFixtures import org.isoron.uhabits.core.test.HabitFixtures
import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.rules.ExpectedException import org.junit.jupiter.api.Assertions.assertThrows
class Version22Test : BaseUnitTest() { class Version22Test : BaseUnitTest() {
@get:Rule
var exception = ExpectedException.none()!!
private lateinit var db: Database private lateinit var db: Database
private lateinit var helper: MigrationHelper private lateinit var helper: MigrationHelper
@@ -76,8 +73,8 @@ class Version22Test : BaseUnitTest() {
@Throws(Exception::class) @Throws(Exception::class)
fun testDisallowNewRepsWithInvalidRef() { fun testDisallowNewRepsWithInvalidRef() {
helper.migrateTo(22) helper.migrateTo(22)
exception.expectMessage(Matchers.containsString("SQLITE_CONSTRAINT")) val exception = assertThrows(java.lang.RuntimeException::class.java) { db.execute("insert into Repetitions(habit, timestamp, value) values (99999, 100, 2)") }
db.execute("insert into Repetitions(habit, timestamp, value) values (99999, 100, 2)") assertThat(exception.message, Matchers.containsString("SQLITE_CONSTRAINT"))
} }
@Test @Test
@@ -97,10 +94,14 @@ class Version22Test : BaseUnitTest() {
@Throws(Exception::class) @Throws(Exception::class)
fun testDisallowNullTimestamp() { fun testDisallowNullTimestamp() {
helper.migrateTo(22) helper.migrateTo(22)
exception.expectMessage(Matchers.containsString("SQLITE_CONSTRAINT"))
val exception = assertThrows(java.lang.RuntimeException::class.java) {
db.execute("insert into Repetitions(habit, value) " + "values (0, 2)") db.execute("insert into Repetitions(habit, value) " + "values (0, 2)")
} }
assertThat(exception.message, Matchers.containsString("SQLITE_CONSTRAINT"))
}
@Test @Test
@Throws(Exception::class) @Throws(Exception::class)
fun testRemoveRepetitionsWithNullHabit() { fun testRemoveRepetitionsWithNullHabit() {
@@ -118,10 +119,14 @@ class Version22Test : BaseUnitTest() {
@Throws(Exception::class) @Throws(Exception::class)
fun testDisallowNullHabit() { fun testDisallowNullHabit() {
helper.migrateTo(22) helper.migrateTo(22)
exception.expectMessage(Matchers.containsString("SQLITE_CONSTRAINT"))
val exception = assertThrows(java.lang.RuntimeException::class.java) {
db.execute("insert into Repetitions(timestamp, value) " + "values (5, 2)") db.execute("insert into Repetitions(timestamp, value) " + "values (5, 2)")
} }
assertThat(exception.message, Matchers.containsString("SQLITE_CONSTRAINT"))
}
@Test @Test
@Throws(Exception::class) @Throws(Exception::class)
fun testRemoveDuplicateRepetitions() { fun testRemoveDuplicateRepetitions() {
@@ -142,7 +147,11 @@ class Version22Test : BaseUnitTest() {
fun testDisallowNewDuplicateTimestamps() { fun testDisallowNewDuplicateTimestamps() {
helper.migrateTo(22) helper.migrateTo(22)
db.execute("insert into repetitions(habit, timestamp, value)values (0, 100, 2)") db.execute("insert into repetitions(habit, timestamp, value)values (0, 100, 2)")
exception.expectMessage(Matchers.containsString("SQLITE_CONSTRAINT"))
val exception = assertThrows(java.lang.RuntimeException::class.java) {
db.execute("insert into repetitions(habit, timestamp, value)values (0, 100, 5)") db.execute("insert into repetitions(habit, timestamp, value)values (0, 100, 5)")
} }
assertThat(exception.message, Matchers.containsString("SQLITE_CONSTRAINT"))
}
} }

View File

@@ -18,7 +18,6 @@
*/ */
package org.isoron.uhabits.core.io package org.isoron.uhabits.core.io
import junit.framework.Assert.assertTrue
import org.apache.commons.io.FileUtils import org.apache.commons.io.FileUtils
import org.apache.commons.io.IOUtils import org.apache.commons.io.IOUtils
import org.isoron.uhabits.core.BaseUnitTest import org.isoron.uhabits.core.BaseUnitTest
@@ -29,8 +28,9 @@ import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
import java.io.IOException import java.io.IOException
import java.nio.file.Files import java.nio.file.Files
import java.util.LinkedList import java.util.*
import java.util.zip.ZipFile import java.util.zip.ZipFile
import kotlin.test.assertTrue
class HabitsCSVExporterTest : BaseUnitTest() { class HabitsCSVExporterTest : BaseUnitTest() {
private lateinit var baseDir: File private lateinit var baseDir: File
@@ -102,9 +102,8 @@ class HabitsCSVExporterTest : BaseUnitTest() {
private fun assertAbsolutePathExists(s: String) { private fun assertAbsolutePathExists(s: String) {
val file = File(s) val file = File(s)
assertTrue( assertTrue(
String.format("File %s should exist", file.absolutePath), String.format("File %s should exist", file.absolutePath)
file.exists() ) { file.exists() }
)
} }
private fun assertFileAndReferenceAreEqual(s: String) { private fun assertFileAndReferenceAreEqual(s: String) {

View File

@@ -18,8 +18,6 @@
*/ */
package org.isoron.uhabits.core.io package org.isoron.uhabits.core.io
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.core.IsEqual.equalTo import org.hamcrest.core.IsEqual.equalTo
import org.isoron.uhabits.core.BaseUnitTest import org.isoron.uhabits.core.BaseUnitTest
@@ -34,6 +32,8 @@ import org.junit.Before
import org.junit.Test import org.junit.Test
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class ImportTest : BaseUnitTest() { class ImportTest : BaseUnitTest() {
@Before @Before

View File

@@ -18,23 +18,19 @@
*/ */
package org.isoron.uhabits.core.models package org.isoron.uhabits.core.models
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertNull
import org.hamcrest.CoreMatchers.equalTo import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.CoreMatchers.not import org.hamcrest.CoreMatchers.not
import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.MatcherAssert.assertThat
import org.isoron.uhabits.core.BaseUnitTest import org.isoron.uhabits.core.BaseUnitTest
import org.junit.Rule import org.junit.Assert.assertThrows
import org.junit.Test import org.junit.Test
import org.junit.rules.ExpectedException
import java.io.IOException import java.io.IOException
import java.io.StringWriter import java.io.StringWriter
import java.util.ArrayList import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNull
class HabitListTest : BaseUnitTest() { class HabitListTest : BaseUnitTest() {
@get:Rule
var thrown = ExpectedException.none()!!
private lateinit var habitsArray: ArrayList<Habit> private lateinit var habitsArray: ArrayList<Habit>
private lateinit var activeHabits: HabitList private lateinit var activeHabits: HabitList
private lateinit var reminderHabits: HabitList private lateinit var reminderHabits: HabitList
@@ -173,9 +169,10 @@ class HabitListTest : BaseUnitTest() {
fun testReorder_withInvalidArguments() { fun testReorder_withInvalidArguments() {
val h1 = habitsArray[0] val h1 = habitsArray[0]
val h2 = fixtures.createEmptyHabit() val h2 = fixtures.createEmptyHabit()
thrown.expect(IllegalArgumentException::class.java) assertThrows(IllegalArgumentException::class.java) {
habitList.reorder(h1, h2) habitList.reorder(h1, h2)
} }
}
@Test @Test
fun testOrder_inherit() { fun testOrder_inherit() {
@@ -235,25 +232,28 @@ class HabitListTest : BaseUnitTest() {
@Test @Test
@Throws(Exception::class) @Throws(Exception::class)
fun testAdd_withFilteredList() { fun testAdd_withFilteredList() {
thrown.expect(IllegalStateException::class.java) assertThrows(IllegalStateException::class.java) {
activeHabits.add(fixtures.createEmptyHabit()) activeHabits.add(fixtures.createEmptyHabit())
} }
}
@Test @Test
@Throws(Exception::class) @Throws(Exception::class)
fun testRemove_onFilteredList() { fun testRemove_onFilteredList() {
thrown.expect(IllegalStateException::class.java) assertThrows(IllegalStateException::class.java) {
activeHabits.remove(fixtures.createEmptyHabit()) activeHabits.remove(fixtures.createEmptyHabit())
} }
}
@Test @Test
@Throws(Exception::class) @Throws(Exception::class)
fun testReorder_onFilteredList() { fun testReorder_onFilteredList() {
val h1 = fixtures.createEmptyHabit() val h1 = fixtures.createEmptyHabit()
val h2 = fixtures.createEmptyHabit() val h2 = fixtures.createEmptyHabit()
thrown.expect(IllegalStateException::class.java) assertThrows(IllegalStateException::class.java) {
activeHabits.reorder(h1, h2) activeHabits.reorder(h1, h2)
} }
}
@Test @Test
@Throws(Exception::class) @Throws(Exception::class)
@@ -261,7 +261,8 @@ class HabitListTest : BaseUnitTest() {
habitList.primaryOrder = HabitList.Order.BY_SCORE_DESC habitList.primaryOrder = HabitList.Order.BY_SCORE_DESC
val h1 = habitsArray[1] val h1 = habitsArray[1]
val h2 = habitsArray[2] val h2 = habitsArray[2]
thrown.expect(IllegalStateException::class.java) assertThrows(IllegalStateException::class.java) {
habitList.reorder(h1, h2) habitList.reorder(h1, h2)
} }
}
} }

View File

@@ -18,21 +18,18 @@
*/ */
package org.isoron.uhabits.core.models package org.isoron.uhabits.core.models
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
import org.hamcrest.CoreMatchers.`is` import org.hamcrest.CoreMatchers.`is`
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.core.IsEqual.equalTo import org.hamcrest.core.IsEqual.equalTo
import org.isoron.uhabits.core.BaseUnitTest import org.isoron.uhabits.core.BaseUnitTest
import org.isoron.uhabits.core.utils.DateUtils.Companion.getToday import org.isoron.uhabits.core.utils.DateUtils.Companion.getToday
import org.junit.Assert.assertNotEquals import org.junit.Assert.assertNotEquals
import org.junit.Assert.assertThat
import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.rules.ExpectedException import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class HabitTest : BaseUnitTest() { class HabitTest : BaseUnitTest() {
@get:Rule
val exception = ExpectedException.none()!!
@Throws(Exception::class) @Throws(Exception::class)
override fun setUp() { override fun setUp() {
@@ -55,7 +52,7 @@ class HabitTest : BaseUnitTest() {
model.reminder = Reminder(8, 30, WeekdayList(1)) model.reminder = Reminder(8, 30, WeekdayList(1))
val habit = modelFactory.buildHabit() val habit = modelFactory.buildHabit()
habit.copyFrom(model) habit.copyFrom(model)
assertTrue(habit.isArchived == model.isArchived) assertEquals(habit.isArchived, model.isArchived)
assertThat(habit.isArchived, `is`(model.isArchived)) assertThat(habit.isArchived, `is`(model.isArchived))
assertThat(habit.color, `is`(model.color)) assertThat(habit.color, `is`(model.color))
assertThat(habit.frequency, equalTo(model.frequency)) assertThat(habit.frequency, equalTo(model.frequency))

View File

@@ -18,7 +18,6 @@
*/ */
package org.isoron.uhabits.core.models package org.isoron.uhabits.core.models
import junit.framework.Assert.assertTrue
import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.number.IsCloseTo import org.hamcrest.number.IsCloseTo
import org.hamcrest.number.OrderingComparison import org.hamcrest.number.OrderingComparison
@@ -28,6 +27,7 @@ import org.isoron.uhabits.core.utils.DateUtils.Companion.getToday
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import java.util.ArrayList import java.util.ArrayList
import kotlin.test.assertTrue
open class BaseScoreListTest : BaseUnitTest() { open class BaseScoreListTest : BaseUnitTest() {
protected lateinit var habit: Habit protected lateinit var habit: Habit

View File

@@ -18,8 +18,6 @@
*/ */
package org.isoron.uhabits.core.models package org.isoron.uhabits.core.models
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.equalTo import org.hamcrest.Matchers.equalTo
import org.hamcrest.Matchers.greaterThan import org.hamcrest.Matchers.greaterThan
@@ -27,6 +25,8 @@ import org.hamcrest.Matchers.lessThan
import org.isoron.uhabits.core.BaseUnitTest import org.isoron.uhabits.core.BaseUnitTest
import org.isoron.uhabits.core.utils.DateUtils.Companion.getToday import org.isoron.uhabits.core.utils.DateUtils.Companion.getToday
import org.junit.Test import org.junit.Test
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class TimestampTest : BaseUnitTest() { class TimestampTest : BaseUnitTest() {
@Test @Test

View File

@@ -18,12 +18,12 @@
*/ */
package org.isoron.uhabits.core.models package org.isoron.uhabits.core.models
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
import org.hamcrest.CoreMatchers.equalTo import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.MatcherAssert.assertThat
import org.isoron.uhabits.core.BaseUnitTest import org.isoron.uhabits.core.BaseUnitTest
import org.junit.Test import org.junit.Test
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class WeekdayListTest : BaseUnitTest() { class WeekdayListTest : BaseUnitTest() {
@Test @Test

View File

@@ -19,8 +19,6 @@
package org.isoron.uhabits.core.models.sqlite package org.isoron.uhabits.core.models.sqlite
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertNull
import org.isoron.uhabits.core.BaseUnitTest.Companion.buildMemoryDatabase import org.isoron.uhabits.core.BaseUnitTest.Companion.buildMemoryDatabase
import org.isoron.uhabits.core.database.Repository import org.isoron.uhabits.core.database.Repository
import org.isoron.uhabits.core.models.Entry import org.isoron.uhabits.core.models.Entry
@@ -30,6 +28,8 @@ import org.isoron.uhabits.core.models.sqlite.records.EntryRecord
import org.isoron.uhabits.core.utils.DateUtils import org.isoron.uhabits.core.utils.DateUtils
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertNull
class SQLiteEntryListTest { class SQLiteEntryListTest {

View File

@@ -18,9 +18,6 @@
*/ */
package org.isoron.uhabits.core.models.sqlite package org.isoron.uhabits.core.models.sqlite
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.verify
import junit.framework.Assert.assertNull
import org.hamcrest.CoreMatchers.equalTo import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.MatcherAssert.assertThat
import org.isoron.uhabits.core.BaseUnitTest import org.isoron.uhabits.core.BaseUnitTest
@@ -34,14 +31,14 @@ import org.isoron.uhabits.core.models.Reminder
import org.isoron.uhabits.core.models.WeekdayList import org.isoron.uhabits.core.models.WeekdayList
import org.isoron.uhabits.core.models.sqlite.records.HabitRecord import org.isoron.uhabits.core.models.sqlite.records.HabitRecord
import org.isoron.uhabits.core.test.HabitFixtures import org.isoron.uhabits.core.test.HabitFixtures
import org.junit.Rule import org.junit.Assert.assertThrows
import org.junit.Test import org.junit.Test
import org.junit.rules.ExpectedException import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import java.util.ArrayList import java.util.ArrayList
import kotlin.test.assertNull
class SQLiteHabitListTest : BaseUnitTest() { class SQLiteHabitListTest : BaseUnitTest() {
@get:Rule
var exception = ExpectedException.none()!!
private lateinit var repository: Repository<HabitRecord> private lateinit var repository: Repository<HabitRecord>
private var listener: ModelObservable.Listener = mock() private var listener: ModelObservable.Listener = mock()
private lateinit var habitsArray: ArrayList<Habit> private lateinit var habitsArray: ArrayList<Habit>
@@ -90,9 +87,10 @@ class SQLiteHabitListTest : BaseUnitTest() {
val habit = modelFactory.buildHabit() val habit = modelFactory.buildHabit()
habitList.add(habit) habitList.add(habit)
verify(listener).onModelChange() verify(listener).onModelChange()
exception.expect(IllegalArgumentException::class.java) assertThrows(IllegalArgumentException::class.java) {
habitList.add(habit) habitList.add(habit)
} }
}
@Test @Test
fun testAdd_withId() { fun testAdd_withId() {

View File

@@ -18,10 +18,6 @@
*/ */
package org.isoron.uhabits.core.preferences package org.isoron.uhabits.core.preferences
import com.nhaarman.mockitokotlin2.mock
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertNull
import junit.framework.Assert.assertTrue
import org.hamcrest.CoreMatchers.equalTo import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.MatcherAssert.assertThat
import org.isoron.uhabits.core.BaseUnitTest import org.isoron.uhabits.core.BaseUnitTest
@@ -30,7 +26,11 @@ import org.isoron.uhabits.core.models.Timestamp.Companion.ZERO
import org.isoron.uhabits.core.ui.ThemeSwitcher import org.isoron.uhabits.core.ui.ThemeSwitcher
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.mockito.kotlin.mock
import java.io.File import java.io.File
import kotlin.test.assertFalse
import kotlin.test.assertNull
import kotlin.test.assertTrue
class PreferencesTest : BaseUnitTest() { class PreferencesTest : BaseUnitTest() {
private lateinit var prefs: Preferences private lateinit var prefs: Preferences

View File

@@ -18,9 +18,6 @@
*/ */
package org.isoron.uhabits.core.preferences package org.isoron.uhabits.core.preferences
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
import org.hamcrest.CoreMatchers.equalTo import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.MatcherAssert.assertThat
import org.isoron.uhabits.core.BaseUnitTest import org.isoron.uhabits.core.BaseUnitTest
@@ -28,6 +25,9 @@ import org.junit.Before
import org.junit.Test import org.junit.Test
import java.io.File import java.io.File
import java.util.Arrays import java.util.Arrays
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class PropertiesStorageTest : BaseUnitTest() { class PropertiesStorageTest : BaseUnitTest() {
private lateinit var storage: PropertiesStorage private lateinit var storage: PropertiesStorage

View File

@@ -18,10 +18,6 @@
*/ */
package org.isoron.uhabits.core.reminders package org.isoron.uhabits.core.reminders
import com.nhaarman.mockitokotlin2.eq
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.whenever
import org.isoron.uhabits.core.BaseUnitTest import org.isoron.uhabits.core.BaseUnitTest
import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.Reminder import org.isoron.uhabits.core.models.Reminder
@@ -37,6 +33,10 @@ import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyLong import org.mockito.ArgumentMatchers.anyLong
import org.mockito.junit.MockitoJUnitRunner import org.mockito.junit.MockitoJUnitRunner
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import java.util.Calendar import java.util.Calendar
import java.util.TimeZone import java.util.TimeZone

View File

@@ -18,12 +18,12 @@
*/ */
package org.isoron.uhabits.core.tasks package org.isoron.uhabits.core.tasks
import com.nhaarman.mockitokotlin2.inOrder
import com.nhaarman.mockitokotlin2.mock
import org.isoron.uhabits.core.BaseUnitTest import org.isoron.uhabits.core.BaseUnitTest
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.junit.runners.JUnit4 import org.junit.runners.JUnit4
import org.mockito.kotlin.inOrder
import org.mockito.kotlin.mock
@RunWith(JUnit4::class) @RunWith(JUnit4::class)
class SingleThreadTaskRunnerTest : BaseUnitTest() { class SingleThreadTaskRunnerTest : BaseUnitTest() {

View File

@@ -18,10 +18,6 @@
*/ */
package org.isoron.uhabits.core.ui.screens.habits.list package org.isoron.uhabits.core.ui.screens.habits.list
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.reset
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions
import org.hamcrest.CoreMatchers.equalTo import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.MatcherAssert.assertThat
import org.isoron.uhabits.core.BaseUnitTest import org.isoron.uhabits.core.BaseUnitTest
@@ -30,6 +26,10 @@ import org.isoron.uhabits.core.commands.DeleteHabitsCommand
import org.isoron.uhabits.core.models.Entry import org.isoron.uhabits.core.models.Entry
import org.isoron.uhabits.core.utils.DateUtils.Companion.getToday import org.isoron.uhabits.core.utils.DateUtils.Companion.getToday
import org.junit.Test import org.junit.Test
import org.mockito.kotlin.mock
import org.mockito.kotlin.reset
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyNoMoreInteractions
class HabitCardListCacheTest : BaseUnitTest() { class HabitCardListCacheTest : BaseUnitTest() {
private lateinit var cache: HabitCardListCache private lateinit var cache: HabitCardListCache

View File

@@ -18,12 +18,6 @@
*/ */
package org.isoron.uhabits.core.ui.screens.habits.list package org.isoron.uhabits.core.ui.screens.habits.list
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.whenever
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertNull
import junit.framework.Assert.assertTrue
import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.equalTo import org.hamcrest.Matchers.equalTo
import org.isoron.uhabits.core.BaseUnitTest import org.isoron.uhabits.core.BaseUnitTest
@@ -31,6 +25,12 @@ import org.isoron.uhabits.core.models.Timestamp
import org.isoron.uhabits.core.preferences.Preferences import org.isoron.uhabits.core.preferences.Preferences
import org.isoron.uhabits.core.utils.DateUtils.Companion.getToday import org.isoron.uhabits.core.utils.DateUtils.Companion.getToday
import org.junit.Test import org.junit.Test
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import kotlin.test.assertFalse
import kotlin.test.assertNull
import kotlin.test.assertTrue
class HintListTest : BaseUnitTest() { class HintListTest : BaseUnitTest() {
private lateinit var hintList: HintList private lateinit var hintList: HintList

View File

@@ -18,16 +18,6 @@
*/ */
package org.isoron.uhabits.core.ui.screens.habits.list package org.isoron.uhabits.core.ui.screens.habits.list
import com.nhaarman.mockitokotlin2.KArgumentCaptor
import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.argumentCaptor
import com.nhaarman.mockitokotlin2.clearInvocations
import com.nhaarman.mockitokotlin2.eq
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.whenever
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
import org.apache.commons.io.FileUtils import org.apache.commons.io.FileUtils
import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.core.IsEqual.equalTo import org.hamcrest.core.IsEqual.equalTo
@@ -39,8 +29,18 @@ import org.isoron.uhabits.core.utils.DateUtils.Companion.getToday
import org.isoron.uhabits.core.utils.DateUtils.Companion.getTodayWithOffset import org.isoron.uhabits.core.utils.DateUtils.Companion.getTodayWithOffset
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.mockito.kotlin.KArgumentCaptor
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.clearInvocations
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import java.io.IOException import java.io.IOException
import java.nio.file.Files import java.nio.file.Files
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class ListHabitsBehaviorTest : BaseUnitTest() { class ListHabitsBehaviorTest : BaseUnitTest() {
private val dirFinder: ListHabitsBehavior.DirFinder = mock() private val dirFinder: ListHabitsBehavior.DirFinder = mock()
@@ -84,7 +84,7 @@ class ListHabitsBehaviorTest : BaseUnitTest() {
eq(""), eq(""),
picker.capture() picker.capture()
) )
picker.lastValue.onNumberPicked(100.0, "") picker.lastValue.onNumberPicked(100.0, "", 0f, 0f)
val today = getTodayWithOffset() val today = getTodayWithOffset()
assertThat(habit2.computedEntries.get(today).value, equalTo(100000)) assertThat(habit2.computedEntries.get(today).value, equalTo(100000))
} }
@@ -168,7 +168,9 @@ class ListHabitsBehaviorTest : BaseUnitTest() {
habit = habit1, habit = habit1,
timestamp = getToday(), timestamp = getToday(),
value = Entry.NO, value = Entry.NO,
notes = "" notes = "",
x = 0f,
y = 0f
) )
assertFalse(habit1.isCompletedToday()) assertFalse(habit1.isCompletedToday())
} }

View File

@@ -18,17 +18,6 @@
*/ */
package org.isoron.uhabits.core.ui.screens.habits.list package org.isoron.uhabits.core.ui.screens.habits.list
import com.nhaarman.mockitokotlin2.KArgumentCaptor
import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.argumentCaptor
import com.nhaarman.mockitokotlin2.clearInvocations
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.never
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions
import com.nhaarman.mockitokotlin2.whenever
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.equalTo import org.hamcrest.Matchers.equalTo
import org.isoron.uhabits.core.BaseUnitTest import org.isoron.uhabits.core.BaseUnitTest
@@ -37,6 +26,17 @@ import org.isoron.uhabits.core.models.HabitMatcher
import org.isoron.uhabits.core.preferences.Preferences import org.isoron.uhabits.core.preferences.Preferences
import org.isoron.uhabits.core.ui.ThemeSwitcher import org.isoron.uhabits.core.ui.ThemeSwitcher
import org.junit.Test import org.junit.Test
import org.mockito.kotlin.KArgumentCaptor
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.clearInvocations
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyNoMoreInteractions
import org.mockito.kotlin.whenever
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class ListHabitsMenuBehaviorTest : BaseUnitTest() { class ListHabitsMenuBehaviorTest : BaseUnitTest() {
private lateinit var behavior: ListHabitsMenuBehavior private lateinit var behavior: ListHabitsMenuBehavior

View File

@@ -18,15 +18,6 @@
*/ */
package org.isoron.uhabits.core.ui.screens.habits.list package org.isoron.uhabits.core.ui.screens.habits.list
import com.nhaarman.mockitokotlin2.KArgumentCaptor
import com.nhaarman.mockitokotlin2.argumentCaptor
import com.nhaarman.mockitokotlin2.eq
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.whenever
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertNull
import junit.framework.Assert.assertTrue
import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.equalTo import org.hamcrest.Matchers.equalTo
import org.isoron.uhabits.core.BaseUnitTest import org.isoron.uhabits.core.BaseUnitTest
@@ -35,6 +26,15 @@ import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.ui.callbacks.OnColorPickedCallback import org.isoron.uhabits.core.ui.callbacks.OnColorPickedCallback
import org.isoron.uhabits.core.ui.callbacks.OnConfirmedCallback import org.isoron.uhabits.core.ui.callbacks.OnConfirmedCallback
import org.junit.Test import org.junit.Test
import org.mockito.kotlin.KArgumentCaptor
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import kotlin.test.assertFalse
import kotlin.test.assertNull
import kotlin.test.assertTrue
class ListHabitsSelectionMenuBehaviorTest : BaseUnitTest() { class ListHabitsSelectionMenuBehaviorTest : BaseUnitTest() {
private val screen: ListHabitsSelectionMenuBehavior.Screen = mock() private val screen: ListHabitsSelectionMenuBehavior.Screen = mock()

View File

@@ -18,15 +18,15 @@
*/ */
package org.isoron.uhabits.core.ui.screens.habits.show package org.isoron.uhabits.core.ui.screens.habits.show
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.whenever
import org.apache.commons.io.FileUtils import org.apache.commons.io.FileUtils
import org.hamcrest.CoreMatchers.equalTo import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.MatcherAssert.assertThat
import org.isoron.uhabits.core.BaseUnitTest import org.isoron.uhabits.core.BaseUnitTest
import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.models.Habit
import org.junit.Test import org.junit.Test
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import java.nio.file.Files import java.nio.file.Files
class ShowHabitMenuPresenterTest : BaseUnitTest() { class ShowHabitMenuPresenterTest : BaseUnitTest() {

View File

@@ -19,10 +19,6 @@
package org.isoron.uhabits.core.ui.views package org.isoron.uhabits.core.ui.views
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.reset
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.isoron.platform.gui.assertRenders import org.isoron.platform.gui.assertRenders
import org.isoron.platform.time.DayOfWeek import org.isoron.platform.time.DayOfWeek
@@ -35,6 +31,10 @@ import org.isoron.uhabits.core.ui.views.HistoryChart.Square.HATCHED
import org.isoron.uhabits.core.ui.views.HistoryChart.Square.OFF import org.isoron.uhabits.core.ui.views.HistoryChart.Square.OFF
import org.isoron.uhabits.core.ui.views.HistoryChart.Square.ON import org.isoron.uhabits.core.ui.views.HistoryChart.Square.ON
import org.junit.Test import org.junit.Test
import org.mockito.kotlin.mock
import org.mockito.kotlin.reset
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyNoMoreInteractions
import java.util.Locale import java.util.Locale
class HistoryChartTest { class HistoryChartTest {

View File

@@ -18,11 +18,6 @@
*/ */
package org.isoron.uhabits.core.ui.widgets package org.isoron.uhabits.core.ui.widgets
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.reset
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.verifyZeroInteractions
import com.nhaarman.mockitokotlin2.whenever
import org.isoron.uhabits.core.BaseUnitTest import org.isoron.uhabits.core.BaseUnitTest
import org.isoron.uhabits.core.commands.CreateRepetitionCommand import org.isoron.uhabits.core.commands.CreateRepetitionCommand
import org.isoron.uhabits.core.models.Entry import org.isoron.uhabits.core.models.Entry
@@ -34,6 +29,11 @@ import org.isoron.uhabits.core.ui.NotificationTray
import org.isoron.uhabits.core.utils.DateUtils.Companion.getTodayWithOffset import org.isoron.uhabits.core.utils.DateUtils.Companion.getTodayWithOffset
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.mockito.kotlin.mock
import org.mockito.kotlin.reset
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyNoInteractions
import org.mockito.kotlin.whenever
class WidgetBehaviorTest : BaseUnitTest() { class WidgetBehaviorTest : BaseUnitTest() {
private lateinit var notificationTray: NotificationTray private lateinit var notificationTray: NotificationTray
@@ -61,7 +61,7 @@ class WidgetBehaviorTest : BaseUnitTest() {
CreateRepetitionCommand(habitList, habit, today, Entry.YES_MANUAL, "") CreateRepetitionCommand(habitList, habit, today, Entry.YES_MANUAL, "")
) )
verify(notificationTray).cancel(habit) verify(notificationTray).cancel(habit)
verifyZeroInteractions(preferences) verifyNoInteractions(preferences)
} }
@Test @Test
@@ -71,7 +71,7 @@ class WidgetBehaviorTest : BaseUnitTest() {
CreateRepetitionCommand(habitList, habit, today, Entry.NO, "") CreateRepetitionCommand(habitList, habit, today, Entry.NO, "")
) )
verify(notificationTray).cancel(habit) verify(notificationTray).cancel(habit)
verifyZeroInteractions(preferences) verifyNoInteractions(preferences)
} }
@Test @Test
@@ -113,7 +113,7 @@ class WidgetBehaviorTest : BaseUnitTest() {
CreateRepetitionCommand(habitList, habit, today, 600, "") CreateRepetitionCommand(habitList, habit, today, 600, "")
) )
verify(notificationTray).cancel(habit) verify(notificationTray).cancel(habit)
verifyZeroInteractions(preferences) verifyNoInteractions(preferences)
} }
@Test @Test
@@ -126,6 +126,6 @@ class WidgetBehaviorTest : BaseUnitTest() {
CreateRepetitionCommand(habitList, habit, today, 400, "") CreateRepetitionCommand(habitList, habit, today, 400, "")
) )
verify(notificationTray).cancel(habit) verify(notificationTray).cancel(habit)
verifyZeroInteractions(preferences) verifyNoInteractions(preferences)
} }
} }

View File

@@ -18,7 +18,6 @@
*/ */
package org.isoron.uhabits.core.utils package org.isoron.uhabits.core.utils
import junit.framework.Assert.assertEquals
import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.core.IsEqual.equalTo import org.hamcrest.core.IsEqual.equalTo
import org.isoron.uhabits.core.BaseUnitTest import org.isoron.uhabits.core.BaseUnitTest
@@ -40,6 +39,7 @@ import java.util.Calendar
import java.util.GregorianCalendar import java.util.GregorianCalendar
import java.util.Locale import java.util.Locale
import java.util.TimeZone import java.util.TimeZone
import kotlin.test.assertEquals
class DateUtilsTest : BaseUnitTest() { class DateUtilsTest : BaseUnitTest() {
var firstWeekday = Calendar.SUNDAY var firstWeekday = Calendar.SUNDAY

View File

@@ -4,6 +4,7 @@ import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.isoron.uhabits.core.BaseUnitTest import org.isoron.uhabits.core.BaseUnitTest
import org.isoron.uhabits.core.io.StandardLogging
import org.junit.Test import org.junit.Test
import java.util.Calendar import java.util.Calendar
import java.util.TimeZone import java.util.TimeZone
@@ -34,7 +35,7 @@ class MidnightTimerTest : BaseUnitTest() {
) )
val suspendedListener = suspendCoroutine<Boolean> { continuation -> val suspendedListener = suspendCoroutine<Boolean> { continuation ->
MidnightTimer().apply { MidnightTimer(StandardLogging()).apply {
addListener { continuation.resume(true) } addListener { continuation.resume(true) }
// When // When
onResume(1, executor) onResume(1, executor)

View File

@@ -22,7 +22,11 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
plugins { plugins {
application application
id("kotlin") id("kotlin")
id("com.github.johnrengelman.shadow") version "7.1.2" id("com.github.johnrengelman.shadow") version "8.1.1"
}
kotlin {
jvmToolchain(17)
} }
@@ -34,11 +38,9 @@ application {
dependencies { dependencies {
val ktorVersion = "1.6.8" val ktorVersion = "1.6.8"
val kotlinVersion = "1.7.21" implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.22")
val logbackVersion = "1.4.5"
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion")
implementation("io.ktor:ktor-server-netty:$ktorVersion") implementation("io.ktor:ktor-server-netty:$ktorVersion")
implementation("ch.qos.logback:logback-classic:$logbackVersion") implementation("ch.qos.logback:logback-classic:1.4.14")
implementation("io.ktor:ktor-server-core:$ktorVersion") implementation("io.ktor:ktor-server-core:$ktorVersion")
implementation("io.ktor:ktor-html-builder:$ktorVersion") implementation("io.ktor:ktor-html-builder:$ktorVersion")
implementation("io.ktor:ktor-jackson:$ktorVersion") implementation("io.ktor:ktor-jackson:$ktorVersion")
@@ -47,7 +49,7 @@ dependencies {
implementation("io.prometheus:simpleclient_httpserver:0.16.0") implementation("io.prometheus:simpleclient_httpserver:0.16.0")
implementation("io.prometheus:simpleclient_hotspot:0.16.0") implementation("io.prometheus:simpleclient_hotspot:0.16.0")
testImplementation("io.ktor:ktor-server-tests:$ktorVersion") testImplementation("io.ktor:ktor-server-tests:$ktorVersion")
testImplementation("com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0") testImplementation("org.mockito.kotlin:mockito-kotlin:5.2.1")
testImplementation(kotlin("test")) testImplementation(kotlin("test"))
testImplementation(kotlin("test-junit")) testImplementation(kotlin("test-junit"))
} }
@@ -57,3 +59,4 @@ tasks.withType<ShadowJar> {
archiveClassifier.set("") archiveClassifier.set("")
archiveVersion.set("") archiveVersion.set("")
} }

View File

@@ -19,7 +19,7 @@
package org.isoron.uhabits.sync.app package org.isoron.uhabits.sync.app
import com.nhaarman.mockitokotlin2.mock import org.mockito.kotlin.mock
import io.ktor.application.* import io.ktor.application.*
import org.isoron.uhabits.sync.server.* import org.isoron.uhabits.sync.server.*

View File

@@ -19,7 +19,7 @@
package org.isoron.uhabits.sync.app package org.isoron.uhabits.sync.app
import com.nhaarman.mockitokotlin2.whenever import org.mockito.kotlin.whenever
import io.ktor.http.ContentType import io.ktor.http.ContentType
import io.ktor.http.HttpHeaders import io.ktor.http.HttpHeaders
import io.ktor.http.HttpMethod import io.ktor.http.HttpMethod

View File

@@ -19,7 +19,7 @@
package org.isoron.uhabits.sync.app package org.isoron.uhabits.sync.app
import com.nhaarman.mockitokotlin2.whenever import org.mockito.kotlin.whenever
import io.ktor.http.HttpMethod import io.ktor.http.HttpMethod
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
import io.ktor.server.testing.handleRequest import io.ktor.server.testing.handleRequest

View File

@@ -19,8 +19,8 @@
package org.isoron.uhabits.sync.app package org.isoron.uhabits.sync.app
import com.nhaarman.mockitokotlin2.verify import org.mockito.kotlin.verify
import com.nhaarman.mockitokotlin2.whenever import org.mockito.kotlin.whenever
import io.ktor.http.* import io.ktor.http.*
import io.ktor.server.testing.* import io.ktor.server.testing.*
import kotlinx.coroutines.* import kotlinx.coroutines.*