Improve sources catalog

pull/605/head
Koitharu 2 years ago
parent b878f358ff
commit 71f2c91e5a
Signed by: Koitharu
GPG Key ID: 676DEE768C17A9D7

@ -61,13 +61,20 @@ class CaptchaNotifier(
override fun onError(request: ImageRequest, result: ErrorResult) { override fun onError(request: ImageRequest, result: ErrorResult) {
super.onError(request, result) super.onError(request, result)
val e = result.throwable val e = result.throwable
if (e is CloudFlareProtectedException) { if (e is CloudFlareProtectedException && request.parameters.value<Boolean>(PARAM_IGNORE_CAPTCHA) != true) {
notify(e) notify(e)
} }
} }
private companion object { companion object {
fun ImageRequest.Builder.ignoreCaptchaErrors() = setParameter(
key = PARAM_IGNORE_CAPTCHA,
value = true,
memoryCacheKey = null,
)
private const val PARAM_IGNORE_CAPTCHA = "ignore_captcha"
private const val CHANNEL_ID = "captcha" private const val CHANNEL_ID = "captcha"
private const val TAG = CHANNEL_ID private const val TAG = CHANNEL_ID
} }

@ -10,18 +10,15 @@ import androidx.annotation.StringRes
import androidx.core.text.buildSpannedString import androidx.core.text.buildSpannedString
import androidx.core.text.inSpans import androidx.core.text.inSpans
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.util.ext.getDisplayName
import org.koitharu.kotatsu.core.util.ext.getThemeColor import org.koitharu.kotatsu.core.util.ext.getThemeColor
import org.koitharu.kotatsu.core.util.ext.toLocale
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.toTitleCase import org.koitharu.kotatsu.parsers.util.toTitleCase
import java.util.Locale import java.util.Locale
import com.google.android.material.R as materialR import com.google.android.material.R as materialR
fun MangaSource.getLocaleTitle(): String? {
val lc = Locale(locale ?: return null)
return lc.getDisplayLanguage(lc).toTitleCase(lc)
}
fun MangaSource(name: String): MangaSource { fun MangaSource(name: String): MangaSource {
MangaSource.entries.forEach { MangaSource.entries.forEach {
if (it.name == name) return it if (it.name == name) return it
@ -42,7 +39,7 @@ val ContentType.titleResId
fun MangaSource.getSummary(context: Context): String { fun MangaSource.getSummary(context: Context): String {
val type = context.getString(contentType.titleResId) val type = context.getString(contentType.titleResId)
val locale = getLocaleTitle() ?: context.getString(R.string.various_languages) val locale = locale?.toLocale().getDisplayName(context)
return context.getString(R.string.source_summary_pattern, type, locale) return context.getString(R.string.source_summary_pattern, type, locale)
} }

@ -70,7 +70,7 @@ class WindowInsetsDelegate : OnApplyWindowInsetsListener, View.OnLayoutChangeLis
lastInsets = null lastInsets = null
} }
interface WindowInsetsListener { fun interface WindowInsetsListener {
fun onWindowInsetsChanged(insets: Insets) fun onWindowInsetsChanged(insets: Insets)
} }

@ -20,12 +20,13 @@ inline fun <T> LocaleListCompat.mapToSet(block: (Locale) -> T): Set<T> {
fun LocaleListCompat.getOrThrow(index: Int) = get(index) ?: throw NoSuchElementException() fun LocaleListCompat.getOrThrow(index: Int) = get(index) ?: throw NoSuchElementException()
fun String?.getLocaleDisplayName(context: Context): String { fun String.toLocale() = Locale(this)
fun Locale?.getDisplayName(context: Context): String {
if (this == null) { if (this == null) {
return context.getString(R.string.various_languages) return context.getString(R.string.various_languages)
} }
val lc = Locale(this) return getDisplayLanguage(this).toTitleCase(this)
return lc.getDisplayLanguage(lc).toTitleCase(lc)
} }
private class LocaleListCompatIterator(private val list: LocaleListCompat) : ListIterator<Locale> { private class LocaleListCompatIterator(private val list: LocaleListCompat) : ListIterator<Locale> {

@ -1,20 +1,25 @@
package org.koitharu.kotatsu.settings.sources.catalog package org.koitharu.kotatsu.settings.sources.catalog
import androidx.core.view.ViewCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.view.updatePadding
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import coil.ImageLoader import coil.ImageLoader
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.browser.cloudflare.CaptchaNotifier.Companion.ignoreCaptchaErrors
import org.koitharu.kotatsu.core.model.getSummary import org.koitharu.kotatsu.core.model.getSummary
import org.koitharu.kotatsu.core.model.getTitle import org.koitharu.kotatsu.core.model.getTitle
import org.koitharu.kotatsu.core.parser.favicon.faviconUri import org.koitharu.kotatsu.core.parser.favicon.faviconUri
import org.koitharu.kotatsu.core.ui.image.FaviconDrawable import org.koitharu.kotatsu.core.ui.image.FaviconDrawable
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.ui.util.WindowInsetsDelegate
import org.koitharu.kotatsu.core.util.ext.crossfade import org.koitharu.kotatsu.core.util.ext.crossfade
import org.koitharu.kotatsu.core.util.ext.enqueueWith import org.koitharu.kotatsu.core.util.ext.enqueueWith
import org.koitharu.kotatsu.core.util.ext.newImageRequest import org.koitharu.kotatsu.core.util.ext.newImageRequest
import org.koitharu.kotatsu.core.util.ext.setTextAndVisible import org.koitharu.kotatsu.core.util.ext.setTextAndVisible
import org.koitharu.kotatsu.core.util.ext.source import org.koitharu.kotatsu.core.util.ext.source
import org.koitharu.kotatsu.databinding.ItemCatalogPageBinding
import org.koitharu.kotatsu.databinding.ItemEmptyHintBinding import org.koitharu.kotatsu.databinding.ItemEmptyHintBinding
import org.koitharu.kotatsu.databinding.ItemSourceCatalogBinding import org.koitharu.kotatsu.databinding.ItemSourceCatalogBinding
@ -47,6 +52,7 @@ fun sourceCatalogItemSourceAD(
placeholder(fallbackIcon) placeholder(fallbackIcon)
fallback(fallbackIcon) fallback(fallbackIcon)
source(item.source) source(item.source)
ignoreCaptchaErrors()
enqueueWith(coil) enqueueWith(coil)
} }
} }
@ -67,3 +73,30 @@ fun sourceCatalogItemHintAD(
binding.textSecondary.setTextAndVisible(item.text) binding.textSecondary.setTextAndVisible(item.text)
} }
} }
fun sourceCatalogPageAD(
listener: OnListItemClickListener<SourceCatalogItem.Source>,
coil: ImageLoader,
lifecycleOwner: LifecycleOwner,
) = adapterDelegateViewBinding<SourceCatalogPage, SourceCatalogPage, ItemCatalogPageBinding>(
{ inflater, parent -> ItemCatalogPageBinding.inflate(inflater, parent, false) },
) {
val sourcesAdapter = SourcesCatalogAdapter(listener, coil, lifecycleOwner)
with(binding.recyclerView) {
setHasFixedSize(true)
adapter = sourcesAdapter
}
val insetsDelegate = WindowInsetsDelegate()
ViewCompat.setOnApplyWindowInsetsListener(itemView, insetsDelegate)
itemView.addOnLayoutChangeListener(insetsDelegate)
insetsDelegate.addInsetsListener { insets ->
binding.recyclerView.updatePadding(
bottom = insets.bottom + binding.recyclerView.paddingTop,
)
}
bind {
sourcesAdapter.items = item.items
}
}

