Sources settings screen

This commit is contained in:
Koitharu
2023-11-12 16:30:11 +02:00
parent b928c4123c
commit fee35cceab
22 changed files with 262 additions and 63 deletions

View File

@@ -4,10 +4,15 @@ import androidx.room.Dao
import androidx.room.Insert import androidx.room.Insert
import androidx.room.OnConflictStrategy import androidx.room.OnConflictStrategy
import androidx.room.Query import androidx.room.Query
import androidx.room.RawQuery
import androidx.room.Transaction import androidx.room.Transaction
import androidx.room.Upsert import androidx.room.Upsert
import androidx.sqlite.db.SimpleSQLiteQuery
import androidx.sqlite.db.SupportSQLiteQuery
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import org.intellij.lang.annotations.Language
import org.koitharu.kotatsu.core.db.entity.MangaSourceEntity import org.koitharu.kotatsu.core.db.entity.MangaSourceEntity
import org.koitharu.kotatsu.explore.data.SourcesSortOrder
@Dao @Dao
abstract class MangaSourcesDao { abstract class MangaSourcesDao {
@@ -15,14 +20,11 @@ abstract class MangaSourcesDao {
@Query("SELECT * FROM sources ORDER BY sort_key") @Query("SELECT * FROM sources ORDER BY sort_key")
abstract suspend fun findAll(): List<MangaSourceEntity> abstract suspend fun findAll(): List<MangaSourceEntity>
@Query("SELECT * FROM sources WHERE enabled = 1 ORDER BY sort_key")
abstract suspend fun findAllEnabled(): List<MangaSourceEntity>
@Query("SELECT * FROM sources WHERE enabled = 0 ORDER BY sort_key") @Query("SELECT * FROM sources WHERE enabled = 0 ORDER BY sort_key")
abstract suspend fun findAllDisabled(): List<MangaSourceEntity> abstract suspend fun findAllDisabled(): List<MangaSourceEntity>
@Query("SELECT * FROM sources WHERE enabled = 1 ORDER BY sort_key") @Query("SELECT * FROM sources WHERE enabled = 0")
abstract fun observeEnabled(): Flow<List<MangaSourceEntity>> abstract fun observeDisabled(): Flow<List<MangaSourceEntity>>
@Query("SELECT * FROM sources ORDER BY sort_key") @Query("SELECT * FROM sources ORDER BY sort_key")
abstract fun observeAll(): Flow<List<MangaSourceEntity>> abstract fun observeAll(): Flow<List<MangaSourceEntity>>
@@ -43,6 +45,22 @@ abstract class MangaSourcesDao {
@Upsert @Upsert
abstract suspend fun upsert(entry: MangaSourceEntity) abstract suspend fun upsert(entry: MangaSourceEntity)
fun observeEnabled(order: SourcesSortOrder): Flow<List<MangaSourceEntity>> {
val orderBy = getOrderBy(order)
@Language("RoomSql")
val query = SimpleSQLiteQuery("SELECT * FROM sources WHERE enabled = 1 ORDER BY $orderBy")
return observeImpl(query)
}
suspend fun findAllEnabled(order: SourcesSortOrder): List<MangaSourceEntity> {
val orderBy = getOrderBy(order)
@Language("RoomSql")
val query = SimpleSQLiteQuery("SELECT * FROM sources WHERE enabled = 1 ORDER BY $orderBy")
return findAllImpl(query)
}
@Transaction @Transaction
open suspend fun setEnabled(source: String, isEnabled: Boolean) { open suspend fun setEnabled(source: String, isEnabled: Boolean) {
if (updateIsEnabled(source, isEnabled) == 0) { if (updateIsEnabled(source, isEnabled) == 0) {
@@ -57,4 +75,16 @@ abstract class MangaSourcesDao {
@Query("UPDATE sources SET enabled = :isEnabled WHERE source = :source") @Query("UPDATE sources SET enabled = :isEnabled WHERE source = :source")
protected abstract suspend fun updateIsEnabled(source: String, isEnabled: Boolean): Int protected abstract suspend fun updateIsEnabled(source: String, isEnabled: Boolean): Int
@RawQuery(observedEntities = [MangaSourceEntity::class])
protected abstract fun observeImpl(query: SupportSQLiteQuery): Flow<List<MangaSourceEntity>>
@RawQuery
protected abstract suspend fun findAllImpl(query: SupportSQLiteQuery): List<MangaSourceEntity>
private fun getOrderBy(order: SourcesSortOrder) = when (order) {
SourcesSortOrder.ALPHABETIC -> "source ASC"
SourcesSortOrder.POPULARITY -> "(SELECT COUNT(*) FROM manga WHERE source = sources.source) DESC"
SourcesSortOrder.MANUAL -> "sort_key ASC"
}
} }

View File

@@ -22,6 +22,7 @@ import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.putEnumValue import org.koitharu.kotatsu.core.util.ext.putEnumValue
import org.koitharu.kotatsu.core.util.ext.takeIfReadable import org.koitharu.kotatsu.core.util.ext.takeIfReadable
import org.koitharu.kotatsu.core.util.ext.toUriOrNull import org.koitharu.kotatsu.core.util.ext.toUriOrNull
import org.koitharu.kotatsu.explore.data.SourcesSortOrder
import org.koitharu.kotatsu.list.domain.ListSortOrder import org.koitharu.kotatsu.list.domain.ListSortOrder
import org.koitharu.kotatsu.parsers.model.SortOrder import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.parsers.util.find import org.koitharu.kotatsu.parsers.util.find
@@ -209,6 +210,10 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
return policy.isNetworkAllowed(connectivityManager) return policy.isNetworkAllowed(connectivityManager)
} }
var sourcesSortOrder: SourcesSortOrder
get() = prefs.getEnumValue(KEY_SOURCES_ORDER, SourcesSortOrder.MANUAL)
set(value) = prefs.edit { putEnumValue(KEY_SOURCES_ORDER, value) }
var isSourcesGridMode: Boolean var isSourcesGridMode: Boolean
get() = prefs.getBoolean(KEY_SOURCES_GRID, false) get() = prefs.getBoolean(KEY_SOURCES_GRID, false)
set(value) = prefs.edit { putBoolean(KEY_SOURCES_GRID, value) } set(value) = prefs.edit { putBoolean(KEY_SOURCES_GRID, value) }
@@ -528,6 +533,8 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
const val KEY_RELATED_MANGA = "related_manga" const val KEY_RELATED_MANGA = "related_manga"
const val KEY_NAV_MAIN = "nav_main" const val KEY_NAV_MAIN = "nav_main"
const val KEY_32BIT_COLOR = "enhanced_colors" const val KEY_32BIT_COLOR = "enhanced_colors"
const val KEY_SOURCES_ORDER = "sources_sort_order"
const val KEY_SOURCES_CATALOG = "sources_catalog"
// About // About
const val KEY_APP_UPDATE = "app_update" const val KEY_APP_UPDATE = "app_update"

View File

@@ -19,6 +19,7 @@ import org.koitharu.kotatsu.core.prefs.observeAsFlow
import org.koitharu.kotatsu.core.ui.util.ReversibleHandle import org.koitharu.kotatsu.core.ui.util.ReversibleHandle
import org.koitharu.kotatsu.parsers.model.ContentType import org.koitharu.kotatsu.parsers.model.ContentType
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.util.mapToSet
import java.util.Collections import java.util.Collections
import java.util.EnumSet import java.util.EnumSet
import javax.inject.Inject import javax.inject.Inject
@@ -43,19 +44,44 @@ class MangaSourcesRepository @Inject constructor(
get() = Collections.unmodifiableSet(remoteSources) get() = Collections.unmodifiableSet(remoteSources)
suspend fun getEnabledSources(): List<MangaSource> { suspend fun getEnabledSources(): List<MangaSource> {
return dao.findAllEnabled().toSources(settings.isNsfwContentDisabled) val order = settings.sourcesSortOrder
return dao.findAllEnabled(order).toSources(settings.isNsfwContentDisabled, order)
} }
suspend fun getDisabledSources(): List<MangaSource> { suspend fun getDisabledSources(): List<MangaSource> {
return dao.findAllDisabled().toSources(settings.isNsfwContentDisabled) return dao.findAllDisabled().toSources(settings.isNsfwContentDisabled, null)
} }
fun observeEnabledSources(): Flow<List<MangaSource>> = observeIsNsfwDisabled().flatMapLatest { skipNsfw -> fun observeEnabledSourcesCount(): Flow<Int> {
dao.observeEnabled().map { return combine(
it.toSources(skipNsfw) observeIsNsfwDisabled(),
} dao.observeEnabled(SourcesSortOrder.MANUAL),
) { skipNsfw, sources ->
sources.count { skipNsfw || !MangaSource(it.source).isNsfw() }
}.distinctUntilChanged()
} }
fun observeAvailableSourcesCount(): Flow<Int> {
return combine(
observeIsNsfwDisabled(),
dao.observeEnabled(SourcesSortOrder.MANUAL),
) { skipNsfw, enabledSources ->
val enabled = enabledSources.mapToSet { it.source }
allMangaSources.count { x ->
x.name !in enabled && (!skipNsfw || !x.isNsfw())
}
}.distinctUntilChanged()
}
fun observeEnabledSources(): Flow<List<MangaSource>> = combine(
observeIsNsfwDisabled(),
observeSortOrder(),
) { skipNsfw, order ->
dao.observeEnabled(order).map {
it.toSources(skipNsfw, order)
}
}.flatMapLatest { it }
fun observeAll(): Flow<List<Pair<MangaSource, Boolean>>> = dao.observeAll().map { entities -> fun observeAll(): Flow<List<Pair<MangaSource, Boolean>>> = dao.observeAll().map { entities ->
val result = ArrayList<Pair<MangaSource, Boolean>>(entities.size) val result = ArrayList<Pair<MangaSource, Boolean>>(entities.size)
for (entity in entities) { for (entity in entities) {
@@ -150,7 +176,10 @@ class MangaSourcesRepository @Inject constructor(
return result return result
} }
private fun List<MangaSourceEntity>.toSources(skipNsfwSources: Boolean): List<MangaSource> { private fun List<MangaSourceEntity>.toSources(
skipNsfwSources: Boolean,
sortOrder: SourcesSortOrder?,
): List<MangaSource> {
val result = ArrayList<MangaSource>(size) val result = ArrayList<MangaSource>(size)
for (entity in this) { for (entity in this) {
val source = MangaSource(entity.source) val source = MangaSource(entity.source)
@@ -161,6 +190,9 @@ class MangaSourcesRepository @Inject constructor(
result.add(source) result.add(source)
} }
} }
if (sortOrder == SourcesSortOrder.ALPHABETIC) {
result.sortBy { it.title }
}
return result return result
} }
@@ -171,4 +203,8 @@ class MangaSourcesRepository @Inject constructor(
private fun observeIsNewSourcesEnabled() = settings.observeAsFlow(AppSettings.KEY_SOURCES_NEW) { private fun observeIsNewSourcesEnabled() = settings.observeAsFlow(AppSettings.KEY_SOURCES_NEW) {
isNewSourcesTipEnabled isNewSourcesTipEnabled
} }
private fun observeSortOrder() = settings.observeAsFlow(AppSettings.KEY_SOURCES_ORDER) {
sourcesSortOrder
}
} }

View File

@@ -0,0 +1,12 @@
package org.koitharu.kotatsu.explore.data
import androidx.annotation.StringRes
import org.koitharu.kotatsu.R
enum class SourcesSortOrder(
@StringRes val titleResId: Int,
) {
ALPHABETIC(R.string.by_name),
POPULARITY(R.string.popular),
MANUAL(R.string.manual),
}

View File

@@ -85,7 +85,7 @@ class ExploreFragment :
SpanSizeResolver(this, resources.getDimensionPixelSize(R.dimen.explore_grid_width)).attach() SpanSizeResolver(this, resources.getDimensionPixelSize(R.dimen.explore_grid_width)).attach()
addItemDecoration(TypedListSpacingDecoration(context, false)) addItemDecoration(TypedListSpacingDecoration(context, false))
} }
addMenuProvider(ExploreMenuProvider(binding.root.context, viewModel)) addMenuProvider(ExploreMenuProvider(binding.root.context))
viewModel.content.observe(viewLifecycleOwner) { viewModel.content.observe(viewLifecycleOwner) {
exploreAdapter?.items = it exploreAdapter?.items = it
} }
@@ -176,7 +176,6 @@ class ExploreFragment :
} else { } else {
LinearLayoutManager(requireContext()) LinearLayoutManager(requireContext())
} }
activity?.invalidateOptionsMenu()
} }
private fun showSuggestionsTip() { private fun showSuggestionsTip() {

View File

@@ -10,7 +10,6 @@ import org.koitharu.kotatsu.settings.SettingsActivity
class ExploreMenuProvider( class ExploreMenuProvider(
private val context: Context, private val context: Context,
private val viewModel: ExploreViewModel,
) : MenuProvider { ) : MenuProvider {
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
@@ -19,22 +18,12 @@ class ExploreMenuProvider(
override fun onMenuItemSelected(menuItem: MenuItem): Boolean { override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
return when (menuItem.itemId) { return when (menuItem.itemId) {
R.id.action_grid -> {
viewModel.setGridMode(!menuItem.isChecked)
true
}
R.id.action_manage -> { R.id.action_manage -> {
context.startActivity(SettingsActivity.newManageSourcesIntent(context)) context.startActivity(SettingsActivity.newSourcesSettingsIntent(context))
true true
} }
else -> false else -> false
} }
} }
override fun onPrepareMenu(menu: Menu) {
super.onPrepareMenu(menu)
menu.findItem(R.id.action_grid)?.isChecked = viewModel.isGrid.value == true
}
} }

View File

@@ -21,6 +21,7 @@ import org.koitharu.kotatsu.core.ui.util.ReversibleAction
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
import org.koitharu.kotatsu.core.util.ext.call import org.koitharu.kotatsu.core.util.ext.call
import org.koitharu.kotatsu.explore.data.MangaSourcesRepository import org.koitharu.kotatsu.explore.data.MangaSourcesRepository
import org.koitharu.kotatsu.explore.data.SourcesSortOrder
import org.koitharu.kotatsu.explore.domain.ExploreRepository import org.koitharu.kotatsu.explore.domain.ExploreRepository
import org.koitharu.kotatsu.explore.ui.model.ExploreButtons import org.koitharu.kotatsu.explore.ui.model.ExploreButtons
import org.koitharu.kotatsu.explore.ui.model.MangaSourceItem import org.koitharu.kotatsu.explore.ui.model.MangaSourceItem
@@ -50,11 +51,13 @@ class ExploreViewModel @Inject constructor(
valueProducer = { isSourcesGridMode }, valueProducer = { isSourcesGridMode },
) )
val isSuggestionsEnabled = settings.observeAsFlow( private val isSuggestionsEnabled = settings.observeAsFlow(
key = AppSettings.KEY_SUGGESTIONS, key = AppSettings.KEY_SUGGESTIONS,
valueProducer = { isSuggestionsEnabled }, valueProducer = { isSuggestionsEnabled },
) )
val sortOrder = MutableStateFlow(SourcesSortOrder.MANUAL) // TODO
val onOpenManga = MutableEventFlow<Manga>() val onOpenManga = MutableEventFlow<Manga>()
val onActionDone = MutableEventFlow<ReversibleAction>() val onActionDone = MutableEventFlow<ReversibleAction>()
val onShowSuggestionsTip = MutableEventFlow<Unit>() val onShowSuggestionsTip = MutableEventFlow<Unit>()
@@ -104,10 +107,6 @@ class ExploreViewModel @Inject constructor(
} }
} }
fun setGridMode(value: Boolean) {
settings.isSourcesGridMode = value
}
fun respondSuggestionTip(isAccepted: Boolean) { fun respondSuggestionTip(isAccepted: Boolean) {
settings.isSuggestionsEnabled = isAccepted settings.isSuggestionsEnabled = isAccepted
settings.closeTip(TIP_SUGGESTIONS) settings.closeTip(TIP_SUGGESTIONS)

View File

@@ -4,7 +4,6 @@ import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.plus import kotlinx.coroutines.plus
import org.koitharu.kotatsu.core.ui.BaseViewModel import org.koitharu.kotatsu.core.ui.BaseViewModel
@@ -18,7 +17,6 @@ class RootSettingsViewModel @Inject constructor(
val totalSourcesCount = sourcesRepository.allMangaSources.size val totalSourcesCount = sourcesRepository.allMangaSources.size
val enabledSourcesCount = sourcesRepository.observeEnabledSources() val enabledSourcesCount = sourcesRepository.observeEnabledSourcesCount()
.map { it.size }
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, -1) .stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, -1)
} }

