Store manga sources in database #426
This commit is contained in:
@@ -17,7 +17,7 @@ class MangaDatabaseTest {
|
|||||||
MangaDatabase::class.java,
|
MangaDatabase::class.java,
|
||||||
)
|
)
|
||||||
|
|
||||||
private val migrations = databaseMigrations
|
private val migrations = getDatabaseMigrations()
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun versions() {
|
fun versions() {
|
||||||
|
|||||||
@@ -12,11 +12,13 @@ import kotlinx.coroutines.launch
|
|||||||
import org.koitharu.kotatsu.bookmarks.data.BookmarkEntity
|
import org.koitharu.kotatsu.bookmarks.data.BookmarkEntity
|
||||||
import org.koitharu.kotatsu.bookmarks.data.BookmarksDao
|
import org.koitharu.kotatsu.bookmarks.data.BookmarksDao
|
||||||
import org.koitharu.kotatsu.core.db.dao.MangaDao
|
import org.koitharu.kotatsu.core.db.dao.MangaDao
|
||||||
|
import org.koitharu.kotatsu.core.db.dao.MangaSourcesDao
|
||||||
import org.koitharu.kotatsu.core.db.dao.PreferencesDao
|
import org.koitharu.kotatsu.core.db.dao.PreferencesDao
|
||||||
import org.koitharu.kotatsu.core.db.dao.TagsDao
|
import org.koitharu.kotatsu.core.db.dao.TagsDao
|
||||||
import org.koitharu.kotatsu.core.db.dao.TrackLogsDao
|
import org.koitharu.kotatsu.core.db.dao.TrackLogsDao
|
||||||
import org.koitharu.kotatsu.core.db.entity.MangaEntity
|
import org.koitharu.kotatsu.core.db.entity.MangaEntity
|
||||||
import org.koitharu.kotatsu.core.db.entity.MangaPrefsEntity
|
import org.koitharu.kotatsu.core.db.entity.MangaPrefsEntity
|
||||||
|
import org.koitharu.kotatsu.core.db.entity.MangaSourceEntity
|
||||||
import org.koitharu.kotatsu.core.db.entity.MangaTagsEntity
|
import org.koitharu.kotatsu.core.db.entity.MangaTagsEntity
|
||||||
import org.koitharu.kotatsu.core.db.entity.TagEntity
|
import org.koitharu.kotatsu.core.db.entity.TagEntity
|
||||||
import org.koitharu.kotatsu.core.db.migrations.Migration10To11
|
import org.koitharu.kotatsu.core.db.migrations.Migration10To11
|
||||||
@@ -25,6 +27,7 @@ import org.koitharu.kotatsu.core.db.migrations.Migration12To13
|
|||||||
import org.koitharu.kotatsu.core.db.migrations.Migration13To14
|
import org.koitharu.kotatsu.core.db.migrations.Migration13To14
|
||||||
import org.koitharu.kotatsu.core.db.migrations.Migration14To15
|
import org.koitharu.kotatsu.core.db.migrations.Migration14To15
|
||||||
import org.koitharu.kotatsu.core.db.migrations.Migration15To16
|
import org.koitharu.kotatsu.core.db.migrations.Migration15To16
|
||||||
|
import org.koitharu.kotatsu.core.db.migrations.Migration16To17
|
||||||
import org.koitharu.kotatsu.core.db.migrations.Migration1To2
|
import org.koitharu.kotatsu.core.db.migrations.Migration1To2
|
||||||
import org.koitharu.kotatsu.core.db.migrations.Migration2To3
|
import org.koitharu.kotatsu.core.db.migrations.Migration2To3
|
||||||
import org.koitharu.kotatsu.core.db.migrations.Migration3To4
|
import org.koitharu.kotatsu.core.db.migrations.Migration3To4
|
||||||
@@ -49,14 +52,14 @@ import org.koitharu.kotatsu.tracker.data.TrackEntity
|
|||||||
import org.koitharu.kotatsu.tracker.data.TrackLogEntity
|
import org.koitharu.kotatsu.tracker.data.TrackLogEntity
|
||||||
import org.koitharu.kotatsu.tracker.data.TracksDao
|
import org.koitharu.kotatsu.tracker.data.TracksDao
|
||||||
|
|
||||||
const val DATABASE_VERSION = 16
|
const val DATABASE_VERSION = 17
|
||||||
|
|
||||||
@Database(
|
@Database(
|
||||||
entities = [
|
entities = [
|
||||||
MangaEntity::class, TagEntity::class, HistoryEntity::class, MangaTagsEntity::class,
|
MangaEntity::class, TagEntity::class, HistoryEntity::class, MangaTagsEntity::class,
|
||||||
FavouriteCategoryEntity::class, FavouriteEntity::class, MangaPrefsEntity::class,
|
FavouriteCategoryEntity::class, FavouriteEntity::class, MangaPrefsEntity::class,
|
||||||
TrackEntity::class, TrackLogEntity::class, SuggestionEntity::class, BookmarkEntity::class,
|
TrackEntity::class, TrackLogEntity::class, SuggestionEntity::class, BookmarkEntity::class,
|
||||||
ScrobblingEntity::class,
|
ScrobblingEntity::class, MangaSourceEntity::class,
|
||||||
],
|
],
|
||||||
version = DATABASE_VERSION,
|
version = DATABASE_VERSION,
|
||||||
)
|
)
|
||||||
@@ -83,30 +86,32 @@ abstract class MangaDatabase : RoomDatabase() {
|
|||||||
abstract val bookmarksDao: BookmarksDao
|
abstract val bookmarksDao: BookmarksDao
|
||||||
|
|
||||||
abstract val scrobblingDao: ScrobblingDao
|
abstract val scrobblingDao: ScrobblingDao
|
||||||
|
|
||||||
|
abstract val sourcesDao: MangaSourcesDao
|
||||||
}
|
}
|
||||||
|
|
||||||
val databaseMigrations: Array<Migration>
|
fun getDatabaseMigrations(context: Context): Array<Migration> = arrayOf(
|
||||||
get() = arrayOf(
|
Migration1To2(),
|
||||||
Migration1To2(),
|
Migration2To3(),
|
||||||
Migration2To3(),
|
Migration3To4(),
|
||||||
Migration3To4(),
|
Migration4To5(),
|
||||||
Migration4To5(),
|
Migration5To6(),
|
||||||
Migration5To6(),
|
Migration6To7(),
|
||||||
Migration6To7(),
|
Migration7To8(),
|
||||||
Migration7To8(),
|
Migration8To9(),
|
||||||
Migration8To9(),
|
Migration9To10(),
|
||||||
Migration9To10(),
|
Migration10To11(),
|
||||||
Migration10To11(),
|
Migration11To12(),
|
||||||
Migration11To12(),
|
Migration12To13(),
|
||||||
Migration12To13(),
|
Migration13To14(),
|
||||||
Migration13To14(),
|
Migration14To15(),
|
||||||
Migration14To15(),
|
Migration15To16(),
|
||||||
Migration15To16(),
|
Migration16To17(context),
|
||||||
)
|
)
|
||||||
|
|
||||||
fun MangaDatabase(context: Context): MangaDatabase = Room
|
fun MangaDatabase(context: Context): MangaDatabase = Room
|
||||||
.databaseBuilder(context, MangaDatabase::class.java, "kotatsu-db")
|
.databaseBuilder(context, MangaDatabase::class.java, "kotatsu-db")
|
||||||
.addMigrations(*databaseMigrations)
|
.addMigrations(*getDatabaseMigrations(context))
|
||||||
.addCallback(DatabasePrePopulateCallback(context.resources))
|
.addCallback(DatabasePrePopulateCallback(context.resources))
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package org.koitharu.kotatsu.core.db.dao
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.OnConflictStrategy
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Transaction
|
||||||
|
import androidx.room.Upsert
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import org.koitharu.kotatsu.core.db.entity.MangaSourceEntity
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
abstract class MangaSourcesDao {
|
||||||
|
|
||||||
|
@Query("SELECT * FROM sources ORDER BY sort_key")
|
||||||
|
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 = 1 ORDER BY sort_key")
|
||||||
|
abstract fun observeEnabled(): Flow<List<MangaSourceEntity>>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM sources ORDER BY sort_key")
|
||||||
|
abstract fun observeAll(): Flow<List<MangaSourceEntity>>
|
||||||
|
|
||||||
|
@Query("SELECT MAX(sort_key) FROM sources")
|
||||||
|
abstract suspend fun getMaxSortKey(): Int
|
||||||
|
|
||||||
|
@Query("UPDATE sources SET enabled = 0")
|
||||||
|
abstract suspend fun disableAllSources()
|
||||||
|
|
||||||
|
@Query("UPDATE sources SET sort_key = :sortKey WHERE source = :source")
|
||||||
|
abstract suspend fun setSortKey(source: String, sortKey: Int)
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||||
|
@Transaction
|
||||||
|
abstract suspend fun insertIfAbsent(entries: Iterable<MangaSourceEntity>)
|
||||||
|
|
||||||
|
@Upsert
|
||||||
|
abstract suspend fun upsert(entry: MangaSourceEntity)
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
open suspend fun setEnabled(source: String, isEnabled: Boolean) {
|
||||||
|
if (updateIsEnabled(source, isEnabled) == 0) {
|
||||||
|
val entity = MangaSourceEntity(
|
||||||
|
source = source,
|
||||||
|
isEnabled = isEnabled,
|
||||||
|
sortKey = getMaxSortKey() + 1,
|
||||||
|
)
|
||||||
|
upsert(entity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Query("UPDATE sources SET enabled = :isEnabled WHERE source = :source")
|
||||||
|
protected abstract suspend fun updateIsEnabled(source: String, isEnabled: Boolean): Int
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package org.koitharu.kotatsu.core.db.entity
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
|
||||||
|
@Entity(
|
||||||
|
tableName = "sources",
|
||||||
|
)
|
||||||
|
data class MangaSourceEntity(
|
||||||
|
@PrimaryKey(autoGenerate = false)
|
||||||
|
@ColumnInfo(name = "source")
|
||||||
|
val source: String,
|
||||||
|
@ColumnInfo(name = "enabled") val isEnabled: Boolean,
|
||||||
|
@ColumnInfo(name = "sort_key", index = true) val sortKey: Int,
|
||||||
|
)
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package org.koitharu.kotatsu.core.db.migrations
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import androidx.room.migration.Migration
|
||||||
|
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||||
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
|
|
||||||
|
class Migration16To17(context: Context) : Migration(16, 17) {
|
||||||
|
|
||||||
|
private val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
|
||||||
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
|
database.execSQL("CREATE TABLE `sources` (`source` TEXT NOT NULL, `enabled` INTEGER NOT NULL, `sort_key` INTEGER NOT NULL, PRIMARY KEY(`source`))")
|
||||||
|
database.execSQL("CREATE INDEX `index_sources_sort_key` ON `sources` (`sort_key`)")
|
||||||
|
val hiddenSources = prefs.getStringSet("sources_hidden", null).orEmpty()
|
||||||
|
val order = prefs.getString("sources_order_2", null)?.split('|').orEmpty()
|
||||||
|
val sources = MangaSource.values()
|
||||||
|
for (source in sources) {
|
||||||
|
if (source == MangaSource.LOCAL) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
val name = source.name
|
||||||
|
var sortKey = order.indexOf(name)
|
||||||
|
if (sortKey == -1) {
|
||||||
|
sortKey = order.size + source.ordinal
|
||||||
|
}
|
||||||
|
database.execSQL(
|
||||||
|
"INSERT INTO `sources` (`source`, `enabled`, `sort_key`) VALUES (?, ?, ?)",
|
||||||
|
arrayOf(name, (name !in hiddenSources).toInt(), sortKey),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Boolean.toInt() = if (this) 1 else 0
|
||||||
|
}
|
||||||
@@ -15,24 +15,19 @@ import androidx.core.os.LocaleListCompat
|
|||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import org.json.JSONArray
|
import org.json.JSONArray
|
||||||
import org.koitharu.kotatsu.BuildConfig
|
|
||||||
import org.koitharu.kotatsu.core.model.ZoomMode
|
import org.koitharu.kotatsu.core.model.ZoomMode
|
||||||
import org.koitharu.kotatsu.core.network.DoHProvider
|
import org.koitharu.kotatsu.core.network.DoHProvider
|
||||||
import org.koitharu.kotatsu.core.util.ext.connectivityManager
|
import org.koitharu.kotatsu.core.util.ext.connectivityManager
|
||||||
import org.koitharu.kotatsu.core.util.ext.filterToSet
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.getEnumValue
|
import org.koitharu.kotatsu.core.util.ext.getEnumValue
|
||||||
import org.koitharu.kotatsu.core.util.ext.observe
|
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.parsers.model.MangaSource
|
|
||||||
import org.koitharu.kotatsu.parsers.model.SortOrder
|
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||||
import org.koitharu.kotatsu.parsers.util.mapNotNullToSet
|
import org.koitharu.kotatsu.parsers.util.mapNotNullToSet
|
||||||
import org.koitharu.kotatsu.parsers.util.mapToSet
|
import org.koitharu.kotatsu.parsers.util.mapToSet
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.net.Proxy
|
import java.net.Proxy
|
||||||
import java.util.Collections
|
|
||||||
import java.util.EnumSet
|
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
@@ -43,16 +38,6 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
|||||||
private val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
private val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
private val connectivityManager = context.connectivityManager
|
private val connectivityManager = context.connectivityManager
|
||||||
|
|
||||||
private val remoteSources = EnumSet.allOf(MangaSource::class.java).apply {
|
|
||||||
remove(MangaSource.LOCAL)
|
|
||||||
if (!BuildConfig.DEBUG) {
|
|
||||||
remove(MangaSource.DUMMY)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val remoteMangaSources: Set<MangaSource>
|
|
||||||
get() = Collections.unmodifiableSet(remoteSources)
|
|
||||||
|
|
||||||
var listMode: ListMode
|
var listMode: ListMode
|
||||||
get() = prefs.getEnumValue(KEY_LIST_MODE, ListMode.GRID)
|
get() = prefs.getEnumValue(KEY_LIST_MODE, ListMode.GRID)
|
||||||
set(value) = prefs.edit { putEnumValue(KEY_LIST_MODE, value) }
|
set(value) = prefs.edit { putEnumValue(KEY_LIST_MODE, value) }
|
||||||
@@ -183,37 +168,6 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
|||||||
return policy.isNetworkAllowed(connectivityManager)
|
return policy.isNetworkAllowed(connectivityManager)
|
||||||
}
|
}
|
||||||
|
|
||||||
var sourcesOrder: List<String>
|
|
||||||
get() = prefs.getString(KEY_SOURCES_ORDER, null)
|
|
||||||
?.split('|')
|
|
||||||
.orEmpty()
|
|
||||||
set(value) = prefs.edit {
|
|
||||||
putString(KEY_SOURCES_ORDER, value.joinToString("|"))
|
|
||||||
}
|
|
||||||
|
|
||||||
var hiddenSources: Set<String>
|
|
||||||
get() = prefs.getStringSet(KEY_SOURCES_HIDDEN, null)?.filterToSet { name ->
|
|
||||||
remoteSources.any { it.name == name }
|
|
||||||
}.orEmpty()
|
|
||||||
set(value) = prefs.edit { putStringSet(KEY_SOURCES_HIDDEN, value) }
|
|
||||||
|
|
||||||
val isSourcesSelected: Boolean
|
|
||||||
get() = KEY_SOURCES_HIDDEN in prefs
|
|
||||||
|
|
||||||
val newSources: Set<MangaSource>
|
|
||||||
get() {
|
|
||||||
val known = sourcesOrder.toSet()
|
|
||||||
val hidden = hiddenSources
|
|
||||||
return remoteMangaSources
|
|
||||||
.filterNotTo(EnumSet.noneOf(MangaSource::class.java)) { x ->
|
|
||||||
x.name in known || x.name in hidden
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun markKnownSources(sources: Collection<MangaSource>) {
|
|
||||||
sourcesOrder = (sourcesOrder + sources.map { it.name }).distinct()
|
|
||||||
}
|
|
||||||
|
|
||||||
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) }
|
||||||
@@ -335,20 +289,6 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
|||||||
return policy.isNetworkAllowed(connectivityManager)
|
return policy.isNetworkAllowed(connectivityManager)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getMangaSources(includeHidden: Boolean): List<MangaSource> {
|
|
||||||
val list = remoteSources.toMutableList()
|
|
||||||
val order = sourcesOrder
|
|
||||||
list.sortBy { x ->
|
|
||||||
val e = order.indexOf(x.name)
|
|
||||||
if (e == -1) order.size + x.ordinal else e
|
|
||||||
}
|
|
||||||
if (!includeHidden) {
|
|
||||||
val hidden = hiddenSources
|
|
||||||
list.removeAll { x -> x.name in hidden }
|
|
||||||
}
|
|
||||||
return list
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isTipEnabled(tip: String): Boolean {
|
fun isTipEnabled(tip: String): Boolean {
|
||||||
return prefs.getStringSet(KEY_TIPS_CLOSED, emptySet())?.contains(tip) != true
|
return prefs.getStringSet(KEY_TIPS_CLOSED, emptySet())?.contains(tip) != true
|
||||||
}
|
}
|
||||||
@@ -417,8 +357,6 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
|||||||
const val KEY_THEME = "theme"
|
const val KEY_THEME = "theme"
|
||||||
const val KEY_COLOR_THEME = "color_theme"
|
const val KEY_COLOR_THEME = "color_theme"
|
||||||
const val KEY_THEME_AMOLED = "amoled_theme"
|
const val KEY_THEME_AMOLED = "amoled_theme"
|
||||||
const val KEY_SOURCES_ORDER = "sources_order_2"
|
|
||||||
const val KEY_SOURCES_HIDDEN = "sources_hidden"
|
|
||||||
const val KEY_TRAFFIC_WARNING = "traffic_warning"
|
const val KEY_TRAFFIC_WARNING = "traffic_warning"
|
||||||
const val KEY_PAGES_CACHE_CLEAR = "pages_cache_clear"
|
const val KEY_PAGES_CACHE_CLEAR = "pages_cache_clear"
|
||||||
const val KEY_HTTP_CACHE_CLEAR = "http_cache_clear"
|
const val KEY_HTTP_CACHE_CLEAR = "http_cache_clear"
|
||||||
|
|||||||
@@ -0,0 +1,150 @@
|
|||||||
|
package org.koitharu.kotatsu.explore.data
|
||||||
|
|
||||||
|
import androidx.room.withTransaction
|
||||||
|
import dagger.Reusable
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import org.koitharu.kotatsu.BuildConfig
|
||||||
|
import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||||
|
import org.koitharu.kotatsu.core.db.dao.MangaSourcesDao
|
||||||
|
import org.koitharu.kotatsu.core.db.entity.MangaSourceEntity
|
||||||
|
import org.koitharu.kotatsu.core.model.MangaSource
|
||||||
|
import org.koitharu.kotatsu.core.ui.util.ReversibleHandle
|
||||||
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
|
import org.koitharu.kotatsu.parsers.util.move
|
||||||
|
import java.util.Collections
|
||||||
|
import java.util.EnumSet
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@Reusable
|
||||||
|
class MangaSourcesRepository @Inject constructor(
|
||||||
|
private val db: MangaDatabase,
|
||||||
|
) {
|
||||||
|
|
||||||
|
private val dao: MangaSourcesDao
|
||||||
|
get() = db.sourcesDao
|
||||||
|
|
||||||
|
private val remoteSources = EnumSet.allOf(MangaSource::class.java).apply {
|
||||||
|
remove(MangaSource.LOCAL)
|
||||||
|
if (!BuildConfig.DEBUG) {
|
||||||
|
remove(MangaSource.DUMMY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val allMangaSources: Set<MangaSource>
|
||||||
|
get() = Collections.unmodifiableSet(remoteSources)
|
||||||
|
|
||||||
|
suspend fun getEnabledSources(): List<MangaSource> {
|
||||||
|
return dao.findAllEnabled().toSources()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun observeEnabledSources(): Flow<List<MangaSource>> = dao.observeEnabled().map {
|
||||||
|
it.toSources()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun observeAll(): Flow<List<Pair<MangaSource, Boolean>>> = dao.observeAll().map { entities ->
|
||||||
|
val result = ArrayList<Pair<MangaSource, Boolean>>(entities.size)
|
||||||
|
for (entity in entities) {
|
||||||
|
val source = MangaSource(entity.source)
|
||||||
|
if (source in remoteSources) {
|
||||||
|
result.add(source to entity.isEnabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun setSourceEnabled(source: MangaSource, isEnabled: Boolean): ReversibleHandle {
|
||||||
|
dao.setEnabled(source.name, isEnabled)
|
||||||
|
return ReversibleHandle {
|
||||||
|
dao.setEnabled(source.name, !isEnabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun setSourcesEnabled(sources: Iterable<MangaSource>, isEnabled: Boolean) {
|
||||||
|
db.withTransaction {
|
||||||
|
for (s in sources) {
|
||||||
|
dao.setEnabled(s.name, isEnabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun disableAllSources() {
|
||||||
|
db.withTransaction {
|
||||||
|
assimilateNewSources()
|
||||||
|
dao.disableAllSources()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun setPosition(source: MangaSource, index: Int) {
|
||||||
|
db.withTransaction {
|
||||||
|
val all = dao.findAll().toMutableList()
|
||||||
|
val sourceIndex = all.indexOfFirst { x -> x.source == source.name }
|
||||||
|
if (sourceIndex !in all.indices) {
|
||||||
|
val entity = MangaSourceEntity(
|
||||||
|
source = source.name,
|
||||||
|
isEnabled = false,
|
||||||
|
sortKey = index,
|
||||||
|
)
|
||||||
|
all.add(index, entity)
|
||||||
|
dao.upsert(entity)
|
||||||
|
} else {
|
||||||
|
all.move(sourceIndex, index)
|
||||||
|
}
|
||||||
|
for ((i, e) in all.withIndex()) {
|
||||||
|
if (e.sortKey != i) {
|
||||||
|
dao.setSortKey(e.source, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun observeNewSources(): Flow<Set<MangaSource>> = dao.observeAll().map { entities ->
|
||||||
|
val result = EnumSet.copyOf(remoteSources)
|
||||||
|
for (e in entities) {
|
||||||
|
result.remove(MangaSource(e.source))
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}.distinctUntilChanged()
|
||||||
|
|
||||||
|
suspend fun getNewSources(): Set<MangaSource> {
|
||||||
|
val entities = dao.findAll()
|
||||||
|
val result = EnumSet.copyOf(remoteSources)
|
||||||
|
for (e in entities) {
|
||||||
|
result.remove(MangaSource(e.source))
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun assimilateNewSources(): Set<MangaSource> {
|
||||||
|
val new = getNewSources()
|
||||||
|
if (new.isEmpty()) {
|
||||||
|
return emptySet()
|
||||||
|
}
|
||||||
|
var maxSortKey = dao.getMaxSortKey()
|
||||||
|
val entities = new.map { x ->
|
||||||
|
MangaSourceEntity(
|
||||||
|
source = x.name,
|
||||||
|
isEnabled = false,
|
||||||
|
sortKey = ++maxSortKey,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
dao.insertIfAbsent(entities)
|
||||||
|
return new
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun isSetupRequired(): Boolean {
|
||||||
|
return dao.findAll().isEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun List<MangaSourceEntity>.toSources(): List<MangaSource> {
|
||||||
|
val result = ArrayList<MangaSource>(size)
|
||||||
|
for (entity in this) {
|
||||||
|
val source = MangaSource(entity.source)
|
||||||
|
if (source in remoteSources) {
|
||||||
|
result.add(source)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,16 +4,18 @@ import org.koitharu.kotatsu.core.parser.MangaRepository
|
|||||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
import org.koitharu.kotatsu.core.util.ext.almostEquals
|
import org.koitharu.kotatsu.core.util.ext.almostEquals
|
||||||
import org.koitharu.kotatsu.core.util.ext.asArrayList
|
import org.koitharu.kotatsu.core.util.ext.asArrayList
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||||
|
import org.koitharu.kotatsu.explore.data.MangaSourcesRepository
|
||||||
import org.koitharu.kotatsu.history.data.HistoryRepository
|
import org.koitharu.kotatsu.history.data.HistoryRepository
|
||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||||
import org.koitharu.kotatsu.suggestions.domain.TagsBlacklist
|
import org.koitharu.kotatsu.suggestions.domain.TagsBlacklist
|
||||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class ExploreRepository @Inject constructor(
|
class ExploreRepository @Inject constructor(
|
||||||
private val settings: AppSettings,
|
private val settings: AppSettings,
|
||||||
|
private val sourcesRepository: MangaSourcesRepository,
|
||||||
private val historyRepository: HistoryRepository,
|
private val historyRepository: HistoryRepository,
|
||||||
private val mangaRepositoryFactory: MangaRepository.Factory,
|
private val mangaRepositoryFactory: MangaRepository.Factory,
|
||||||
) {
|
) {
|
||||||
@@ -23,7 +25,7 @@ class ExploreRepository @Inject constructor(
|
|||||||
val tags = historyRepository.getPopularTags(tagsLimit).mapNotNull {
|
val tags = historyRepository.getPopularTags(tagsLimit).mapNotNull {
|
||||||
if (it in blacklistTagRegex) null else it.title
|
if (it in blacklistTagRegex) null else it.title
|
||||||
}
|
}
|
||||||
val sources = settings.getMangaSources(includeHidden = false)
|
val sources = sourcesRepository.getEnabledSources()
|
||||||
check(sources.isNotEmpty()) { "No sources available" }
|
check(sources.isNotEmpty()) { "No sources available" }
|
||||||
for (i in 0..4) {
|
for (i in 0..4) {
|
||||||
val list = getList(sources.random(), tags, blacklistTagRegex)
|
val list = getList(sources.random(), tags, blacklistTagRegex)
|
||||||
|
|||||||
@@ -7,13 +7,9 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
|
||||||
import kotlinx.coroutines.flow.filter
|
|
||||||
import kotlinx.coroutines.flow.flatMapLatest
|
import kotlinx.coroutines.flow.flatMapLatest
|
||||||
import kotlinx.coroutines.flow.flowOf
|
import kotlinx.coroutines.flow.flowOf
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import kotlinx.coroutines.flow.mapLatest
|
import kotlinx.coroutines.flow.mapLatest
|
||||||
import kotlinx.coroutines.flow.onStart
|
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.plus
|
import kotlinx.coroutines.plus
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
@@ -22,9 +18,9 @@ import org.koitharu.kotatsu.core.prefs.observeAsFlow
|
|||||||
import org.koitharu.kotatsu.core.prefs.observeAsStateFlow
|
import org.koitharu.kotatsu.core.prefs.observeAsStateFlow
|
||||||
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.ui.util.ReversibleHandle
|
|
||||||
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.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
|
||||||
@@ -45,6 +41,7 @@ class ExploreViewModel @Inject constructor(
|
|||||||
private val settings: AppSettings,
|
private val settings: AppSettings,
|
||||||
private val suggestionRepository: SuggestionRepository,
|
private val suggestionRepository: SuggestionRepository,
|
||||||
private val exploreRepository: ExploreRepository,
|
private val exploreRepository: ExploreRepository,
|
||||||
|
private val sourcesRepository: MangaSourcesRepository,
|
||||||
) : BaseViewModel() {
|
) : BaseViewModel() {
|
||||||
|
|
||||||
val isGrid = settings.observeAsStateFlow(
|
val isGrid = settings.observeAsStateFlow(
|
||||||
@@ -96,10 +93,7 @@ class ExploreViewModel @Inject constructor(
|
|||||||
|
|
||||||
fun hideSource(source: MangaSource) {
|
fun hideSource(source: MangaSource) {
|
||||||
launchJob(Dispatchers.Default) {
|
launchJob(Dispatchers.Default) {
|
||||||
settings.hiddenSources += source.name
|
val rollback = sourcesRepository.setSourceEnabled(source, isEnabled = false)
|
||||||
val rollback = ReversibleHandle {
|
|
||||||
settings.hiddenSources -= source.name
|
|
||||||
}
|
|
||||||
onActionDone.call(ReversibleAction(R.string.source_disabled, rollback))
|
onActionDone.call(ReversibleAction(R.string.source_disabled, rollback))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -114,11 +108,11 @@ class ExploreViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun createContentFlow() = combine(
|
private fun createContentFlow() = combine(
|
||||||
observeSources(),
|
sourcesRepository.observeEnabledSources(),
|
||||||
getSuggestionFlow(),
|
getSuggestionFlow(),
|
||||||
isGrid,
|
isGrid,
|
||||||
isRandomLoading,
|
isRandomLoading,
|
||||||
observeNewSources(),
|
sourcesRepository.observeNewSources(),
|
||||||
) { content, suggestions, grid, randomLoading, newSources ->
|
) { content, suggestions, grid, randomLoading, newSources ->
|
||||||
buildList(content, suggestions, grid, randomLoading, newSources)
|
buildList(content, suggestions, grid, randomLoading, newSources)
|
||||||
}
|
}
|
||||||
@@ -160,15 +154,6 @@ class ExploreViewModel @Inject constructor(
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun observeSources() = settings.observe()
|
|
||||||
.filter {
|
|
||||||
it == AppSettings.KEY_SOURCES_HIDDEN ||
|
|
||||||
it == AppSettings.KEY_SOURCES_ORDER ||
|
|
||||||
it == AppSettings.KEY_SUGGESTIONS
|
|
||||||
}
|
|
||||||
.onStart { emit("") }
|
|
||||||
.map { settings.getMangaSources(includeHidden = false) }
|
|
||||||
|
|
||||||
private fun getLoadingStateList() = listOf(
|
private fun getLoadingStateList() = listOf(
|
||||||
ExploreButtons(isRandomLoading.value),
|
ExploreButtons(isRandomLoading.value),
|
||||||
LoadingState,
|
LoadingState,
|
||||||
@@ -184,12 +169,6 @@ class ExploreViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun observeNewSources() = settings.observe()
|
|
||||||
.filter { it == AppSettings.KEY_SOURCES_ORDER || it == AppSettings.KEY_SOURCES_HIDDEN }
|
|
||||||
.onStart { emit("") }
|
|
||||||
.map { settings.newSources }
|
|
||||||
.distinctUntilChanged()
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
private const val TIP_SUGGESTIONS = "suggestions"
|
private const val TIP_SUGGESTIONS = "suggestions"
|
||||||
|
|||||||
@@ -63,7 +63,6 @@ import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener
|
|||||||
import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionViewModel
|
import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionViewModel
|
||||||
import org.koitharu.kotatsu.settings.SettingsActivity
|
import org.koitharu.kotatsu.settings.SettingsActivity
|
||||||
import org.koitharu.kotatsu.settings.about.AppUpdateDialog
|
import org.koitharu.kotatsu.settings.about.AppUpdateDialog
|
||||||
import org.koitharu.kotatsu.settings.newsources.NewSourcesDialogFragment
|
|
||||||
import org.koitharu.kotatsu.settings.onboard.OnboardDialogFragment
|
import org.koitharu.kotatsu.settings.onboard.OnboardDialogFragment
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import com.google.android.material.R as materialR
|
import com.google.android.material.R as materialR
|
||||||
@@ -136,6 +135,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNav
|
|||||||
viewModel.isResumeEnabled.observe(this, this::onResumeEnabledChanged)
|
viewModel.isResumeEnabled.observe(this, this::onResumeEnabledChanged)
|
||||||
viewModel.counters.observe(this, ::onCountersChanged)
|
viewModel.counters.observe(this, ::onCountersChanged)
|
||||||
viewModel.appUpdate.observe(this) { invalidateMenu() }
|
viewModel.appUpdate.observe(this) { invalidateMenu() }
|
||||||
|
viewModel.onFirstStart.observeEvent(this) { OnboardDialogFragment.showWelcome(supportFragmentManager) }
|
||||||
viewModel.isFeedAvailable.observe(this, ::onFeedAvailabilityChanged)
|
viewModel.isFeedAvailable.observe(this, ::onFeedAvailabilityChanged)
|
||||||
searchSuggestionViewModel.isIncognitoModeEnabled.observe(this, this::onIncognitoModeChanged)
|
searchSuggestionViewModel.isIncognitoModeEnabled.observe(this, this::onIncognitoModeChanged)
|
||||||
}
|
}
|
||||||
@@ -324,15 +324,6 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNav
|
|||||||
|
|
||||||
private fun onFirstStart() {
|
private fun onFirstStart() {
|
||||||
lifecycleScope.launch(Dispatchers.Main) { // not a default `Main.immediate` dispatcher
|
lifecycleScope.launch(Dispatchers.Main) { // not a default `Main.immediate` dispatcher
|
||||||
when {
|
|
||||||
!settings.isSourcesSelected -> withResumed {
|
|
||||||
OnboardDialogFragment.showWelcome(supportFragmentManager)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.newSources.isNotEmpty() -> withResumed {
|
|
||||||
NewSourcesDialogFragment.show(supportFragmentManager)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
withContext(Dispatchers.Default) {
|
withContext(Dispatchers.Default) {
|
||||||
LocalStorageCleanupWorker.enqueue(applicationContext)
|
LocalStorageCleanupWorker.enqueue(applicationContext)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,7 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.filter
|
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.onStart
|
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.plus
|
import kotlinx.coroutines.plus
|
||||||
import org.koitharu.kotatsu.core.exceptions.EmptyHistoryException
|
import org.koitharu.kotatsu.core.exceptions.EmptyHistoryException
|
||||||
@@ -18,6 +16,7 @@ import org.koitharu.kotatsu.core.prefs.observeAsStateFlow
|
|||||||
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
||||||
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.history.data.HistoryRepository
|
import org.koitharu.kotatsu.history.data.HistoryRepository
|
||||||
import org.koitharu.kotatsu.main.domain.ReadingResumeEnabledUseCase
|
import org.koitharu.kotatsu.main.domain.ReadingResumeEnabledUseCase
|
||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
@@ -31,9 +30,11 @@ class MainViewModel @Inject constructor(
|
|||||||
trackingRepository: TrackingRepository,
|
trackingRepository: TrackingRepository,
|
||||||
private val settings: AppSettings,
|
private val settings: AppSettings,
|
||||||
readingResumeEnabledUseCase: ReadingResumeEnabledUseCase,
|
readingResumeEnabledUseCase: ReadingResumeEnabledUseCase,
|
||||||
|
private val sourcesRepository: MangaSourcesRepository,
|
||||||
) : BaseViewModel() {
|
) : BaseViewModel() {
|
||||||
|
|
||||||
val onOpenReader = MutableEventFlow<Manga>()
|
val onOpenReader = MutableEventFlow<Manga>()
|
||||||
|
val onFirstStart = MutableEventFlow<Unit>()
|
||||||
|
|
||||||
val isResumeEnabled = readingResumeEnabledUseCase().stateIn(
|
val isResumeEnabled = readingResumeEnabledUseCase().stateIn(
|
||||||
scope = viewModelScope + Dispatchers.Default,
|
scope = viewModelScope + Dispatchers.Default,
|
||||||
@@ -64,6 +65,11 @@ class MainViewModel @Inject constructor(
|
|||||||
launchJob {
|
launchJob {
|
||||||
appUpdateRepository.fetchUpdate()
|
appUpdateRepository.fetchUpdate()
|
||||||
}
|
}
|
||||||
|
launchJob(Dispatchers.Default) {
|
||||||
|
if (sourcesRepository.isSetupRequired()) {
|
||||||
|
onFirstStart.call(Unit)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun openLastReader() {
|
fun openLastReader() {
|
||||||
@@ -77,10 +83,7 @@ class MainViewModel @Inject constructor(
|
|||||||
settings.isIncognitoModeEnabled = isEnabled
|
settings.isIncognitoModeEnabled = isEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun observeNewSourcesCount() = settings.observe()
|
private fun observeNewSourcesCount() = sourcesRepository.observeNewSources()
|
||||||
.filter { it == AppSettings.KEY_SOURCES_ORDER || it == AppSettings.KEY_SOURCES_HIDDEN }
|
.map { it.size }
|
||||||
.onStart { emit("") }
|
|
||||||
.map { settings.newSources.size }
|
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import org.koitharu.kotatsu.core.db.MangaDatabase
|
|||||||
import org.koitharu.kotatsu.core.db.entity.toManga
|
import org.koitharu.kotatsu.core.db.entity.toManga
|
||||||
import org.koitharu.kotatsu.core.db.entity.toMangaTag
|
import org.koitharu.kotatsu.core.db.entity.toMangaTag
|
||||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
import org.koitharu.kotatsu.explore.data.MangaSourcesRepository
|
||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||||
@@ -26,27 +26,28 @@ import javax.inject.Inject
|
|||||||
|
|
||||||
@Reusable
|
@Reusable
|
||||||
class MangaSearchRepository @Inject constructor(
|
class MangaSearchRepository @Inject constructor(
|
||||||
private val settings: AppSettings,
|
|
||||||
private val db: MangaDatabase,
|
private val db: MangaDatabase,
|
||||||
|
private val sourcesRepository: MangaSourcesRepository,
|
||||||
@ApplicationContext private val context: Context,
|
@ApplicationContext private val context: Context,
|
||||||
private val recentSuggestions: SearchRecentSuggestions,
|
private val recentSuggestions: SearchRecentSuggestions,
|
||||||
private val mangaRepositoryFactory: MangaRepository.Factory,
|
private val mangaRepositoryFactory: MangaRepository.Factory,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun globalSearch(query: String, concurrency: Int = DEFAULT_CONCURRENCY): Flow<Manga> =
|
fun globalSearch(query: String, concurrency: Int = DEFAULT_CONCURRENCY): Flow<Manga> =
|
||||||
settings.getMangaSources(includeHidden = false).asFlow()
|
flow {
|
||||||
.flatMapMerge(concurrency) { source ->
|
emitAll(sourcesRepository.getEnabledSources().asFlow())
|
||||||
runCatchingCancellable {
|
}.flatMapMerge(concurrency) { source ->
|
||||||
mangaRepositoryFactory.create(source).getList(
|
runCatchingCancellable {
|
||||||
offset = 0,
|
mangaRepositoryFactory.create(source).getList(
|
||||||
query = query,
|
offset = 0,
|
||||||
)
|
query = query,
|
||||||
}.getOrElse {
|
)
|
||||||
emptyList()
|
}.getOrElse {
|
||||||
}.asFlow()
|
emptyList()
|
||||||
}.filter {
|
}.asFlow()
|
||||||
match(it, query)
|
}.filter {
|
||||||
}
|
match(it, query)
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun getMangaSuggestion(query: String, limit: Int, source: MangaSource?): List<Manga> {
|
suspend fun getMangaSuggestion(query: String, limit: Int, source: MangaSource?): List<Manga> {
|
||||||
if (query.isEmpty()) {
|
if (query.isEmpty()) {
|
||||||
@@ -101,7 +102,7 @@ class MangaSearchRepository @Inject constructor(
|
|||||||
if (query.length < 3) {
|
if (query.length < 3) {
|
||||||
return emptyList()
|
return emptyList()
|
||||||
}
|
}
|
||||||
val sources = settings.remoteMangaSources
|
val sources = sourcesRepository.allMangaSources
|
||||||
.filter { x -> x.title.contains(query, ignoreCase = true) }
|
.filter { x -> x.title.contains(query, ignoreCase = true) }
|
||||||
return if (limit == 0) {
|
return if (limit == 0) {
|
||||||
sources
|
sources
|
||||||
|
|||||||
@@ -19,12 +19,13 @@ import kotlinx.coroutines.plus
|
|||||||
import kotlinx.coroutines.withTimeout
|
import kotlinx.coroutines.withTimeout
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
|
||||||
import org.koitharu.kotatsu.core.prefs.ListMode
|
import org.koitharu.kotatsu.core.prefs.ListMode
|
||||||
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
||||||
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.core.util.ext.printStackTraceDebug
|
||||||
import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
|
import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
|
||||||
|
import org.koitharu.kotatsu.explore.data.MangaSourcesRepository
|
||||||
import org.koitharu.kotatsu.list.domain.ListExtraProvider
|
import org.koitharu.kotatsu.list.domain.ListExtraProvider
|
||||||
import org.koitharu.kotatsu.list.ui.model.EmptyState
|
import org.koitharu.kotatsu.list.ui.model.EmptyState
|
||||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||||
@@ -33,7 +34,6 @@ import org.koitharu.kotatsu.list.ui.model.LoadingState
|
|||||||
import org.koitharu.kotatsu.list.ui.model.toUi
|
import org.koitharu.kotatsu.list.ui.model.toUi
|
||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
private const val MAX_PARALLELISM = 4
|
private const val MAX_PARALLELISM = 4
|
||||||
@@ -43,9 +43,9 @@ private const val MIN_HAS_MORE_ITEMS = 8
|
|||||||
class MultiSearchViewModel @Inject constructor(
|
class MultiSearchViewModel @Inject constructor(
|
||||||
savedStateHandle: SavedStateHandle,
|
savedStateHandle: SavedStateHandle,
|
||||||
private val extraProvider: ListExtraProvider,
|
private val extraProvider: ListExtraProvider,
|
||||||
private val settings: AppSettings,
|
|
||||||
private val mangaRepositoryFactory: MangaRepository.Factory,
|
private val mangaRepositoryFactory: MangaRepository.Factory,
|
||||||
private val downloadScheduler: DownloadWorker.Scheduler,
|
private val downloadScheduler: DownloadWorker.Scheduler,
|
||||||
|
private val sourcesRepository: MangaSourcesRepository,
|
||||||
) : BaseViewModel() {
|
) : BaseViewModel() {
|
||||||
|
|
||||||
private var searchJob: Job? = null
|
private var searchJob: Job? = null
|
||||||
@@ -117,7 +117,7 @@ class MultiSearchViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun searchImpl(q: String) = coroutineScope {
|
private suspend fun searchImpl(q: String) = coroutineScope {
|
||||||
val sources = settings.getMangaSources(includeHidden = false)
|
val sources = sourcesRepository.getEnabledSources()
|
||||||
val dispatcher = Dispatchers.Default.limitedParallelism(MAX_PARALLELISM)
|
val dispatcher = Dispatchers.Default.limitedParallelism(MAX_PARALLELISM)
|
||||||
val deferredList = sources.map { source ->
|
val deferredList = sources.map { source ->
|
||||||
async(dispatcher) {
|
async(dispatcher) {
|
||||||
|
|||||||
@@ -11,18 +11,20 @@ import kotlinx.coroutines.flow.combine
|
|||||||
import kotlinx.coroutines.flow.debounce
|
import kotlinx.coroutines.flow.debounce
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.mapLatest
|
import kotlinx.coroutines.flow.mapLatest
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.plus
|
import kotlinx.coroutines.plus
|
||||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
import org.koitharu.kotatsu.core.prefs.observeAsFlow
|
|
||||||
import org.koitharu.kotatsu.core.prefs.observeAsStateFlow
|
import org.koitharu.kotatsu.core.prefs.observeAsStateFlow
|
||||||
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
||||||
import org.koitharu.kotatsu.core.ui.widgets.ChipsView
|
import org.koitharu.kotatsu.core.ui.widgets.ChipsView
|
||||||
|
import org.koitharu.kotatsu.explore.data.MangaSourcesRepository
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||||
import org.koitharu.kotatsu.search.domain.MangaSearchRepository
|
import org.koitharu.kotatsu.search.domain.MangaSearchRepository
|
||||||
import org.koitharu.kotatsu.search.ui.suggestion.model.SearchSuggestionItem
|
import org.koitharu.kotatsu.search.ui.suggestion.model.SearchSuggestionItem
|
||||||
|
import java.util.EnumSet
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
private const val DEBOUNCE_TIMEOUT = 500L
|
private const val DEBOUNCE_TIMEOUT = 500L
|
||||||
@@ -35,6 +37,7 @@ private const val MAX_SOURCES_ITEMS = 6
|
|||||||
class SearchSuggestionViewModel @Inject constructor(
|
class SearchSuggestionViewModel @Inject constructor(
|
||||||
private val repository: MangaSearchRepository,
|
private val repository: MangaSearchRepository,
|
||||||
private val settings: AppSettings,
|
private val settings: AppSettings,
|
||||||
|
private val sourcesRepository: MangaSourcesRepository,
|
||||||
) : BaseViewModel() {
|
) : BaseViewModel() {
|
||||||
|
|
||||||
private val query = MutableStateFlow("")
|
private val query = MutableStateFlow("")
|
||||||
@@ -72,10 +75,8 @@ class SearchSuggestionViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun onSourceToggle(source: MangaSource, isEnabled: Boolean) {
|
fun onSourceToggle(source: MangaSource, isEnabled: Boolean) {
|
||||||
settings.hiddenSources = if (isEnabled) {
|
launchJob(Dispatchers.Default) {
|
||||||
settings.hiddenSources - source.name
|
sourcesRepository.setSourceEnabled(source, isEnabled)
|
||||||
} else {
|
|
||||||
settings.hiddenSources + source.name
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,10 +91,10 @@ class SearchSuggestionViewModel @Inject constructor(
|
|||||||
suggestionJob?.cancel()
|
suggestionJob?.cancel()
|
||||||
suggestionJob = combine(
|
suggestionJob = combine(
|
||||||
query.debounce(DEBOUNCE_TIMEOUT),
|
query.debounce(DEBOUNCE_TIMEOUT),
|
||||||
settings.observeAsFlow(AppSettings.KEY_SOURCES_HIDDEN) { hiddenSources },
|
sourcesRepository.observeEnabledSources().map { EnumSet.copyOf(it) },
|
||||||
::Pair,
|
::Pair,
|
||||||
).mapLatest { (searchQuery, hiddenSources) ->
|
).mapLatest { (searchQuery, enabledSources) ->
|
||||||
buildSearchSuggestion(searchQuery, hiddenSources)
|
buildSearchSuggestion(searchQuery, enabledSources)
|
||||||
}.distinctUntilChanged()
|
}.distinctUntilChanged()
|
||||||
.onEach {
|
.onEach {
|
||||||
suggestion.value = it
|
suggestion.value = it
|
||||||
@@ -102,7 +103,7 @@ class SearchSuggestionViewModel @Inject constructor(
|
|||||||
|
|
||||||
private suspend fun buildSearchSuggestion(
|
private suspend fun buildSearchSuggestion(
|
||||||
searchQuery: String,
|
searchQuery: String,
|
||||||
hiddenSources: Set<String>,
|
enabledSources: Set<MangaSource>,
|
||||||
): List<SearchSuggestionItem> = coroutineScope {
|
): List<SearchSuggestionItem> = coroutineScope {
|
||||||
val queriesDeferred = async {
|
val queriesDeferred = async {
|
||||||
repository.getQuerySuggestion(searchQuery, MAX_QUERY_ITEMS)
|
repository.getQuerySuggestion(searchQuery, MAX_QUERY_ITEMS)
|
||||||
@@ -127,7 +128,7 @@ class SearchSuggestionViewModel @Inject constructor(
|
|||||||
add(SearchSuggestionItem.MangaList(mangaList))
|
add(SearchSuggestionItem.MangaList(mangaList))
|
||||||
}
|
}
|
||||||
queries.mapTo(this) { SearchSuggestionItem.RecentQuery(it) }
|
queries.mapTo(this) { SearchSuggestionItem.RecentQuery(it) }
|
||||||
sources.mapTo(this) { SearchSuggestionItem.Source(it, it.name !in hiddenSources) }
|
sources.mapTo(this) { SearchSuggestionItem.Source(it, it in enabledSources) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,21 @@
|
|||||||
package org.koitharu.kotatsu.settings
|
package org.koitharu.kotatsu.settings
|
||||||
|
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import org.koitharu.kotatsu.BuildConfig
|
import org.koitharu.kotatsu.BuildConfig
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
import org.koitharu.kotatsu.core.ui.BasePreferenceFragment
|
import org.koitharu.kotatsu.core.ui.BasePreferenceFragment
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.observe
|
||||||
|
|
||||||
class RootSettingsFragment : BasePreferenceFragment(0), SharedPreferences.OnSharedPreferenceChangeListener {
|
@AndroidEntryPoint
|
||||||
|
class RootSettingsFragment : BasePreferenceFragment(0) {
|
||||||
|
|
||||||
|
private val viewModel: RootSettingsViewModel by viewModels()
|
||||||
|
|
||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
addPreferencesFromResource(R.xml.pref_root)
|
addPreferencesFromResource(R.xml.pref_root)
|
||||||
@@ -22,23 +27,18 @@ class RootSettingsFragment : BasePreferenceFragment(0), SharedPreferences.OnShar
|
|||||||
bindPreferenceSummary("tracker", R.string.track_sources, R.string.notifications_settings)
|
bindPreferenceSummary("tracker", R.string.track_sources, R.string.notifications_settings)
|
||||||
bindPreferenceSummary("services", R.string.suggestions, R.string.sync, R.string.tracking)
|
bindPreferenceSummary("services", R.string.suggestions, R.string.sync, R.string.tracking)
|
||||||
findPreference<Preference>("about")?.summary = getString(R.string.app_version, BuildConfig.VERSION_NAME)
|
findPreference<Preference>("about")?.summary = getString(R.string.app_version, BuildConfig.VERSION_NAME)
|
||||||
bindRemoteSourcesSummary()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
settings.subscribe(this)
|
findPreference<Preference>(AppSettings.KEY_REMOTE_SOURCES)?.let { pref ->
|
||||||
}
|
val total = viewModel.totalSourcesCount
|
||||||
|
viewModel.enabledSourcesCount.observe(viewLifecycleOwner) {
|
||||||
override fun onDestroyView() {
|
pref.summary = if (it >= 0) {
|
||||||
settings.unsubscribe(this)
|
getString(R.string.enabled_d_of_d, it, total)
|
||||||
super.onDestroyView()
|
} else {
|
||||||
}
|
resources.getQuantityString(R.plurals.items, total, total)
|
||||||
|
}
|
||||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
|
|
||||||
when (key) {
|
|
||||||
AppSettings.KEY_SOURCES_HIDDEN -> {
|
|
||||||
bindRemoteSourcesSummary()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -46,11 +46,4 @@ class RootSettingsFragment : BasePreferenceFragment(0), SharedPreferences.OnShar
|
|||||||
private fun bindPreferenceSummary(key: String, @StringRes vararg items: Int) {
|
private fun bindPreferenceSummary(key: String, @StringRes vararg items: Int) {
|
||||||
findPreference<Preference>(key)?.summary = items.joinToString { getString(it) }
|
findPreference<Preference>(key)?.summary = items.joinToString { getString(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun bindRemoteSourcesSummary() {
|
|
||||||
findPreference<Preference>(AppSettings.KEY_REMOTE_SOURCES)?.run {
|
|
||||||
val total = settings.remoteMangaSources.size
|
|
||||||
summary = getString(R.string.enabled_d_of_d, total - settings.hiddenSources.size, total)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package org.koitharu.kotatsu.settings
|
||||||
|
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
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 RootSettingsViewModel @Inject constructor(
|
||||||
|
sourcesRepository: MangaSourcesRepository,
|
||||||
|
) : BaseViewModel() {
|
||||||
|
|
||||||
|
val totalSourcesCount = sourcesRepository.allMangaSources.size
|
||||||
|
|
||||||
|
val enabledSourcesCount = sourcesRepository.observeEnabledSources()
|
||||||
|
.map { it.size }
|
||||||
|
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, -1)
|
||||||
|
}
|
||||||
@@ -9,7 +9,6 @@ import androidx.fragment.app.viewModels
|
|||||||
import coil.ImageLoader
|
import coil.ImageLoader
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.flow.filterNotNull
|
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.core.ui.AlertDialogFragment
|
import org.koitharu.kotatsu.core.ui.AlertDialogFragment
|
||||||
import org.koitharu.kotatsu.core.util.ext.observe
|
import org.koitharu.kotatsu.core.util.ext.observe
|
||||||
@@ -39,8 +38,7 @@ class NewSourcesDialogFragment :
|
|||||||
binding.recyclerView.adapter = adapter
|
binding.recyclerView.adapter = adapter
|
||||||
binding.textViewTitle.setText(R.string.new_sources_text)
|
binding.textViewTitle.setText(R.string.new_sources_text)
|
||||||
|
|
||||||
viewModel.sources.filterNotNull()
|
viewModel.content.observe(viewLifecycleOwner) { adapter.items = it }
|
||||||
.observe(viewLifecycleOwner) { adapter.items = it }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBuildDialog(builder: MaterialAlertDialogBuilder): MaterialAlertDialogBuilder {
|
override fun onBuildDialog(builder: MaterialAlertDialogBuilder): MaterialAlertDialogBuilder {
|
||||||
@@ -51,7 +49,6 @@ class NewSourcesDialogFragment :
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onClick(dialog: DialogInterface, which: Int) {
|
override fun onClick(dialog: DialogInterface, which: Int) {
|
||||||
viewModel.apply()
|
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,74 +1,43 @@
|
|||||||
package org.koitharu.kotatsu.settings.newsources
|
package org.koitharu.kotatsu.settings.newsources
|
||||||
|
|
||||||
import androidx.annotation.WorkerThread
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.core.os.LocaleListCompat
|
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.cancelAndJoin
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.ensureActive
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
import kotlinx.coroutines.plus
|
||||||
import org.koitharu.kotatsu.core.model.getLocaleTitle
|
import org.koitharu.kotatsu.core.model.getLocaleTitle
|
||||||
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.util.ext.mapToSet
|
import org.koitharu.kotatsu.explore.data.MangaSourcesRepository
|
||||||
|
import org.koitharu.kotatsu.parsers.util.SuspendLazy
|
||||||
import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem
|
import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class NewSourcesViewModel @Inject constructor(
|
class NewSourcesViewModel @Inject constructor(
|
||||||
private val settings: AppSettings,
|
private val repository: MangaSourcesRepository,
|
||||||
) : BaseViewModel() {
|
) : BaseViewModel() {
|
||||||
|
|
||||||
private val initialList = settings.newSources
|
private val newSources = SuspendLazy {
|
||||||
val sources = MutableStateFlow<List<SourceConfigItem>?>(null)
|
repository.assimilateNewSources()
|
||||||
private var listUpdateJob: Job? = null
|
|
||||||
|
|
||||||
init {
|
|
||||||
listUpdateJob = launchJob(Dispatchers.Default) {
|
|
||||||
sources.value = buildList()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
val content: StateFlow<List<SourceConfigItem>> = repository.observeAll()
|
||||||
|
.map { sources ->
|
||||||
|
val new = newSources.get()
|
||||||
|
sources.mapNotNull { (source, enabled) ->
|
||||||
|
if (source in new) {
|
||||||
|
SourceConfigItem.SourceItem(source, enabled, source.getLocaleTitle(), false)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Lazily, emptyList())
|
||||||
|
|
||||||
fun onItemEnabledChanged(item: SourceConfigItem.SourceItem, isEnabled: Boolean) {
|
fun onItemEnabledChanged(item: SourceConfigItem.SourceItem, isEnabled: Boolean) {
|
||||||
val prevJob = listUpdateJob
|
launchJob(Dispatchers.Default) {
|
||||||
listUpdateJob = launchJob(Dispatchers.Default) {
|
repository.setSourceEnabled(item.source, isEnabled)
|
||||||
if (isEnabled) {
|
|
||||||
settings.hiddenSources -= item.source.name
|
|
||||||
} else {
|
|
||||||
settings.hiddenSources += item.source.name
|
|
||||||
}
|
|
||||||
prevJob?.cancelAndJoin()
|
|
||||||
val list = buildList()
|
|
||||||
ensureActive()
|
|
||||||
sources.value = list
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun apply() {
|
|
||||||
settings.markKnownSources(initialList)
|
|
||||||
}
|
|
||||||
|
|
||||||
@WorkerThread
|
|
||||||
private fun buildList(): List<SourceConfigItem.SourceItem> {
|
|
||||||
val locales = LocaleListCompat.getDefault().mapToSet { it.language }
|
|
||||||
val pendingHidden = HashSet<String>()
|
|
||||||
return initialList.map {
|
|
||||||
val locale = it.locale
|
|
||||||
val isEnabledByLocale = locale == null || locale in locales
|
|
||||||
if (!isEnabledByLocale) {
|
|
||||||
pendingHidden += it.name
|
|
||||||
}
|
|
||||||
SourceConfigItem.SourceItem(
|
|
||||||
source = it,
|
|
||||||
summary = it.getLocaleTitle(),
|
|
||||||
isEnabled = isEnabledByLocale,
|
|
||||||
isDraggable = false,
|
|
||||||
)
|
|
||||||
}.also {
|
|
||||||
if (pendingHidden.isNotEmpty()) {
|
|
||||||
settings.hiddenSources += pendingHidden
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,9 +66,9 @@ class OnboardDialogFragment :
|
|||||||
viewModel.setItemChecked(item.key, isChecked)
|
viewModel.setItemChecked(item.key, isChecked)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onClick(dialog: DialogInterface?, which: Int) {
|
override fun onClick(dialog: DialogInterface, which: Int) {
|
||||||
when (which) {
|
when (which) {
|
||||||
DialogInterface.BUTTON_POSITIVE -> viewModel.apply()
|
DialogInterface.BUTTON_POSITIVE -> dialog.dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
package org.koitharu.kotatsu.settings.onboard
|
package org.koitharu.kotatsu.settings.onboard
|
||||||
|
|
||||||
|
import androidx.annotation.WorkerThread
|
||||||
import androidx.core.os.LocaleListCompat
|
import androidx.core.os.LocaleListCompat
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import org.koitharu.kotatsu.core.model.MangaSource
|
|
||||||
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.util.ext.map
|
import org.koitharu.kotatsu.core.util.ext.map
|
||||||
import org.koitharu.kotatsu.core.util.ext.mapToSet
|
import org.koitharu.kotatsu.core.util.ext.mapToSet
|
||||||
|
import org.koitharu.kotatsu.explore.data.MangaSourcesRepository
|
||||||
import org.koitharu.kotatsu.parsers.util.mapNotNullToSet
|
import org.koitharu.kotatsu.parsers.util.mapNotNullToSet
|
||||||
import org.koitharu.kotatsu.parsers.util.mapToSet
|
|
||||||
import org.koitharu.kotatsu.parsers.util.toTitleCase
|
import org.koitharu.kotatsu.parsers.util.toTitleCase
|
||||||
import org.koitharu.kotatsu.settings.onboard.model.SourceLocale
|
import org.koitharu.kotatsu.settings.onboard.model.SourceLocale
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
@@ -17,31 +18,34 @@ import javax.inject.Inject
|
|||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class OnboardViewModel @Inject constructor(
|
class OnboardViewModel @Inject constructor(
|
||||||
private val settings: AppSettings,
|
private val repository: MangaSourcesRepository,
|
||||||
) : BaseViewModel() {
|
) : BaseViewModel() {
|
||||||
|
|
||||||
private val allSources = settings.remoteMangaSources
|
private val allSources = repository.allMangaSources
|
||||||
|
|
||||||
private val locales = allSources.groupBy { it.locale }
|
private val locales = allSources.groupBy { it.locale }
|
||||||
|
private val selectedLocales = HashSet<String?>()
|
||||||
private val selectedLocales = locales.keys.toMutableSet()
|
|
||||||
|
|
||||||
val list = MutableStateFlow<List<SourceLocale>?>(null)
|
val list = MutableStateFlow<List<SourceLocale>?>(null)
|
||||||
|
private var updateJob: Job
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (settings.isSourcesSelected) {
|
updateJob = launchJob(Dispatchers.Default) {
|
||||||
selectedLocales.removeAll(settings.hiddenSources.mapNotNullToSet { x -> MangaSource(x).locale })
|
if (repository.isSetupRequired()) {
|
||||||
} else {
|
val deviceLocales = LocaleListCompat.getDefault().mapToSet { x ->
|
||||||
val deviceLocales = LocaleListCompat.getDefault().mapToSet { x ->
|
x.language
|
||||||
x.language
|
}
|
||||||
|
selectedLocales.addAll(deviceLocales)
|
||||||
|
if (selectedLocales.isEmpty()) {
|
||||||
|
selectedLocales += "en"
|
||||||
|
}
|
||||||
|
selectedLocales += null
|
||||||
|
} else {
|
||||||
|
selectedLocales.addAll(
|
||||||
|
repository.getEnabledSources().mapNotNullToSet { x -> x.locale },
|
||||||
|
)
|
||||||
}
|
}
|
||||||
selectedLocales.retainAll(deviceLocales)
|
rebuildList()
|
||||||
if (selectedLocales.isEmpty()) {
|
repository.assimilateNewSources()
|
||||||
selectedLocales += "en"
|
|
||||||
}
|
|
||||||
selectedLocales += null
|
|
||||||
}
|
}
|
||||||
rebuildList()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setItemChecked(key: String?, isChecked: Boolean) {
|
fun setItemChecked(key: String?, isChecked: Boolean) {
|
||||||
@@ -51,17 +55,17 @@ class OnboardViewModel @Inject constructor(
|
|||||||
selectedLocales.remove(key)
|
selectedLocales.remove(key)
|
||||||
}
|
}
|
||||||
if (isModified) {
|
if (isModified) {
|
||||||
rebuildList()
|
val prevJob = updateJob
|
||||||
|
updateJob = launchJob(Dispatchers.Default) {
|
||||||
|
prevJob.join()
|
||||||
|
val sources = allSources.filter { x -> x.locale == key }
|
||||||
|
repository.setSourcesEnabled(sources, isChecked)
|
||||||
|
rebuildList()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun apply() {
|
@WorkerThread
|
||||||
settings.hiddenSources = allSources.filterNot { x ->
|
|
||||||
x.locale in selectedLocales
|
|
||||||
}.mapToSet { x -> x.name }
|
|
||||||
settings.markKnownSources(settings.newSources)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun rebuildList() {
|
private fun rebuildList() {
|
||||||
list.value = locales.map { (key, srcs) ->
|
list.value = locales.map { (key, srcs) ->
|
||||||
val locale = if (key != null) {
|
val locale = if (key != null) {
|
||||||
|
|||||||
@@ -3,25 +3,26 @@ package org.koitharu.kotatsu.settings.sources
|
|||||||
import androidx.core.os.LocaleListCompat
|
import androidx.core.os.LocaleListCompat
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.CoroutineStart
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.runInterruptible
|
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.core.model.getLocaleTitle
|
import org.koitharu.kotatsu.core.model.getLocaleTitle
|
||||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
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.ui.util.ReversibleHandle
|
import org.koitharu.kotatsu.core.util.AlphanumComparator
|
||||||
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.core.util.ext.map
|
import org.koitharu.kotatsu.core.util.ext.map
|
||||||
|
import org.koitharu.kotatsu.explore.data.MangaSourcesRepository
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
import org.koitharu.kotatsu.parsers.util.mapToSet
|
|
||||||
import org.koitharu.kotatsu.parsers.util.move
|
|
||||||
import org.koitharu.kotatsu.parsers.util.toTitleCase
|
import org.koitharu.kotatsu.parsers.util.toTitleCase
|
||||||
import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem
|
import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem
|
||||||
|
import java.util.EnumSet
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import java.util.TreeMap
|
import java.util.TreeMap
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -34,6 +35,7 @@ private const val TIP_REORDER = "src_reorder"
|
|||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class SourcesListViewModel @Inject constructor(
|
class SourcesListViewModel @Inject constructor(
|
||||||
private val settings: AppSettings,
|
private val settings: AppSettings,
|
||||||
|
private val repository: MangaSourcesRepository,
|
||||||
) : BaseViewModel() {
|
) : BaseViewModel() {
|
||||||
|
|
||||||
val items = MutableStateFlow<List<SourceConfigItem>>(emptyList())
|
val items = MutableStateFlow<List<SourceConfigItem>>(emptyList())
|
||||||
@@ -51,13 +53,19 @@ class SourcesListViewModel @Inject constructor(
|
|||||||
|
|
||||||
fun reorderSources(oldPos: Int, newPos: Int): Boolean {
|
fun reorderSources(oldPos: Int, newPos: Int): Boolean {
|
||||||
val snapshot = items.value.toMutableList()
|
val snapshot = items.value.toMutableList()
|
||||||
if ((snapshot[oldPos] as? SourceConfigItem.SourceItem)?.isEnabled != true) return false
|
val item = (snapshot[oldPos] as? SourceConfigItem.SourceItem) ?: return false
|
||||||
if ((snapshot[newPos] as? SourceConfigItem.SourceItem)?.isEnabled != true) return false
|
if ((snapshot[newPos] as? SourceConfigItem.SourceItem)?.isDraggable != true) return false
|
||||||
launchAtomicJob(Dispatchers.Default) {
|
launchAtomicJob(Dispatchers.Default) {
|
||||||
snapshot.move(oldPos, newPos)
|
var targetPosition = 0
|
||||||
settings.sourcesOrder = snapshot.mapNotNull {
|
for ((i, x) in snapshot.withIndex()) {
|
||||||
(it as? SourceConfigItem.SourceItem)?.source?.name
|
if (i == newPos) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (x is SourceConfigItem.SourceItem) {
|
||||||
|
targetPosition++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
repository.setPosition(item.source, targetPosition)
|
||||||
buildList()
|
buildList()
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@@ -71,17 +79,8 @@ class SourcesListViewModel @Inject constructor(
|
|||||||
|
|
||||||
fun setEnabled(source: MangaSource, isEnabled: Boolean) {
|
fun setEnabled(source: MangaSource, isEnabled: Boolean) {
|
||||||
launchAtomicJob(Dispatchers.Default) {
|
launchAtomicJob(Dispatchers.Default) {
|
||||||
settings.hiddenSources = if (isEnabled) {
|
val rollback = repository.setSourceEnabled(source, isEnabled)
|
||||||
settings.hiddenSources - source.name
|
if (!isEnabled) {
|
||||||
} else {
|
|
||||||
settings.hiddenSources + source.name
|
|
||||||
}
|
|
||||||
if (isEnabled) {
|
|
||||||
settings.markKnownSources(setOf(source))
|
|
||||||
} else {
|
|
||||||
val rollback = ReversibleHandle {
|
|
||||||
setEnabled(source, true)
|
|
||||||
}
|
|
||||||
onActionDone.call(ReversibleAction(R.string.source_disabled, rollback))
|
onActionDone.call(ReversibleAction(R.string.source_disabled, rollback))
|
||||||
}
|
}
|
||||||
buildList()
|
buildList()
|
||||||
@@ -90,9 +89,7 @@ class SourcesListViewModel @Inject constructor(
|
|||||||
|
|
||||||
fun disableAll() {
|
fun disableAll() {
|
||||||
launchAtomicJob(Dispatchers.Default) {
|
launchAtomicJob(Dispatchers.Default) {
|
||||||
settings.hiddenSources = settings.getMangaSources(includeHidden = true).mapToSet {
|
repository.disableAllSources()
|
||||||
it.name
|
|
||||||
}
|
|
||||||
buildList()
|
buildList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -122,36 +119,37 @@ class SourcesListViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun buildList() = runInterruptible(Dispatchers.Default) {
|
private suspend fun buildList() = withContext(Dispatchers.Default) {
|
||||||
val sources = settings.getMangaSources(includeHidden = true)
|
val allSources = repository.allMangaSources
|
||||||
val hiddenSources = settings.hiddenSources
|
val enabledSources = repository.getEnabledSources()
|
||||||
|
val enabledSet = EnumSet.copyOf(enabledSources)
|
||||||
val query = searchQuery
|
val query = searchQuery
|
||||||
if (!query.isNullOrEmpty()) {
|
if (!query.isNullOrEmpty()) {
|
||||||
items.value = sources.mapNotNull {
|
items.value = allSources.mapNotNull {
|
||||||
if (!it.title.contains(query, ignoreCase = true)) {
|
if (!it.title.contains(query, ignoreCase = true)) {
|
||||||
return@mapNotNull null
|
return@mapNotNull null
|
||||||
}
|
}
|
||||||
SourceConfigItem.SourceItem(
|
SourceConfigItem.SourceItem(
|
||||||
source = it,
|
source = it,
|
||||||
summary = it.getLocaleTitle(),
|
summary = it.getLocaleTitle(),
|
||||||
isEnabled = it.name !in hiddenSources,
|
isEnabled = it in enabledSet,
|
||||||
isDraggable = false,
|
isDraggable = false,
|
||||||
)
|
)
|
||||||
}.ifEmpty {
|
}.ifEmpty {
|
||||||
listOf(SourceConfigItem.EmptySearchResult)
|
listOf(SourceConfigItem.EmptySearchResult)
|
||||||
}
|
}
|
||||||
return@runInterruptible
|
return@withContext
|
||||||
}
|
}
|
||||||
val map = sources.groupByTo(TreeMap(LocaleKeyComparator())) {
|
val map = allSources.groupByTo(TreeMap(LocaleKeyComparator())) {
|
||||||
if (it.name !in hiddenSources) {
|
if (it in enabledSet) {
|
||||||
KEY_ENABLED
|
KEY_ENABLED
|
||||||
} else {
|
} else {
|
||||||
it.locale
|
it.locale
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val result = ArrayList<SourceConfigItem>(sources.size + map.size + 2)
|
map.remove(KEY_ENABLED)
|
||||||
val enabledSources = map.remove(KEY_ENABLED)
|
val result = ArrayList<SourceConfigItem>(allSources.size + map.size + 2)
|
||||||
if (!enabledSources.isNullOrEmpty()) {
|
if (enabledSources.isNotEmpty()) {
|
||||||
result += SourceConfigItem.Header(R.string.enabled_sources)
|
result += SourceConfigItem.Header(R.string.enabled_sources)
|
||||||
if (settings.isTipEnabled(TIP_REORDER)) {
|
if (settings.isTipEnabled(TIP_REORDER)) {
|
||||||
result += SourceConfigItem.Tip(TIP_REORDER, R.drawable.ic_tap_reorder, R.string.sources_reorder_tip)
|
result += SourceConfigItem.Tip(TIP_REORDER, R.drawable.ic_tap_reorder, R.string.sources_reorder_tip)
|
||||||
@@ -165,10 +163,11 @@ class SourcesListViewModel @Inject constructor(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (enabledSources?.size != sources.size) {
|
if (enabledSources.size != allSources.size) {
|
||||||
result += SourceConfigItem.Header(R.string.available_sources)
|
result += SourceConfigItem.Header(R.string.available_sources)
|
||||||
|
val comparator = compareBy<MangaSource, String>(AlphanumComparator()) { it.name }
|
||||||
for ((key, list) in map) {
|
for ((key, list) in map) {
|
||||||
list.sortBy { it.ordinal }
|
list.sortWith(comparator)
|
||||||
val isExpanded = key in expandedGroups
|
val isExpanded = key in expandedGroups
|
||||||
result += SourceConfigItem.LocaleGroup(
|
result += SourceConfigItem.LocaleGroup(
|
||||||
localeId = key,
|
localeId = key,
|
||||||
@@ -195,12 +194,12 @@ class SourcesListViewModel @Inject constructor(
|
|||||||
return locale.getDisplayLanguage(locale).toTitleCase(locale)
|
return locale.getDisplayLanguage(locale).toTitleCase(locale)
|
||||||
}
|
}
|
||||||
|
|
||||||
private inline fun launchAtomicJob(
|
private fun launchAtomicJob(
|
||||||
context: CoroutineContext = EmptyCoroutineContext,
|
context: CoroutineContext = EmptyCoroutineContext,
|
||||||
crossinline block: suspend CoroutineScope.() -> Unit
|
block: suspend CoroutineScope.() -> Unit
|
||||||
) = launchJob(context) {
|
) = launchJob(start = CoroutineStart.ATOMIC) {
|
||||||
mutex.withLock {
|
mutex.withLock {
|
||||||
block()
|
withContext(context, block)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ import org.koitharu.kotatsu.core.util.ext.takeMostFrequent
|
|||||||
import org.koitharu.kotatsu.core.util.ext.toBitmapOrNull
|
import org.koitharu.kotatsu.core.util.ext.toBitmapOrNull
|
||||||
import org.koitharu.kotatsu.core.util.ext.trySetForeground
|
import org.koitharu.kotatsu.core.util.ext.trySetForeground
|
||||||
import org.koitharu.kotatsu.details.ui.DetailsActivity
|
import org.koitharu.kotatsu.details.ui.DetailsActivity
|
||||||
|
import org.koitharu.kotatsu.explore.data.MangaSourcesRepository
|
||||||
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
|
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
|
||||||
import org.koitharu.kotatsu.history.data.HistoryRepository
|
import org.koitharu.kotatsu.history.data.HistoryRepository
|
||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
@@ -79,6 +80,7 @@ class SuggestionsWorker @AssistedInject constructor(
|
|||||||
private val favouritesRepository: FavouritesRepository,
|
private val favouritesRepository: FavouritesRepository,
|
||||||
private val appSettings: AppSettings,
|
private val appSettings: AppSettings,
|
||||||
private val mangaRepositoryFactory: MangaRepository.Factory,
|
private val mangaRepositoryFactory: MangaRepository.Factory,
|
||||||
|
private val sourcesRepository: MangaSourcesRepository,
|
||||||
) : CoroutineWorker(appContext, params) {
|
) : CoroutineWorker(appContext, params) {
|
||||||
|
|
||||||
override suspend fun doWork(): Result {
|
override suspend fun doWork(): Result {
|
||||||
@@ -128,7 +130,7 @@ class SuggestionsWorker @AssistedInject constructor(
|
|||||||
historyRepository.getList(0, 20) +
|
historyRepository.getList(0, 20) +
|
||||||
favouritesRepository.getLastManga(20)
|
favouritesRepository.getLastManga(20)
|
||||||
).distinctById()
|
).distinctById()
|
||||||
val sources = appSettings.getMangaSources(includeHidden = false)
|
val sources = sourcesRepository.getEnabledSources()
|
||||||
if (seed.isEmpty() || sources.isEmpty()) {
|
if (seed.isEmpty() || sources.isEmpty()) {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user