mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-10-31 14:27:57 +01:00 
			
		
		
		
	Migrate More screen to Compose (#6990)
This commit is contained in:
		| @@ -0,0 +1,38 @@ | ||||
| package eu.kanade.core.prefs | ||||
|  | ||||
| import androidx.compose.runtime.MutableState | ||||
| import androidx.compose.runtime.mutableStateOf | ||||
| import com.fredporciuncula.flow.preferences.Preference | ||||
| import kotlinx.coroutines.CoroutineScope | ||||
| import kotlinx.coroutines.flow.distinctUntilChanged | ||||
| import kotlinx.coroutines.flow.launchIn | ||||
| import kotlinx.coroutines.flow.onEach | ||||
|  | ||||
| class PreferenceMutableState<T>( | ||||
|     private val preference: Preference<T>, | ||||
|     scope: CoroutineScope, | ||||
| ) : MutableState<T> { | ||||
|  | ||||
|     private val state = mutableStateOf(preference.get()) | ||||
|  | ||||
|     init { | ||||
|         preference.asFlow() | ||||
|             .distinctUntilChanged() | ||||
|             .onEach { state.value = it } | ||||
|             .launchIn(scope) | ||||
|     } | ||||
|  | ||||
|     override var value: T | ||||
|         get() = state.value | ||||
|         set(value) { | ||||
|             preference.set(value) | ||||
|         } | ||||
|  | ||||
|     override fun component1(): T { | ||||
|         return state.value | ||||
|     } | ||||
|  | ||||
|     override fun component2(): (T) -> Unit { | ||||
|         return { preference.set(it) } | ||||
|     } | ||||
| } | ||||
| @@ -11,12 +11,14 @@ import androidx.compose.foundation.layout.size | ||||
| import androidx.compose.foundation.layout.widthIn | ||||
| import androidx.compose.material3.Icon | ||||
| import androidx.compose.material3.MaterialTheme | ||||
| import androidx.compose.material3.Switch | ||||
| import androidx.compose.material3.Text | ||||
| import androidx.compose.runtime.Composable | ||||
| import androidx.compose.ui.Alignment | ||||
| import androidx.compose.ui.Modifier | ||||
| import androidx.compose.ui.graphics.vector.ImageVector | ||||
| import androidx.compose.ui.graphics.painter.Painter | ||||
| import androidx.compose.ui.unit.dp | ||||
| import eu.kanade.core.prefs.PreferenceMutableState | ||||
| import eu.kanade.presentation.util.horizontalPadding | ||||
|  | ||||
| @Composable | ||||
| @@ -29,7 +31,7 @@ fun Divider() { | ||||
| @Composable | ||||
| fun PreferenceRow( | ||||
|     title: String, | ||||
|     icon: ImageVector? = null, | ||||
|     painter: Painter? = null, | ||||
|     onClick: () -> Unit = {}, | ||||
|     onLongClick: () -> Unit = {}, | ||||
|     subtitle: String? = null, | ||||
| @@ -50,18 +52,18 @@ fun PreferenceRow( | ||||
|             .heightIn(min = height) | ||||
|             .combinedClickable( | ||||
|                 onLongClick = onLongClick, | ||||
|                 onClick = onClick | ||||
|                 onClick = onClick, | ||||
|             ), | ||||
|         verticalAlignment = Alignment.CenterVertically | ||||
|     ) { | ||||
|         if (icon != null) { | ||||
|         if (painter != null) { | ||||
|             Icon( | ||||
|                 imageVector = icon, | ||||
|                 painter = painter, | ||||
|                 modifier = Modifier | ||||
|                     .padding(horizontal = horizontalPadding) | ||||
|                     .size(24.dp), | ||||
|                 tint = MaterialTheme.colorScheme.primary, | ||||
|                 contentDescription = null | ||||
|                 contentDescription = null, | ||||
|             ) | ||||
|         } | ||||
|         Column( | ||||
| @@ -88,3 +90,23 @@ fun PreferenceRow( | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @Composable | ||||
| fun SwitchPreference( | ||||
|     preference: PreferenceMutableState<Boolean>, | ||||
|     title: String, | ||||
|     subtitle: String? = null, | ||||
|     painter: Painter? = null, | ||||
| ) { | ||||
|     PreferenceRow( | ||||
|         title = title, | ||||
|         subtitle = subtitle, | ||||
|         painter = painter, | ||||
|         action = { | ||||
|             Switch(checked = preference.value, onCheckedChange = null) | ||||
|             // TODO: remove this once switch checked state is fixed: https://issuetracker.google.com/issues/228336571 | ||||
|             Text(preference.value.toString()) | ||||
|         }, | ||||
|         onClick = { preference.value = !preference.value }, | ||||
|     ) | ||||
| } | ||||
|   | ||||
							
								
								
									
										131
									
								
								app/src/main/java/eu/kanade/presentation/more/MoreScreen.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								app/src/main/java/eu/kanade/presentation/more/MoreScreen.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,131 @@ | ||||
| package eu.kanade.presentation.more | ||||
|  | ||||
| import androidx.compose.foundation.lazy.LazyColumn | ||||
| import androidx.compose.material.icons.Icons | ||||
| import androidx.compose.material.icons.outlined.CloudOff | ||||
| import androidx.compose.material.icons.outlined.GetApp | ||||
| import androidx.compose.material.icons.outlined.HelpOutline | ||||
| import androidx.compose.material.icons.outlined.Info | ||||
| import androidx.compose.material.icons.outlined.Label | ||||
| import androidx.compose.material.icons.outlined.Settings | ||||
| import androidx.compose.material.icons.outlined.SettingsBackupRestore | ||||
| import androidx.compose.runtime.Composable | ||||
| import androidx.compose.runtime.collectAsState | ||||
| import androidx.compose.runtime.getValue | ||||
| import androidx.compose.ui.Modifier | ||||
| import androidx.compose.ui.graphics.vector.rememberVectorPainter | ||||
| import androidx.compose.ui.input.nestedscroll.NestedScrollConnection | ||||
| import androidx.compose.ui.input.nestedscroll.nestedScroll | ||||
| import androidx.compose.ui.platform.LocalUriHandler | ||||
| import androidx.compose.ui.res.painterResource | ||||
| import androidx.compose.ui.res.stringResource | ||||
| import eu.kanade.presentation.components.Divider | ||||
| import eu.kanade.presentation.components.PreferenceRow | ||||
| import eu.kanade.presentation.components.SwitchPreference | ||||
| import eu.kanade.presentation.util.quantityStringResource | ||||
| import eu.kanade.tachiyomi.R | ||||
| import eu.kanade.tachiyomi.ui.more.DownloadQueueState | ||||
| import eu.kanade.tachiyomi.ui.more.MoreController | ||||
| import eu.kanade.tachiyomi.ui.more.MorePresenter | ||||
|  | ||||
| @Composable | ||||
| fun MoreScreen( | ||||
|     nestedScrollInterop: NestedScrollConnection, | ||||
|     presenter: MorePresenter, | ||||
|     onClickDownloadQueue: () -> Unit, | ||||
|     onClickCategories: () -> Unit, | ||||
|     onClickBackupAndRestore: () -> Unit, | ||||
|     onClickSettings: () -> Unit, | ||||
|     onClickAbout: () -> Unit, | ||||
| ) { | ||||
|     val uriHandler = LocalUriHandler.current | ||||
|     val downloadQueueState by presenter.downloadQueueState.collectAsState() | ||||
|  | ||||
|     LazyColumn( | ||||
|         modifier = Modifier.nestedScroll(nestedScrollInterop), | ||||
|     ) { | ||||
|         item { | ||||
|             LogoHeader() | ||||
|         } | ||||
|  | ||||
|         item { | ||||
|             SwitchPreference( | ||||
|                 preference = presenter.downloadedOnly, | ||||
|                 title = stringResource(R.string.label_downloaded_only), | ||||
|                 subtitle = stringResource(R.string.downloaded_only_summary), | ||||
|                 painter = rememberVectorPainter(Icons.Outlined.CloudOff), | ||||
|             ) | ||||
|         } | ||||
|         item { | ||||
|             SwitchPreference( | ||||
|                 preference = presenter.incognitoMode, | ||||
|                 title = stringResource(R.string.pref_incognito_mode), | ||||
|                 subtitle = stringResource(R.string.pref_incognito_mode_summary), | ||||
|                 painter = painterResource(R.drawable.ic_glasses_24dp), | ||||
|             ) | ||||
|         } | ||||
|  | ||||
|         item { Divider() } | ||||
|  | ||||
|         item { | ||||
|             PreferenceRow( | ||||
|                 title = stringResource(R.string.label_download_queue), | ||||
|                 subtitle = when (downloadQueueState) { | ||||
|                     DownloadQueueState.Stopped -> null | ||||
|                     is DownloadQueueState.Paused -> { | ||||
|                         val pending = (downloadQueueState as DownloadQueueState.Paused).pending | ||||
|                         if (pending == 0) { | ||||
|                             stringResource(R.string.paused) | ||||
|                         } else { | ||||
|                             "${stringResource(R.string.paused)} • ${quantityStringResource(R.plurals.download_queue_summary, pending, pending)}" | ||||
|                         } | ||||
|                     } | ||||
|                     is DownloadQueueState.Downloading -> { | ||||
|                         val pending = (downloadQueueState as DownloadQueueState.Downloading).pending | ||||
|                         quantityStringResource(R.plurals.download_queue_summary, pending, pending) | ||||
|                     } | ||||
|                 }, | ||||
|                 painter = rememberVectorPainter(Icons.Outlined.GetApp), | ||||
|                 onClick = { onClickDownloadQueue() }, | ||||
|             ) | ||||
|         } | ||||
|         item { | ||||
|             PreferenceRow( | ||||
|                 title = stringResource(R.string.categories), | ||||
|                 painter = rememberVectorPainter(Icons.Outlined.Label), | ||||
|                 onClick = { onClickCategories() }, | ||||
|             ) | ||||
|         } | ||||
|         item { | ||||
|             PreferenceRow( | ||||
|                 title = stringResource(R.string.label_backup), | ||||
|                 painter = rememberVectorPainter(Icons.Outlined.SettingsBackupRestore), | ||||
|                 onClick = { onClickBackupAndRestore() }, | ||||
|             ) | ||||
|         } | ||||
|  | ||||
|         item { Divider() } | ||||
|  | ||||
|         item { | ||||
|             PreferenceRow( | ||||
|                 title = stringResource(R.string.label_settings), | ||||
|                 painter = rememberVectorPainter(Icons.Outlined.Settings), | ||||
|                 onClick = { onClickSettings() }, | ||||
|             ) | ||||
|         } | ||||
|         item { | ||||
|             PreferenceRow( | ||||
|                 title = stringResource(R.string.pref_category_about), | ||||
|                 painter = rememberVectorPainter(Icons.Outlined.Info), | ||||
|                 onClick = { onClickAbout() }, | ||||
|             ) | ||||
|         } | ||||
|         item { | ||||
|             PreferenceRow( | ||||
|                 title = stringResource(R.string.label_help), | ||||
|                 painter = rememberVectorPainter(Icons.Outlined.HelpOutline), | ||||
|                 onClick = { uriHandler.openUri(MoreController.URL_HELP) }, | ||||
|             ) | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										32
									
								
								app/src/main/java/eu/kanade/presentation/util/Resources.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								app/src/main/java/eu/kanade/presentation/util/Resources.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| package eu.kanade.presentation.util | ||||
|  | ||||
| import androidx.annotation.PluralsRes | ||||
| import androidx.compose.runtime.Composable | ||||
| import androidx.compose.ui.platform.LocalContext | ||||
|  | ||||
| /** | ||||
|  * Load a quantity string resource. | ||||
|  * | ||||
|  * @param id the resource identifier | ||||
|  * @param quantity The number used to get the string for the current language's plural rules. | ||||
|  * @return the string data associated with the resource | ||||
|  */ | ||||
| @Composable | ||||
| fun quantityStringResource(@PluralsRes id: Int, quantity: Int): String { | ||||
|     val context = LocalContext.current | ||||
|     return context.resources.getQuantityString(id, quantity, quantity) | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Load a quantity string resource with formatting. | ||||
|  * | ||||
|  * @param id the resource identifier | ||||
|  * @param quantity The number used to get the string for the current language's plural rules. | ||||
|  * @param formatArgs the format arguments | ||||
|  * @return the string data associated with the resource | ||||
|  */ | ||||
| @Composable | ||||
| fun quantityStringResource(@PluralsRes id: Int, quantity: Int, vararg formatArgs: Any): String { | ||||
|     val context = LocalContext.current | ||||
|     return context.resources.getQuantityString(id, quantity, *formatArgs) | ||||
| } | ||||
| @@ -1,6 +1,8 @@ | ||||
| package eu.kanade.tachiyomi.ui.base.presenter | ||||
|  | ||||
| import android.os.Bundle | ||||
| import com.fredporciuncula.flow.preferences.Preference | ||||
| import eu.kanade.core.prefs.PreferenceMutableState | ||||
| import kotlinx.coroutines.CoroutineScope | ||||
| import kotlinx.coroutines.MainScope | ||||
| import kotlinx.coroutines.cancel | ||||
| @@ -10,7 +12,7 @@ import rx.Observable | ||||
|  | ||||
| open class BasePresenter<V> : RxPresenter<V>() { | ||||
|  | ||||
|     lateinit var presenterScope: CoroutineScope | ||||
|     var presenterScope: CoroutineScope = MainScope() | ||||
|  | ||||
|     /** | ||||
|      * Query from the view where applicable | ||||
| @@ -20,7 +22,6 @@ open class BasePresenter<V> : RxPresenter<V>() { | ||||
|     override fun onCreate(savedState: Bundle?) { | ||||
|         try { | ||||
|             super.onCreate(savedState) | ||||
|             presenterScope = MainScope() | ||||
|         } catch (e: NullPointerException) { | ||||
|             // Swallow this error. This should be fixed in the library but since it's not critical | ||||
|             // (only used by restartables) it should be enough. It saves me a fork. | ||||
| @@ -38,6 +39,8 @@ open class BasePresenter<V> : RxPresenter<V>() { | ||||
|         return super.getView() | ||||
|     } | ||||
|  | ||||
|     fun <T> Preference<T>.asState() = PreferenceMutableState(this, presenterScope) | ||||
|  | ||||
|     /** | ||||
|      * Subscribes an observable with [deliverFirst] and adds it to the presenter's lifecycle | ||||
|      * subscription list. | ||||
|   | ||||
| @@ -1,187 +1,38 @@ | ||||
| package eu.kanade.tachiyomi.ui.more | ||||
|  | ||||
| import android.os.Bundle | ||||
| import android.view.LayoutInflater | ||||
| import android.view.View | ||||
| import android.view.ViewGroup | ||||
| import androidx.preference.Preference | ||||
| import androidx.preference.PreferenceScreen | ||||
| import androidx.compose.runtime.Composable | ||||
| import androidx.compose.ui.input.nestedscroll.NestedScrollConnection | ||||
| import eu.kanade.presentation.more.MoreScreen | ||||
| import eu.kanade.tachiyomi.R | ||||
| import eu.kanade.tachiyomi.data.download.DownloadManager | ||||
| import eu.kanade.tachiyomi.data.download.DownloadService | ||||
| import eu.kanade.tachiyomi.ui.base.controller.ComposeController | ||||
| import eu.kanade.tachiyomi.ui.base.controller.NoAppBarElevationController | ||||
| import eu.kanade.tachiyomi.ui.base.controller.RootController | ||||
| import eu.kanade.tachiyomi.ui.base.controller.pushController | ||||
| import eu.kanade.tachiyomi.ui.category.CategoryController | ||||
| import eu.kanade.tachiyomi.ui.download.DownloadController | ||||
| import eu.kanade.tachiyomi.ui.setting.SettingsBackupController | ||||
| import eu.kanade.tachiyomi.ui.setting.SettingsController | ||||
| import eu.kanade.tachiyomi.ui.setting.SettingsMainController | ||||
| import eu.kanade.tachiyomi.util.preference.add | ||||
| import eu.kanade.tachiyomi.util.preference.bindTo | ||||
| import eu.kanade.tachiyomi.util.preference.iconRes | ||||
| import eu.kanade.tachiyomi.util.preference.iconTint | ||||
| import eu.kanade.tachiyomi.util.preference.onClick | ||||
| import eu.kanade.tachiyomi.util.preference.preference | ||||
| import eu.kanade.tachiyomi.util.preference.preferenceCategory | ||||
| import eu.kanade.tachiyomi.util.preference.summaryRes | ||||
| import eu.kanade.tachiyomi.util.preference.switchPreference | ||||
| import eu.kanade.tachiyomi.util.preference.titleRes | ||||
| import eu.kanade.tachiyomi.util.system.getResourceColor | ||||
| import eu.kanade.tachiyomi.util.system.openInBrowser | ||||
| import kotlinx.coroutines.flow.launchIn | ||||
| import kotlinx.coroutines.flow.onEach | ||||
| import rx.Observable | ||||
| import rx.Subscription | ||||
| import rx.android.schedulers.AndroidSchedulers | ||||
| import rx.subscriptions.CompositeSubscription | ||||
| import uy.kohesive.injekt.injectLazy | ||||
|  | ||||
| class MoreController : | ||||
|     SettingsController(), | ||||
|     ComposeController<MorePresenter>(), | ||||
|     RootController, | ||||
|     NoAppBarElevationController { | ||||
|  | ||||
|     private val downloadManager: DownloadManager by injectLazy() | ||||
|     private var isDownloading: Boolean = false | ||||
|     private var downloadQueueSize: Int = 0 | ||||
|     override fun getTitle() = resources?.getString(R.string.label_more) | ||||
|  | ||||
|     private var untilDestroySubscriptions = CompositeSubscription() | ||||
|         private set | ||||
|     override fun createPresenter() = MorePresenter() | ||||
|  | ||||
|     override fun setupPreferenceScreen(screen: PreferenceScreen) = screen.apply { | ||||
|         titleRes = R.string.label_more | ||||
|  | ||||
|         val tintColor = context.getResourceColor(R.attr.colorAccent) | ||||
|  | ||||
|         add(MoreHeaderPreference(context)) | ||||
|  | ||||
|         switchPreference { | ||||
|             bindTo(preferences.downloadedOnly()) | ||||
|             titleRes = R.string.label_downloaded_only | ||||
|             summaryRes = R.string.downloaded_only_summary | ||||
|             iconRes = R.drawable.ic_cloud_off_24dp | ||||
|             iconTint = tintColor | ||||
|         } | ||||
|  | ||||
|         switchPreference { | ||||
|             bindTo(preferences.incognitoMode()) | ||||
|             summaryRes = R.string.pref_incognito_mode_summary | ||||
|             titleRes = R.string.pref_incognito_mode | ||||
|             iconRes = R.drawable.ic_glasses_24dp | ||||
|             iconTint = tintColor | ||||
|  | ||||
|             preferences.incognitoMode().asFlow() | ||||
|                 .onEach { isChecked = it } | ||||
|                 .launchIn(viewScope) | ||||
|         } | ||||
|  | ||||
|         preferenceCategory { | ||||
|             preference { | ||||
|                 titleRes = R.string.label_download_queue | ||||
|  | ||||
|                 if (downloadManager.queue.isNotEmpty()) { | ||||
|                     initDownloadQueueSummary(this) | ||||
|                 } | ||||
|  | ||||
|                 iconRes = R.drawable.ic_get_app_24dp | ||||
|                 iconTint = tintColor | ||||
|                 onClick { | ||||
|                     router.pushController(DownloadController()) | ||||
|                 } | ||||
|             } | ||||
|             preference { | ||||
|                 titleRes = R.string.categories | ||||
|                 iconRes = R.drawable.ic_label_24dp | ||||
|                 iconTint = tintColor | ||||
|                 onClick { | ||||
|                     router.pushController(CategoryController()) | ||||
|                 } | ||||
|             } | ||||
|             preference { | ||||
|                 titleRes = R.string.label_backup | ||||
|                 iconRes = R.drawable.ic_settings_backup_restore_24dp | ||||
|                 iconTint = tintColor | ||||
|                 onClick { | ||||
|                     router.pushController(SettingsBackupController()) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         preferenceCategory { | ||||
|             preference { | ||||
|                 titleRes = R.string.label_settings | ||||
|                 iconRes = R.drawable.ic_settings_24dp | ||||
|                 iconTint = tintColor | ||||
|                 onClick { | ||||
|                     router.pushController(SettingsMainController()) | ||||
|                 } | ||||
|             } | ||||
|             preference { | ||||
|                 iconRes = R.drawable.ic_info_24dp | ||||
|                 iconTint = tintColor | ||||
|                 titleRes = R.string.pref_category_about | ||||
|                 onClick { | ||||
|                     router.pushController(AboutController()) | ||||
|                 } | ||||
|             } | ||||
|             preference { | ||||
|                 titleRes = R.string.label_help | ||||
|                 iconRes = R.drawable.ic_help_24dp | ||||
|                 iconTint = tintColor | ||||
|                 onClick { | ||||
|                     activity?.openInBrowser(URL_HELP) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     override fun onCreateView(inflater: LayoutInflater, container: ViewGroup, savedInstanceState: Bundle?): View { | ||||
|         if (untilDestroySubscriptions.isUnsubscribed) { | ||||
|             untilDestroySubscriptions = CompositeSubscription() | ||||
|         } | ||||
|  | ||||
|         return super.onCreateView(inflater, container, savedInstanceState) | ||||
|     } | ||||
|  | ||||
|     override fun onDestroyView(view: View) { | ||||
|         super.onDestroyView(view) | ||||
|         untilDestroySubscriptions.unsubscribe() | ||||
|     } | ||||
|  | ||||
|     private fun initDownloadQueueSummary(preference: Preference) { | ||||
|         // Handle running/paused status change | ||||
|         DownloadService.runningRelay | ||||
|             .observeOn(AndroidSchedulers.mainThread()) | ||||
|             .subscribeUntilDestroy { isRunning -> | ||||
|                 isDownloading = isRunning | ||||
|                 updateDownloadQueueSummary(preference) | ||||
|             } | ||||
|  | ||||
|         // Handle queue progress updating | ||||
|         downloadManager.queue.getUpdatedObservable() | ||||
|             .observeOn(AndroidSchedulers.mainThread()) | ||||
|             .subscribeUntilDestroy { | ||||
|                 downloadQueueSize = it.size | ||||
|                 updateDownloadQueueSummary(preference) | ||||
|             } | ||||
|     } | ||||
|  | ||||
|     private fun updateDownloadQueueSummary(preference: Preference) { | ||||
|         var pendingDownloadExists = downloadQueueSize != 0 | ||||
|         var pauseMessage = resources?.getString(R.string.paused) | ||||
|         var numberOfPendingDownloads = resources?.getQuantityString(R.plurals.download_queue_summary, downloadQueueSize, downloadQueueSize) | ||||
|  | ||||
|         preference.summary = when { | ||||
|             !pendingDownloadExists -> null | ||||
|             !isDownloading && !pendingDownloadExists -> pauseMessage | ||||
|             !isDownloading && pendingDownloadExists -> "$pauseMessage • $numberOfPendingDownloads" | ||||
|             else -> numberOfPendingDownloads | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun <T> Observable<T>.subscribeUntilDestroy(onNext: (T) -> Unit): Subscription { | ||||
|         return subscribe(onNext).also { untilDestroySubscriptions.add(it) } | ||||
|     @Composable | ||||
|     override fun ComposeContent(nestedScrollInterop: NestedScrollConnection) { | ||||
|         MoreScreen( | ||||
|             nestedScrollInterop = nestedScrollInterop, | ||||
|             presenter = presenter, | ||||
|             onClickDownloadQueue = { router.pushController(DownloadController()) }, | ||||
|             onClickCategories = { router.pushController(CategoryController()) }, | ||||
|             onClickBackupAndRestore = { router.pushController(SettingsBackupController()) }, | ||||
|             onClickSettings = { router.pushController(SettingsMainController()) }, | ||||
|             onClickAbout = { router.pushController(AboutController()) }, | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|     companion object { | ||||
|   | ||||
| @@ -0,0 +1,89 @@ | ||||
| package eu.kanade.tachiyomi.ui.more | ||||
|  | ||||
| import android.os.Bundle | ||||
| import eu.kanade.tachiyomi.data.download.DownloadManager | ||||
| import eu.kanade.tachiyomi.data.download.DownloadService | ||||
| import eu.kanade.tachiyomi.data.preference.PreferencesHelper | ||||
| import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter | ||||
| import eu.kanade.tachiyomi.util.lang.launchIO | ||||
| import kotlinx.coroutines.flow.MutableStateFlow | ||||
| import kotlinx.coroutines.flow.StateFlow | ||||
| import rx.Observable | ||||
| import rx.Subscription | ||||
| import rx.android.schedulers.AndroidSchedulers | ||||
| import rx.subscriptions.CompositeSubscription | ||||
| import uy.kohesive.injekt.Injekt | ||||
| import uy.kohesive.injekt.api.get | ||||
|  | ||||
| class MorePresenter( | ||||
|     private val downloadManager: DownloadManager = Injekt.get(), | ||||
|     preferencesHelper: PreferencesHelper = Injekt.get(), | ||||
| ) : BasePresenter<MoreController>() { | ||||
|  | ||||
|     val downloadedOnly = preferencesHelper.downloadedOnly().asState() | ||||
|     val incognitoMode = preferencesHelper.incognitoMode().asState() | ||||
|  | ||||
|     private var _state: MutableStateFlow<DownloadQueueState> = MutableStateFlow(DownloadQueueState.Stopped) | ||||
|     val downloadQueueState: StateFlow<DownloadQueueState> = _state | ||||
|  | ||||
|     private var isDownloading: Boolean = false | ||||
|     private var downloadQueueSize: Int = 0 | ||||
|     private var untilDestroySubscriptions = CompositeSubscription() | ||||
|  | ||||
|     override fun onCreate(savedState: Bundle?) { | ||||
|         super.onCreate(savedState) | ||||
|  | ||||
|         if (untilDestroySubscriptions.isUnsubscribed) { | ||||
|             untilDestroySubscriptions = CompositeSubscription() | ||||
|         } | ||||
|  | ||||
|         initDownloadQueueSummary() | ||||
|     } | ||||
|  | ||||
|     override fun onDestroy() { | ||||
|         super.onDestroy() | ||||
|         untilDestroySubscriptions.unsubscribe() | ||||
|     } | ||||
|  | ||||
|     private fun initDownloadQueueSummary() { | ||||
|         // Handle running/paused status change | ||||
|         DownloadService.runningRelay | ||||
|             .observeOn(AndroidSchedulers.mainThread()) | ||||
|             .subscribeUntilDestroy { isRunning -> | ||||
|                 isDownloading = isRunning | ||||
|                 updateDownloadQueueState() | ||||
|             } | ||||
|  | ||||
|         // Handle queue progress updating | ||||
|         downloadManager.queue.getUpdatedObservable() | ||||
|             .observeOn(AndroidSchedulers.mainThread()) | ||||
|             .subscribeUntilDestroy { | ||||
|                 downloadQueueSize = it.size | ||||
|                 updateDownloadQueueState() | ||||
|             } | ||||
|     } | ||||
|  | ||||
|     private fun updateDownloadQueueState() { | ||||
|         presenterScope.launchIO { | ||||
|             val pendingDownloadExists = downloadQueueSize != 0 | ||||
|             _state.emit( | ||||
|                 when { | ||||
|                     !pendingDownloadExists -> DownloadQueueState.Stopped | ||||
|                     !isDownloading && !pendingDownloadExists -> DownloadQueueState.Paused(0) | ||||
|                     !isDownloading && pendingDownloadExists -> DownloadQueueState.Paused(downloadQueueSize) | ||||
|                     else -> DownloadQueueState.Downloading(downloadQueueSize) | ||||
|                 } | ||||
|             ) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun <T> Observable<T>.subscribeUntilDestroy(onNext: (T) -> Unit): Subscription { | ||||
|         return subscribe(onNext).also { untilDestroySubscriptions.add(it) } | ||||
|     } | ||||
| } | ||||
|  | ||||
| sealed class DownloadQueueState { | ||||
|     object Stopped : DownloadQueueState() | ||||
|     data class Paused(val pending: Int) : DownloadQueueState() | ||||
|     data class Downloading(val pending: Int) : DownloadQueueState() | ||||
| } | ||||
		Reference in New Issue
	
	Block a user