Use Voyager on Extension Filter screen (#8503)
- Use sealed class for state - Minor changes
This commit is contained in:
parent
6ada3c90ff
commit
0270878748
6 changed files with 150 additions and 127 deletions
|
@ -4,28 +4,24 @@ import androidx.compose.foundation.layout.PaddingValues
|
|||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import eu.kanade.presentation.components.AppBar
|
||||
import eu.kanade.presentation.components.EmptyScreen
|
||||
import eu.kanade.presentation.components.FastScrollLazyColumn
|
||||
import eu.kanade.presentation.components.LoadingScreen
|
||||
import eu.kanade.presentation.components.Scaffold
|
||||
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionFilterPresenter
|
||||
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionFilterState
|
||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
|
||||
@Composable
|
||||
fun ExtensionFilterScreen(
|
||||
navigateUp: () -> Unit,
|
||||
presenter: ExtensionFilterPresenter,
|
||||
state: ExtensionFilterState.Success,
|
||||
onClickToggle: (String) -> Unit,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
Scaffold(
|
||||
topBar = { scrollBehavior ->
|
||||
AppBar(
|
||||
|
@ -35,50 +31,37 @@ fun ExtensionFilterScreen(
|
|||
)
|
||||
},
|
||||
) { contentPadding ->
|
||||
when {
|
||||
presenter.isLoading -> LoadingScreen()
|
||||
presenter.isEmpty -> EmptyScreen(
|
||||
if (state.isEmpty) {
|
||||
EmptyScreen(
|
||||
textResource = R.string.empty_screen,
|
||||
modifier = Modifier.padding(contentPadding),
|
||||
)
|
||||
else -> ExtensionFilterContent(
|
||||
return@Scaffold
|
||||
}
|
||||
ExtensionFilterContent(
|
||||
contentPadding = contentPadding,
|
||||
state = presenter,
|
||||
onClickLang = {
|
||||
presenter.toggleLanguage(it)
|
||||
},
|
||||
state = state,
|
||||
onClickLang = onClickToggle,
|
||||
)
|
||||
}
|
||||
}
|
||||
LaunchedEffect(Unit) {
|
||||
presenter.events.collectLatest {
|
||||
when (it) {
|
||||
ExtensionFilterPresenter.Event.FailedFetchingLanguages -> {
|
||||
context.toast(R.string.internal_error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ExtensionFilterContent(
|
||||
contentPadding: PaddingValues,
|
||||
state: ExtensionFilterState,
|
||||
state: ExtensionFilterState.Success,
|
||||
onClickLang: (String) -> Unit,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
FastScrollLazyColumn(
|
||||
contentPadding = contentPadding,
|
||||
) {
|
||||
items(
|
||||
items = state.items,
|
||||
) { model ->
|
||||
val lang = model.lang
|
||||
items(state.languages) { language ->
|
||||
SwitchPreferenceWidget(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
title = LocaleHelper.getSourceDisplayName(lang, LocalContext.current),
|
||||
checked = model.enabled,
|
||||
onCheckedChanged = { onClickLang(lang) },
|
||||
title = LocaleHelper.getSourceDisplayName(language, context),
|
||||
checked = language in state.enabledLanguages,
|
||||
onCheckedChanged = { onClickLang(language) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
package eu.kanade.presentation.browse
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import eu.kanade.tachiyomi.ui.browse.extension.FilterUiModel
|
||||
|
||||
@Stable
|
||||
interface ExtensionFilterState {
|
||||
val isLoading: Boolean
|
||||
val items: List<FilterUiModel>
|
||||
val isEmpty: Boolean
|
||||
}
|
||||
|
||||
fun ExtensionFilterState(): ExtensionFilterState {
|
||||
return ExtensionFilterStateImpl()
|
||||
}
|
||||
|
||||
class ExtensionFilterStateImpl : ExtensionFilterState {
|
||||
override var isLoading: Boolean by mutableStateOf(true)
|
||||
override var items: List<FilterUiModel> by mutableStateOf(emptyList())
|
||||
override val isEmpty: Boolean by derivedStateOf { items.isEmpty() }
|
||||
}
|
|
@ -1,20 +1,17 @@
|
|||
package eu.kanade.tachiyomi.ui.browse.extension
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import eu.kanade.presentation.browse.ExtensionFilterScreen
|
||||
import eu.kanade.tachiyomi.ui.base.controller.FullComposeController
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import cafe.adriel.voyager.navigator.Navigator
|
||||
import eu.kanade.presentation.util.LocalRouter
|
||||
import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
|
||||
|
||||
class ExtensionFilterController : FullComposeController<ExtensionFilterPresenter>() {
|
||||
|
||||
override fun createPresenter() = ExtensionFilterPresenter()
|
||||
class ExtensionFilterController : BasicFullComposeController() {
|
||||
|
||||
@Composable
|
||||
override fun ComposeContent() {
|
||||
ExtensionFilterScreen(
|
||||
navigateUp = router::popCurrentController,
|
||||
presenter = presenter,
|
||||
)
|
||||
CompositionLocalProvider(LocalRouter provides router) {
|
||||
Navigator(screen = ExtensionFilterScreen())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class FilterUiModel(val lang: String, val enabled: Boolean)
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
package eu.kanade.tachiyomi.ui.browse.extension
|
||||
|
||||
import android.os.Bundle
|
||||
import eu.kanade.domain.extension.interactor.GetExtensionLanguages
|
||||
import eu.kanade.domain.source.interactor.ToggleLanguage
|
||||
import eu.kanade.domain.source.service.SourcePreferences
|
||||
import eu.kanade.presentation.browse.ExtensionFilterState
|
||||
import eu.kanade.presentation.browse.ExtensionFilterStateImpl
|
||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import logcat.LogPriority
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class ExtensionFilterPresenter(
|
||||
private val state: ExtensionFilterStateImpl = ExtensionFilterState() as ExtensionFilterStateImpl,
|
||||
private val getExtensionLanguages: GetExtensionLanguages = Injekt.get(),
|
||||
private val toggleLanguage: ToggleLanguage = Injekt.get(),
|
||||
private val preferences: SourcePreferences = Injekt.get(),
|
||||
) : BasePresenter<ExtensionFilterController>(), ExtensionFilterState by state {
|
||||
|
||||
private val _events = Channel<Event>(Int.MAX_VALUE)
|
||||
val events = _events.receiveAsFlow()
|
||||
|
||||
override fun onCreate(savedState: Bundle?) {
|
||||
super.onCreate(savedState)
|
||||
presenterScope.launchIO {
|
||||
getExtensionLanguages.subscribe()
|
||||
.catch { exception ->
|
||||
logcat(LogPriority.ERROR, exception)
|
||||
_events.send(Event.FailedFetchingLanguages)
|
||||
}
|
||||
.collectLatest(::collectLatestSourceLangMap)
|
||||
}
|
||||
}
|
||||
|
||||
private fun collectLatestSourceLangMap(extLangs: List<String>) {
|
||||
val enabledLanguages = preferences.enabledLanguages().get()
|
||||
state.items = extLangs.map {
|
||||
FilterUiModel(it, it in enabledLanguages)
|
||||
}
|
||||
state.isLoading = false
|
||||
}
|
||||
|
||||
fun toggleLanguage(language: String) {
|
||||
toggleLanguage.await(language)
|
||||
}
|
||||
|
||||
sealed class Event {
|
||||
object FailedFetchingLanguages : Event()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
package eu.kanade.tachiyomi.ui.browse.extension
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||
import cafe.adriel.voyager.core.screen.Screen
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import eu.kanade.presentation.browse.ExtensionFilterScreen
|
||||
import eu.kanade.presentation.components.LoadingScreen
|
||||
import eu.kanade.presentation.util.LocalRouter
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
|
||||
class ExtensionFilterScreen : Screen {
|
||||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val context = LocalContext.current
|
||||
val router = LocalRouter.currentOrThrow
|
||||
val screenModel = rememberScreenModel { ExtensionFilterScreenModel() }
|
||||
val state by screenModel.state.collectAsState()
|
||||
|
||||
if (state is ExtensionFilterState.Loading) {
|
||||
LoadingScreen()
|
||||
return
|
||||
}
|
||||
|
||||
val successState = state as ExtensionFilterState.Success
|
||||
|
||||
ExtensionFilterScreen(
|
||||
navigateUp = router::popCurrentController,
|
||||
state = successState,
|
||||
onClickToggle = { screenModel.toggle(it) },
|
||||
)
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
screenModel.events.collectLatest {
|
||||
when (it) {
|
||||
ExtensionFilterEvent.FailedFetchingLanguages -> {
|
||||
context.toast(R.string.internal_error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
package eu.kanade.tachiyomi.ui.browse.extension
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||
import cafe.adriel.voyager.core.model.coroutineScope
|
||||
import eu.kanade.domain.extension.interactor.GetExtensionLanguages
|
||||
import eu.kanade.domain.source.interactor.ToggleLanguage
|
||||
import eu.kanade.domain.source.service.SourcePreferences
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import logcat.LogPriority
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class ExtensionFilterScreenModel(
|
||||
private val preferences: SourcePreferences = Injekt.get(),
|
||||
private val getExtensionLanguages: GetExtensionLanguages = Injekt.get(),
|
||||
private val toggleLanguage: ToggleLanguage = Injekt.get(),
|
||||
) : StateScreenModel<ExtensionFilterState>(ExtensionFilterState.Loading) {
|
||||
|
||||
private val _events: Channel<ExtensionFilterEvent> = Channel()
|
||||
val events: Flow<ExtensionFilterEvent> = _events.receiveAsFlow()
|
||||
|
||||
init {
|
||||
coroutineScope.launch {
|
||||
combine(
|
||||
getExtensionLanguages.subscribe(),
|
||||
preferences.enabledLanguages().changes(),
|
||||
) { a, b -> a to b }
|
||||
.catch { throwable ->
|
||||
logcat(LogPriority.ERROR, throwable)
|
||||
_events.send(ExtensionFilterEvent.FailedFetchingLanguages)
|
||||
}
|
||||
.collectLatest { (extensionLanguages, enabledLanguages) ->
|
||||
mutableState.update {
|
||||
ExtensionFilterState.Success(
|
||||
languages = extensionLanguages,
|
||||
enabledLanguages = enabledLanguages,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun toggle(language: String) {
|
||||
toggleLanguage.await(language)
|
||||
}
|
||||
}
|
||||
|
||||
sealed class ExtensionFilterEvent {
|
||||
object FailedFetchingLanguages : ExtensionFilterEvent()
|
||||
}
|
||||
|
||||
sealed class ExtensionFilterState {
|
||||
|
||||
@Immutable
|
||||
object Loading : ExtensionFilterState()
|
||||
|
||||
@Immutable
|
||||
data class Success(
|
||||
val languages: List<String>,
|
||||
val enabledLanguages: Set<String> = emptySet(),
|
||||
) : ExtensionFilterState() {
|
||||
|
||||
val isEmpty: Boolean
|
||||
get() = languages.isEmpty()
|
||||
}
|
||||
}
|
Reference in a new issue