View File

@@ -31,7 +31,8 @@ import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.settings.about.AboutSettingsFragment import org.koitharu.kotatsu.settings.about.AboutSettingsFragment
import org.koitharu.kotatsu.settings.about.AppUpdateDialog import org.koitharu.kotatsu.settings.about.AppUpdateDialog
import org.koitharu.kotatsu.settings.sources.SourceSettingsFragment import org.koitharu.kotatsu.settings.sources.SourceSettingsFragment
import org.koitharu.kotatsu.settings.sources.SourcesManageFragment import org.koitharu.kotatsu.settings.sources.SourcesSettingsFragment
import org.koitharu.kotatsu.settings.sources.manage.SourcesManageFragment
import org.koitharu.kotatsu.settings.tracker.TrackerSettingsFragment import org.koitharu.kotatsu.settings.tracker.TrackerSettingsFragment
import org.koitharu.kotatsu.settings.userdata.UserDataSettingsFragment import org.koitharu.kotatsu.settings.userdata.UserDataSettingsFragment
@@ -153,6 +154,7 @@ class SettingsActivity :
ACTION_SUGGESTIONS -> SuggestionsSettingsFragment() ACTION_SUGGESTIONS -> SuggestionsSettingsFragment()
ACTION_HISTORY -> UserDataSettingsFragment() ACTION_HISTORY -> UserDataSettingsFragment()
ACTION_TRACKER -> TrackerSettingsFragment() ACTION_TRACKER -> TrackerSettingsFragment()
ACTION_SOURCES -> SourcesSettingsFragment()
ACTION_MANAGE_DOWNLOADS -> DownloadsSettingsFragment() ACTION_MANAGE_DOWNLOADS -> DownloadsSettingsFragment()
ACTION_SOURCE -> SourceSettingsFragment.newInstance( ACTION_SOURCE -> SourceSettingsFragment.newInstance(
intent.getSerializableExtraCompat(EXTRA_SOURCE) as? MangaSource ?: MangaSource.LOCAL, intent.getSerializableExtraCompat(EXTRA_SOURCE) as? MangaSource ?: MangaSource.LOCAL,
@@ -182,6 +184,7 @@ class SettingsActivity :
private const val ACTION_TRACKER = "${BuildConfig.APPLICATION_ID}.action.MANAGE_TRACKER" private const val ACTION_TRACKER = "${BuildConfig.APPLICATION_ID}.action.MANAGE_TRACKER"
private const val ACTION_HISTORY = "${BuildConfig.APPLICATION_ID}.action.MANAGE_HISTORY" private const val ACTION_HISTORY = "${BuildConfig.APPLICATION_ID}.action.MANAGE_HISTORY"
private const val ACTION_SOURCE = "${BuildConfig.APPLICATION_ID}.action.MANAGE_SOURCE_SETTINGS" private const val ACTION_SOURCE = "${BuildConfig.APPLICATION_ID}.action.MANAGE_SOURCE_SETTINGS"
private const val ACTION_SOURCES = "${BuildConfig.APPLICATION_ID}.action.MANAGE_SOURCES"
private const val ACTION_MANAGE_SOURCES = "${BuildConfig.APPLICATION_ID}.action.MANAGE_SOURCES_LIST" private const val ACTION_MANAGE_SOURCES = "${BuildConfig.APPLICATION_ID}.action.MANAGE_SOURCES_LIST"
private const val ACTION_MANAGE_DOWNLOADS = "${BuildConfig.APPLICATION_ID}.action.MANAGE_DOWNLOADS" private const val ACTION_MANAGE_DOWNLOADS = "${BuildConfig.APPLICATION_ID}.action.MANAGE_DOWNLOADS"
private const val EXTRA_SOURCE = "source" private const val EXTRA_SOURCE = "source"
@@ -206,6 +209,10 @@ class SettingsActivity :
Intent(context, SettingsActivity::class.java) Intent(context, SettingsActivity::class.java)
.setAction(ACTION_HISTORY) .setAction(ACTION_HISTORY)
fun newSourcesSettingsIntent(context: Context) =
Intent(context, SettingsActivity::class.java)
.setAction(ACTION_SOURCES)
fun newManageSourcesIntent(context: Context) = fun newManageSourcesIntent(context: Context) =
Intent(context, SettingsActivity::class.java) Intent(context, SettingsActivity::class.java)
.setAction(ACTION_MANAGE_SOURCES) .setAction(ACTION_MANAGE_SOURCES)

View File

@@ -0,0 +1,63 @@
package org.koitharu.kotatsu.settings.sources
import android.content.Intent
import android.os.Bundle
import android.view.View
import androidx.fragment.app.viewModels
import androidx.preference.ListPreference
import androidx.preference.Preference
import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.BasePreferenceFragment
import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.setDefaultValueCompat
import org.koitharu.kotatsu.explore.data.SourcesSortOrder
import org.koitharu.kotatsu.parsers.util.names
import org.koitharu.kotatsu.settings.sources.catalog.SourcesCatalogActivity
@AndroidEntryPoint
class SourcesSettingsFragment : BasePreferenceFragment(R.string.remote_sources) {
private val viewModel by viewModels<SourcesSettingsViewModel>()
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.pref_sources)
findPreference<ListPreference>(AppSettings.KEY_SOURCES_ORDER)?.run {
entryValues = SourcesSortOrder.entries.names()
entries = SourcesSortOrder.entries.map { context.getString(it.titleResId) }.toTypedArray()
setDefaultValueCompat(SourcesSortOrder.MANUAL.name)
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
findPreference<Preference>(AppSettings.KEY_REMOTE_SOURCES)?.let { pref ->
viewModel.enabledSourcesCount.observe(viewLifecycleOwner) {
pref.summary = if (it >= 0) {
resources.getQuantityString(R.plurals.items, it, it)
} else {
null
}
}
}
findPreference<Preference>(AppSettings.KEY_SOURCES_CATALOG)?.let { pref ->
viewModel.availableSourcesCount.observe(viewLifecycleOwner) {
pref.summary = if (it >= 0) {
getString(R.string.available_d, it)
} else {
null
}
}
}
}
override fun onPreferenceTreeClick(preference: Preference): Boolean = when (preference.key) {
AppSettings.KEY_SOURCES_CATALOG -> {
startActivity(Intent(preference.context, SourcesCatalogActivity::class.java))
true
}
else -> super.onPreferenceTreeClick(preference)
}
}

View File

@@ -0,0 +1,25 @@
package org.koitharu.kotatsu.settings.sources
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.plus
import org.koitharu.kotatsu.core.ui.BaseViewModel
import org.koitharu.kotatsu.explore.data.MangaSourcesRepository
import javax.inject.Inject
@HiltViewModel
class SourcesSettingsViewModel @Inject constructor(
private val sourcesRepository: MangaSourcesRepository,
) : BaseViewModel() {
val totalSourcesCount = sourcesRepository.allMangaSources.size
val enabledSourcesCount = sourcesRepository.observeEnabledSourcesCount()
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, -1)
val availableSourcesCount = sourcesRepository.observeAvailableSourcesCount()
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, -1)
}

