Bläddra i källkod

Migrate extensions language filter screen to compose (#7169)

FourTOne5 2 år sedan
förälder
incheckning
fd9510e18f

+ 2 - 0
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<SourceRepository> { SourceRepositoryImpl(get(), get()) }
         addFactory { GetLanguagesWithSources(get(), get()) }

+ 30 - 0
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<List<String>> {
+        return combine(
+            preferences.enabledLanguages().asFlow(),
+            extensionManager.getAvailableExtensionsObservable().asFlow(),
+        ) { enabledLanguage, availableExtensions ->
+            availableExtensions
+                .map { it.lang }
+                .distinct()
+                .sortedWith(
+                    compareBy(
+                        { it !in enabledLanguage },
+                        { LocaleHelper.getDisplayName(it) },
+                    ),
+                )
+        }
+    }
+}

+ 91 - 0
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<FilterUiModel>,
+    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) },
+    )
+}

+ 18 - 36
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<ExtensionFilterPresenter>() {
 
-    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)

+ 54 - 0
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<ExtensionFilterController>() {
+
+    private val _state: MutableStateFlow<ExtensionFilterState> = MutableStateFlow(ExtensionFilterState.Loading)
+    val state: StateFlow<ExtensionFilterState> = _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<String>) {
+        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<FilterUiModel>) : ExtensionFilterState()
+}