Migrate More screen to Compose (#6990)
This commit is contained in:
parent
8933b41937
commit
c25cffafc6
@ -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.foundation.layout.widthIn
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Switch
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
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 androidx.compose.ui.unit.dp
|
||||||
|
import eu.kanade.core.prefs.PreferenceMutableState
|
||||||
import eu.kanade.presentation.util.horizontalPadding
|
import eu.kanade.presentation.util.horizontalPadding
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -29,7 +31,7 @@ fun Divider() {
|
|||||||
@Composable
|
@Composable
|
||||||
fun PreferenceRow(
|
fun PreferenceRow(
|
||||||
title: String,
|
title: String,
|
||||||
icon: ImageVector? = null,
|
painter: Painter? = null,
|
||||||
onClick: () -> Unit = {},
|
onClick: () -> Unit = {},
|
||||||
onLongClick: () -> Unit = {},
|
onLongClick: () -> Unit = {},
|
||||||
subtitle: String? = null,
|
subtitle: String? = null,
|
||||||
@ -50,18 +52,18 @@ fun PreferenceRow(
|
|||||||
.heightIn(min = height)
|
.heightIn(min = height)
|
||||||
.combinedClickable(
|
.combinedClickable(
|
||||||
onLongClick = onLongClick,
|
onLongClick = onLongClick,
|
||||||
onClick = onClick
|
onClick = onClick,
|
||||||
),
|
),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
if (icon != null) {
|
if (painter != null) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = icon,
|
painter = painter,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(horizontal = horizontalPadding)
|
.padding(horizontal = horizontalPadding)
|
||||||
.size(24.dp),
|
.size(24.dp),
|
||||||
tint = MaterialTheme.colorScheme.primary,
|
tint = MaterialTheme.colorScheme.primary,
|
||||||
contentDescription = null
|
contentDescription = null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Column(
|
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
|
package eu.kanade.tachiyomi.ui.base.presenter
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import com.fredporciuncula.flow.preferences.Preference
|
||||||
|
import eu.kanade.core.prefs.PreferenceMutableState
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.MainScope
|
import kotlinx.coroutines.MainScope
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
@ -10,7 +12,7 @@ import rx.Observable
|
|||||||
|
|
||||||
open class BasePresenter<V> : RxPresenter<V>() {
|
open class BasePresenter<V> : RxPresenter<V>() {
|
||||||
|
|
||||||
lateinit var presenterScope: CoroutineScope
|
var presenterScope: CoroutineScope = MainScope()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Query from the view where applicable
|
* Query from the view where applicable
|
||||||
@ -20,7 +22,6 @@ open class BasePresenter<V> : RxPresenter<V>() {
|
|||||||
override fun onCreate(savedState: Bundle?) {
|
override fun onCreate(savedState: Bundle?) {
|
||||||
try {
|
try {
|
||||||
super.onCreate(savedState)
|
super.onCreate(savedState)
|
||||||
presenterScope = MainScope()
|
|
||||||
} catch (e: NullPointerException) {
|
} catch (e: NullPointerException) {
|
||||||
// Swallow this error. This should be fixed in the library but since it's not critical
|
// 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.
|
// (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()
|
return super.getView()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun <T> Preference<T>.asState() = PreferenceMutableState(this, presenterScope)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subscribes an observable with [deliverFirst] and adds it to the presenter's lifecycle
|
* Subscribes an observable with [deliverFirst] and adds it to the presenter's lifecycle
|
||||||
* subscription list.
|
* subscription list.
|
||||||
|
@ -1,187 +1,38 @@
|
|||||||
package eu.kanade.tachiyomi.ui.more
|
package eu.kanade.tachiyomi.ui.more
|
||||||
|
|
||||||
import android.os.Bundle
|
import androidx.compose.runtime.Composable
|
||||||
import android.view.LayoutInflater
|
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||||
import android.view.View
|
import eu.kanade.presentation.more.MoreScreen
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.preference.Preference
|
|
||||||
import androidx.preference.PreferenceScreen
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
import eu.kanade.tachiyomi.ui.base.controller.ComposeController
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadService
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.NoAppBarElevationController
|
import eu.kanade.tachiyomi.ui.base.controller.NoAppBarElevationController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.RootController
|
import eu.kanade.tachiyomi.ui.base.controller.RootController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
||||||
import eu.kanade.tachiyomi.ui.category.CategoryController
|
import eu.kanade.tachiyomi.ui.category.CategoryController
|
||||||
import eu.kanade.tachiyomi.ui.download.DownloadController
|
import eu.kanade.tachiyomi.ui.download.DownloadController
|
||||||
import eu.kanade.tachiyomi.ui.setting.SettingsBackupController
|
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.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 :
|
class MoreController :
|
||||||
SettingsController(),
|
ComposeController<MorePresenter>(),
|
||||||
RootController,
|
RootController,
|
||||||
NoAppBarElevationController {
|
NoAppBarElevationController {
|
||||||
|
|
||||||
private val downloadManager: DownloadManager by injectLazy()
|
override fun getTitle() = resources?.getString(R.string.label_more)
|
||||||
private var isDownloading: Boolean = false
|
|
||||||
private var downloadQueueSize: Int = 0
|
|
||||||
|
|
||||||
private var untilDestroySubscriptions = CompositeSubscription()
|
override fun createPresenter() = MorePresenter()
|
||||||
private set
|
|
||||||
|
|
||||||
override fun setupPreferenceScreen(screen: PreferenceScreen) = screen.apply {
|
@Composable
|
||||||
titleRes = R.string.label_more
|
override fun ComposeContent(nestedScrollInterop: NestedScrollConnection) {
|
||||||
|
MoreScreen(
|
||||||
val tintColor = context.getResourceColor(R.attr.colorAccent)
|
nestedScrollInterop = nestedScrollInterop,
|
||||||
|
presenter = presenter,
|
||||||
add(MoreHeaderPreference(context))
|
onClickDownloadQueue = { router.pushController(DownloadController()) },
|
||||||
|
onClickCategories = { router.pushController(CategoryController()) },
|
||||||
switchPreference {
|
onClickBackupAndRestore = { router.pushController(SettingsBackupController()) },
|
||||||
bindTo(preferences.downloadedOnly())
|
onClickSettings = { router.pushController(SettingsMainController()) },
|
||||||
titleRes = R.string.label_downloaded_only
|
onClickAbout = { router.pushController(AboutController()) },
|
||||||
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) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
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()
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user