From fd9510e18fe9e9d42631c40bcaf254453604337b Mon Sep 17 00:00:00 2001 From: FourTOne5 <59261191+FourTOne5@users.noreply.github.com> Date: Fri, 20 May 2022 03:29:40 +0600 Subject: [PATCH] Migrate extensions language filter screen to compose (#7169) --- .../java/eu/kanade/domain/DomainModule.kt | 2 + .../interactor/GetExtensionLanguages.kt | 30 ++++++ .../browse/ExtensionLangFilterScreen.kt | 91 +++++++++++++++++++ .../extension/ExtensionFilterController.kt | 54 ++++------- .../extension/ExtensionFilterPresenter.kt | 54 +++++++++++ 5 files changed, 195 insertions(+), 36 deletions(-) create mode 100644 app/src/main/java/eu/kanade/domain/extension/interactor/GetExtensionLanguages.kt create mode 100644 app/src/main/java/eu/kanade/presentation/browse/ExtensionLangFilterScreen.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionFilterPresenter.kt diff --git a/app/src/main/java/eu/kanade/domain/DomainModule.kt b/app/src/main/java/eu/kanade/domain/DomainModule.kt index 5d5ab0788..99da63a5f 100644 --- a/app/src/main/java/eu/kanade/domain/DomainModule.kt +++ b/app/src/main/java/eu/kanade/domain/DomainModule.kt @@ -3,6 +3,7 @@ package eu.kanade.domain import eu.kanade.data.history.HistoryRepositoryImpl import eu.kanade.data.manga.MangaRepositoryImpl import eu.kanade.data.source.SourceRepositoryImpl +import eu.kanade.domain.extension.interactor.GetExtensionLanguages import eu.kanade.domain.extension.interactor.GetExtensionSources import eu.kanade.domain.extension.interactor.GetExtensionUpdates import eu.kanade.domain.extension.interactor.GetExtensions @@ -46,6 +47,7 @@ class DomainModule : InjektModule { addFactory { GetExtensions(get(), get()) } addFactory { GetExtensionSources(get()) } addFactory { GetExtensionUpdates(get(), get()) } + addFactory { GetExtensionLanguages(get(), get()) } addSingletonFactory { SourceRepositoryImpl(get(), get()) } addFactory { GetLanguagesWithSources(get(), get()) } diff --git a/app/src/main/java/eu/kanade/domain/extension/interactor/GetExtensionLanguages.kt b/app/src/main/java/eu/kanade/domain/extension/interactor/GetExtensionLanguages.kt new file mode 100644 index 000000000..7bda92177 --- /dev/null +++ b/app/src/main/java/eu/kanade/domain/extension/interactor/GetExtensionLanguages.kt @@ -0,0 +1,30 @@ +package eu.kanade.domain.extension.interactor + +import eu.kanade.core.util.asFlow +import eu.kanade.tachiyomi.data.preference.PreferencesHelper +import eu.kanade.tachiyomi.extension.ExtensionManager +import eu.kanade.tachiyomi.util.system.LocaleHelper +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine + +class GetExtensionLanguages( + private val preferences: PreferencesHelper, + private val extensionManager: ExtensionManager, +) { + fun subscribe(): Flow> { + return combine( + preferences.enabledLanguages().asFlow(), + extensionManager.getAvailableExtensionsObservable().asFlow(), + ) { enabledLanguage, availableExtensions -> + availableExtensions + .map { it.lang } + .distinct() + .sortedWith( + compareBy( + { it !in enabledLanguage }, + { LocaleHelper.getDisplayName(it) }, + ), + ) + } + } +} diff --git a/app/src/main/java/eu/kanade/presentation/browse/ExtensionLangFilterScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/ExtensionLangFilterScreen.kt new file mode 100644 index 000000000..9fd777dce --- /dev/null +++ b/app/src/main/java/eu/kanade/presentation/browse/ExtensionLangFilterScreen.kt @@ -0,0 +1,91 @@ +package eu.kanade.presentation.browse + +import androidx.compose.animation.core.LinearOutSlowInEasing +import androidx.compose.animation.core.tween +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.asPaddingValues +import androidx.compose.foundation.layout.navigationBars +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.Switch +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.LocalContext +import eu.kanade.presentation.components.EmptyScreen +import eu.kanade.presentation.components.LoadingScreen +import eu.kanade.presentation.components.PreferenceRow +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.ui.browse.extension.FilterUiModel +import eu.kanade.tachiyomi.util.system.LocaleHelper + +@Composable +fun ExtensionFilterScreen( + nestedScrollInterop: NestedScrollConnection, + presenter: ExtensionFilterPresenter, + onClickLang: (String) -> Unit, +) { + val state by presenter.state.collectAsState() + + when (state) { + is ExtensionFilterState.Loading -> LoadingScreen() + is ExtensionFilterState.Error -> Text(text = (state as ExtensionFilterState.Error).error.message!!) + is ExtensionFilterState.Success -> + SourceFilterContent( + nestedScrollInterop = nestedScrollInterop, + items = (state as ExtensionFilterState.Success).models, + onClickLang = onClickLang, + ) + } +} + +@Composable +fun SourceFilterContent( + nestedScrollInterop: NestedScrollConnection, + items: List, + onClickLang: (String) -> Unit, +) { + if (items.isEmpty()) { + EmptyScreen(textResource = R.string.empty_screen) + return + } + + LazyColumn( + modifier = Modifier.nestedScroll(nestedScrollInterop), + contentPadding = WindowInsets.navigationBars.asPaddingValues(), + ) { + items( + items = items, + ) { model -> + ExtensionFilterItem( + modifier = Modifier.animateItemPlacement(tween(1000, easing = LinearOutSlowInEasing)), + lang = model.lang, + isEnabled = model.isEnabled, + onClickItem = onClickLang, + ) + } + } +} + +@Composable +fun ExtensionFilterItem( + modifier: Modifier, + lang: String, + isEnabled: Boolean, + onClickItem: (String) -> Unit, +) { + PreferenceRow( + modifier = modifier, + title = LocaleHelper.getSourceDisplayName(lang, LocalContext.current), + action = { + Switch(checked = isEnabled, onCheckedChange = null) + }, + onClick = { onClickItem(lang) }, + ) +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionFilterController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionFilterController.kt index f3898d63a..59b3d5250 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionFilterController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionFilterController.kt @@ -1,45 +1,27 @@ package eu.kanade.tachiyomi.ui.browse.extension -import androidx.preference.PreferenceScreen +import androidx.compose.runtime.Composable +import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import eu.kanade.presentation.browse.ExtensionFilterScreen import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.extension.ExtensionManager -import eu.kanade.tachiyomi.ui.setting.SettingsController -import eu.kanade.tachiyomi.util.preference.minusAssign -import eu.kanade.tachiyomi.util.preference.onChange -import eu.kanade.tachiyomi.util.preference.plusAssign -import eu.kanade.tachiyomi.util.preference.switchPreference -import eu.kanade.tachiyomi.util.preference.titleRes -import eu.kanade.tachiyomi.util.system.LocaleHelper -import uy.kohesive.injekt.injectLazy +import eu.kanade.tachiyomi.ui.base.controller.ComposeController -class ExtensionFilterController : SettingsController() { +class ExtensionFilterController : ComposeController() { - private val extensionManager: ExtensionManager by injectLazy() + override fun getTitle() = resources?.getString(R.string.label_extensions) - override fun setupPreferenceScreen(screen: PreferenceScreen) = screen.apply { - titleRes = R.string.label_extensions + override fun createPresenter(): ExtensionFilterPresenter = ExtensionFilterPresenter() - val activeLangs = preferences.enabledLanguages().get() - - val availableLangs = extensionManager.availableExtensions.groupBy { it.lang }.keys - .sortedWith(compareBy({ it !in activeLangs }, { LocaleHelper.getSourceDisplayName(it, context) })) - - availableLangs.forEach { - switchPreference { - preferenceScreen.addPreference(this) - title = LocaleHelper.getSourceDisplayName(it, context) - isPersistent = false - isChecked = it in activeLangs - - onChange { newValue -> - if (newValue as Boolean) { - preferences.enabledLanguages() += it - } else { - preferences.enabledLanguages() -= it - } - true - } - } - } + @Composable + override fun ComposeContent(nestedScrollInterop: NestedScrollConnection) { + ExtensionFilterScreen( + nestedScrollInterop = nestedScrollInterop, + presenter = presenter, + onClickLang = { language -> + presenter.toggleLanguage(language) + }, + ) } } + +data class FilterUiModel(val lang: String, val isEnabled: Boolean) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionFilterPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionFilterPresenter.kt new file mode 100644 index 000000000..c1e3aae66 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionFilterPresenter.kt @@ -0,0 +1,54 @@ +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.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 kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.collectLatest +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + +class ExtensionFilterPresenter( + private val getExtensionLanguages: GetExtensionLanguages = Injekt.get(), + private val toggleLanguage: ToggleLanguage = Injekt.get(), + private val preferences: PreferencesHelper = Injekt.get(), +) : BasePresenter() { + + private val _state: MutableStateFlow = MutableStateFlow(ExtensionFilterState.Loading) + val state: StateFlow = _state.asStateFlow() + + override fun onCreate(savedState: Bundle?) { + super.onCreate(savedState) + presenterScope.launchIO { + getExtensionLanguages.subscribe() + .catch { exception -> + _state.value = ExtensionFilterState.Error(exception) + } + .collectLatest(::collectLatestSourceLangMap) + } + } + + private fun collectLatestSourceLangMap(extLangs: List) { + val enabledLanguages = preferences.enabledLanguages().get() + val uiModels = extLangs.map { + FilterUiModel(it, it in enabledLanguages) + } + _state.value = ExtensionFilterState.Success(uiModels) + } + + fun toggleLanguage(language: String) { + toggleLanguage.await(language) + } +} + +sealed class ExtensionFilterState { + object Loading : ExtensionFilterState() + data class Error(val error: Throwable) : ExtensionFilterState() + data class Success(val models: List) : ExtensionFilterState() +}