Library presenter no longer using oberserables

This lagged sorting for D&D so time to get rid of it
This commit is contained in:
Jay 2020-02-08 04:40:41 -08:00
parent 362c39056b
commit 5d31737c68
11 changed files with 224 additions and 80 deletions

View File

@ -13,7 +13,10 @@ class LibraryManga : MangaImpl() {
fun mangaType(): Int {
val sourceManager:SourceManager by injectLazy()
return if (currentGenres()?.split(",")?.any
{ tag -> tag.trim().toLowerCase(Locale.getDefault()) == "long strip" } == true ||
{ tag ->
val trimmedTag = tag.trim().toLowerCase(Locale.getDefault())
trimmedTag == "long strip" || trimmedTag == "manwha"
} == true ||
sourceManager.getOrStub(source).name.contains("webtoon", true))
MANWHA
else MANGA

View File

@ -22,6 +22,7 @@ import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.MangaImpl
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.download.DownloadService
import eu.kanade.tachiyomi.data.download.DownloadServiceListener
import eu.kanade.tachiyomi.data.glide.GlideApp
import eu.kanade.tachiyomi.data.library.LibraryUpdateRanker.rankingScheme
import eu.kanade.tachiyomi.data.library.LibraryUpdateService.Companion.start
@ -52,6 +53,7 @@ import rx.schedulers.Schedulers
import timber.log.Timber
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.util.ArrayList
import java.util.Date
import java.util.concurrent.TimeUnit
@ -233,6 +235,16 @@ class LibraryUpdateService(
val categoryId = intent.getIntExtra(KEY_CATEGORY, -1)
return getMangaToUpdate(categoryId, target)
}
private var listener:LibraryServiceListener? = null
fun setListener(listener: LibraryServiceListener) {
this.listener = listener
}
fun removeListener() {
listener = null
}
}
/**
@ -353,9 +365,11 @@ class LibraryUpdateService(
val fetchedChapters = try { source.fetchChapterList(manga).toBlocking().single() }
catch(e: java.lang.Exception) {
failedUpdates.add(manga)
emptyList<SChapter>() }
emptyList<SChapter>()
}
if (fetchedChapters.isNotEmpty()) {
val newChapters = syncChaptersWithSource(db, fetchedChapters, manga, source).first
listener?.updatedManga(manga)
if (newChapters.isNotEmpty()) {
if (downloadNew && (categoriesToDownload.isEmpty() || manga.category in categoriesToDownload)) {
downloadChapters(manga, newChapters.sortedBy { it.chapter_number })
@ -592,5 +606,8 @@ class LibraryUpdateService(
intent.action = MainActivity.SHORTCUT_RECENTLY_UPDATED
return PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
}
interface LibraryServiceListener {
fun updatedManga(manga: LibraryManga)
}

View File

@ -63,7 +63,7 @@ class ExtensionHolder(view: View, override val adapter: ExtensionAdapter) :
//background = VectorDrawableCompat.create(resources!!, R.drawable.button_bg_transparent, null)
setTextColor(ContextCompat.getColorStateList(context, R.drawable.button_text_state))
backgroundTintList = ContextCompat.getColorStateList(context, R.drawable.button_bg_transparent)
backgroundTintList = ContextCompat.getColorStateList(context, R.color.button_bg)
val extension = item.extension

View File

@ -28,6 +28,7 @@ import eu.kanade.tachiyomi.util.view.updateLayoutParams
import eu.kanade.tachiyomi.util.view.updatePaddingRelative
import eu.kanade.tachiyomi.widget.AutofitRecyclerView
import kotlinx.android.synthetic.main.library_category.view.*
import kotlinx.android.synthetic.main.library_controller.*
import kotlinx.coroutines.delay
import rx.subscriptions.CompositeSubscription
import uy.kohesive.injekt.injectLazy
@ -128,7 +129,7 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
swipe_refresh.setOnRefreshListener {
val inQueue = LibraryUpdateService.categoryInQueue(category.id)
controller.snack?.dismiss()
controller.snack = snack(
controller.snack = controller.snackbar_layout.snack(
resources.getString(
when {
inQueue -> R.string.category_already_in_queue

View File

@ -37,6 +37,8 @@ import eu.kanade.tachiyomi.data.database.models.LibraryManga
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.download.DownloadService
import eu.kanade.tachiyomi.data.download.DownloadServiceListener
import eu.kanade.tachiyomi.data.library.LibraryServiceListener
import eu.kanade.tachiyomi.data.library.LibraryUpdateService
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
@ -79,7 +81,8 @@ class LibraryController(
ActionMode.Callback,
ChangeMangaCategoriesDialog.Listener,
MigrationInterface,
DownloadServiceListener {
DownloadServiceListener,
LibraryServiceListener {
/**
* Position of the active category.
@ -273,15 +276,17 @@ class LibraryController(
super.onChangeStarted(handler, type)
if (type.isEnter) {
activity?.tabs?.setupWithViewPager(library_pager)
presenter.subscribeLibrary()
presenter.getLibrary()
DownloadService.addListener(this)
DownloadService.callListeners()
LibraryUpdateService.setListener(this)
}
}
override fun onDestroyView(view: View) {
adapter?.onDestroy()
DownloadService.removeListener(this)
LibraryUpdateService.removeListener()
adapter = null
actionMode = null
tabsVisibilitySubscription?.unsubscribe()
@ -298,6 +303,11 @@ class LibraryController(
bottom_sheet.adjustTitleMargin(downloading)
}
}
override fun updatedManga(manga: LibraryManga) {
presenter.updateManga(manga)
}
override fun onDetach(view: View) {
destroyActionModeIfNeeded()
snack?.dismiss()
@ -347,9 +357,9 @@ class LibraryController(
tabsVisibilitySubscription?.unsubscribe()
tabsVisibilitySubscription = tabsVisibilityRelay.subscribe { visible ->
val tabAnimator = (activity as? MainActivity)?.tabAnimator ?: return@subscribe
if (visible && tabAnimator.getHeight() == 0) {
if (visible) {
tabAnimator.expand()
} else if (!visible && tabAnimator.getHeight() != 0) {
} else if (!visible) {
tabAnimator.collapse()
}
}
@ -746,7 +756,7 @@ class LibraryController(
presenter.removeMangaFromLibrary(mangas)
destroyActionModeIfNeeded()
snack?.dismiss()
snack = pager_layout?.snack(activity?.getString(R.string.manga_removed_library) ?: "", Snackbar
snack = snackbar_layout?.snack(activity?.getString(R.string.manga_removed_library) ?: "", Snackbar
.LENGTH_INDEFINITE) {
var undoing = false
setAction(R.string.action_undo) {

View File

@ -33,10 +33,16 @@ import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import eu.kanade.tachiyomi.util.lang.combineLatest
import eu.kanade.tachiyomi.util.lang.isNullOrUnsubscribed
import eu.kanade.tachiyomi.util.lang.removeArticles
import eu.kanade.tachiyomi.util.system.launchUI
import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup.Companion.STATE_EXCLUDE
import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup.Companion.STATE_IGNORE
import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup.Companion.STATE_INCLUDE
import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup.Companion.STATE_REALLY_EXCLUDE
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import rx.Observable
import rx.Subscription
import rx.android.schedulers.AndroidSchedulers
@ -80,36 +86,21 @@ class LibraryPresenter(
var allCategories: List<Category> = emptyList()
private set
/**
* Relay used to apply the UI filters to the last emission of the library.
* List of all manga to update the
*/
private val filterTriggerRelay = BehaviorRelay.create(Unit)
private var rawMangaMap:LibraryMap? = null
/**
* Relay used to apply the UI update to the last emission of the library.
*/
private val downloadTriggerRelay = BehaviorRelay.create(Unit)
/**
* Relay used to apply the selected sorting method to the last emission of the library.
*/
private val sortTriggerRelay = BehaviorRelay.create(Unit)
/**
* Library subscription.
*/
private var librarySubscription: Subscription? = null
private var lastCategoryId:Int? = null
private var currentMangaMap:LibraryMap? = null
override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState)
subscribeLibrary()
getLibrary()
}
/**
* Subscribes to library if needed.
*/
fun subscribeLibrary() {
/* fun subscribeLibrary() {
if (librarySubscription.isNullOrUnsubscribed()) {
librarySubscription = getLibraryObservable()
.combineLatest(downloadTriggerRelay.observeOn(Schedulers.io())) {
@ -126,6 +117,22 @@ class LibraryPresenter(
view.onNextLibraryUpdate(categories, mangaMap)
})
}
}*/
fun getLibrary() {
launchUI {
val mangaMap = withContext(Dispatchers.IO) {
val library = getLibraryFromDB()
library.apply { setDownloadCount(library.mangaMap) }
rawMangaMap = library.mangaMap
var mangaMap = library.mangaMap
mangaMap = applyFilters(mangaMap)
mangaMap = applySort(mangaMap)
mangaMap
}
currentMangaMap = mangaMap
view?.onNextLibraryUpdate(categories, mangaMap)
}
}
/**
@ -228,7 +235,6 @@ class LibraryPresenter(
val catSorted = applySort(mapOf(catId to categoryManga), catId)
val mutableMap = map.toMutableMap()
mutableMap[catId] = catSorted.values.first()
lastCategoryId = null
return mutableMap
}
@ -289,8 +295,6 @@ class LibraryPresenter(
* @param map the map to sort.
*/
private fun applySort(map: LibraryMap): LibraryMap {
if (lastCategoryId != null) return applyCatSort(map, lastCategoryId)
val sortingMode = preferences.librarySortingMode().getOrDefault()
val lastReadManga by lazy {
@ -408,7 +412,34 @@ class LibraryPresenter(
*
* @return an observable of the categories and its manga.
*/
private fun getLibraryObservable(): Observable<Library> {
private fun getLibraryFromDB(): Library {
val categories = db.getCategories().executeAsBlocking().toMutableList()
val libraryAsList = preferences.libraryAsList()
val showCategories = preferences.showCategories().getOrDefault()
var libraryManga = db.getLibraryMangas().executeAsBlocking()
if (!showCategories)
libraryManga = libraryManga.distinctBy { it.id }
val libraryMap = libraryManga.map { manga ->
LibraryItem(manga, libraryAsList)
}.groupBy {
if (showCategories) it.manga.category else 0
}
if (libraryMap.containsKey(0))
categories.add(0, createDefaultCategory())
this.allCategories = categories
this.categories = if (!preferences.showCategories().getOrDefault())
arrayListOf(createDefaultCategory())
else categories
return Library(this.categories, libraryMap)
}
/**
* Get the categories and all its manga from the database.
*
* @return an observable of the categories and its manga.
*/
/*private fun getLibraryObservable(): Observable<Library> {
return Observable.combineLatest(getCategoriesObservable(), getLibraryMangasObservable()) { dbCategories, libraryManga ->
val categories = if (libraryManga.containsKey(0))
arrayListOf(createDefaultCategory()) + dbCategories
@ -420,7 +451,7 @@ class LibraryPresenter(
else categories
Library(this.categories, libraryManga)
}
}
}*/
private fun createDefaultCategory(): Category {
val default = Category.createDefault(context)
@ -429,7 +460,7 @@ class LibraryPresenter(
else default.mangaOrder = defOrder.split("/").mapNotNull { it.toLongOrNull() }
return default
}
/*
/**
* Get the categories from the database.
*
@ -458,36 +489,60 @@ class LibraryPresenter(
}
}
}
*/
/**
* Requests the library to be filtered.
*/
fun requestFilterUpdate() {
filterTriggerRelay.call(Unit)
launchUI {
var mangaMap = rawMangaMap ?: return@launchUI
mangaMap = withContext(Dispatchers.IO) { applyFilters(mangaMap) }
mangaMap = withContext(Dispatchers.IO) { applySort(mangaMap) }
currentMangaMap = mangaMap
view?.onNextLibraryUpdate(categories, mangaMap)
}
}
/**
* Requests the library to have download badges added.
*/
fun requestDownloadBadgesUpdate() {
downloadTriggerRelay.call(Unit)
//getLibrary()
launchUI {
val mangaMap = rawMangaMap ?: return@launchUI
withContext(Dispatchers.IO) { setDownloadCount(mangaMap) }
rawMangaMap = mangaMap
val current = currentMangaMap ?: return@launchUI
withContext(Dispatchers.IO) { setDownloadCount(current) }
currentMangaMap = current
view?.onNextLibraryUpdate(categories, current)
}
}
/**
* Requests the library to be sorted.
*/
fun requestSortUpdate() {
sortTriggerRelay.call(Unit)
launchUI {
var mangaMap = currentMangaMap ?: return@launchUI
mangaMap = withContext(Dispatchers.IO) { applySort(mangaMap) }
currentMangaMap = mangaMap
view?.onNextLibraryUpdate(categories, mangaMap)
}
}
fun requestCatSortUpdate(catId: Int) {
lastCategoryId = catId
sortTriggerRelay.call(Unit)
launchUI {
var mangaMap = currentMangaMap ?: return@launchUI
mangaMap = withContext(Dispatchers.IO) { applyCatSort(mangaMap, catId) }
currentMangaMap = mangaMap
view?.onNextLibraryUpdate(categories, mangaMap)
}
}
fun requestFullUpdate() {
librarySubscription?.unsubscribe()
subscribeLibrary()
//librarySubscription?.unsubscribe()
getLibrary()
}
/**
@ -495,7 +550,7 @@ class LibraryPresenter(
*/
fun onOpenManga() {
// Avoid further db updates for the library when it's not needed
librarySubscription?.let { remove(it) }
//librarySubscription?.let { remove(it) }
}
/**
@ -517,18 +572,19 @@ class LibraryPresenter(
* @param deleteChapters whether to also delete downloaded chapters.
*/
fun removeMangaFromLibrary(mangas: List<Manga>) {
GlobalScope.launch(Dispatchers.IO, CoroutineStart.DEFAULT) {
// Create a set of the list
val mangaToDelete = mangas.distinctBy { it.id }
mangaToDelete.forEach { it.favorite = false }
Observable.fromCallable { db.insertMangas(mangaToDelete).executeAsBlocking() }
.onErrorResumeNext { Observable.empty() }
.subscribeOn(Schedulers.io())
.subscribe()
db.insertMangas(mangaToDelete).executeAsBlocking()
getLibrary()
}
}
fun confirmDeletion(mangas: List<Manga>) {
Observable.fromCallable {
GlobalScope.launch(Dispatchers.IO, CoroutineStart.DEFAULT) {
val mangaToDelete = mangas.distinctBy { it.id }
mangaToDelete.forEach { manga ->
db.resetMangaInfo(manga).executeAsBlocking()
@ -537,19 +593,45 @@ class LibraryPresenter(
if (source != null)
downloadManager.deleteManga(manga, source)
}
}.subscribeOn(Schedulers.io()).subscribe()
}
}
fun updateManga(manga: LibraryManga) {
GlobalScope.launch(Dispatchers.IO, CoroutineStart.DEFAULT) {
val rawMap = rawMangaMap ?: return@launch
val currentMap = currentMangaMap ?: return@launch
rawMap.apply {
forEach {
it.value.forEach { item ->
if (item.manga.id == manga.id)
item.manga.unread = manga.unread
}
}
}
currentMap.apply {
forEach {
it.value.forEach { item ->
if (item.manga.id == manga.id)
item.manga.unread = manga.unread
}
}
}
rawMangaMap = rawMap
currentMangaMap = currentMap
requestSortUpdate()
}
}
fun addMangas(mangas: List<Manga>) {
GlobalScope.launch(Dispatchers.IO, CoroutineStart.DEFAULT) {
val mangaToAdd = mangas.distinctBy { it.id }
mangaToAdd.forEach { it.favorite = true }
Observable.fromCallable { db.insertMangas(mangaToAdd).executeAsBlocking() }
.onErrorResumeNext { Observable.empty() }
.subscribeOn(Schedulers.io())
.subscribe()
db.insertMangas(mangaToAdd).executeAsBlocking()
getLibrary()
mangaToAdd.forEach { db.insertManga(it).executeAsBlocking() }
}
}
/**
* Move the given list of manga to categories.

View File

@ -28,8 +28,6 @@ import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.system.launchUI
import eu.kanade.tachiyomi.util.view.gone
import eu.kanade.tachiyomi.util.view.inflate
import eu.kanade.tachiyomi.util.view.marginBottom
import eu.kanade.tachiyomi.util.view.marginTop
import eu.kanade.tachiyomi.util.view.updateLayoutParams
import eu.kanade.tachiyomi.util.view.updatePadding
import eu.kanade.tachiyomi.util.view.updatePaddingRelative
@ -85,21 +83,18 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri
}
var onGroupClicked: (Int) -> Unit = { _ -> }
val recycler = androidx.recyclerview.widget.RecyclerView(context)
var pager:View? = null
fun onCreate(pagerView:View) {
if (context.resources.configuration?.orientation == Configuration.ORIENTATION_LANDSCAPE) {
if (context.resources.configuration?.orientation == Configuration.ORIENTATION_LANDSCAPE
|| isTablet()) {
sideLayout.orientation = HORIZONTAL
val marginValue = 10.dpToPx
arrayListOf(sortingLayout).forEach {
it.updateLayoutParams<MarginLayoutParams> {
sortingLayout.updateLayoutParams<MarginLayoutParams> {
bottomMargin = 0
topMargin = 0
}
}
sortScrollView.updatePadding(
bottom = marginValue,
bottom = 10.dpToPx,
top = 0
)
}
@ -116,12 +111,14 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri
}
pager = pagerView
pager?.setPadding(0, 0, 0, topbar.height)
updateTitle()
val shadow:View = (pagerView.parent as ViewGroup).findViewById(R.id.shadow2)
val coordLayout:View = (pagerView.parent as ViewGroup).findViewById(R.id.snackbar_layout)
sheetBehavior?.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
override fun onSlide(bottomSheet: View, progress: Float) {
updateRootPadding(progress)
topbar.alpha = 1 - progress
shadow.alpha = (1 - progress) * 0.25f
}
override fun onStateChanged(p0: View, state: Int) {
@ -136,6 +133,7 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri
if (sheetBehavior?.state == BottomSheetBehavior.STATE_COLLAPSED) {
val height = context.resources.getDimensionPixelSize(R.dimen.rounder_radius)
pager?.setPadding(0, 0, 0, topbar.height - height)
coordLayout.setPadding(0, 0, 0, topbar.height)
}
else {
updateRootPadding()
@ -146,10 +144,20 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri
mainSortTextView.setOnClickListener { showMainSortOptions() }
catSortTextView.setOnClickListener { showCatSortOptions() }
clearButton.setOnClickListener { clearFilters() }
downloadCheckbox.isChecked = preferences.downloadBadge().getOrDefault()
downloadCheckbox.setOnCheckedChangeListener { _, isChecked ->
preferences.downloadBadge().set(isChecked)
onGroupClicked(ACTION_BADGE)
}
displayGroup.bindToPreference(preferences.libraryAsList())
}
private fun isTablet(): Boolean {
return (context.resources.configuration.screenLayout and Configuration
.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE
}
fun updateTitle() {
launchUI {
val text = withContext(Dispatchers.IO) {
@ -422,7 +430,7 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri
)
}
mainSortTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(
drawable, null, null, null
null, null, drawable, null
)
mainSortTextView.text = withContext(Dispatchers.IO) {
if (sortId == LibrarySort.DRAG_AND_DROP)
@ -471,7 +479,7 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri
)
}
catSortTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(
drawable, null, null, null
null, null, drawable, null
)
catSortTextView.text = withContext(Dispatchers.IO) {
context.getString(

View File

@ -178,7 +178,7 @@ open class MainActivity : BaseActivity(), DownloadServiceListener {
}
nav_view.setCheckedItem(id)
}
else if (currentRoot?.tag()?.toIntOrNull() == id) {
else if (currentRoot.tag()?.toIntOrNull() == id) {
when (id) {
R.id.nav_drawer_recents -> {
if (router.backstack.size > 1) router.popToRoot()

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/gray_button">
<item android:id="@android:id/mask">
<shape android:shape="rectangle">
<solid android:color="@color/gray_button" />
</shape>
</item>
</ripple>

View File

@ -39,7 +39,6 @@
android:paddingStart="20dp"
android:paddingTop="10dp"
android:paddingEnd="20dp"
android:paddingBottom="10dp"
android:scrollbars="none">
<LinearLayout
@ -72,10 +71,8 @@
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:clipToPadding="false"
android:paddingStart="30dp"
android:paddingTop="0dp"
android:paddingStart="23dp"
android:paddingEnd="20dp"
android:paddingBottom="10dp"
android:scrollbars="none">
<LinearLayout
@ -103,8 +100,10 @@
android:layout_marginEnd="20dp"
android:clickable="true"
android:drawablePadding="16dp"
android:background="@drawable/square_ripple"
android:focusable="true"
android:gravity="center"
android:padding="5dp"
android:text="srgdg"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body2"
android:textColor="?android:attr/textColorPrimary"
@ -115,7 +114,9 @@
android:id="@+id/catSortTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/square_ripple"
android:layout_marginEnd="20dp"
android:padding="5dp"
android:clickable="true"
android:drawablePadding="16dp"
android:focusable="true"
@ -132,6 +133,8 @@
android:id="@+id/displayLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="7dp"
android:layout_marginEnd="14dp"
android:baselineAligned="false"
android:gravity="center|start"
android:orientation="horizontal">
@ -169,6 +172,12 @@
android:text="@string/action_display_list" />
</RadioGroup>
</LinearLayout>
<CheckBox
android:id="@+id/downloadCheckbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/action_display_download_badge"/>
</LinearLayout>
</HorizontalScrollView>
</LinearLayout>

View File

@ -15,6 +15,11 @@
android:layout_height="match_parent" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/snackbar_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<eu.kanade.tachiyomi.widget.EmptyView
android:id="@+id/empty_view"
android:layout_width="wrap_content"