From 274aefb3b36c9194db4b0b00f97dd139d6d426fd Mon Sep 17 00:00:00 2001 From: Cuong-Tran Date: Fri, 5 Jul 2024 22:10:09 +0700 Subject: [PATCH] feat(ui): Add restoring & syncing banner --- .../kanade/presentation/components/Banners.kt | 26 ++++++++++++++++--- .../data/backup/restore/BackupRestoreJob.kt | 13 ++++++++++ .../backup/restore/BackupRestoreStatus.kt | 26 +++++++++++++++++++ .../kanade/tachiyomi/data/sync/SyncDataJob.kt | 11 ++++++++ .../kanade/tachiyomi/data/sync/SyncStatus.kt | 26 +++++++++++++++++++ .../java/eu/kanade/tachiyomi/di/AppModule.kt | 7 +++++ .../kanade/tachiyomi/ui/main/MainActivity.kt | 15 +++++++++++ 7 files changed, 121 insertions(+), 3 deletions(-) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestoreStatus.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/sync/SyncStatus.kt diff --git a/app/src/main/java/eu/kanade/presentation/components/Banners.kt b/app/src/main/java/eu/kanade/presentation/components/Banners.kt index 7e702a810..df45bab99 100644 --- a/app/src/main/java/eu/kanade/presentation/components/Banners.kt +++ b/app/src/main/java/eu/kanade/presentation/components/Banners.kt @@ -63,6 +63,10 @@ fun AppStateBanners( downloadedOnlyMode: Boolean, incognitoMode: Boolean, indexing: Boolean, + // KMK --> + restoring: Boolean, + syncing: Boolean, + // KMK <-- modifier: Modifier = Modifier, ) { val density = LocalDensity.current @@ -71,12 +75,21 @@ fun AppStateBanners( SubcomposeLayout(modifier = modifier) { constraints -> val indexingPlaceable = subcompose(0) { AnimatedVisibility( - visible = indexing, + // KMK --> + visible = indexing || restoring || syncing, + // KMK <-- enter = expandVertically(), exit = shrinkVertically(), ) { IndexingDownloadBanner( modifier = Modifier.windowInsetsPadding(mainInsets), + // KMK --> + text = when { + syncing -> stringResource(MR.strings.syncing_library) + restoring -> stringResource(MR.strings.restoring_backup) + else -> stringResource(MR.strings.download_notifier_cache_renewal) + }, + // KMK <-- ) } }.fastMap { it.measure(constraints) } @@ -155,7 +168,12 @@ private fun IncognitoModeBanner(modifier: Modifier = Modifier) { } @Composable -private fun IndexingDownloadBanner(modifier: Modifier = Modifier) { +private fun IndexingDownloadBanner( + modifier: Modifier = Modifier, + // KMK --> + text: String = stringResource(MR.strings.download_notifier_cache_renewal), + // KMK <-- +) { val density = LocalDensity.current Row( modifier = Modifier @@ -173,7 +191,9 @@ private fun IndexingDownloadBanner(modifier: Modifier = Modifier) { ) Spacer(modifier = Modifier.width(8.dp)) Text( - text = stringResource(MR.strings.download_notifier_cache_renewal), + // KMK --> + text = text, + // KMK <-- color = MaterialTheme.colorScheme.onSecondary, textAlign = TextAlign.Center, style = MaterialTheme.typography.labelMedium, diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestoreJob.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestoreJob.kt index 0ecf97e06..504ce07ea 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestoreJob.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestoreJob.kt @@ -22,12 +22,18 @@ import logcat.LogPriority import tachiyomi.core.common.i18n.stringResource import tachiyomi.core.common.util.system.logcat import tachiyomi.i18n.MR +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get class BackupRestoreJob(private val context: Context, workerParams: WorkerParameters) : CoroutineWorker(context, workerParams) { private val notifier = BackupNotifier(context) + // KMK --> + private val backupRestoreStatus: BackupRestoreStatus = Injekt.get() + // KMK <-- + override suspend fun doWork(): Result { val uri = inputData.getString(LOCATION_URI_KEY)?.toUri() val options = inputData.getBooleanArray(OPTIONS_KEY)?.let { RestoreOptions.fromBooleanArray(it) } @@ -36,6 +42,10 @@ class BackupRestoreJob(private val context: Context, workerParams: WorkerParamet return Result.failure() } + // KMK --> + backupRestoreStatus.start() + // KMK <-- + val isSync = inputData.getBoolean(SYNC_KEY, false) setForegroundSafely() @@ -54,6 +64,9 @@ class BackupRestoreJob(private val context: Context, workerParams: WorkerParamet } } finally { context.cancelNotification(Notifications.ID_RESTORE_PROGRESS) + // KMK --> + backupRestoreStatus.stop() + // KMK <-- } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestoreStatus.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestoreStatus.kt new file mode 100644 index 000000000..b6a55c7d3 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestoreStatus.kt @@ -0,0 +1,26 @@ +package eu.kanade.tachiyomi.data.backup.restore + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.stateIn + +class BackupRestoreStatus { + private val scope = CoroutineScope(Dispatchers.IO) + + private val _isRunning = MutableStateFlow(false) + + val isRunning = _isRunning + .debounce(1000L) // Don't notify if it finishes quickly enough + .stateIn(scope, SharingStarted.WhileSubscribed(), false) + + suspend fun start() { + _isRunning.emit(true) + } + + suspend fun stop() { + _isRunning.emit(false) + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/sync/SyncDataJob.kt b/app/src/main/java/eu/kanade/tachiyomi/data/sync/SyncDataJob.kt index 3aeca8fad..bd3a78b41 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/sync/SyncDataJob.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/sync/SyncDataJob.kt @@ -29,6 +29,10 @@ class SyncDataJob(private val context: Context, workerParams: WorkerParameters) private val notifier = SyncNotifier(context) + // KMK --> + private val syncStatus: SyncStatus = Injekt.get() + // KMK <-- + override suspend fun doWork(): Result { if (tags.contains(TAG_AUTO)) { // Find a running manual worker. If exists, try again later @@ -37,6 +41,10 @@ class SyncDataJob(private val context: Context, workerParams: WorkerParameters) } } + // KMK --> + syncStatus.start() + // KMK <-- + setForegroundSafely() return try { @@ -48,6 +56,9 @@ class SyncDataJob(private val context: Context, workerParams: WorkerParameters) Result.success() // try again next time } finally { context.cancelNotification(Notifications.ID_RESTORE_PROGRESS) + // KMK --> + syncStatus.stop() + // KMK <-- } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/sync/SyncStatus.kt b/app/src/main/java/eu/kanade/tachiyomi/data/sync/SyncStatus.kt new file mode 100644 index 000000000..471f4df45 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/sync/SyncStatus.kt @@ -0,0 +1,26 @@ +package eu.kanade.tachiyomi.data.sync + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.stateIn + +class SyncStatus { + private val scope = CoroutineScope(Dispatchers.IO) + + private val _isRunning = MutableStateFlow(false) + + val isRunning = _isRunning + .debounce(1000L) // Don't notify if it finishes quickly enough + .stateIn(scope, SharingStarted.WhileSubscribed(), false) + + suspend fun start() { + _isRunning.emit(true) + } + + suspend fun stop() { + _isRunning.emit(false) + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/di/AppModule.kt b/app/src/main/java/eu/kanade/tachiyomi/di/AppModule.kt index 0e80d4ff1..cd1ffc5fc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/di/AppModule.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/di/AppModule.kt @@ -10,6 +10,7 @@ import app.cash.sqldelight.driver.android.AndroidSqliteDriver import eu.kanade.domain.track.store.DelayedTrackingStore import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.core.security.SecurityPreferences +import eu.kanade.tachiyomi.data.backup.restore.BackupRestoreStatus import eu.kanade.tachiyomi.data.cache.ChapterCache import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.cache.PagePreviewCache @@ -17,6 +18,7 @@ import eu.kanade.tachiyomi.data.download.DownloadCache import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadProvider import eu.kanade.tachiyomi.data.saver.ImageSaver +import eu.kanade.tachiyomi.data.sync.SyncStatus import eu.kanade.tachiyomi.data.sync.service.GoogleDriveService import eu.kanade.tachiyomi.data.track.TrackerManager import eu.kanade.tachiyomi.extension.ExtensionManager @@ -172,6 +174,11 @@ class AppModule(val app: Application) : InjektModule { addSingletonFactory { PagePreviewCache(app) } // SY <-- + // KMK --> + addSingletonFactory { BackupRestoreStatus() } + addSingletonFactory { SyncStatus() } + // KMK <-- + // Asynchronously init expensive components for a faster cold start ContextCompat.getMainExecutor(app).execute { get() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt index 551fb26c8..11cfc6e32 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt @@ -61,10 +61,12 @@ import eu.kanade.presentation.more.settings.screen.data.RestoreBackupScreen import eu.kanade.presentation.util.AssistContentScreen import eu.kanade.presentation.util.DefaultNavigatorScreenTransition import eu.kanade.tachiyomi.BuildConfig +import eu.kanade.tachiyomi.data.backup.restore.BackupRestoreStatus import eu.kanade.tachiyomi.data.cache.ChapterCache import eu.kanade.tachiyomi.data.coil.MangaCoverMetadata import eu.kanade.tachiyomi.data.download.DownloadCache import eu.kanade.tachiyomi.data.notification.NotificationReceiver +import eu.kanade.tachiyomi.data.sync.SyncStatus import eu.kanade.tachiyomi.data.updater.AppUpdateChecker import eu.kanade.tachiyomi.extension.api.ExtensionApi import eu.kanade.tachiyomi.ui.base.activity.BaseActivity @@ -122,6 +124,11 @@ class MainActivity : BaseActivity() { private val unsortedPreferences: UnsortedPreferences by injectLazy() // SY <-- + // KMK --> + private val backupRestoreStatus: BackupRestoreStatus by injectLazy() + private val syncStatus: SyncStatus by injectLazy() + // KMK <-- + private val downloadCache: DownloadCache by injectLazy() private val chapterCache: ChapterCache by injectLazy() @@ -189,6 +196,10 @@ class MainActivity : BaseActivity() { val incognito by preferences.incognitoMode().collectAsState() val downloadOnly by preferences.downloadedOnly().collectAsState() val indexing by downloadCache.isInitializing.collectAsState() + // KMK --> + val syncing by syncStatus.isRunning.collectAsState() + val restoring by backupRestoreStatus.isRunning.collectAsState() + // KMK <-- // Set status bar color considering the top app state banner val systemUiController = rememberSystemUiController() @@ -260,6 +271,10 @@ class MainActivity : BaseActivity() { downloadedOnlyMode = downloadOnly, incognitoMode = incognito, indexing = indexing, + // KMK --> + restoring = restoring, + syncing = syncing, + // KMK <-- modifier = Modifier.windowInsetsPadding(scaffoldInsets), ) },