diff --git a/uhabits-android/build.gradle.kts b/uhabits-android/build.gradle.kts
index ac7b5cfaf..12c91c3f2 100644
--- a/uhabits-android/build.gradle.kts
+++ b/uhabits-android/build.gradle.kts
@@ -42,13 +42,13 @@ kotlin {
android {
- compileSdk = 32
+ compileSdk = 33
defaultConfig {
versionCode = 20200
versionName = "2.2.0"
minSdk = 28
- targetSdk = 32
+ targetSdk = 33
applicationId = "org.isoron.uhabits"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/common/views/FrequencyChartTest.kt b/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/common/views/FrequencyChartTest.kt
index 0c4225f99..3fb97bb4c 100644
--- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/common/views/FrequencyChartTest.kt
+++ b/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/common/views/FrequencyChartTest.kt
@@ -18,6 +18,7 @@
*/
package org.isoron.uhabits.activities.common.views
+import android.view.MotionEvent
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import org.isoron.uhabits.BaseViewTest
@@ -52,7 +53,8 @@ class FrequencyChartTest : BaseViewTest() {
@Test
@Throws(Throwable::class)
fun testRender_withDataOffset() {
- view.onScroll(null, null, -dpToPixels(150), 0f)
+ val e = MotionEvent.obtain(0, 0, 0, 0f, 0f, 0)
+ view.onScroll(e, e, -dpToPixels(150), 0f)
view.invalidate()
assertRenders(view, BASE_PATH + "renderDataOffset.png")
}
diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/common/views/ScoreChartTest.kt b/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/common/views/ScoreChartTest.kt
index 4f504d8be..83d695ca4 100644
--- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/common/views/ScoreChartTest.kt
+++ b/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/common/views/ScoreChartTest.kt
@@ -18,6 +18,7 @@
*/
package org.isoron.uhabits.activities.common.views
+import android.view.MotionEvent
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import org.isoron.uhabits.BaseViewTest
@@ -63,7 +64,8 @@ class ScoreChartTest : BaseViewTest() {
@Test
@Throws(Throwable::class)
fun testRender_withDataOffset() {
- view.onScroll(null, null, -dpToPixels(150), 0f)
+ val e = MotionEvent.obtain(0, 0, 0, 0f, 0f, 0)
+ view.onScroll(e, e, -dpToPixels(150), 0f)
view.invalidate()
assertRenders(view, BASE_PATH + "renderDataOffset.png")
}
diff --git a/uhabits-android/src/main/AndroidManifest.xml b/uhabits-android/src/main/AndroidManifest.xml
index 9377e42c0..dd170e10e 100644
--- a/uhabits-android/src/main/AndroidManifest.xml
+++ b/uhabits-android/src/main/AndroidManifest.xml
@@ -22,6 +22,7 @@
+
abs(dy)) {
diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsActivity.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsActivity.kt
index 918e28180..21936d4ea 100644
--- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsActivity.kt
+++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsActivity.kt
@@ -19,12 +19,17 @@
package org.isoron.uhabits.activities.habits.list
+import android.Manifest.permission.POST_NOTIFICATIONS
import android.content.Intent
+import android.content.pm.PackageManager.PERMISSION_GRANTED
+import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.Menu
import android.view.MenuItem
+import androidx.activity.result.contract.ActivityResultContracts.RequestPermission
import androidx.appcompat.app.AppCompatActivity
+import androidx.core.content.ContextCompat.checkSelfPermission
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import org.isoron.uhabits.BaseExceptionHandler
@@ -56,6 +61,16 @@ class ListHabitsActivity : AppCompatActivity(), Preferences.Listener {
lateinit var midnightTimer: MidnightTimer
private val scope = CoroutineScope(Dispatchers.Main)
+ private var permissionAlreadyRequested = false
+ private val permissionLauncher =
+ registerForActivityResult(RequestPermission()) { isGranted: Boolean ->
+ if (isGranted) {
+ scheduleReminders()
+ } else {
+ Log.i("ListHabitsActivity", "POST_NOTIFICATIONS denied")
+ }
+ }
+
private lateinit var menu: ListHabitsMenu
override fun onQuestionMarksChanged() {
@@ -101,7 +116,26 @@ class ListHabitsActivity : AppCompatActivity(), Preferences.Listener {
screen.onAttached()
rootView.postInvalidate()
midnightTimer.onResume()
- appComponent.reminderScheduler.scheduleAll()
+
+ if (appComponent.reminderScheduler.hasHabitsWithReminders()) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
+ scheduleReminders()
+ } else {
+ if (checkSelfPermission(this, POST_NOTIFICATIONS) == PERMISSION_GRANTED) {
+ scheduleReminders()
+ } else {
+ // If we have not requested the permission yet, request it. Otherwide do
+ // nothing. This check is necessary to avoid an infinite onResume loop in case
+ // the user denies the permission.
+ if (!permissionAlreadyRequested) {
+ Log.i("ListHabitsActivity", "Requestion permission: POST_NOTIFICATIONS")
+ permissionLauncher.launch(POST_NOTIFICATIONS)
+ permissionAlreadyRequested = true
+ }
+ }
+ }
+ }
+
taskRunner.run {
try {
AutoBackup(this@ListHabitsActivity).run()
@@ -117,6 +151,10 @@ class ListHabitsActivity : AppCompatActivity(), Preferences.Listener {
super.onResume()
}
+ private fun scheduleReminders() {
+ appComponent.reminderScheduler.scheduleAll()
+ }
+
override fun onCreateOptionsMenu(m: Menu): Boolean {
menu.onCreate(menuInflater, m)
return true
diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/reminders/ReminderScheduler.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/reminders/ReminderScheduler.kt
index 8318429f6..bae15bb00 100644
--- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/reminders/ReminderScheduler.kt
+++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/reminders/ReminderScheduler.kt
@@ -115,6 +115,11 @@ class ReminderScheduler @Inject constructor(
for (habit in reminderHabits) schedule(habit)
}
+ @Synchronized
+ fun hasHabitsWithReminders(): Boolean {
+ return !habitList.getFiltered(HabitMatcher.WITH_ALARM).isEmpty
+ }
+
@Synchronized
fun startListening() {
commandRunner.addListener(this)