Compare commits

..

49 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
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
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
33 changed files with 338 additions and 62 deletions

View File

@@ -22,7 +22,7 @@ jobs:
- 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: |

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

View File

@@ -18,7 +18,7 @@
*/ */
plugins { plugins {
id("com.github.triplet.play") version "3.8.4" id("com.github.triplet.play") version "3.8.6"
id("com.android.application") version "8.1.4" 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")
@@ -94,8 +94,8 @@ android {
} }
dependencies { dependencies {
val daggerVersion = "2.48.1" val daggerVersion = "2.51.1"
val kotlinVersion = "1.8.20" val kotlinVersion = "1.9.22"
val kxCoroutinesVersion = "1.7.3" 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"
@@ -106,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.7.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("org.mockito.kotlin:mockito-kotlin:5.1.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:32.1.2-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")
@@ -124,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.9.0") implementation("com.google.android.material:material:1.11.0")
implementation("com.opencsv:opencsv:5.8") 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("org.mockito.kotlin:mockito-kotlin:5.1.0") testImplementation("org.mockito.kotlin:mockito-kotlin:5.2.1")
} }
kapt { kapt {

View File

@@ -132,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"

View File

@@ -30,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

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

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

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

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

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

@@ -44,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.48.1") compileOnly("com.google.dagger:dagger:2.51.1")
implementation("com.google.guava:guava:32.1.2-android") implementation("com.google.guava:guava:33.1.0-android")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.3") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.3")
implementation("androidx.annotation:annotation:1.7.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.8") implementation("com.opencsv:opencsv:5.9")
implementation("commons-codec:commons-codec:1.16.0") implementation("commons-codec:commons-codec:1.16.0")
implementation("org.apache.commons:commons-lang3:3.13.0") implementation("org.apache.commons:commons-lang3:3.14.0")
} }
} }
@@ -59,11 +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.42.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("org.mockito.kotlin:mockito-kotlin:5.1.0") implementation("org.mockito.kotlin:mockito-kotlin:5.2.1")
implementation("org.junit.jupiter:junit-jupiter:5.10.0") implementation("org.junit.jupiter:junit-jupiter:5.10.1")
} }
} }
} }

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

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

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

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

@@ -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,7 @@ 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 { kotlin {
@@ -38,11 +38,9 @@ application {
dependencies { dependencies {
val ktorVersion = "1.6.8" val ktorVersion = "1.6.8"
val kotlinVersion = "1.8.20" implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.22")
val logbackVersion = "1.4.9"
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")
@@ -51,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("org.mockito.kotlin:mockito-kotlin:5.1.0") testImplementation("org.mockito.kotlin:mockito-kotlin:5.2.1")
testImplementation(kotlin("test")) testImplementation(kotlin("test"))
testImplementation(kotlin("test-junit")) testImplementation(kotlin("test-junit"))
} }