Skip to content

Commit

Permalink
- Feat: Add manual check and work info display to Notification Settings
Browse files Browse the repository at this point in the history
This commit introduces a new manual check functionality and displays work info in the Notification Settings.

- A new "Check Now" button is added to the Notification Settings screen, allowing users to manually trigger an update check for all sources.
- The screen now displays the status, source name, and progress of the scheduled and manual update checks using `LinearWavyProgressIndicator`.
- The work info display is achieved by collecting `WorkInfo` objects and displaying their relevant information.
- The "Reset Notifications" button now also prunes completed work items.
- The `OtakuApp.updateSetup` function is modified to use `workDataOf` for input data and adjusted for potential errors.
- Updates the shortcut setup to use more idiomatic Kotlin functions.
  • Loading branch information
jacobrein committed Nov 20, 2024
1 parent e83389e commit d19dcda
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 38 deletions.
14 changes: 4 additions & 10 deletions UIViews/src/main/java/com/programmersbox/uiviews/OtakuApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import android.content.pm.ShortcutManager
import androidx.compose.ui.ComposeUiFlags
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.work.Constraints
import androidx.work.Data
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.NetworkType
import androidx.work.PeriodicWorkRequest
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.workDataOf
import com.google.android.material.color.DynamicColors
import com.google.firebase.FirebaseApp
import com.google.firebase.analytics.ktx.analytics
Expand Down Expand Up @@ -197,9 +197,9 @@ abstract class OtakuApp : Application() {

private fun shortcutSetup() {
val manager = getSystemService(ShortcutManager::class.java)
if (manager.dynamicShortcuts.size == 0) {
if (manager.dynamicShortcuts.isEmpty()) {
// Application restored. Need to re-publish dynamic shortcuts.
if (manager.pinnedShortcuts.size > 0) {
if (manager.pinnedShortcuts.isNotEmpty()) {
// Pinned shortcuts have been restored. Use
// updateShortcuts() to make sure they contain
// up-to-date information.
Expand Down Expand Up @@ -235,13 +235,7 @@ abstract class OtakuApp : Application() {
1, TimeUnit.HOURS,
5, TimeUnit.MINUTES
)
.setInputData(
Data.Builder()
.putAll(
mapOf(UpdateFlowWorker.CHECK_ALL to false)
)
.build()
)
.setInputData(workDataOf(UpdateFlowWorker.CHECK_ALL to false))
.setConstraints(
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import androidx.compose.ui.util.fastMap
import androidx.compose.ui.util.fastMaxBy
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import com.programmersbox.extensionloader.SourceLoader
import com.programmersbox.extensionloader.SourceRepository
import com.programmersbox.favoritesdatabase.DbModel
Expand Down Expand Up @@ -124,12 +125,22 @@ class UpdateFlowWorker(context: Context, workerParams: WorkerParameters) : Corou

logFirebaseMessage("Sources: ${sourceRepository.apiServiceList.joinToString { it.serviceName }}")

val sourceSize = sourceRepository.apiServiceList.size

// Getting all recent updates
val newList = list.intersect(
sourceRepository.apiServiceList
.filter { s -> list.fastAny { m -> m.source == s.serviceName } }
.mapNotNull { m ->
.mapIndexedNotNull { index, m ->
runCatching {
//TODO: Make this easier to handle
setProgress(
workDataOf(
"max" to sourceSize,
"progress" to index,
"source" to m.serviceName,
)
)
withTimeoutOrNull(10000) {
m.getRecentFlow()
.catch { emit(emptyList()) }
Expand All @@ -149,6 +160,13 @@ class UpdateFlowWorker(context: Context, workerParams: WorkerParameters) : Corou
println("Checking for updates")
val items = newList.mapIndexedNotNull { index, model ->
update.sendRunningNotification(newList.size, index, model.title)
setProgress(
workDataOf(
"max" to newList.size,
"progress" to index,
"source" to model.title,
)
)
runCatching {
val newData = sourceRepository.toSourceByApiServiceName(model.source)
?.apiService
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,16 @@ package com.programmersbox.uiviews.settings

import android.content.Context
import android.widget.Toast
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.LinearWavyProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.ripple
Expand All @@ -20,13 +26,16 @@ import androidx.compose.ui.draw.alpha
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.work.Constraints
import androidx.work.Data
import androidx.work.ExistingWorkPolicy
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkInfo
import androidx.work.WorkManager
import androidx.work.hasKeyWithValueOfType
import androidx.work.workDataOf
import com.programmersbox.favoritesdatabase.ItemDao
import com.programmersbox.helpfulutils.notificationManager
import com.programmersbox.uiviews.OtakuApp
Expand All @@ -42,14 +51,17 @@ import com.programmersbox.uiviews.utils.SwitchSetting
import com.programmersbox.uiviews.utils.updatePref
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.text.DateFormat
import java.text.SimpleDateFormat

@OptIn(ExperimentalMaterial3Api::class)
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun NotificationSettings(
dao: ItemDao = LocalItemDao.current,
context: Context = LocalContext.current,
notiViewModel: NotificationViewModel = viewModel { NotificationViewModel(dao, context) }
notiViewModel: NotificationViewModel = viewModel { NotificationViewModel(dao, context) },
) {
val work = remember { WorkManager.getInstance(context) }
SettingsScaffold(stringResource(R.string.notification_settings)) {
val scope = rememberCoroutineScope()
ShowWhen(notiViewModel.savedNotifications > 0) {
Expand Down Expand Up @@ -100,29 +112,23 @@ fun NotificationSettings(
indication = ripple(),
interactionSource = null
) {
WorkManager.getInstance(context)
.enqueueUniqueWork(
"oneTimeUpdate",
ExistingWorkPolicy.KEEP,
OneTimeWorkRequestBuilder<UpdateFlowWorker>()
.setInputData(
Data.Builder()
.putAll(
mapOf(UpdateFlowWorker.CHECK_ALL to true)
)
.build()
)
.setConstraints(
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresBatteryNotLow(false)
.setRequiresCharging(false)
.setRequiresDeviceIdle(false)
.setRequiresStorageNotLow(false)
.build()
)
.build()
)
work.enqueueUniqueWork(
"oneTimeUpdate",
ExistingWorkPolicy.KEEP,
OneTimeWorkRequestBuilder<UpdateFlowWorker>()
.setInputData(workDataOf(UpdateFlowWorker.CHECK_ALL to true))
.addTag("ManualCheck")
.setConstraints(
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresBatteryNotLow(false)
.setRequiresCharging(false)
.setRequiresDeviceIdle(false)
.setRequiresStorageNotLow(false)
.build()
)
.build()
)
}
)

Expand Down Expand Up @@ -173,7 +179,6 @@ fun NotificationSettings(
indication = ripple(),
interactionSource = null
) {
val work = WorkManager.getInstance(context)
work.cancelUniqueWork("updateFlowChecks")
work.pruneWork()
OtakuApp.updateSetup(context)
Expand All @@ -182,10 +187,83 @@ fun NotificationSettings(
.show()
}
)

Spacer(Modifier.padding(16.dp))

val dateFormat = remember { SimpleDateFormat.getDateTimeInstance() }

val workInfo by work
.getWorkInfosForUniqueWorkFlow("updateFlowChecks")
.collectAsStateWithLifecycle(emptyList())

workInfo.forEach { WorkInfoItem(it, dateFormat) }
}

val manualWorkInfo by work
.getWorkInfosByTagFlow("ManualCheck")
.collectAsStateWithLifecycle(emptyList())

manualWorkInfo.forEach { workInfo ->
val (progress, max) = if (workInfo.progress.hasKeyWithValueOfType<Int>("progress") && workInfo.progress.hasKeyWithValueOfType<Int>("max")) {
workInfo.progress.getInt("progress", 0) to workInfo.progress.getInt("max", 0)
} else null to null
PreferenceSetting(
settingTitle = { Text("Manual Check:") },
summaryValue = {
Column(Modifier.animateContentSize()) {
Text(workInfo.state.toString())
Text(workInfo.progress.getString("source").orEmpty())
if (progress != null && max != null) {
if (progress == 0) {
LinearWavyProgressIndicator()
} else {
val animatedProgress by animateFloatAsState(progress.toFloat() / max.toFloat())
LinearWavyProgressIndicator(progress = { animatedProgress })
}
}
}
},
endIcon = {
if (progress != null && max != null) {
Text("$progress/$max")
}
}
)
}
}
}

@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
private fun WorkInfoItem(workInfo: WorkInfo, dateFormat: DateFormat) {
val (progress, max) = if (workInfo.progress.hasKeyWithValueOfType<Int>("progress") && workInfo.progress.hasKeyWithValueOfType<Int>("max")) {
workInfo.progress.getInt("progress", 0) to workInfo.progress.getInt("max", 0)
} else null to null
PreferenceSetting(
settingTitle = { Text("Scheduled Check:") },
summaryValue = {
Column(Modifier.animateContentSize()) {
Text(dateFormat.format(workInfo.nextScheduleTimeMillis))
Text(workInfo.state.toString())
Text(workInfo.progress.getString("source").orEmpty())
if (progress != null && max != null) {
if (progress == 0) {
LinearWavyProgressIndicator()
} else {
val animatedProgress by animateFloatAsState(progress.toFloat() / max.toFloat())
LinearWavyProgressIndicator(progress = { animatedProgress })
}
}
}
},
endIcon = {
if (progress != null && max != null) {
Text("$progress/$max")
}
}
)
}

@LightAndDarkPreviews
@Composable
private fun NotificationSettingsPreview() {
Expand Down

0 comments on commit d19dcda

Please sign in to comment.