diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAdvancedScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAdvancedScreen.kt index d1c8de7a1..92f2053cd 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAdvancedScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAdvancedScreen.kt @@ -118,6 +118,7 @@ object SettingsAdvancedScreen : SearchableSettings { private fun getBackgroundActivityGroup(): Preference.PreferenceGroup { val context = LocalContext.current val uriHandler = LocalUriHandler.current + val navigator = LocalNavigator.currentOrThrow return Preference.PreferenceGroup( title = stringResource(R.string.label_background_activity), @@ -149,6 +150,10 @@ object SettingsAdvancedScreen : SearchableSettings { subtitle = stringResource(R.string.about_dont_kill_my_app), onClick = { uriHandler.openUri("https://dontkillmyapp.com/") }, ), + Preference.PreferenceItem.TextPreference( + title = stringResource(R.string.pref_worker_info), + onClick = { navigator.push(WorkerInfoScreen) }, + ), ), ) } diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/WorkerInfoScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/WorkerInfoScreen.kt new file mode 100644 index 000000000..48dc95de0 --- /dev/null +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/WorkerInfoScreen.kt @@ -0,0 +1,161 @@ +package eu.kanade.presentation.more.settings.screen + +import android.content.Context +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.filled.ContentCopy +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalClipboardManager +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.unit.dp +import androidx.lifecycle.asFlow +import androidx.work.WorkInfo +import androidx.work.WorkManager +import androidx.work.WorkQuery +import cafe.adriel.voyager.core.model.ScreenModel +import cafe.adriel.voyager.core.model.coroutineScope +import cafe.adriel.voyager.core.model.rememberScreenModel +import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.navigator.LocalNavigator +import cafe.adriel.voyager.navigator.currentOrThrow +import eu.kanade.presentation.components.LazyColumn +import eu.kanade.presentation.components.Scaffold +import eu.kanade.presentation.util.plus +import eu.kanade.tachiyomi.R +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch + +object WorkerInfoScreen : Screen { + + @Composable + override fun Content() { + val context = LocalContext.current + val navigator = LocalNavigator.currentOrThrow + val clipboardManager = LocalClipboardManager.current + + val screenModel = rememberScreenModel { Model(context) } + val enqueued by screenModel.enqueued.collectAsState() + val finished by screenModel.finished.collectAsState() + val running by screenModel.running.collectAsState() + + val snackbarHostState = remember { SnackbarHostState() } + val scope = rememberCoroutineScope() + + Scaffold( + topBar = { + TopAppBar( + title = { Text(text = stringResource(R.string.pref_worker_info)) }, + navigationIcon = { + IconButton(onClick = navigator::pop) { + Icon(imageVector = Icons.Default.ArrowBack, contentDescription = null) + } + }, + actions = { + val copiedString = stringResource(R.string.copied_to_clipboard_plain) + IconButton( + onClick = { + clipboardManager.setText(AnnotatedString(enqueued + finished + running)) + scope.launch { snackbarHostState.showSnackbar(copiedString) } + }, + ) { + Icon(imageVector = Icons.Default.ContentCopy, contentDescription = null) + } + }, + scrollBehavior = it, + ) + }, + snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, + ) { contentPadding -> + LazyColumn( + contentPadding = contentPadding + PaddingValues(horizontal = 16.dp), + modifier = Modifier.horizontalScroll(rememberScrollState()), + ) { + item { SectionTitle(title = "Enqueued") } + item { SectionText(text = enqueued) } + + item { SectionTitle(title = "Finished") } + item { SectionText(text = finished) } + + item { SectionTitle(title = "Running") } + item { SectionText(text = running) } + } + } + } + + @Composable + private fun SectionTitle(title: String) { + Text( + text = title, + style = MaterialTheme.typography.titleMedium, + modifier = Modifier.padding(vertical = 8.dp), + ) + } + + @Composable + private fun SectionText(text: String) { + Text( + text = text, + softWrap = false, + fontFamily = FontFamily.Monospace, + ) + } + + private class Model(context: Context) : ScreenModel { + private val workManager = WorkManager.getInstance(context) + + val finished = workManager + .getWorkInfosLiveData(WorkQuery.fromStates(WorkInfo.State.SUCCEEDED, WorkInfo.State.FAILED, WorkInfo.State.CANCELLED)) + .asFlow() + .map(::constructString) + .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), "") + + val running = workManager + .getWorkInfosLiveData(WorkQuery.fromStates(WorkInfo.State.RUNNING)) + .asFlow() + .map(::constructString) + .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), "") + + val enqueued = workManager + .getWorkInfosLiveData(WorkQuery.fromStates(WorkInfo.State.ENQUEUED)) + .asFlow() + .map(::constructString) + .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), "") + + private fun constructString(list: List) = buildString { + if (list.isEmpty()) { + appendLine("-") + } else { + list.forEach { workInfo -> + appendLine("Id: ${workInfo.id}") + appendLine("Tags:") + workInfo.tags.forEach { + appendLine(" - $it") + } + appendLine("State: ${workInfo.state}") + appendLine() + } + } + } + } +} diff --git a/i18n/src/main/res/values/strings.xml b/i18n/src/main/res/values/strings.xml index 6cbd16821..94c10aa81 100644 --- a/i18n/src/main/res/values/strings.xml +++ b/i18n/src/main/res/values/strings.xml @@ -538,6 +538,7 @@ Tablet UI Verbose logging Print verbose logs to system log (reduces app performance) + Worker info Website @@ -634,6 +635,7 @@ %1$s chapters Delete downloaded chapters? + Copied to clipboard Copied to clipboard:\n%1$s Failed to copy to clipboard Source not installed: %1$s