View File

@@ -169,6 +169,7 @@ private fun showSourceMenu(
menu.inflate(R.menu.popup_source_config) menu.inflate(R.menu.popup_source_config)
menu.menu.findItem(R.id.action_shortcut) menu.menu.findItem(R.id.action_shortcut)
?.isVisible = ShortcutManagerCompat.isRequestPinShortcutSupported(anchor.context) ?.isVisible = ShortcutManagerCompat.isRequestPinShortcutSupported(anchor.context)
menu.menu.findItem(R.id.action_lift)?.isVisible = item.isDraggable
menu.setOnMenuItemClickListener { menu.setOnMenuItemClickListener {
when (it.itemId) { when (it.itemId) {
R.id.action_settings -> listener.onItemSettingsClick(item) R.id.action_settings -> listener.onItemSettingsClick(item)

View File

@@ -74,7 +74,7 @@ class SourcesCatalogActivity : BaseActivity<ActivitySourcesCatalogBinding>(),
} }
override fun onTabSelected(tab: TabLayout.Tab) { override fun onTabSelected(tab: TabLayout.Tab) {
viewModel.setContentType(ContentType.entries[tab.position]) viewModel.setContentType(tab.tag as ContentType)
} }
override fun onTabUnselected(tab: TabLayout.Tab) = Unit override fun onTabUnselected(tab: TabLayout.Tab) = Unit
@@ -86,8 +86,12 @@ class SourcesCatalogActivity : BaseActivity<ActivitySourcesCatalogBinding>(),
private fun initTabs() { private fun initTabs() {
val tabs = viewBinding.tabs val tabs = viewBinding.tabs
for (type in ContentType.entries) { for (type in ContentType.entries) {
if (viewModel.isNsfwDisabled && type == ContentType.HENTAI) {
continue
}
val tab = tabs.newTab() val tab = tabs.newTab()
tab.setText(type.titleResId) tab.setText(type.titleResId)
tab.tag = type
tabs.addTab(tab) tabs.addTab(tab)
} }
tabs.addOnTabSelectedListener(this) tabs.addOnTabSelectedListener(this)

View File

@@ -12,6 +12,7 @@ import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.BaseViewModel import org.koitharu.kotatsu.core.ui.BaseViewModel
import org.koitharu.kotatsu.core.ui.util.ReversibleAction import org.koitharu.kotatsu.core.ui.util.ReversibleAction
import org.koitharu.kotatsu.core.util.LocaleComparator import org.koitharu.kotatsu.core.util.LocaleComparator
@@ -28,6 +29,7 @@ import javax.inject.Inject
class SourcesCatalogViewModel @Inject constructor( class SourcesCatalogViewModel @Inject constructor(
private val repository: MangaSourcesRepository, private val repository: MangaSourcesRepository,
private val listProducerFactory: SourcesCatalogListProducer.Factory, private val listProducerFactory: SourcesCatalogListProducer.Factory,
private val settings: AppSettings,
) : BaseViewModel() { ) : BaseViewModel() {
private val lifecycle = RetainedLifecycleImpl() private val lifecycle = RetainedLifecycleImpl()
@@ -37,6 +39,8 @@ class SourcesCatalogViewModel @Inject constructor(
val locales = getLocalesImpl() val locales = getLocalesImpl()
val locale = MutableStateFlow(locales.firstOrNull()?.language) val locale = MutableStateFlow(locales.firstOrNull()?.language)
val isNsfwDisabled = settings.isNsfwContentDisabled
private val listProducer: StateFlow<SourcesCatalogListProducer?> = combine( private val listProducer: StateFlow<SourcesCatalogListProducer?> = combine(
locale, locale,
contentType, contentType,

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.settings.sources package org.koitharu.kotatsu.settings.sources.manage
import androidx.room.InvalidationTracker import androidx.room.InvalidationTracker
import dagger.hilt.android.ViewModelLifecycle import dagger.hilt.android.ViewModelLifecycle
@@ -19,6 +19,7 @@ import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.util.ext.lifecycleScope import org.koitharu.kotatsu.core.util.ext.lifecycleScope
import org.koitharu.kotatsu.core.util.ext.toEnumSet import org.koitharu.kotatsu.core.util.ext.toEnumSet
import org.koitharu.kotatsu.explore.data.MangaSourcesRepository import org.koitharu.kotatsu.explore.data.MangaSourcesRepository
import org.koitharu.kotatsu.explore.data.SourcesSortOrder
import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem
import javax.inject.Inject import javax.inject.Inject
@@ -61,7 +62,8 @@ class SourcesListProducer @Inject constructor(
private suspend fun buildList(): List<SourceConfigItem> { private suspend fun buildList(): List<SourceConfigItem> {
val enabledSources = repository.getEnabledSources() val enabledSources = repository.getEnabledSources()
val isNsfwDisabled = settings.isNsfwContentDisabled val isNsfwDisabled = settings.isNsfwContentDisabled
val withTip = settings.isTipEnabled(TIP_REORDER) val isReorderAvailable = settings.sourcesSortOrder == SourcesSortOrder.MANUAL
val withTip = isReorderAvailable && settings.isTipEnabled(TIP_REORDER)
val enabledSet = enabledSources.toEnumSet() val enabledSet = enabledSources.toEnumSet()
if (query.isNotEmpty()) { if (query.isNotEmpty()) {
return enabledSources.mapNotNull { return enabledSources.mapNotNull {
@@ -91,7 +93,7 @@ class SourcesListProducer @Inject constructor(
SourceConfigItem.SourceItem( SourceConfigItem.SourceItem(
source = it, source = it,
isEnabled = true, isEnabled = true,
isDraggable = true, isDraggable = isReorderAvailable,
isAvailable = false, isAvailable = false,
) )
} }

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.settings.sources package org.koitharu.kotatsu.settings.sources.manage
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
@@ -31,6 +31,7 @@ import org.koitharu.kotatsu.core.util.ext.viewLifecycleScope
import org.koitharu.kotatsu.databinding.FragmentSettingsSourcesBinding import org.koitharu.kotatsu.databinding.FragmentSettingsSourcesBinding
import org.koitharu.kotatsu.main.ui.owners.AppBarOwner import org.koitharu.kotatsu.main.ui.owners.AppBarOwner
import org.koitharu.kotatsu.settings.SettingsActivity import org.koitharu.kotatsu.settings.SettingsActivity
import org.koitharu.kotatsu.settings.sources.SourceSettingsFragment
import org.koitharu.kotatsu.settings.sources.adapter.SourceConfigAdapter import org.koitharu.kotatsu.settings.sources.adapter.SourceConfigAdapter
import org.koitharu.kotatsu.settings.sources.adapter.SourceConfigListener import org.koitharu.kotatsu.settings.sources.adapter.SourceConfigListener
import org.koitharu.kotatsu.settings.sources.catalog.SourcesCatalogActivity import org.koitharu.kotatsu.settings.sources.catalog.SourcesCatalogActivity

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.settings.sources package org.koitharu.kotatsu.settings.sources.manage
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers

View File

@@ -2,12 +2,6 @@
<menu <menu
xmlns:android="http://schemas.android.com/apk/res/android"> xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/action_grid"
android:checkable="true"
android:title="@string/show_in_grid_view"
android:titleCondensed="@string/grid" />
<item <item
android:id="@+id/action_manage" android:id="@+id/action_manage"
android:title="@string/manage_sources" android:title="@string/manage_sources"

View File

@@ -523,4 +523,7 @@
<string name="no_manga_sources_found">No available manga sources found by your query</string> <string name="no_manga_sources_found">No available manga sources found by your query</string>
<string name="catalog">Catalog</string> <string name="catalog">Catalog</string>
<string name="manage_sources">Manage sources</string> <string name="manage_sources">Manage sources</string>
<string name="manual">Manual</string>
<string name="available_d">Available: %1$d</string>
<string name="disable_nsfw_summary">Disable NSFW sources and hide adult manga from list if possible</string>
</resources> </resources>

View File

@@ -46,21 +46,6 @@
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory android:title="@string/remote_sources">
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="sources_grid"
android:title="@string/show_in_grid_view" />
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="sources_new"
android:summary="@string/suggest_new_sources_summary"
android:title="@string/suggest_new_sources" />
</PreferenceCategory>
<PreferenceScreen <PreferenceScreen
android:fragment="org.koitharu.kotatsu.settings.nav.NavConfigFragment" android:fragment="org.koitharu.kotatsu.settings.nav.NavConfigFragment"
android:key="nav_main" android:key="nav_main"

View File

@@ -9,7 +9,7 @@
android:title="@string/appearance" /> android:title="@string/appearance" />
<PreferenceScreen <PreferenceScreen
android:fragment="org.koitharu.kotatsu.settings.sources.SourcesManageFragment" android:fragment="org.koitharu.kotatsu.settings.sources.SourcesSettingsFragment"
android:icon="@drawable/ic_manga_source" android:icon="@drawable/ic_manga_source"
android:key="remote_sources" android:key="remote_sources"
android:title="@string/remote_sources" /> android:title="@string/remote_sources" />

View File

@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<ListPreference
android:key="sources_sort_order"
android:title="@string/sort_order"
app:useSimpleSummaryProvider="true" />
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="sources_grid"
android:title="@string/show_in_grid_view" />
<PreferenceScreen
android:fragment="org.koitharu.kotatsu.settings.sources.manage.SourcesManageFragment"
android:key="remote_sources"
android:persistent="false"
android:title="@string/manage_sources" />
<Preference
android:key="sources_catalog"
android:persistent="false"
android:title="@string/sources_catalog"
app:allowDividerAbove="true" />
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="no_nsfw"
android:summary="@string/disable_nsfw_summary"
android:title="@string/disable_nsfw" />
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="sources_new"
android:summary="@string/suggest_new_sources_summary"
android:title="@string/suggest_new_sources" />
</androidx.preference.PreferenceScreen>