@@ -69,6 +69,7 @@ class MigrateUseCase @Inject constructor(
|
|||||||
lastCheckTime = System.currentTimeMillis(),
|
lastCheckTime = System.currentTimeMillis(),
|
||||||
lastChapterDate = lastChapter?.uploadDate ?: 0L,
|
lastChapterDate = lastChapter?.uploadDate ?: 0L,
|
||||||
lastResult = TrackEntity.RESULT_EXTERNAL_MODIFICATION,
|
lastResult = TrackEntity.RESULT_EXTERNAL_MODIFICATION,
|
||||||
|
lastError = null,
|
||||||
)
|
)
|
||||||
tracksDao.delete(oldDetails.id)
|
tracksDao.delete(oldDetails.id)
|
||||||
tracksDao.upsert(newTrack)
|
tracksDao.upsert(newTrack)
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ class JsonDeserializer(private val json: JSONObject) {
|
|||||||
source = json.getString("source"),
|
source = json.getString("source"),
|
||||||
isEnabled = json.getBoolean("enabled"),
|
isEnabled = json.getBoolean("enabled"),
|
||||||
sortKey = json.getInt("sort_key"),
|
sortKey = json.getInt("sort_key"),
|
||||||
|
addedIn = json.getIntOrDefault("added_in", 0),
|
||||||
)
|
)
|
||||||
|
|
||||||
fun toMap(): Map<String, Any?> {
|
fun toMap(): Map<String, Any?> {
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import org.koitharu.kotatsu.core.db.migrations.Migration17To18
|
|||||||
import org.koitharu.kotatsu.core.db.migrations.Migration18To19
|
import org.koitharu.kotatsu.core.db.migrations.Migration18To19
|
||||||
import org.koitharu.kotatsu.core.db.migrations.Migration19To20
|
import org.koitharu.kotatsu.core.db.migrations.Migration19To20
|
||||||
import org.koitharu.kotatsu.core.db.migrations.Migration1To2
|
import org.koitharu.kotatsu.core.db.migrations.Migration1To2
|
||||||
|
import org.koitharu.kotatsu.core.db.migrations.Migration20To21
|
||||||
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
|
||||||
import org.koitharu.kotatsu.core.db.migrations.Migration4To5
|
import org.koitharu.kotatsu.core.db.migrations.Migration4To5
|
||||||
@@ -58,7 +59,7 @@ 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 = 20
|
const val DATABASE_VERSION = 21
|
||||||
|
|
||||||
@Database(
|
@Database(
|
||||||
entities = [
|
entities = [
|
||||||
@@ -118,6 +119,7 @@ fun getDatabaseMigrations(context: Context): Array<Migration> = arrayOf(
|
|||||||
Migration17To18(),
|
Migration17To18(),
|
||||||
Migration18To19(),
|
Migration18To19(),
|
||||||
Migration19To20(),
|
Migration19To20(),
|
||||||
|
Migration20To21(),
|
||||||
)
|
)
|
||||||
|
|
||||||
fun MangaDatabase(context: Context): MangaDatabase = Room
|
fun MangaDatabase(context: Context): MangaDatabase = Room
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import androidx.sqlite.db.SimpleSQLiteQuery
|
|||||||
import androidx.sqlite.db.SupportSQLiteQuery
|
import androidx.sqlite.db.SupportSQLiteQuery
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import org.intellij.lang.annotations.Language
|
import org.intellij.lang.annotations.Language
|
||||||
|
import org.koitharu.kotatsu.BuildConfig
|
||||||
import org.koitharu.kotatsu.core.db.entity.MangaSourceEntity
|
import org.koitharu.kotatsu.core.db.entity.MangaSourceEntity
|
||||||
import org.koitharu.kotatsu.explore.data.SourcesSortOrder
|
import org.koitharu.kotatsu.explore.data.SourcesSortOrder
|
||||||
|
|
||||||
@@ -68,6 +69,7 @@ abstract class MangaSourcesDao {
|
|||||||
source = source,
|
source = source,
|
||||||
isEnabled = isEnabled,
|
isEnabled = isEnabled,
|
||||||
sortKey = getMaxSortKey() + 1,
|
sortKey = getMaxSortKey() + 1,
|
||||||
|
addedIn = BuildConfig.VERSION_CODE,
|
||||||
)
|
)
|
||||||
upsert(entity)
|
upsert(entity)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,4 +14,5 @@ data class MangaSourceEntity(
|
|||||||
val source: String,
|
val source: String,
|
||||||
@ColumnInfo(name = "enabled") val isEnabled: Boolean,
|
@ColumnInfo(name = "enabled") val isEnabled: Boolean,
|
||||||
@ColumnInfo(name = "sort_key", index = true) val sortKey: Int,
|
@ColumnInfo(name = "sort_key", index = true) val sortKey: Int,
|
||||||
|
@ColumnInfo(name = "added_in") val addedIn: Int,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package org.koitharu.kotatsu.core.db.migrations
|
||||||
|
|
||||||
|
import androidx.room.migration.Migration
|
||||||
|
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||||
|
|
||||||
|
class Migration20To21 : Migration(20, 21) {
|
||||||
|
|
||||||
|
override fun migrate(db: SupportSQLiteDatabase) {
|
||||||
|
db.execSQL("ALTER TABLE tracks ADD COLUMN `last_error` TEXT DEFAULT NULL")
|
||||||
|
db.execSQL("ALTER TABLE sources ADD COLUMN `added_in` INTEGER NOT NULL DEFAULT 0")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -156,6 +156,7 @@ class MangaSourcesRepository @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("")
|
||||||
suspend fun assimilateNewSources(): Set<MangaSource> {
|
suspend fun assimilateNewSources(): Set<MangaSource> {
|
||||||
val new = getNewSources()
|
val new = getNewSources()
|
||||||
if (new.isEmpty()) {
|
if (new.isEmpty()) {
|
||||||
@@ -167,6 +168,7 @@ class MangaSourcesRepository @Inject constructor(
|
|||||||
source = x.name,
|
source = x.name,
|
||||||
isEnabled = false,
|
isEnabled = false,
|
||||||
sortKey = ++maxSortKey,
|
sortKey = ++maxSortKey,
|
||||||
|
addedIn = BuildConfig.VERSION_CODE,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
dao.insertIfAbsent(entities)
|
dao.insertIfAbsent(entities)
|
||||||
|
|||||||
@@ -6,30 +6,32 @@ import android.view.View
|
|||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.widget.SearchView
|
import androidx.appcompat.widget.SearchView
|
||||||
import androidx.core.graphics.Insets
|
import androidx.core.graphics.Insets
|
||||||
import androidx.core.view.isVisible
|
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
import coil.ImageLoader
|
import coil.ImageLoader
|
||||||
import com.google.android.material.appbar.AppBarLayout
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
|
import com.google.android.material.chip.Chip
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.google.android.material.tabs.TabLayoutMediator
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.core.ui.BaseActivity
|
import org.koitharu.kotatsu.core.ui.BaseActivity
|
||||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||||
import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver
|
import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver
|
||||||
|
import org.koitharu.kotatsu.core.ui.widgets.ChipsView
|
||||||
import org.koitharu.kotatsu.core.util.ext.getDisplayName
|
import org.koitharu.kotatsu.core.util.ext.getDisplayName
|
||||||
import org.koitharu.kotatsu.core.util.ext.observe
|
import org.koitharu.kotatsu.core.util.ext.observe
|
||||||
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
||||||
import org.koitharu.kotatsu.core.util.ext.toLocale
|
import org.koitharu.kotatsu.core.util.ext.toLocale
|
||||||
import org.koitharu.kotatsu.databinding.ActivitySourcesCatalogBinding
|
import org.koitharu.kotatsu.databinding.ActivitySourcesCatalogBinding
|
||||||
|
import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration
|
||||||
import org.koitharu.kotatsu.main.ui.owners.AppBarOwner
|
import org.koitharu.kotatsu.main.ui.owners.AppBarOwner
|
||||||
|
import org.koitharu.kotatsu.parsers.model.ContentType
|
||||||
import org.koitharu.kotatsu.settings.newsources.NewSourcesDialogFragment
|
import org.koitharu.kotatsu.settings.newsources.NewSourcesDialogFragment
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class SourcesCatalogActivity : BaseActivity<ActivitySourcesCatalogBinding>(),
|
class SourcesCatalogActivity : BaseActivity<ActivitySourcesCatalogBinding>(),
|
||||||
OnListItemClickListener<SourceCatalogItem.Source>,
|
OnListItemClickListener<SourceCatalogItem.Source>,
|
||||||
AppBarOwner, MenuItem.OnActionExpandListener {
|
AppBarOwner, MenuItem.OnActionExpandListener, ChipsView.OnChipClickListener {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var coil: ImageLoader
|
lateinit var coil: ImageLoader
|
||||||
@@ -45,18 +47,24 @@ class SourcesCatalogActivity : BaseActivity<ActivitySourcesCatalogBinding>(),
|
|||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(ActivitySourcesCatalogBinding.inflate(layoutInflater))
|
setContentView(ActivitySourcesCatalogBinding.inflate(layoutInflater))
|
||||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||||
val pagerAdapter = SourcesCatalogPagerAdapter(this, coil, this)
|
val sourcesAdapter = SourcesCatalogAdapter(this, coil, this)
|
||||||
viewBinding.pager.adapter = pagerAdapter
|
with(viewBinding.recyclerView) {
|
||||||
val tabMediator = TabLayoutMediator(viewBinding.tabs, viewBinding.pager, pagerAdapter)
|
setHasFixedSize(true)
|
||||||
tabMediator.attach()
|
addItemDecoration(TypedListSpacingDecoration(context, false))
|
||||||
viewModel.content.observe(this, pagerAdapter)
|
adapter = sourcesAdapter
|
||||||
|
}
|
||||||
|
viewBinding.chipsFilter.onChipClickListener = this
|
||||||
|
viewModel.content.observe(this, sourcesAdapter)
|
||||||
viewModel.hasNewSources.observe(this, ::onHasNewSourcesChanged)
|
viewModel.hasNewSources.observe(this, ::onHasNewSourcesChanged)
|
||||||
viewModel.onActionDone.observeEvent(
|
viewModel.onActionDone.observeEvent(
|
||||||
this,
|
this,
|
||||||
ReversibleActionObserver(viewBinding.pager),
|
ReversibleActionObserver(viewBinding.recyclerView),
|
||||||
)
|
)
|
||||||
viewModel.locale.observe(this) {
|
viewModel.appliedFilter.observe(this) {
|
||||||
supportActionBar?.subtitle = it?.toLocale().getDisplayName(this)
|
supportActionBar?.subtitle = it.locale?.toLocale().getDisplayName(this)
|
||||||
|
}
|
||||||
|
viewModel.filter.observe(this) {
|
||||||
|
viewBinding.chipsFilter.setChips(it)
|
||||||
}
|
}
|
||||||
addMenuProvider(SourcesCatalogMenuProvider(this, viewModel, this))
|
addMenuProvider(SourcesCatalogMenuProvider(this, viewModel, this))
|
||||||
}
|
}
|
||||||
@@ -68,21 +76,23 @@ class SourcesCatalogActivity : BaseActivity<ActivitySourcesCatalogBinding>(),
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onChipClick(chip: Chip, data: Any?) {
|
||||||
|
when (data) {
|
||||||
|
is ContentType -> viewModel.setContentType(data, chip.isChecked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onItemClick(item: SourceCatalogItem.Source, view: View) {
|
override fun onItemClick(item: SourceCatalogItem.Source, view: View) {
|
||||||
viewModel.addSource(item.source)
|
viewModel.addSource(item.source)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onMenuItemActionExpand(item: MenuItem): Boolean {
|
override fun onMenuItemActionExpand(item: MenuItem): Boolean {
|
||||||
viewBinding.tabs.isVisible = false
|
|
||||||
viewBinding.pager.isUserInputEnabled = false
|
|
||||||
val sq = (item.actionView as? SearchView)?.query?.trim()?.toString().orEmpty()
|
val sq = (item.actionView as? SearchView)?.query?.trim()?.toString().orEmpty()
|
||||||
viewModel.performSearch(sq)
|
viewModel.performSearch(sq)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
|
override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
|
||||||
viewBinding.tabs.isVisible = true
|
|
||||||
viewBinding.pager.isUserInputEnabled = true
|
|
||||||
viewModel.performSearch(null)
|
viewModel.performSearch(null)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -92,7 +102,7 @@ class SourcesCatalogActivity : BaseActivity<ActivitySourcesCatalogBinding>(),
|
|||||||
if (newSourcesSnackbar?.isShownOrQueued == true) {
|
if (newSourcesSnackbar?.isShownOrQueued == true) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val snackbar = Snackbar.make(viewBinding.pager, R.string.new_sources_text, Snackbar.LENGTH_INDEFINITE)
|
val snackbar = Snackbar.make(viewBinding.recyclerView, R.string.new_sources_text, Snackbar.LENGTH_INDEFINITE)
|
||||||
snackbar.setAction(R.string.explore) {
|
snackbar.setAction(R.string.explore) {
|
||||||
NewSourcesDialogFragment.show(supportFragmentManager)
|
NewSourcesDialogFragment.show(supportFragmentManager)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package org.koitharu.kotatsu.settings.sources.catalog
|
||||||
|
|
||||||
|
import org.koitharu.kotatsu.parsers.model.ContentType
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
data class SourcesCatalogFilter(
|
||||||
|
val types: Set<ContentType>,
|
||||||
|
val locale: String?,
|
||||||
|
)
|
||||||
@@ -19,6 +19,7 @@ import org.koitharu.kotatsu.core.util.ext.lifecycleScope
|
|||||||
import org.koitharu.kotatsu.explore.data.MangaSourcesRepository
|
import org.koitharu.kotatsu.explore.data.MangaSourcesRepository
|
||||||
import org.koitharu.kotatsu.parsers.model.ContentType
|
import org.koitharu.kotatsu.parsers.model.ContentType
|
||||||
|
|
||||||
|
@Deprecated("")
|
||||||
class SourcesCatalogListProducer @AssistedInject constructor(
|
class SourcesCatalogListProducer @AssistedInject constructor(
|
||||||
@Assisted private val locale: String?,
|
@Assisted private val locale: String?,
|
||||||
@Assisted private val contentType: ContentType,
|
@Assisted private val contentType: ContentType,
|
||||||
|
|||||||
@@ -1,29 +1,25 @@
|
|||||||
package org.koitharu.kotatsu.settings.sources.catalog
|
package org.koitharu.kotatsu.settings.sources.catalog
|
||||||
|
|
||||||
import androidx.annotation.MainThread
|
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import dagger.hilt.android.internal.lifecycle.RetainedLifecycleImpl
|
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
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.flatMapLatest
|
|
||||||
import kotlinx.coroutines.flow.map
|
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.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.ui.widgets.ChipsView.ChipModel
|
||||||
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.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 org.koitharu.kotatsu.parsers.util.mapToSet
|
||||||
import java.util.EnumMap
|
|
||||||
import java.util.EnumSet
|
import java.util.EnumSet
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -31,41 +27,40 @@ import javax.inject.Inject
|
|||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class SourcesCatalogViewModel @Inject constructor(
|
class SourcesCatalogViewModel @Inject constructor(
|
||||||
private val repository: MangaSourcesRepository,
|
private val repository: MangaSourcesRepository,
|
||||||
private val listProducerFactory: SourcesCatalogListProducer.Factory,
|
|
||||||
private val settings: AppSettings,
|
|
||||||
) : BaseViewModel() {
|
) : BaseViewModel() {
|
||||||
|
|
||||||
private val lifecycle = RetainedLifecycleImpl()
|
|
||||||
private var searchQuery: String? = null
|
|
||||||
val onActionDone = MutableEventFlow<ReversibleAction>()
|
val onActionDone = MutableEventFlow<ReversibleAction>()
|
||||||
val locales = repository.allMangaSources.mapToSet { it.locale }
|
val locales = repository.allMangaSources.mapToSet { it.locale }
|
||||||
val locale = MutableStateFlow(Locale.getDefault().language.takeIf { it in locales })
|
|
||||||
|
private val searchQuery = MutableStateFlow<String?>(null)
|
||||||
|
val appliedFilter = MutableStateFlow(
|
||||||
|
SourcesCatalogFilter(
|
||||||
|
types = emptySet(),
|
||||||
|
locale = Locale.getDefault().language.takeIf { it in locales },
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
val hasNewSources = repository.observeNewSources()
|
val hasNewSources = repository.observeNewSources()
|
||||||
.map { it.isNotEmpty() }
|
.map { it.isNotEmpty() }
|
||||||
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Lazily, false)
|
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Lazily, false)
|
||||||
|
|
||||||
private val listProducers = locale.map { lc ->
|
val filter: StateFlow<List<ChipModel>> = appliedFilter.map {
|
||||||
createListProducers(lc)
|
buildFilter(it)
|
||||||
}.stateIn(viewModelScope, SharingStarted.Eagerly, createListProducers(locale.value))
|
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, buildFilter(appliedFilter.value))
|
||||||
|
|
||||||
val content: StateFlow<List<SourceCatalogPage>> = listProducers.flatMapLatest {
|
val content: StateFlow<List<SourceCatalogItem>> = combine(
|
||||||
val flows = it.entries.map { (type, producer) -> producer.list.map { x -> SourceCatalogPage(type, x) } }
|
searchQuery,
|
||||||
combine<SourceCatalogPage, List<SourceCatalogPage>>(flows, Array<SourceCatalogPage>::toList)
|
appliedFilter,
|
||||||
|
) { q, f ->
|
||||||
|
buildSourcesList(f, q)
|
||||||
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, emptyList())
|
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, emptyList())
|
||||||
|
|
||||||
override fun onCleared() {
|
|
||||||
super.onCleared()
|
|
||||||
lifecycle.dispatchOnCleared()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun performSearch(query: String?) {
|
fun performSearch(query: String?) {
|
||||||
searchQuery = query
|
searchQuery.value = query?.trim()
|
||||||
listProducers.value.forEach { (_, v) -> v.setQuery(query) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setLocale(value: String?) {
|
fun setLocale(value: String?) {
|
||||||
locale.value = value
|
appliedFilter.value = appliedFilter.value.copy(locale = value)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addSource(source: MangaSource) {
|
fun addSource(source: MangaSource) {
|
||||||
@@ -81,15 +76,64 @@ class SourcesCatalogViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainThread
|
fun setContentType(value: ContentType, isAdd: Boolean) {
|
||||||
private fun createListProducers(lc: String?): Map<ContentType, SourcesCatalogListProducer> {
|
val filter = appliedFilter.value
|
||||||
val types = EnumSet.allOf(ContentType::class.java)
|
val types = EnumSet.noneOf(ContentType::class.java)
|
||||||
if (settings.isNsfwContentDisabled) {
|
types.addAll(filter.types)
|
||||||
types.remove(ContentType.HENTAI)
|
if (isAdd) {
|
||||||
|
types.add(value)
|
||||||
|
} else {
|
||||||
|
types.remove(value)
|
||||||
}
|
}
|
||||||
return types.associateWithTo(EnumMap(ContentType::class.java)) { type ->
|
appliedFilter.value = filter.copy(types = types)
|
||||||
listProducerFactory.create(lc, type, lifecycle).also {
|
}
|
||||||
it.setQuery(searchQuery)
|
|
||||||
|
private fun buildFilter(applied: SourcesCatalogFilter): List<ChipModel> = buildList(ContentType.entries.size) {
|
||||||
|
for (ct in ContentType.entries) {
|
||||||
|
add(
|
||||||
|
ChipModel(
|
||||||
|
tint = 0,
|
||||||
|
title = ct.name,
|
||||||
|
icon = 0,
|
||||||
|
isCheckable = true,
|
||||||
|
isChecked = ct in applied.types,
|
||||||
|
data = ct,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun buildSourcesList(filter: SourcesCatalogFilter, query: String?): List<SourceCatalogItem> {
|
||||||
|
val sources = repository.getDisabledSources().toMutableList()
|
||||||
|
sources.retainAll {
|
||||||
|
(filter.types.isEmpty() || it.contentType in filter.types) && it.locale == filter.locale
|
||||||
|
}
|
||||||
|
if (!query.isNullOrEmpty()) {
|
||||||
|
sources.retainAll { it.title.contains(query, ignoreCase = true) }
|
||||||
|
}
|
||||||
|
return if (sources.isEmpty()) {
|
||||||
|
listOf(
|
||||||
|
if (query == null) {
|
||||||
|
SourceCatalogItem.Hint(
|
||||||
|
icon = R.drawable.ic_empty_feed,
|
||||||
|
title = R.string.no_manga_sources,
|
||||||
|
text = R.string.no_manga_sources_catalog_text,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
SourceCatalogItem.Hint(
|
||||||
|
icon = R.drawable.ic_empty_feed,
|
||||||
|
title = R.string.nothing_found,
|
||||||
|
text = R.string.no_manga_sources_found,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
sources.sortBy { it.title }
|
||||||
|
sources.map {
|
||||||
|
SourceCatalogItem.Source(
|
||||||
|
source = it,
|
||||||
|
showSummary = query != null,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ class TrackEntity(
|
|||||||
@ColumnInfo(name = "last_check_time") val lastCheckTime: Long,
|
@ColumnInfo(name = "last_check_time") val lastCheckTime: Long,
|
||||||
@ColumnInfo(name = "last_chapter_date") val lastChapterDate: Long,
|
@ColumnInfo(name = "last_chapter_date") val lastChapterDate: Long,
|
||||||
@ColumnInfo(name = "last_result") val lastResult: Int,
|
@ColumnInfo(name = "last_result") val lastResult: Int,
|
||||||
|
@ColumnInfo(name = "last_error") val lastError: String?,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -42,6 +43,7 @@ class TrackEntity(
|
|||||||
lastCheckTime = 0L,
|
lastCheckTime = 0L,
|
||||||
lastChapterDate = 0,
|
lastChapterDate = 0,
|
||||||
lastResult = RESULT_NONE,
|
lastResult = RESULT_NONE,
|
||||||
|
lastError = null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -174,6 +174,7 @@ class TrackingRepository @Inject constructor(
|
|||||||
lastCheckTime = tracking.lastCheck?.toEpochMilli() ?: 0L,
|
lastCheckTime = tracking.lastCheck?.toEpochMilli() ?: 0L,
|
||||||
lastChapterDate = tracking.lastChapterDate?.toEpochMilli() ?: 0L,
|
lastChapterDate = tracking.lastChapterDate?.toEpochMilli() ?: 0L,
|
||||||
lastResult = TrackEntity.RESULT_EXTERNAL_MODIFICATION,
|
lastResult = TrackEntity.RESULT_EXTERNAL_MODIFICATION,
|
||||||
|
lastError = null,
|
||||||
)
|
)
|
||||||
db.getTracksDao().upsert(entity)
|
db.getTracksDao().upsert(entity)
|
||||||
}
|
}
|
||||||
@@ -230,6 +231,7 @@ class TrackingRepository @Inject constructor(
|
|||||||
lastCheckTime = System.currentTimeMillis(),
|
lastCheckTime = System.currentTimeMillis(),
|
||||||
lastChapterDate = lastChapterDate,
|
lastChapterDate = lastChapterDate,
|
||||||
lastResult = TrackEntity.RESULT_FAILED,
|
lastResult = TrackEntity.RESULT_FAILED,
|
||||||
|
lastError = updates.error?.toString(),
|
||||||
)
|
)
|
||||||
|
|
||||||
is MangaUpdates.Success -> TrackEntity(
|
is MangaUpdates.Success -> TrackEntity(
|
||||||
@@ -239,6 +241,7 @@ class TrackingRepository @Inject constructor(
|
|||||||
lastCheckTime = System.currentTimeMillis(),
|
lastCheckTime = System.currentTimeMillis(),
|
||||||
lastChapterDate = updates.lastChapterDate().ifZero { lastChapterDate },
|
lastChapterDate = updates.lastChapterDate().ifZero { lastChapterDate },
|
||||||
lastResult = if (updates.isNotEmpty()) TrackEntity.RESULT_HAS_UPDATE else TrackEntity.RESULT_NO_UPDATE,
|
lastResult = if (updates.isNotEmpty()) TrackEntity.RESULT_HAS_UPDATE else TrackEntity.RESULT_NO_UPDATE,
|
||||||
|
lastError = null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,19 +19,36 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="?attr/actionBarSize" />
|
android:layout_height="?attr/actionBarSize" />
|
||||||
|
|
||||||
<com.google.android.material.tabs.TabLayout
|
<HorizontalScrollView
|
||||||
android:id="@+id/tabs"
|
android:id="@+id/scrollView_chips"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:tabGravity="start"
|
android:clipToPadding="false"
|
||||||
app:tabMode="scrollable" />
|
android:paddingHorizontal="@dimen/list_spacing_large"
|
||||||
|
android:scrollbars="none">
|
||||||
|
|
||||||
|
<org.koitharu.kotatsu.core.ui.widgets.ChipsView
|
||||||
|
android:id="@+id/chips_filter"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:clipChildren="false"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:paddingVertical="@dimen/margin_small"
|
||||||
|
app:chipStyle="@style/Widget.Kotatsu.Chip.Filter"
|
||||||
|
app:selectionRequired="false"
|
||||||
|
app:singleLine="true"
|
||||||
|
app:singleSelection="false" />
|
||||||
|
|
||||||
|
</HorizontalScrollView>
|
||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
<androidx.viewpager2.widget.ViewPager2
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/pager"
|
android:id="@+id/recyclerView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:scrollbars="vertical"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />
|
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />
|
||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|||||||
Reference in New Issue
Block a user