Implement runtime notification permission; bump targetSdk to 33

pull/1863/head
Alinson S. Xavier 2 years ago
parent 7735247521
commit ed8c60e52f
Signed by: isoron
GPG Key ID: 0DA8E4B9E1109DCA

@ -42,13 +42,13 @@ kotlin {
android { android {
compileSdk = 32 compileSdk = 33
defaultConfig { defaultConfig {
versionCode = 20200 versionCode = 20200
versionName = "2.2.0" versionName = "2.2.0"
minSdk = 28 minSdk = 28
targetSdk = 32 targetSdk = 33
applicationId = "org.isoron.uhabits" applicationId = "org.isoron.uhabits"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
} }

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

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

@ -22,6 +22,7 @@
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" /> <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.VIBRATE" /> <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.POST_NOTIFICATIONS" />
<application <application
android:name=".HabitsApplication" android:name=".HabitsApplication"

@ -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 {
@ -79,8 +79,8 @@ 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) {

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

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

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

Loading…
Cancel
Save