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 (cherry picked from commit 23f8f35354b30aded0749222234f76a4f9eaab5c) # Conflicts: # app/src/main/java/eu/kanade/domain/DomainModule.kt # app/src/main/java/eu/kanade/tachiyomi/source/SourceManager.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourceFilterController.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcePresenter.kt
This commit is contained in:
parent
3f81dfdf99
commit
ef55134c74
@ -20,6 +20,12 @@ class SourceRepositoryImpl(
|
||||
}
|
||||
}
|
||||
|
||||
override fun getOnlineSources(): Flow<List<Source>> {
|
||||
return sourceManager.onlineSources.map { sources ->
|
||||
sources.map(sourceMapper)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getSourcesWithFavoriteCount(): Flow<List<Pair<Source, Long>>> {
|
||||
val sourceIdWithFavoriteCount = handler.subscribeToList { mangasQueries.getSourceIdWithFavoriteCount() }
|
||||
return sourceIdWithFavoriteCount.map { sourceIdsWithCount ->
|
||||
|
@ -11,15 +11,18 @@ import eu.kanade.domain.history.interactor.RemoveHistoryByMangaId
|
||||
import eu.kanade.domain.history.repository.HistoryRepository
|
||||
import eu.kanade.domain.manga.interactor.GetFavoritesBySourceId
|
||||
import eu.kanade.domain.manga.repository.MangaRepository
|
||||
import eu.kanade.domain.source.interactor.DisableSource
|
||||
import eu.kanade.domain.source.interactor.GetEnabledSources
|
||||
import eu.kanade.domain.source.interactor.GetLanguagesWithSources
|
||||
import eu.kanade.domain.source.interactor.GetShowLatest
|
||||
import eu.kanade.domain.source.interactor.GetSourceCategories
|
||||
import eu.kanade.domain.source.interactor.GetSourcesWithFavoriteCount
|
||||
import eu.kanade.domain.source.interactor.SetMigrateSorting
|
||||
import eu.kanade.domain.source.interactor.SetSourceCategories
|
||||
import eu.kanade.domain.source.interactor.ToggleExcludeFromDataSaver
|
||||
import eu.kanade.domain.source.interactor.ToggleLanguage
|
||||
import eu.kanade.domain.source.interactor.ToggleSource
|
||||
import eu.kanade.domain.source.interactor.ToggleSourcePin
|
||||
import eu.kanade.domain.source.interactor.ToggleSources
|
||||
import eu.kanade.domain.source.repository.SourceRepository
|
||||
import uy.kohesive.injekt.api.InjektModule
|
||||
import uy.kohesive.injekt.api.InjektRegistrar
|
||||
@ -41,17 +44,20 @@ class DomainModule : InjektModule {
|
||||
addFactory { RemoveHistoryByMangaId(get()) }
|
||||
|
||||
addSingletonFactory<SourceRepository> { SourceRepositoryImpl(get(), get()) }
|
||||
addFactory { GetLanguagesWithSources(get(), get()) }
|
||||
addFactory { GetEnabledSources(get(), get()) }
|
||||
addFactory { DisableSource(get()) }
|
||||
addFactory { ToggleSource(get()) }
|
||||
addFactory { ToggleSourcePin(get()) }
|
||||
addFactory { GetSourcesWithFavoriteCount(get(), get()) }
|
||||
addFactory { SetMigrateSorting(get()) }
|
||||
addFactory { ToggleLanguage(get()) }
|
||||
|
||||
// SY -->
|
||||
addFactory { GetSourceCategories(get()) }
|
||||
addFactory { GetShowLatest(get()) }
|
||||
addFactory { ToggleExcludeFromDataSaver(get()) }
|
||||
addFactory { SetSourceCategories(get()) }
|
||||
addFactory { ToggleSources(get()) }
|
||||
// SY <--
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +0,0 @@
|
||||
package eu.kanade.domain.source.interactor
|
||||
|
||||
import eu.kanade.domain.source.model.Source
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.util.preference.plusAssign
|
||||
|
||||
class DisableSource(
|
||||
private val preferences: PreferencesHelper
|
||||
) {
|
||||
|
||||
fun await(source: Source) {
|
||||
preferences.disabledSources() += source.id.toString()
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package eu.kanade.domain.source.interactor
|
||||
|
||||
import eu.kanade.domain.source.model.Source
|
||||
import eu.kanade.domain.source.repository.SourceRepository
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
|
||||
class GetLanguagesWithSources(
|
||||
private val repository: SourceRepository,
|
||||
private val preferences: PreferencesHelper,
|
||||
) {
|
||||
|
||||
fun subscribe(): Flow<Map<String, List<Source>>> {
|
||||
return combine(
|
||||
preferences.enabledLanguages().asFlow(),
|
||||
preferences.disabledSources().asFlow(),
|
||||
repository.getOnlineSources()
|
||||
) { enabledLanguage, disabledSource, onlineSources ->
|
||||
val sortedSources = onlineSources.sortedWith(
|
||||
compareBy<Source> { it.id.toString() in disabledSource }
|
||||
.thenBy(String.CASE_INSENSITIVE_ORDER) { it.name }
|
||||
)
|
||||
|
||||
sortedSources.groupBy { it.lang }
|
||||
.toSortedMap(
|
||||
compareBy(
|
||||
{ it !in enabledLanguage },
|
||||
{ LocaleHelper.getDisplayName(it) }
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -6,7 +6,8 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import java.text.Collator
|
||||
import java.util.*
|
||||
import java.util.Collections
|
||||
import java.util.Locale
|
||||
import kotlin.Comparator
|
||||
|
||||
class GetSourcesWithFavoriteCount(
|
||||
|
@ -0,0 +1,19 @@
|
||||
package eu.kanade.domain.source.interactor
|
||||
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.util.preference.minusAssign
|
||||
import eu.kanade.tachiyomi.util.preference.plusAssign
|
||||
|
||||
class ToggleLanguage(
|
||||
val preferences: PreferencesHelper
|
||||
) {
|
||||
|
||||
fun await(language: String) {
|
||||
val isEnabled = language in preferences.enabledLanguages().get()
|
||||
if (isEnabled) {
|
||||
preferences.enabledLanguages() -= language
|
||||
} else {
|
||||
preferences.enabledLanguages() += language
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package eu.kanade.domain.source.interactor
|
||||
|
||||
import eu.kanade.domain.source.model.Source
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.util.preference.minusAssign
|
||||
import eu.kanade.tachiyomi.util.preference.plusAssign
|
||||
|
||||
class ToggleSource(
|
||||
private val preferences: PreferencesHelper
|
||||
) {
|
||||
|
||||
fun await(source: Source) {
|
||||
val isEnabled = source.id.toString() !in preferences.disabledSources().get()
|
||||
if (isEnabled) {
|
||||
preferences.disabledSources() += source.id.toString()
|
||||
} else {
|
||||
preferences.disabledSources() -= source.id.toString()
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package eu.kanade.domain.source.interactor
|
||||
|
||||
import eu.kanade.domain.source.model.Source
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.util.preference.minusAssign
|
||||
import eu.kanade.tachiyomi.util.preference.plusAssign
|
||||
|
||||
class ToggleSources(
|
||||
private val preferences: PreferencesHelper
|
||||
) {
|
||||
|
||||
fun await(isEnable: Boolean, sources: List<Source>) {
|
||||
val newDisabledSources = if (isEnable) {
|
||||
preferences.disabledSources().get() - sources.map { it.id.toString() }
|
||||
} else {
|
||||
preferences.disabledSources().get() + sources.map { it.id.toString() }
|
||||
}
|
||||
preferences.disabledSources().set(newDisabledSources)
|
||||
}
|
||||
}
|
@ -7,5 +7,7 @@ interface SourceRepository {
|
||||
|
||||
fun getSources(): Flow<List<Source>>
|
||||
|
||||
fun getOnlineSources(): Flow<List<Source>>
|
||||
|
||||
fun getSourcesWithFavoriteCount(): Flow<List<Pair<Source, Long>>>
|
||||
}
|
||||
|
@ -0,0 +1,177 @@
|
||||
package eu.kanade.presentation.source
|
||||
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.Checkbox
|
||||
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.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.painter.ColorPainter
|
||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import eu.kanade.domain.source.model.Source
|
||||
import eu.kanade.presentation.components.EmptyScreen
|
||||
import eu.kanade.presentation.components.LoadingScreen
|
||||
import eu.kanade.presentation.components.PreferenceRow
|
||||
import eu.kanade.presentation.source.components.BaseSourceItem
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.browse.source.FilterUiModel
|
||||
import eu.kanade.tachiyomi.ui.browse.source.SourceFilterPresenter
|
||||
import eu.kanade.tachiyomi.ui.browse.source.SourceFilterState
|
||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
|
||||
@Composable
|
||||
fun SourceFilterScreen(
|
||||
nestedScrollInterop: NestedScrollConnection,
|
||||
presenter: SourceFilterPresenter,
|
||||
onClickLang: (String) -> Unit,
|
||||
onClickSource: (Source) -> Unit,
|
||||
// SY -->
|
||||
onClickSources: (Boolean, List<Source>) -> Unit,
|
||||
// SY <--
|
||||
) {
|
||||
val state by presenter.state.collectAsState()
|
||||
|
||||
when (state) {
|
||||
is SourceFilterState.Loading -> LoadingScreen()
|
||||
is SourceFilterState.Error -> Text(text = (state as SourceFilterState.Error).error!!.message!!)
|
||||
is SourceFilterState.Success ->
|
||||
SourceFilterContent(
|
||||
nestedScrollInterop = nestedScrollInterop,
|
||||
items = (state as SourceFilterState.Success).models,
|
||||
onClickLang = onClickLang,
|
||||
onClickSource = onClickSource,
|
||||
// SY -->
|
||||
onClickSources = onClickSources,
|
||||
// SY <--
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SourceFilterContent(
|
||||
nestedScrollInterop: NestedScrollConnection,
|
||||
items: List<FilterUiModel>,
|
||||
onClickLang: (String) -> Unit,
|
||||
onClickSource: (Source) -> Unit,
|
||||
// SY -->
|
||||
onClickSources: (Boolean, List<Source>) -> Unit,
|
||||
// SY <--
|
||||
) {
|
||||
if (items.isEmpty()) {
|
||||
EmptyScreen(textResource = R.string.source_filter_empty_screen)
|
||||
return
|
||||
}
|
||||
LazyColumn(
|
||||
modifier = Modifier.nestedScroll(nestedScrollInterop)
|
||||
) {
|
||||
items(
|
||||
items = items,
|
||||
contentType = {
|
||||
when (it) {
|
||||
is FilterUiModel.Header -> "header"
|
||||
// SY -->
|
||||
is FilterUiModel.ToggleHeader -> "toggle"
|
||||
// SY <--
|
||||
is FilterUiModel.Item -> "item"
|
||||
}
|
||||
},
|
||||
key = {
|
||||
when (it) {
|
||||
is FilterUiModel.Header, is FilterUiModel.ToggleHeader -> it.hashCode()
|
||||
is FilterUiModel.Item -> it.source.key()
|
||||
}
|
||||
}
|
||||
) { model ->
|
||||
when (model) {
|
||||
is FilterUiModel.Header -> {
|
||||
SourceFilterHeader(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
language = model.language,
|
||||
isEnabled = model.isEnabled,
|
||||
onClickItem = onClickLang
|
||||
)
|
||||
}
|
||||
// SY -->
|
||||
is FilterUiModel.ToggleHeader -> {
|
||||
SourceFilterToggle(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
isEnabled = model.isEnabled,
|
||||
onClickItem = {
|
||||
onClickSources(!model.isEnabled, model.sources)
|
||||
}
|
||||
)
|
||||
}
|
||||
// SY <--
|
||||
is FilterUiModel.Item -> SourceFilterItem(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
source = model.source,
|
||||
isEnabled = model.isEnabled,
|
||||
onClickItem = onClickSource
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SourceFilterHeader(
|
||||
modifier: Modifier,
|
||||
language: String,
|
||||
isEnabled: Boolean,
|
||||
onClickItem: (String) -> Unit
|
||||
) {
|
||||
PreferenceRow(
|
||||
modifier = modifier,
|
||||
title = LocaleHelper.getSourceDisplayName(language, LocalContext.current),
|
||||
action = {
|
||||
Switch(checked = isEnabled, onCheckedChange = null)
|
||||
},
|
||||
onClick = { onClickItem(language) },
|
||||
)
|
||||
}
|
||||
|
||||
// SY -->
|
||||
@Composable
|
||||
fun SourceFilterToggle(
|
||||
modifier: Modifier,
|
||||
isEnabled: Boolean,
|
||||
onClickItem: () -> Unit
|
||||
) {
|
||||
PreferenceRow(
|
||||
modifier = modifier,
|
||||
title = stringResource(R.string.pref_category_all_sources),
|
||||
action = {
|
||||
Switch(checked = isEnabled, onCheckedChange = null)
|
||||
},
|
||||
onClick = { onClickItem() },
|
||||
painter = remember { ColorPainter(Color.Transparent) }
|
||||
)
|
||||
}
|
||||
|
||||
// SY <--
|
||||
|
||||
@Composable
|
||||
fun SourceFilterItem(
|
||||
modifier: Modifier,
|
||||
source: Source,
|
||||
isEnabled: Boolean,
|
||||
onClickItem: (Source) -> Unit
|
||||
) {
|
||||
BaseSourceItem(
|
||||
modifier = modifier,
|
||||
source = source,
|
||||
showLanguageInContent = false,
|
||||
onClickItem = { onClickItem(source) },
|
||||
action = {
|
||||
Checkbox(checked = isEnabled, onCheckedChange = null)
|
||||
}
|
||||
)
|
||||
}
|
@ -37,6 +37,7 @@ import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.update
|
||||
import rx.Observable
|
||||
@ -50,6 +51,8 @@ open class SourceManager(private val context: Context) {
|
||||
|
||||
private val _catalogueSources: MutableStateFlow<List<CatalogueSource>> = MutableStateFlow(listOf())
|
||||
val catalogueSources: Flow<List<CatalogueSource>> = _catalogueSources
|
||||
val onlineSources: Flow<List<HttpSource>> =
|
||||
_catalogueSources.map { sources -> sources.filterIsInstance<HttpSource>() }
|
||||
|
||||
// SY -->
|
||||
private val prefs: PreferencesHelper by injectLazy()
|
||||
|
@ -71,7 +71,7 @@ class SourceController(bundle: Bundle? = null) : SearchableComposeController<Sou
|
||||
}
|
||||
},
|
||||
onClickDisable = { source ->
|
||||
presenter.disableSource(source)
|
||||
presenter.toggleSource(source)
|
||||
},
|
||||
onClickLatest = { source ->
|
||||
openSource(source, LatestUpdatesController(source))
|
||||
|
@ -1,258 +1,44 @@
|
||||
package eu.kanade.tachiyomi.ui.browse.source
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import androidx.appcompat.widget.SearchView
|
||||
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.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 eu.kanade.tachiyomi.widget.preference.SwitchPreferenceCategory
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import reactivecircus.flowbinding.appcompat.queryTextChanges
|
||||
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>() {
|
||||
|
||||
init {
|
||||
setHasOptionsMenu(true)
|
||||
}
|
||||
override fun getTitle() = resources?.getString(R.string.label_sources)
|
||||
|
||||
private val onlineSources by lazy {
|
||||
Injekt.get<SourceManager>().getVisibleOnlineSources()
|
||||
}
|
||||
override fun createPresenter(): SourceFilterPresenter = SourceFilterPresenter()
|
||||
|
||||
private var query = ""
|
||||
|
||||
private var orderedLangs = listOf<String>()
|
||||
private var langPrefs = mutableListOf<Pair<String, SwitchPreferenceCategory>>()
|
||||
private var sourcesByLang: TreeMap<String, MutableList<HttpSource>> = TreeMap()
|
||||
private var sorting = SourcesSort.Alpha
|
||||
|
||||
override fun setupPreferenceScreen(screen: PreferenceScreen) = screen.apply {
|
||||
titleRes = R.string.label_sources
|
||||
|
||||
sorting = SourcesSort.from(preferences.sourceSorting().get()) ?: SourcesSort.Alpha
|
||||
activity?.invalidateOptionsMenu()
|
||||
|
||||
// Get the list of active language codes.
|
||||
val activeLangsCodes = preferences.enabledLanguages().get()
|
||||
|
||||
// Get a map of sources grouped by language.
|
||||
sourcesByLang = onlineSources.groupByTo(TreeMap(), { it.lang })
|
||||
|
||||
// Order first by active languages, then inactive ones
|
||||
orderedLangs = sourcesByLang.keys.filter { it in activeLangsCodes } +
|
||||
sourcesByLang.keys.filterNot { it in activeLangsCodes }
|
||||
|
||||
orderedLangs.forEach { lang ->
|
||||
// Create a preference group and set initial state and change listener
|
||||
langPrefs.add(
|
||||
Pair(
|
||||
lang,
|
||||
switchPreferenceCategory {
|
||||
preferenceScreen.addPreference(this)
|
||||
title = LocaleHelper.getSourceDisplayName(lang, context)
|
||||
isPersistent = false
|
||||
if (lang in activeLangsCodes) {
|
||||
setChecked(true)
|
||||
addLanguageSources(this, sortedSources(sourcesByLang[lang]))
|
||||
}
|
||||
|
||||
onChange { newValue ->
|
||||
val checked = newValue as Boolean
|
||||
if (!checked) {
|
||||
preferences.enabledLanguages() -= lang
|
||||
removeAll()
|
||||
} else {
|
||||
preferences.enabledLanguages() += lang
|
||||
addLanguageSources(this, sortedSources(sourcesByLang[lang]))
|
||||
}
|
||||
true
|
||||
}
|
||||
@Composable
|
||||
override fun ComposeContent(nestedScrollInterop: NestedScrollConnection) {
|
||||
SourceFilterScreen(
|
||||
nestedScrollInterop = nestedScrollInterop,
|
||||
presenter = presenter,
|
||||
onClickLang = { language ->
|
||||
presenter.toggleLanguage(language)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
val selectAllPreference = CheckBoxPreference(group.context).apply {
|
||||
title = "\t\t${context.getString(R.string.pref_category_all_sources)}"
|
||||
key = "all_${sources.first().lang}"
|
||||
isPersistent = false
|
||||
isChecked = sources.all { it.id.toString() !in disabledSourceIds }
|
||||
isVisible = query.isEmpty()
|
||||
|
||||
onChange { newValue ->
|
||||
val checked = newValue as Boolean
|
||||
val current = preferences.disabledSources().get().toMutableSet()
|
||||
if (checked) {
|
||||
current.removeAll(sources.map { it.id.toString() })
|
||||
} else {
|
||||
current.addAll(sources.map { it.id.toString() })
|
||||
}
|
||||
preferences.disabledSources().set(current)
|
||||
group.removeAll()
|
||||
addLanguageSources(group, sortedSources(sources))
|
||||
true
|
||||
}
|
||||
}
|
||||
group.addPreference(selectAllPreference)
|
||||
|
||||
sources.forEach { source ->
|
||||
val sourcePreference = CheckBoxPreference(group.context).apply {
|
||||
val id = source.id.toString()
|
||||
title = source.name
|
||||
key = getSourceKey(source.id)
|
||||
isPersistent = false
|
||||
isChecked = id !in disabledSourceIds
|
||||
isVisible = query.isEmpty() || source.name.contains(query, ignoreCase = true)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
group.removeAll()
|
||||
addLanguageSources(group, sortedSources(sources))
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
group.addPreference(sourcePreference)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSourceKey(sourceId: Long): String {
|
||||
return "source_$sourceId"
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds items to the options menu.
|
||||
*
|
||||
* @param menu menu containing options.
|
||||
* @param inflater used to load the menu xml.
|
||||
*/
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.settings_sources, menu)
|
||||
if (sorting == SourcesSort.Alpha) menu.findItem(R.id.action_sort_alpha).isChecked = true
|
||||
else menu.findItem(R.id.action_sort_enabled).isChecked = true
|
||||
|
||||
val searchItem = menu.findItem(R.id.action_search)
|
||||
val searchView = searchItem.actionView as SearchView
|
||||
searchView.maxWidth = Int.MAX_VALUE
|
||||
|
||||
if (this.query.isNotEmpty()) {
|
||||
searchItem.expandActionView()
|
||||
searchView.setQuery(query, true)
|
||||
searchView.clearFocus()
|
||||
}
|
||||
|
||||
searchView.queryTextChanges()
|
||||
.filter { router.backstack.lastOrNull()?.controller == this }
|
||||
.onEach {
|
||||
this.query = it.toString()
|
||||
drawSources()
|
||||
}
|
||||
.launchIn(viewScope)
|
||||
|
||||
// Fixes problem with the overflow icon showing up in lieu of search
|
||||
searchItem.setOnActionExpandListener(
|
||||
object : MenuItem.OnActionExpandListener {
|
||||
override fun onMenuItemActionExpand(item: MenuItem): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
|
||||
activity?.invalidateOptionsMenu()
|
||||
return true
|
||||
}
|
||||
onClickSource = { source ->
|
||||
presenter.toggleSource(source)
|
||||
},
|
||||
// SY -->
|
||||
onClickSources = { isEnable, sources ->
|
||||
presenter.toggleSources(isEnable, sources)
|
||||
},
|
||||
// SY <--
|
||||
)
|
||||
}
|
||||
|
||||
private fun drawSources() {
|
||||
val activeLangsCodes = preferences.enabledLanguages().get()
|
||||
langPrefs.forEach { group ->
|
||||
if (group.first in activeLangsCodes) {
|
||||
group.second.removeAll()
|
||||
addLanguageSources(group.second, sortedSources(sourcesByLang[group.first]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun sortedSources(sources: List<HttpSource>?): List<HttpSource> {
|
||||
val sourceAlpha = sources.orEmpty()
|
||||
.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER, { it.name }))
|
||||
return if (sorting == SourcesSort.Enabled) {
|
||||
val disabledSourceIds = preferences.disabledSources().get()
|
||||
sourceAlpha.filter { it.id.toString() !in disabledSourceIds } +
|
||||
sourceAlpha.filterNot { it.id.toString() !in disabledSourceIds }
|
||||
} else {
|
||||
sourceAlpha
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an option menu item has been selected by the user.
|
||||
*
|
||||
* @param item The selected item.
|
||||
* @return True if this event has been consumed, false if it has not.
|
||||
*/
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
sorting = when (item.itemId) {
|
||||
R.id.action_sort_alpha -> SourcesSort.Alpha
|
||||
R.id.action_sort_enabled -> SourcesSort.Enabled
|
||||
else -> return super.onOptionsItemSelected(item)
|
||||
}
|
||||
item.isChecked = true
|
||||
preferences.sourceSorting().set(sorting.value)
|
||||
drawSources()
|
||||
return true
|
||||
}
|
||||
|
||||
enum class SourcesSort(val value: Int) {
|
||||
Alpha(0), Enabled(1);
|
||||
|
||||
companion object {
|
||||
fun from(i: Int): SourcesSort? = values().find { it.value == i }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class FilterUiModel {
|
||||
data class Header(val language: String, val isEnabled: Boolean) : FilterUiModel()
|
||||
|
||||
// SY -->
|
||||
data class ToggleHeader(val sources: List<Source>, val isEnabled: Boolean) : FilterUiModel()
|
||||
|
||||
// SY <--
|
||||
data class Item(val source: Source, val isEnabled: Boolean) : FilterUiModel()
|
||||
}
|
||||
|
@ -0,0 +1,90 @@
|
||||
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.interactor.ToggleSources
|
||||
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(),
|
||||
// SY -->
|
||||
private val toggleSources: ToggleSources = Injekt.get(),
|
||||
// SY <--
|
||||
) : 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))
|
||||
// SY -->
|
||||
val disabledSources = preferences.disabledSources().get()
|
||||
val toggleHeader = listOf(
|
||||
FilterUiModel.ToggleHeader(
|
||||
it.value,
|
||||
it.value.none { (id) -> id.toString() in disabledSources }
|
||||
)
|
||||
)
|
||||
// SY <--
|
||||
|
||||
if (isLangEnabled.not()) return@flatMap header
|
||||
header + toggleHeader + 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)
|
||||
}
|
||||
|
||||
// SY -->
|
||||
fun toggleSources(isEnable: Boolean, sources: List<Source>) {
|
||||
toggleSources.await(isEnable, sources)
|
||||
}
|
||||
// SY <--
|
||||
}
|
||||
|
||||
sealed class SourceFilterState {
|
||||
object Loading : SourceFilterState()
|
||||
data class Error(val error: Throwable) : SourceFilterState()
|
||||
data class Success(val models: List<FilterUiModel>) : SourceFilterState()
|
||||
}
|
@ -1,12 +1,12 @@
|
||||
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.GetShowLatest
|
||||
import eu.kanade.domain.source.interactor.GetSourceCategories
|
||||
import eu.kanade.domain.source.interactor.SetSourceCategories
|
||||
import eu.kanade.domain.source.interactor.ToggleExcludeFromDataSaver
|
||||
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
|
||||
@ -28,7 +28,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(),
|
||||
// SY -->
|
||||
private val getSourceCategories: GetSourceCategories = Injekt.get(),
|
||||
@ -137,8 +137,8 @@ class SourcePresenter(
|
||||
}
|
||||
// SY <--
|
||||
|
||||
fun disableSource(source: Source) {
|
||||
disableSource.await(source)
|
||||
fun toggleSource(source: Source) {
|
||||
toggleSource.await(source)
|
||||
}
|
||||
|
||||
fun togglePin(source: Source) {
|
||||
|
@ -711,6 +711,9 @@
|
||||
<string name="clear_history_completed">History deleted</string>
|
||||
<string name="clear_history_confirmation">Are you sure? All history will be lost.</string>
|
||||
|
||||
<!-- Source Filter Screen -->
|
||||
<string name="source_filter_empty_screen">No installed source found</string>
|
||||
|
||||
<!-- Source migration screen -->
|
||||
<string name="migration_help_guide">Source migration guide</string>
|
||||
<string name="migration_dialog_what_to_include">Select data to include</string>
|
||||
|
Loading…
x
Reference in New Issue
Block a user