@ -0,0 +1,19 @@
package org.koitharu.kotatsu.settings.sources.catalog
import org.koitharu.kotatsu.list.ui.ListModelDiffCallback
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.parsers.model.ContentType
data class SourceCatalogPage(
val type: ContentType,
val items: List<SourceCatalogItem>,
) : ListModel {
override fun areItemsTheSame(other: ListModel): Boolean {
return other is SourceCatalogPage && other.type == type
}
override fun getChangePayload(previousState: ListModel): Any? {
return ListModelDiffCallback.PAYLOAD_NESTED_LIST_CHANGED
}
}

@ -10,24 +10,21 @@ 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.tabs.TabLayout import com.google.android.material.tabs.TabLayoutMediator
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.core.model.titleResId
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.util.ext.firstVisibleItemPosition import org.koitharu.kotatsu.core.util.ext.getDisplayName
import org.koitharu.kotatsu.core.util.ext.getLocaleDisplayName
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.databinding.ActivitySourcesCatalogBinding import org.koitharu.kotatsu.databinding.ActivitySourcesCatalogBinding
import org.koitharu.kotatsu.main.ui.owners.AppBarOwner import org.koitharu.kotatsu.main.ui.owners.AppBarOwner
import org.koitharu.kotatsu.parsers.model.ContentType
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
class SourcesCatalogActivity : BaseActivity<ActivitySourcesCatalogBinding>(), class SourcesCatalogActivity : BaseActivity<ActivitySourcesCatalogBinding>(),
TabLayout.OnTabSelectedListener,
OnListItemClickListener<SourceCatalogItem.Source>, OnListItemClickListener<SourceCatalogItem.Source>,
AppBarOwner, MenuItem.OnActionExpandListener { AppBarOwner, MenuItem.OnActionExpandListener {
@ -43,19 +40,17 @@ class SourcesCatalogActivity : BaseActivity<ActivitySourcesCatalogBinding>(),
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(ActivitySourcesCatalogBinding.inflate(layoutInflater)) setContentView(ActivitySourcesCatalogBinding.inflate(layoutInflater))
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
initTabs() 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()
adapter = sourcesAdapter viewModel.content.observe(this, pagerAdapter)
}
viewModel.content.observe(this, sourcesAdapter)
viewModel.onActionDone.observeEvent( viewModel.onActionDone.observeEvent(
this, this,
ReversibleActionObserver(viewBinding.recyclerView), ReversibleActionObserver(viewBinding.pager),
) )
viewModel.locale.observe(this) { viewModel.locale.observe(this) {
supportActionBar?.subtitle = it.getLocaleDisplayName(this) supportActionBar?.subtitle = it?.toLocale().getDisplayName(this)
} }
addMenuProvider(SourcesCatalogMenuProvider(this, viewModel, this)) addMenuProvider(SourcesCatalogMenuProvider(this, viewModel, this))
} }
@ -65,27 +60,15 @@ class SourcesCatalogActivity : BaseActivity<ActivitySourcesCatalogBinding>(),
left = insets.left, left = insets.left,
right = insets.right, right = insets.right,
) )
viewBinding.recyclerView.updatePadding(
bottom = insets.bottom + viewBinding.recyclerView.paddingTop,
)
} }
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 onTabSelected(tab: TabLayout.Tab) {
viewModel.setContentType(tab.tag as ContentType)
}
override fun onTabUnselected(tab: TabLayout.Tab) = Unit
override fun onTabReselected(tab: TabLayout.Tab) {
viewBinding.recyclerView.firstVisibleItemPosition = 0
}
override fun onMenuItemActionExpand(item: MenuItem): Boolean { override fun onMenuItemActionExpand(item: MenuItem): Boolean {
viewBinding.tabs.isVisible = false 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
@ -93,21 +76,8 @@ class SourcesCatalogActivity : BaseActivity<ActivitySourcesCatalogBinding>(),
override fun onMenuItemActionCollapse(item: MenuItem): Boolean { override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
viewBinding.tabs.isVisible = true viewBinding.tabs.isVisible = true
viewBinding.pager.isUserInputEnabled = true
viewModel.performSearch(null) viewModel.performSearch(null)
return true return true
} }
private fun initTabs() {
val tabs = viewBinding.tabs
for (type in ContentType.entries) {
if (viewModel.isNsfwDisabled && type == ContentType.HENTAI) {
continue
}
val tab = tabs.newTab()
tab.setText(type.titleResId)
tab.tag = type
tabs.addTab(tab)
}
tabs.addOnTabSelectedListener(this)
}
} }

