Improve background works notifications

master
Koitharu 2 years ago
parent 2310ed06c1
commit 2a500eb2cb
Signed by: Koitharu
GPG Key ID: 676DEE768C17A9D7

@ -11,8 +11,10 @@ import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.transform
import kotlinx.coroutines.flow.transformLatest import kotlinx.coroutines.flow.transformLatest
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import java.util.concurrent.atomic.AtomicInteger
fun <T> Flow<T>.onFirst(action: suspend (T) -> Unit): Flow<T> { fun <T> Flow<T>.onFirst(action: suspend (T) -> Unit): Flow<T> {
var isFirstCall = true var isFirstCall = true
@ -37,6 +39,14 @@ fun <T> Flow<T>.onEachWhile(action: suspend (T) -> Boolean): Flow<T> {
} }
} }
fun <T> Flow<T>.onEachIndexed(action: suspend (index: Int, T) -> Unit): Flow<T> {
val counter = AtomicInteger(0)
return transform { value ->
action(counter.getAndIncrement(), value)
return@transform emit(value)
}
}
inline fun <T, R> Flow<List<T>>.mapItems(crossinline transform: (T) -> R): Flow<List<R>> { inline fun <T, R> Flow<List<T>>.mapItems(crossinline transform: (T) -> R): Flow<List<R>> {
return map { list -> list.map(transform) } return map { list -> list.map(transform) }
} }

