Skip to content

Commit

Permalink
Notification optimize & some fix
Browse files Browse the repository at this point in the history
[Fixed backup file time format is bad-understanding]
[Fixed auto del history task cannot del > 1 history at a time if it has > 6 history task]
[Optimize reminder notification logic]
[Fix reminder notification delay in battery save mode via ALARM]
[Update dependencies]
  • Loading branch information
Z-Siqi committed Dec 2, 2024
1 parent 1f3b729 commit 6b1bc4f
Show file tree
Hide file tree
Showing 14 changed files with 590 additions and 83 deletions.
18 changes: 9 additions & 9 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ plugins {

android {
namespace = "com.sqz.checklist"
compileSdk = 34
compileSdk = 35

defaultConfig {
applicationId = "com.sqz.checklist"
Expand Down Expand Up @@ -52,25 +52,25 @@ android {

dependencies {

implementation("androidx.core:core-ktx:1.13.1")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.6")
implementation("androidx.core:core-ktx:1.15.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.7")
implementation("androidx.activity:activity-compose:1.9.3")
implementation(platform("androidx.compose:compose-bom:2024.10.00"))
implementation(platform("androidx.compose:compose-bom:2024.11.00"))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.compose.material3:material3-android:1.3.0")
implementation("androidx.compose.foundation:foundation-android:1.7.4")
implementation("androidx.work:work-runtime-ktx:2.9.1")
implementation("androidx.compose.material3:material3-android:1.3.1")
implementation("androidx.compose.foundation:foundation-android:1.7.5")
implementation("androidx.work:work-runtime-ktx:2.10.0")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.2.1")
androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
androidTestImplementation(platform("androidx.compose:compose-bom:2024.10.00"))
androidTestImplementation(platform("androidx.compose:compose-bom:2024.11.00"))
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest")
// Navigation
implementation("androidx.navigation:navigation-compose:2.8.3")
implementation("androidx.navigation:navigation-compose:2.8.4")
// Room
ksp("androidx.room:room-compiler:2.6.1")
implementation("androidx.room:room-runtime:2.6.1")
Expand Down
14 changes: 13 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.ACTION_REQUEST_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.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />

<application
android:allowBackup="true"
Expand Down Expand Up @@ -36,6 +40,14 @@
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>

<receiver android:name=".notification.NotificationReceiver" />

<receiver android:name=".notification.BootReceiver" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
</application>

</manifest>
4 changes: 3 additions & 1 deletion app/src/main/java/com/sqz/checklist/database/DatabaseIO.kt
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ fun GetUri(uri: (Uri?) -> Unit) {
}
}

private const val timeFormat = "yyyyMMdd_HHmm" + "ss" // No Android Studio grammar checking this!

@Composable
fun ExportTaskDatabase(
state: Boolean, useChooser: Boolean, view: View,
Expand Down Expand Up @@ -178,7 +180,7 @@ fun ExportTaskDatabase(
if (state) { // Export actions
if (useChooser) exportDatabase(true, null) else {
val currentTime = remember {
val sdf = SimpleDateFormat("msys", Locale.getDefault())
val sdf = SimpleDateFormat(timeFormat, Locale.getDefault())
sdf.format(Date())
}
launcher.launch("${exportName}_$currentTime.db").also {
Expand Down
64 changes: 64 additions & 0 deletions app/src/main/java/com/sqz/checklist/notification/BootReceiver.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.sqz.checklist.notification

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
import androidx.room.Room
import com.sqz.checklist.R
import com.sqz.checklist.database.TaskDatabase
import com.sqz.checklist.database.taskDatabaseName
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import java.util.concurrent.TimeUnit

/**
* Restore delayed notification (reminder) when boot or restart app
*/
class BootReceiver : BroadcastReceiver() {
@OptIn(DelicateCoroutinesApi::class)
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == Intent.ACTION_BOOT_COMPLETED) {
val notificationManager = MutableStateFlow(NotifyManager())
GlobalScope.launch {
notificationManager.value.requestPermission(context)
if (notificationManager.value.getAlarmPermission()) {
val db = Room.databaseBuilder(
context, TaskDatabase::class.java, taskDatabaseName
).build()
val taskDao = db.taskDao()
val list = taskDao.getIsRemindedList()
val notification = NotificationCreator(context)
for (data in list) {
val parts = data.reminder?.split(":")
val queryCharacter = if (parts?.size!! >= 2) parts[0] else null
if (queryCharacter != null) try {
if (!notification.getAlarmNotificationState(queryCharacter.toInt()) &&
parts[1].toLong() >= System.currentTimeMillis()
) {
notificationManager.value.createNotification(
channelId = context.getString(R.string.tasks),
channelName = context.getString(R.string.task_reminder),
channelDescription = context.getString(R.string.description),
description = data.description, notifyId = data.id,
delayDuration = parts[1].toLong(),
timeUnit = TimeUnit.MILLISECONDS, context = context
).also {
Log.d("RestoreReminder", "Restore NotifyId: $queryCharacter")
}
}
} catch (e: NumberFormatException) {
Log.d("RestoreReminder", "Ignored: work manager no need this!")
} catch (e: Exception) {
Log.e("RestoreReminder", "Failed: ${e.message}")
}
}
Log.d("BootReceiver", "Delayed notification is restored!")
db.close()
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,8 @@ class DelayedNotificationWorker(
content != null &&
notifyId != -1
) {
val notification = NotificationCreator()
val notification = NotificationCreator(applicationContext)
notification.creator(
applicationContext,
channelId = channelId,
channelName = channelName,
channelDescription = channelDescription,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package com.sqz.checklist.notification

import android.Manifest
import android.app.AlarmManager
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.util.Log
import android.widget.Toast
import androidx.core.app.ActivityCompat
Expand All @@ -19,10 +21,12 @@ import com.sqz.checklist.MainActivity
import com.sqz.checklist.R
import java.util.UUID

class NotificationCreator {
/** Config delayed notification worker. NOT using to create notification **/
class NotificationCreator(private val context: Context) {

/**
* Config notification. NOT using to create a delayed notification
*/
fun creator(
context: Context,
channelId: String,
channelName: String,
channelDescription: String,
Expand Down Expand Up @@ -54,8 +58,7 @@ class NotificationCreator {
.setFullScreenIntent(fullScreenPendingIntent, true)
with(NotificationManagerCompat.from(context)) {
if (ActivityCompat.checkSelfPermission(
context,
Manifest.permission.POST_NOTIFICATIONS
context, Manifest.permission.POST_NOTIFICATIONS
) != PackageManager.PERMISSION_GRANTED
) {
val packageManager = context.packageManager
Expand Down Expand Up @@ -85,17 +88,12 @@ class NotificationCreator {
}

/** Create delayed notification **/
fun create(
channelId: String,
channelName: String,
channelDescription: String,
description: String,
content: String = "",
notifyId: Int,
delayDuration: Long,
timeUnit: java.util.concurrent.TimeUnit,
context: Context
) : UUID {
fun createWorker(
channelId: String, channelName: String, channelDescription: String,
description: String, content: String = "",
notifyId: Int, delayDuration: Long,
timeUnit: java.util.concurrent.TimeUnit
): UUID {
val workRequest = OneTimeWorkRequestBuilder<DelayedNotificationWorker>()
.setInputData(
Data.Builder()
Expand All @@ -112,4 +110,49 @@ class NotificationCreator {
WorkManager.getInstance(context).enqueue(workRequest)
return workRequest.id
}

/** Create Alarmed notification **/
fun createAlarmed(
channelId: String, channelName: String, channelDescription: String,
description: String, content: String = "",
notifyId: Int, delayDuration: Long
): Int {
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(context, NotificationReceiver::class.java).apply {
putExtra("channelId", channelId)
putExtra("channelName", channelName)
putExtra("channelDescription", channelDescription)
putExtra("title", description)
putExtra("content", content)
putExtra("notifyId", notifyId)
}
val pendingIntent = PendingIntent.getBroadcast(
context, notifyId, intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
try {
alarmManager.setExactAndAllowWhileIdle(
AlarmManager.RTC_WAKEUP, delayDuration, pendingIntent
)
//alarmManager.setAlarmClock(AlarmManager.AlarmClockInfo(delayDuration, pendingIntent), pendingIntent)
} catch (e: SecurityException) {
Log.e("ERROR", "Failed: AlarmManager Permission denied! Notification cannot sent!")
}
} else {
alarmManager.set(AlarmManager.RTC_WAKEUP, delayDuration, pendingIntent)
}
return notifyId
}

fun getAlarmNotificationState(notifyId: Int): Boolean {
val intent = Intent(context, NotificationReceiver::class.java).apply {
putExtra("notifyId", notifyId)
}
val pendingIntent = PendingIntent.getBroadcast(
context, notifyId, intent,
PendingIntent.FLAG_NO_CREATE or PendingIntent.FLAG_IMMUTABLE
)
return pendingIntent != null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.sqz.checklist.notification

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent

class NotificationReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val channelId = intent.getStringExtra("channelId")
val channelName = intent.getStringExtra("channelName")
val channelDescription = intent.getStringExtra("channelDescription")
val title = intent.getStringExtra("title")
val content = intent.getStringExtra("content")
val notifyId = intent.getIntExtra("notifyId", -1)

if (channelId != null && channelName != null && channelDescription != null &&
title != null && content != null && notifyId != -1
) {
val notification = NotificationCreator(context)
notification.creator(
channelId = channelId,
channelName = channelName,
channelDescription = channelDescription,
title = title,
content = content,
notifyId = notifyId
)
} else throw Exception("Notification data error!")
}
}
Loading

0 comments on commit 6b1bc4f

Please sign in to comment.