@ -9,7 +9,9 @@ import androidx.appcompat.widget.PopupMenu
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.core.view.MenuProvider import androidx.core.view.MenuProvider
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.util.ext.getLocaleDisplayName import org.koitharu.kotatsu.core.util.LocaleComparator
import org.koitharu.kotatsu.core.util.ext.getDisplayName
import org.koitharu.kotatsu.core.util.ext.toLocale
import org.koitharu.kotatsu.main.ui.owners.AppBarOwner import org.koitharu.kotatsu.main.ui.owners.AppBarOwner
class SourcesCatalogMenuProvider( class SourcesCatalogMenuProvider(
@ -57,15 +59,17 @@ class SourcesCatalogMenuProvider(
} }
private fun showLocalesMenu() { private fun showLocalesMenu() {
val locales = viewModel.locales.map { val locales = viewModel.locales.mapTo(ArrayList(viewModel.locales.size)) {
it to it.getLocaleDisplayName(activity) it to it?.toLocale()
} }
locales.sortWith(compareBy(nullsFirst(LocaleComparator())) { it.second })
val anchor: View = (activity as AppBarOwner).appBar.let { val anchor: View = (activity as AppBarOwner).appBar.let {
it.findViewById<View?>(R.id.toolbar) ?: it it.findViewById<View?>(R.id.toolbar) ?: it
} }
val menu = PopupMenu(activity, anchor) val menu = PopupMenu(activity, anchor)
for ((i, lc) in locales.withIndex()) { for ((i, lc) in locales.withIndex()) {
menu.menu.add(Menu.NONE, Menu.NONE, i, lc.second) menu.menu.add(Menu.NONE, Menu.NONE, i, lc.second.getDisplayName(activity))
} }
menu.setOnMenuItemClickListener { menu.setOnMenuItemClickListener {
viewModel.setLocale(locales.getOrNull(it.order)?.first) viewModel.setLocale(locales.getOrNull(it.order)?.first)

@ -0,0 +1,25 @@
package org.koitharu.kotatsu.settings.sources.catalog
import androidx.lifecycle.LifecycleOwner
import coil.ImageLoader
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import org.koitharu.kotatsu.core.model.titleResId
import org.koitharu.kotatsu.core.ui.BaseListAdapter
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
class SourcesCatalogPagerAdapter(
listener: OnListItemClickListener<SourceCatalogItem.Source>,
coil: ImageLoader,
lifecycleOwner: LifecycleOwner,
) : BaseListAdapter<SourceCatalogPage>(), TabLayoutMediator.TabConfigurationStrategy {
init {
delegatesManager.addDelegate(sourceCatalogPageAD(listener, coil, lifecycleOwner))
}
override fun onConfigureTab(tab: TabLayout.Tab, position: Int) {
val item = items.getOrNull(position) ?: return
tab.setText(item.type.titleResId)
}
}

@ -1,5 +1,6 @@
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.internal.lifecycle.RetainedLifecycleImpl
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
@ -8,9 +9,10 @@ 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.emptyFlow
import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
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.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.BaseViewModel import org.koitharu.kotatsu.core.ui.BaseViewModel
@ -21,6 +23,8 @@ 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.Locale import java.util.Locale
import javax.inject.Inject import javax.inject.Inject
@ -28,30 +32,23 @@ import javax.inject.Inject
class SourcesCatalogViewModel @Inject constructor( class SourcesCatalogViewModel @Inject constructor(
private val repository: MangaSourcesRepository, private val repository: MangaSourcesRepository,
private val listProducerFactory: SourcesCatalogListProducer.Factory, private val listProducerFactory: SourcesCatalogListProducer.Factory,
settings: AppSettings, private val settings: AppSettings,
) : BaseViewModel() { ) : BaseViewModel() {
private val lifecycle = RetainedLifecycleImpl() private val lifecycle = RetainedLifecycleImpl()
private var searchQuery: String? = null private var searchQuery: String? = null
val onActionDone = MutableEventFlow<ReversibleAction>() val onActionDone = MutableEventFlow<ReversibleAction>()
val contentType = MutableStateFlow(ContentType.entries.first())
val locales = repository.allMangaSources.mapToSet { it.locale } val locales = repository.allMangaSources.mapToSet { it.locale }
val locale = MutableStateFlow(Locale.getDefault().language.takeIf { it in locales }) val locale = MutableStateFlow(Locale.getDefault().language.takeIf { it in locales })
val isNsfwDisabled = settings.isNsfwContentDisabled private val listProducers = locale.map { lc ->
createListProducers(lc)
}.stateIn(viewModelScope, SharingStarted.Eagerly, createListProducers(locale.value))
private val listProducer: StateFlow<SourcesCatalogListProducer?> = combine( val content: StateFlow<List<SourceCatalogPage>> = listProducers.flatMapLatest {
locale, val flows = it.entries.map { (type, producer) -> producer.list.map { x -> SourceCatalogPage(type, x) } }
contentType, combine<SourceCatalogPage, List<SourceCatalogPage>>(flows, Array<SourceCatalogPage>::toList)
) { lc, type -> }.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, emptyList())
listProducerFactory.create(lc, type, lifecycle).also {
it.setQuery(searchQuery)
}
}.stateIn(viewModelScope, SharingStarted.Eagerly, null)
val content = listProducer.flatMapLatest {
it?.list ?: emptyFlow()
}.stateIn(viewModelScope, SharingStarted.Lazily, emptyList())
override fun onCleared() { override fun onCleared() {
super.onCleared() super.onCleared()
@ -60,21 +57,30 @@ class SourcesCatalogViewModel @Inject constructor(
fun performSearch(query: String?) { fun performSearch(query: String?) {
searchQuery = query searchQuery = query
listProducer.value?.setQuery(query) listProducers.value.forEach { (_, v) -> v.setQuery(query) }
} }
fun setLocale(value: String?) { fun setLocale(value: String?) {
locale.value = value locale.value = value
} }
fun setContentType(value: ContentType) {
contentType.value = value
}
fun addSource(source: MangaSource) { fun addSource(source: MangaSource) {
launchJob(Dispatchers.Default) { launchJob(Dispatchers.Default) {
val rollback = repository.setSourceEnabled(source, true) val rollback = repository.setSourceEnabled(source, true)
onActionDone.call(ReversibleAction(R.string.source_enabled, rollback)) onActionDone.call(ReversibleAction(R.string.source_enabled, rollback))
} }
} }
@MainThread
private fun createListProducers(lc: String?): Map<ContentType, SourcesCatalogListProducer> {
val types = EnumSet.allOf(ContentType::class.java)
if (settings.isNsfwContentDisabled) {
types.remove(ContentType.HENTAI)
}
return types.associateWithTo(EnumMap(ContentType::class.java)) { type ->
listProducerFactory.create(lc, type, lifecycle).also {
it.setQuery(searchQuery)
}
}
}
} }

@ -11,7 +11,8 @@
android:id="@+id/appbar" android:id="@+id/appbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:fitsSystemWindows="true"> android:fitsSystemWindows="true"
app:liftOnScroll="false">
<com.google.android.material.appbar.MaterialToolbar <com.google.android.material.appbar.MaterialToolbar
android:id="@id/toolbar" android:id="@id/toolbar"
@ -27,21 +28,10 @@
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<FrameLayout <androidx.viewpager2.widget.ViewPager2
android:id="@+id/layout_list" android:id="@+id/pager"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"> app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />
<org.koitharu.kotatsu.core.ui.list.fastscroll.FastScrollRecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:orientation="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/item_source_config" />
</FrameLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/layout_list"
android:layout_width="match_parent"
android:layout_height="match_parent">
<org.koitharu.kotatsu.core.ui.list.fastscroll.FastScrollRecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:orientation="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/item_source_config" />
</FrameLayout>
Loading…
Cancel
Save