diff --git a/app/src/main/java/eu/kanade/data/source/SourceRepositoryImpl.kt b/app/src/main/java/eu/kanade/data/source/SourceRepositoryImpl.kt index 7fcc2921d..73e840ca9 100644 --- a/app/src/main/java/eu/kanade/data/source/SourceRepositoryImpl.kt +++ b/app/src/main/java/eu/kanade/data/source/SourceRepositoryImpl.kt @@ -20,6 +20,12 @@ class SourceRepositoryImpl( } } + override fun getOnlineSources(): Flow> { + return sourceManager.onlineSources.map { sources -> + sources.map(sourceMapper) + } + } + override fun getSourcesWithFavoriteCount(): Flow>> { val sourceIdWithFavoriteCount = handler.subscribeToList { mangasQueries.getSourceIdWithFavoriteCount() } return sourceIdWithFavoriteCount.map { sourceIdsWithCount -> diff --git a/app/src/main/java/eu/kanade/domain/DomainModule.kt b/app/src/main/java/eu/kanade/domain/DomainModule.kt index e870ff9a9..9ef6ff5e1 100644 --- a/app/src/main/java/eu/kanade/domain/DomainModule.kt +++ b/app/src/main/java/eu/kanade/domain/DomainModule.kt @@ -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 { 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 <-- } } diff --git a/app/src/main/java/eu/kanade/domain/source/interactor/DisableSource.kt b/app/src/main/java/eu/kanade/domain/source/interactor/DisableSource.kt deleted file mode 100644 index 336be8b99..000000000 --- a/app/src/main/java/eu/kanade/domain/source/interactor/DisableSource.kt +++ /dev/null @@ -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() - } -} diff --git a/app/src/main/java/eu/kanade/domain/source/interactor/GetLanguagesWithSources.kt b/app/src/main/java/eu/kanade/domain/source/interactor/GetLanguagesWithSources.kt new file mode 100644 index 000000000..1c157b5c1 --- /dev/null +++ b/app/src/main/java/eu/kanade/domain/source/interactor/GetLanguagesWithSources.kt @@ -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>> { + return combine( + preferences.enabledLanguages().asFlow(), + preferences.disabledSources().asFlow(), + repository.getOnlineSources() + ) { enabledLanguage, disabledSource, onlineSources -> + val sortedSources = onlineSources.sortedWith( + compareBy { it.id.toString() in disabledSource } + .thenBy(String.CASE_INSENSITIVE_ORDER) { it.name } + ) + + sortedSources.groupBy { it.lang } + .toSortedMap( + compareBy( + { it !in enabledLanguage }, + { LocaleHelper.getDisplayName(it) } + ) + ) + } + } +} diff --git a/app/src/main/java/eu/kanade/domain/source/interactor/GetSourcesWithFavoriteCount.kt b/app/src/main/java/eu/kanade/domain/source/interactor/GetSourcesWithFavoriteCount.kt index 54c43e3f1..aaa342665 100644 --- a/app/src/main/java/eu/kanade/domain/source/interactor/GetSourcesWithFavoriteCount.kt +++ b/app/src/main/java/eu/kanade/domain/source/interactor/GetSourcesWithFavoriteCount.kt @@ -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( diff --git a/app/src/main/java/eu/kanade/domain/source/interactor/ToggleLanguage.kt b/app/src/main/java/eu/kanade/domain/source/interactor/ToggleLanguage.kt new file mode 100644 index 000000000..da373e829 --- /dev/null +++ b/app/src/main/java/eu/kanade/domain/source/interactor/ToggleLanguage.kt @@ -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 + } + } +} diff --git a/app/src/main/java/eu/kanade/domain/source/interactor/ToggleSource.kt b/app/src/main/java/eu/kanade/domain/source/interactor/ToggleSource.kt new file mode 100644 index 000000000..8c9296d12 --- /dev/null +++ b/app/src/main/java/eu/kanade/domain/source/interactor/ToggleSource.kt @@ -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() + } + } +} diff --git a/app/src/main/java/eu/kanade/domain/source/interactor/ToggleSources.kt b/app/src/main/java/eu/kanade/domain/source/interactor/ToggleSources.kt new file mode 100644 index 000000000..35af78436 --- /dev/null +++ b/app/src/main/java/eu/kanade/domain/source/interactor/ToggleSources.kt @@ -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) { + 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) + } +} diff --git a/app/src/main/java/eu/kanade/domain/source/repository/SourceRepository.kt b/app/src/main/java/eu/kanade/domain/source/repository/SourceRepository.kt index b58509de9..5f07a30f5 100644 --- a/app/src/main/java/eu/kanade/domain/source/repository/SourceRepository.kt +++ b/app/src/main/java/eu/kanade/domain/source/repository/SourceRepository.kt @@ -7,5 +7,7 @@ interface SourceRepository { fun getSources(): Flow> + fun getOnlineSources(): Flow> + fun getSourcesWithFavoriteCount(): Flow>> } diff --git a/app/src/main/java/eu/kanade/presentation/source/SourceFilterScreen.kt b/app/src/main/java/eu/kanade/presentation/source/SourceFilterScreen.kt new file mode 100644 index 000000000..f87946ff1 --- /dev/null +++ b/app/src/main/java/eu/kanade/presentation/source/SourceFilterScreen.kt @@ -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) -> 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, + onClickLang: (String) -> Unit, + onClickSource: (Source) -> Unit, + // SY --> + onClickSources: (Boolean, List) -> 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) + } + ) +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/SourceManager.kt b/app/src/main/java/eu/kanade/tachiyomi/source/SourceManager.kt index 903867e61..a979d5c9a 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/source/SourceManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/SourceManager.kt @@ -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> = MutableStateFlow(listOf()) val catalogueSources: Flow> = _catalogueSources + val onlineSources: Flow> = + _catalogueSources.map { sources -> sources.filterIsInstance() } // SY --> private val prefs: PreferencesHelper by injectLazy() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourceController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourceController.kt index f26a0eb9c..0326cc702 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourceController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourceController.kt @@ -71,7 +71,7 @@ class SourceController(bundle: Bundle? = null) : SearchableComposeController - presenter.disableSource(source) + presenter.toggleSource(source) }, onClickLatest = { source -> openSource(source, LatestUpdatesController(source)) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourceFilterController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourceFilterController.kt index 6d908e8e2..9f61d06a8 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourceFilterController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourceFilterController.kt @@ -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() { - init { - setHasOptionsMenu(true) - } + override fun getTitle() = resources?.getString(R.string.label_sources) - private val onlineSources by lazy { - Injekt.get().getVisibleOnlineSources() - } + override fun createPresenter(): SourceFilterPresenter = SourceFilterPresenter() - private var query = "" - - private var orderedLangs = listOf() - private var langPrefs = mutableListOf>() - private var sourcesByLang: TreeMap> = 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 - } - }, - ), - ) - } - } - - 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) { - 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 - } + @Composable + override fun ComposeContent(nestedScrollInterop: NestedScrollConnection) { + SourceFilterScreen( + nestedScrollInterop = nestedScrollInterop, + presenter = presenter, + onClickLang = { language -> + presenter.toggleLanguage(language) }, + 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?): List { - 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, val isEnabled: Boolean) : FilterUiModel() + + // SY <-- + data class Item(val source: Source, val isEnabled: Boolean) : FilterUiModel() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourceFilterPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourceFilterPresenter.kt new file mode 100644 index 000000000..2d9847086 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourceFilterPresenter.kt @@ -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() { + + private val _state: MutableStateFlow = MutableStateFlow(SourceFilterState.Loading) + val state: StateFlow = _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>.toFilterUiModels(): List { + 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) { + toggleSources.await(isEnable, sources) + } + // SY <-- +} + +sealed class SourceFilterState { + object Loading : SourceFilterState() + data class Error(val error: Throwable) : SourceFilterState() + data class Success(val models: List) : SourceFilterState() +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcePresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcePresenter.kt index 3541e869b..973f5edc7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcePresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcePresenter.kt @@ -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) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ec5e2854e..6039b8607 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -711,6 +711,9 @@ History deleted Are you sure? All history will be lost. + + No installed source found + Source migration guide Select data to include