Reapply "Update sources catalog ui"

This reverts commit 8d5bde6e60.
This commit is contained in:
Koitharu
2024-06-01 11:55:52 +03:00
parent 59ce5d5e67
commit e642d54929
14 changed files with 162 additions and 55 deletions

View File

@@ -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)

View File

@@ -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?> {

View File

@@ -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

View File

@@ -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)
} }

View File

@@ -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,
) )

View File

@@ -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")
}
}

View File

@@ -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)

View File

@@ -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)
} }

View File

@@ -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?,
)

View File

@@ -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,

View File

@@ -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,
)
} }
} }
} }

View File

@@ -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,
) )
} }
} }

View File

@@ -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,
) )
} }
} }

View File

@@ -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>