Migrate Source Filter Screen to Compose (#7031)

* Migrate Source Filter Screen to Compose

* Changes from Review and some more fixes

* Rename some variable and classes

* Review Change

* Ewbase and Review changes
This commit is contained in:
FourTOne5
2022-04-30 08:49:02 -07:00
committed by GitHub
parent cccd09fb5c
commit 23f8f35354
14 changed files with 324 additions and 123 deletions

View File

@@ -48,7 +48,7 @@ class SourceController : SearchableComposeController<SourcePresenter>() {
openSource(source, BrowseSourceController(source))
},
onClickDisable = { source ->
presenter.disableSource(source)
presenter.toggleSource(source)
},
onClickLatest = { source ->
openSource(source, LatestUpdatesController(source))

View File

@@ -1,112 +1,34 @@
package eu.kanade.tachiyomi.ui.browse.source
import android.graphics.drawable.Drawable
import androidx.preference.CheckBoxPreference
import androidx.preference.PreferenceGroup
import androidx.preference.PreferenceScreen
import androidx.compose.runtime.Composable
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import eu.kanade.domain.source.model.Source
import eu.kanade.presentation.source.SourceFilterScreen
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.getPreferenceKey
import eu.kanade.tachiyomi.source.icon
import eu.kanade.tachiyomi.source.online.HttpSource
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.switchPreferenceCategory
import eu.kanade.tachiyomi.util.preference.titleRes
import eu.kanade.tachiyomi.util.system.LocaleHelper
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.util.TreeMap
import eu.kanade.tachiyomi.ui.base.controller.ComposeController
class SourceFilterController : SettingsController() {
class SourceFilterController : ComposeController<SourceFilterPresenter>() {
private val onlineSources by lazy { Injekt.get<SourceManager>().getOnlineSources() }
override fun getTitle() = resources?.getString(R.string.label_sources)
override fun setupPreferenceScreen(screen: PreferenceScreen) = screen.apply {
titleRes = R.string.label_sources
override fun createPresenter(): SourceFilterPresenter = SourceFilterPresenter()
// Get the list of active language codes.
val activeLangsCodes = preferences.enabledLanguages().get()
// Get a map of sources grouped by language.
val sourcesByLang = onlineSources.groupByTo(TreeMap(), { it.lang })
// Order first by active languages, then inactive ones
val orderedLangs = sourcesByLang.keys.sortedWith(
compareBy(
{ it !in activeLangsCodes },
{ LocaleHelper.getSourceDisplayName(it, context) },
),
@Composable
override fun ComposeContent(nestedScrollInterop: NestedScrollConnection) {
SourceFilterScreen(
nestedScrollInterop = nestedScrollInterop,
presenter = presenter,
onClickLang = { language ->
presenter.toggleLanguage(language)
},
onClickSource = { source ->
presenter.toggleSource(source)
},
)
orderedLangs.forEach { lang ->
val sources = sourcesByLang[lang].orEmpty().sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER, { it.name }))
// Create a preference group and set initial state and change listener
switchPreferenceCategory {
this@apply.addPreference(this)
title = LocaleHelper.getSourceDisplayName(lang, context)
isPersistent = false
if (lang in activeLangsCodes) {
setChecked(true)
addLanguageSources(this, sources)
}
onChange { newValue ->
val checked = newValue as Boolean
if (!checked) {
preferences.enabledLanguages() -= lang
removeAll()
} else {
preferences.enabledLanguages() += lang
addLanguageSources(this, sources)
}
true
}
}
}
}
override fun setDivider(divider: Drawable?) {
super.setDivider(null)
}
/**
* Adds the source list for the given group (language).
*
* @param group the language category.
*/
private fun addLanguageSources(group: PreferenceGroup, sources: List<HttpSource>) {
val disabledSourceIds = preferences.disabledSources().get()
sources
.sortedBy { it.id.toString() in disabledSourceIds }
.map { source ->
CheckBoxPreference(group.context).apply {
val id = source.id.toString()
title = source.name
key = source.getPreferenceKey()
isPersistent = false
isChecked = id !in disabledSourceIds
val sourceIcon = source.icon()
if (sourceIcon != null) {
icon = sourceIcon
}
onChange { newValue ->
val checked = newValue as Boolean
if (checked) {
preferences.disabledSources() -= id
} else {
preferences.disabledSources() += id
}
true
}
}
}
.forEach { group.addPreference(it) }
}
}
sealed class FilterUiModel {
data class Header(val language: String, val isEnabled: Boolean) : FilterUiModel()
data class Item(val source: Source, val isEnabled: Boolean) : FilterUiModel()
}

View File

@@ -0,0 +1,71 @@
package eu.kanade.tachiyomi.ui.browse.source
import android.os.Bundle
import eu.kanade.domain.source.interactor.GetLanguagesWithSources
import eu.kanade.domain.source.interactor.ToggleLanguage
import eu.kanade.domain.source.interactor.ToggleSource
import eu.kanade.domain.source.model.Source
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 SourceFilterPresenter(
private val getLanguagesWithSources: GetLanguagesWithSources = Injekt.get(),
private val toggleSource: ToggleSource = Injekt.get(),
private val toggleLanguage: ToggleLanguage = Injekt.get(),
private val preferences: PreferencesHelper = Injekt.get()
) : BasePresenter<SourceFilterController>() {
private val _state: MutableStateFlow<SourceFilterState> = MutableStateFlow(SourceFilterState.Loading)
val state: StateFlow<SourceFilterState> = _state.asStateFlow()
override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState)
presenterScope.launchIO {
getLanguagesWithSources.subscribe()
.catch { exception ->
_state.emit(SourceFilterState.Error(exception))
}
.collectLatest { sourceLangMap ->
val uiModels = sourceLangMap.toFilterUiModels()
_state.emit(SourceFilterState.Success(uiModels))
}
}
}
private fun Map<String, List<Source>>.toFilterUiModels(): List<FilterUiModel> {
return this.flatMap {
val isLangEnabled = it.key in preferences.enabledLanguages().get()
val header = listOf(FilterUiModel.Header(it.key, isLangEnabled))
if (isLangEnabled.not()) return@flatMap header
header + it.value.map { source ->
FilterUiModel.Item(
source,
source.id.toString() !in preferences.disabledSources().get()
)
}
}
}
fun toggleSource(source: Source) {
toggleSource.await(source)
}
fun toggleLanguage(language: String) {
toggleLanguage.await(language)
}
}
sealed class SourceFilterState {
object Loading : SourceFilterState()
data class Error(val error: Throwable) : SourceFilterState()
data class Success(val models: List<FilterUiModel>) : SourceFilterState()
}

View File

@@ -1,8 +1,8 @@
package eu.kanade.tachiyomi.ui.browse.source
import android.os.Bundle
import eu.kanade.domain.source.interactor.DisableSource
import eu.kanade.domain.source.interactor.GetEnabledSources
import eu.kanade.domain.source.interactor.ToggleSource
import eu.kanade.domain.source.interactor.ToggleSourcePin
import eu.kanade.domain.source.model.Pin
import eu.kanade.domain.source.model.Source
@@ -24,7 +24,7 @@ import java.util.TreeMap
*/
class SourcePresenter(
private val getEnabledSources: GetEnabledSources = Injekt.get(),
private val disableSource: DisableSource = Injekt.get(),
private val toggleSource: ToggleSource = Injekt.get(),
private val toggleSourcePin: ToggleSourcePin = Injekt.get()
) : BasePresenter<SourceController>() {
@@ -79,8 +79,8 @@ class SourcePresenter(
}
}
fun disableSource(source: Source) {
disableSource.await(source)
fun toggleSource(source: Source) {
toggleSource.await(source)
}
fun togglePin(source: Source) {