@ -68,6 +68,7 @@ import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.parsers.model.SortOrder import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import org.koitharu.kotatsu.reader.ui.ReaderActivity.IntentBuilder import org.koitharu.kotatsu.reader.ui.ReaderActivity.IntentBuilder
import org.koitharu.kotatsu.settings.SettingsActivity
import org.koitharu.kotatsu.settings.work.PeriodicWorkScheduler import org.koitharu.kotatsu.settings.work.PeriodicWorkScheduler
import org.koitharu.kotatsu.suggestions.domain.MangaSuggestion import org.koitharu.kotatsu.suggestions.domain.MangaSuggestion
import org.koitharu.kotatsu.suggestions.domain.SuggestionRepository import org.koitharu.kotatsu.suggestions.domain.SuggestionRepository
@ -76,6 +77,7 @@ import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
import kotlin.math.pow import kotlin.math.pow
import kotlin.random.Random import kotlin.random.Random
import com.google.android.material.R as materialR
@HiltWorker @HiltWorker
class SuggestionsWorker @AssistedInject constructor( class SuggestionsWorker @AssistedInject constructor(
@ -86,6 +88,7 @@ class SuggestionsWorker @AssistedInject constructor(
private val historyRepository: HistoryRepository, private val historyRepository: HistoryRepository,
private val favouritesRepository: FavouritesRepository, private val favouritesRepository: FavouritesRepository,
private val appSettings: AppSettings, private val appSettings: AppSettings,
private val workManager: WorkManager,
private val mangaRepositoryFactory: MangaRepository.Factory, private val mangaRepositoryFactory: MangaRepository.Factory,
private val sourcesRepository: MangaSourcesRepository, private val sourcesRepository: MangaSourcesRepository,
) : CoroutineWorker(appContext, params) { ) : CoroutineWorker(appContext, params) {
@ -116,6 +119,19 @@ class SuggestionsWorker @AssistedInject constructor(
val notification = NotificationCompat.Builder(applicationContext, WORKER_CHANNEL_ID) val notification = NotificationCompat.Builder(applicationContext, WORKER_CHANNEL_ID)
.setContentTitle(title) .setContentTitle(title)
.setContentIntent(
PendingIntentCompat.getActivity(
applicationContext,
0,
SettingsActivity.newSuggestionsSettingsIntent(applicationContext),
0,
false,
),
).addAction(
materialR.drawable.material_ic_clear_black_24dp,
applicationContext.getString(android.R.string.cancel),
workManager.createCancelPendingIntent(id),
)
.setPriority(NotificationCompat.PRIORITY_MIN) .setPriority(NotificationCompat.PRIORITY_MIN)
.setCategory(NotificationCompat.CATEGORY_SERVICE) .setCategory(NotificationCompat.CATEGORY_SERVICE)
.setDefaults(0) .setDefaults(0)
@ -123,7 +139,13 @@ class SuggestionsWorker @AssistedInject constructor(
.setSilent(true) .setSilent(true)
.setProgress(0, 0, true) .setProgress(0, 0, true)
.setSmallIcon(android.R.drawable.stat_notify_sync) .setSmallIcon(android.R.drawable.stat_notify_sync)
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_DEFERRED) .setForegroundServiceBehavior(
if (TAG_ONESHOT in tags) {
NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE
} else {
NotificationCompat.FOREGROUND_SERVICE_DEFERRED
},
)
.build() .build()
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {

@ -39,7 +39,6 @@ import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.toList import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runInterruptible import kotlinx.coroutines.runInterruptible
@ -54,6 +53,7 @@ import org.koitharu.kotatsu.core.logs.TrackerLogger
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.util.ext.awaitUniqueWorkInfoByName import org.koitharu.kotatsu.core.util.ext.awaitUniqueWorkInfoByName
import org.koitharu.kotatsu.core.util.ext.checkNotificationPermission import org.koitharu.kotatsu.core.util.ext.checkNotificationPermission
import org.koitharu.kotatsu.core.util.ext.onEachIndexed
import org.koitharu.kotatsu.core.util.ext.toBitmapOrNull import org.koitharu.kotatsu.core.util.ext.toBitmapOrNull
import org.koitharu.kotatsu.core.util.ext.trySetForeground import org.koitharu.kotatsu.core.util.ext.trySetForeground
import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.details.ui.DetailsActivity
@ -61,11 +61,13 @@ import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.util.mapToSet import org.koitharu.kotatsu.parsers.util.mapToSet
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import org.koitharu.kotatsu.settings.SettingsActivity
import org.koitharu.kotatsu.settings.work.PeriodicWorkScheduler import org.koitharu.kotatsu.settings.work.PeriodicWorkScheduler
import org.koitharu.kotatsu.tracker.domain.Tracker import org.koitharu.kotatsu.tracker.domain.Tracker
import org.koitharu.kotatsu.tracker.domain.model.MangaUpdates import org.koitharu.kotatsu.tracker.domain.model.MangaUpdates
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
import com.google.android.material.R as materialR
@HiltWorker @HiltWorker
class TrackWorker @AssistedInject constructor( class TrackWorker @AssistedInject constructor(
@ -74,6 +76,7 @@ class TrackWorker @AssistedInject constructor(
private val coil: ImageLoader, private val coil: ImageLoader,
private val settings: AppSettings, private val settings: AppSettings,
private val tracker: Tracker, private val tracker: Tracker,
private val workManager: WorkManager,
@TrackerLogger private val logger: FileLogger, @TrackerLogger private val logger: FileLogger,
) : CoroutineWorker(context, workerParams) { ) : CoroutineWorker(context, workerParams) {
@ -164,7 +167,10 @@ class TrackWorker @AssistedInject constructor(
} }
} }
} }
}.onEach { }.onEachIndexed { index, it ->
if (applicationContext.checkNotificationPermission()) {
notificationManager.notify(WORKER_NOTIFICATION_ID, createWorkerNotification(tracks.size, index + 1))
}
when (it) { when (it) {
is MangaUpdates.Failure -> { is MangaUpdates.Failure -> {
val e = it.error val e = it.error
@ -254,12 +260,11 @@ class TrackWorker @AssistedInject constructor(
} }
override suspend fun getForegroundInfo(): ForegroundInfo { override suspend fun getForegroundInfo(): ForegroundInfo {
val title = applicationContext.getString(R.string.check_for_new_chapters)
val channel = NotificationChannelCompat.Builder( val channel = NotificationChannelCompat.Builder(
WORKER_CHANNEL_ID, WORKER_CHANNEL_ID,
NotificationManagerCompat.IMPORTANCE_LOW NotificationManagerCompat.IMPORTANCE_LOW,
) )
.setName(title) .setName(applicationContext.getString(R.string.check_for_new_chapters))
.setShowBadge(false) .setShowBadge(false)
.setVibrationEnabled(false) .setVibrationEnabled(false)
.setSound(null, null) .setSound(null, null)
@ -267,28 +272,56 @@ class TrackWorker @AssistedInject constructor(
.build() .build()
notificationManager.createNotificationChannel(channel) notificationManager.createNotificationChannel(channel)
val notification = NotificationCompat.Builder(applicationContext, WORKER_CHANNEL_ID) val notification = createWorkerNotification(0, 0)
.setContentTitle(title)
.setPriority(NotificationCompat.PRIORITY_MIN)
.setCategory(NotificationCompat.CATEGORY_SERVICE)
.setDefaults(0)
.setOngoing(false)
.setSilent(true)
.setProgress(0, 0, true)
.setSmallIcon(android.R.drawable.stat_notify_sync)
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_DEFERRED)
.build()
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
ForegroundInfo( ForegroundInfo(
WORKER_NOTIFICATION_ID, WORKER_NOTIFICATION_ID,
notification, notification,
ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC,
) )
} else { } else {
ForegroundInfo(WORKER_NOTIFICATION_ID, notification) ForegroundInfo(WORKER_NOTIFICATION_ID, notification)
} }
} }
private fun createWorkerNotification(max: Int, progress: Int) = NotificationCompat.Builder(
applicationContext,
WORKER_CHANNEL_ID,
).apply {
setContentTitle(applicationContext.getString(R.string.check_for_new_chapters))
setPriority(NotificationCompat.PRIORITY_MIN)
setCategory(NotificationCompat.CATEGORY_SERVICE)
setDefaults(0)
setOngoing(false)
setSilent(true)
setContentIntent(
PendingIntentCompat.getActivity(
applicationContext,
0,
SettingsActivity.newTrackerSettingsIntent(applicationContext),
0,
false,
),
)
addAction(
materialR.drawable.material_ic_clear_black_24dp,
applicationContext.getString(android.R.string.cancel),
workManager.createCancelPendingIntent(id),
)
if (max > 0) {
setSubText(applicationContext.getString(R.string.fraction_pattern, progress, max))
}
setProgress(max, progress, max == 0)
setSmallIcon(android.R.drawable.stat_notify_sync)
setForegroundServiceBehavior(
if (TAG_ONESHOT in tags) {
NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE
} else {
NotificationCompat.FOREGROUND_SERVICE_DEFERRED
},
)
}.build()
private suspend fun setRetryIds(ids: Set<Long>) = runInterruptible(Dispatchers.IO) { private suspend fun setRetryIds(ids: Set<Long>) = runInterruptible(Dispatchers.IO) {
val prefs = applicationContext.getSharedPreferences(TAG, Context.MODE_PRIVATE) val prefs = applicationContext.getSharedPreferences(TAG, Context.MODE_PRIVATE)
prefs.edit(commit = true) { prefs.edit(commit = true) {

@ -588,4 +588,5 @@
<string name="default_webtoon_zoom_out">Default webtoon zoom out</string> <string name="default_webtoon_zoom_out">Default webtoon zoom out</string>
<string name="fullscreen_mode">Fullscreen mode</string> <string name="fullscreen_mode">Fullscreen mode</string>
<string name="reader_fullscreen_summary">Hide system status and navigation bars</string> <string name="reader_fullscreen_summary">Hide system status and navigation bars</string>
<string name="fraction_pattern">%1$d/%2$d</string>
</resources> </resources>

Loading…
Cancel
Save