Sources settings screen
This commit is contained in:
@@ -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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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),
|
||||||
|
}
|
||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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"
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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" />
|
||||||
|
|||||||
40
app/src/main/res/xml/pref_sources.xml
Normal file
40
app/src/main/res/xml/pref_sources.xml
Normal 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>
|
||||||
Reference in New Issue
Block a user