Attempting making library as one big list

This commit is contained in:
Jay 2020-02-17 00:14:38 -08:00
parent fc0ab3e878
commit 83441a5ebd
20 changed files with 834 additions and 249 deletions

View File

@ -113,6 +113,8 @@ object PreferenceKeys {
const val libraryLayout = "pref_display_library_layout"
const val libraryUsingPager = "library_using_pager"
const val lang = "app_language"
const val dateFormat = "app_date_format"

View File

@ -11,9 +11,9 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.source.Source
import java.io.File
import java.util.Locale
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.Locale
import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
fun <T> Preference<T>.getOrDefault(): T = get() ?: defaultValue()!!
@ -173,6 +173,8 @@ class PreferencesHelper(val context: Context) {
fun libraryLayout() = rxPrefs.getInteger(Keys.libraryLayout, 1)
fun libraryUsingPager() = rxPrefs.getBoolean(Keys.libraryUsingPager, false)
fun downloadBadge() = rxPrefs.getBoolean(Keys.downloadBadge, false)
fun filterDownloaded() = rxPrefs.getInteger(Keys.filterDownloaded, 0)

View File

@ -6,11 +6,10 @@ import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.ui.category.CategoryAdapter
import eu.kanade.tachiyomi.util.lang.chop
import eu.kanade.tachiyomi.util.lang.removeArticles
import eu.kanade.tachiyomi.util.system.launchUI
import kotlinx.coroutines.delay
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat
import java.util.Calendar
@ -22,19 +21,18 @@ import java.util.Locale
*
* @param view the fragment containing this adapter.
*/
class LibraryCategoryAdapter(val view: LibraryCategoryView) :
FlexibleAdapter<LibraryItem>(null, view, true) {
class LibraryCategoryAdapter(val libraryListener: LibraryListener) :
FlexibleAdapter<IFlexible<*>>(null, libraryListener, true) {
init {
setDisplayHeadersAtStartUp(!Injekt.get<PreferencesHelper>().libraryUsingPager()
.getOrDefault())
}
/**
* The list of manga in this category.
*/
private var mangas: List<LibraryItem> = emptyList()
/**
* Listener called when an item of the list press start reading.
*/
val libraryListener: LibraryListener = view
/**
* Sets a list of manga in the adapter.
*
@ -47,13 +45,26 @@ class LibraryCategoryAdapter(val view: LibraryCategoryView) :
performFilter()
}
/**
* Returns the position in the adapter for the given manga.
*
* @param manga the manga to find.
*/
fun indexOf(categoryOrder: Int): Int {
return currentItems.indexOfFirst {
if (it is LibraryHeaderItem) it.category.order == categoryOrder
else false }
}
/**
* Returns the position in the adapter for the given manga.
*
* @param manga the manga to find.
*/
fun indexOf(manga: Manga): Int {
return currentItems.indexOfFirst { it.manga.id == manga.id }
return currentItems.indexOfFirst {
if (it is LibraryItem) it.manga.id == manga.id
else false }
}
fun performFilter() {
@ -64,7 +75,7 @@ class LibraryCategoryAdapter(val view: LibraryCategoryView) :
else {
updateDataSet(mangas.filter { it.filter(s) })
}
isLongPressDragEnabled = view.canDrag() && s.isNullOrBlank()
isLongPressDragEnabled = libraryListener.canDrag() && s.isNullOrBlank()
}
override fun onCreateBubbleText(position: Int):String {
@ -74,6 +85,13 @@ class LibraryCategoryAdapter(val view: LibraryCategoryView) :
"Bottom"
} else { // Get and show the first character
val iFlexible: IFlexible<*>? = getItem(position)
return if (iFlexible is LibraryHeaderItem) {
iFlexible.category.name
} else {
val db:DatabaseHelper by injectLazy()
val category = db.getCategoriesForManga((iFlexible as LibraryItem).manga).executeAsBlocking().firstOrNull()?.name
category?.chop(10) ?: "Default"
}
val preferences:PreferencesHelper by injectLazy()
when (preferences.librarySortingMode().getOrDefault()) {
LibrarySort.DRAG_AND_DROP -> {
@ -145,5 +163,6 @@ class LibraryCategoryAdapter(val view: LibraryCategoryView) :
*/
fun startReading(position: Int)
fun onItemReleased(position: Int)
fun canDrag(): Boolean
}
}

View File

@ -79,6 +79,7 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
fun onCreate(controller: LibraryController) {
this.controller = controller
adapter = LibraryCategoryAdapter(this)
recycler = if (preferences.libraryLayout().getOrDefault() == 0) {
(swipe_refresh.inflate(R.layout.library_list_recycler) as RecyclerView).apply {
layoutManager = LinearLayoutManager(context)
@ -89,7 +90,6 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
}
}
adapter = LibraryCategoryAdapter(this)
recycler.setHasFixedSize(true)
recycler.adapter = adapter
@ -155,7 +155,7 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
.subscribe {
if (it == category.id) {
adapter.currentItems.forEach { item ->
controller.setSelection(item.manga, true)
controller.setSelection((item as LibraryItem).manga, true)
}
controller.invalidateActionMode()
}
@ -167,11 +167,12 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
if (it.second in -2..-1) {
val items = adapter.currentItems.toMutableList()
val mangas = controller.selectedMangas
val selectedManga = items.filter { item -> item.manga in mangas }
val selectedManga = items.filter { item -> (item as LibraryItem).manga in
mangas }
items.removeAll(selectedManga)
if (it.second == -1) items.addAll(0, selectedManga)
else items.addAll(selectedManga)
adapter.setItems(items)
adapter.setItems(items.filterIsInstance<LibraryItem>())
adapter.notifyDataSetChanged()
saveDragSort()
}
@ -179,7 +180,7 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
}
}
fun canDrag(): Boolean {
override fun canDrag(): Boolean {
val sortingMode = preferences.librarySortingMode().getOrDefault()
val filterOff = preferences.filterCompleted().getOrDefault() +
preferences.filterTracked().getOrDefault() +
@ -212,6 +213,7 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
val mangaForCategory = event.getMangaForCategory(category).orEmpty()
adapter.setItems(mangaForCategory)
adapter.hideAllHeaders()
swipe_refresh.isEnabled = !preferences.hideCategories().getOrDefault()
@ -283,7 +285,7 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
*/
override fun onItemClick(view: View?, position: Int): Boolean {
// If the action mode is created and the position is valid, toggle the selection.
val item = adapter.getItem(position) ?: return false
val item = adapter.getItem(position) as? LibraryItem ?: return false
return if (adapter.mode == SelectableAdapter.Mode.MULTI) {
lastClickPosition = position
toggleSelection(position)
@ -328,13 +330,13 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
}
override fun startReading(position: Int) {
val manga = adapter.getItem(position)?.manga ?: return
val manga = (adapter.getItem(position) as? LibraryItem)?.manga ?: return
if (adapter.mode == SelectableAdapter.Mode.MULTI) toggleSelection(position)
else controller.startReading(manga)
}
private fun saveDragSort() {
val mangaIds = adapter.currentItems.mapNotNull { it.manga.id }
val mangaIds = adapter.currentItems.mapNotNull { (it as? LibraryItem)?.manga?.id }
category.mangaSort = null
category.mangaOrder = mangaIds
if (category.id == 0)
@ -373,7 +375,7 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
private fun toggleSelection(position: Int) {
val item = adapter.getItem(position) ?: return
controller.setSelection(item.manga, !adapter.isSelected(position))
controller.setSelection((item as LibraryItem).manga, !adapter.isSelected(position))
controller.invalidateActionMode()
}
@ -386,7 +388,7 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
private fun setSelection(position: Int) {
val item = adapter.getItem(position) ?: return
controller.setSelection(item.manga, true)
controller.setSelection((item as LibraryItem).manga, true)
controller.invalidateActionMode()
}

View File

@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.ui.library
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.res.Configuration
import android.graphics.Color
import android.os.Bundle
@ -14,10 +13,18 @@ import android.view.View
import android.view.ViewGroup
import android.view.WindowInsets
import android.view.inputmethod.InputMethodManager
import android.widget.ArrayAdapter
import android.widget.Spinner
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.AppCompatSpinner
import androidx.appcompat.widget.SearchView
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.graphics.drawable.DrawableCompat
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager.widget.ViewPager
import com.afollestad.materialdialogs.MaterialDialog
import com.bluelinelabs.conductor.ControllerChangeHandler
@ -26,9 +33,10 @@ import com.f2prateek.rx.preferences.Preference
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.snackbar.BaseTransientBottomBar
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.tabs.TabLayout
import com.jakewharton.rxrelay.BehaviorRelay
import com.jakewharton.rxrelay.PublishRelay
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.SelectableAdapter
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.data.database.models.LibraryManga
@ -40,7 +48,6 @@ 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.BaseController
import eu.kanade.tachiyomi.ui.base.controller.TabbedController
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.category.CategoryController
import eu.kanade.tachiyomi.ui.download.DownloadController
@ -53,14 +60,20 @@ import eu.kanade.tachiyomi.ui.migration.manga.design.PreMigrationController
import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationListController
import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationProcedureConfig
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.util.system.dpToPx
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.setOnQueryTextChangeListener
import eu.kanade.tachiyomi.util.view.snack
import eu.kanade.tachiyomi.util.view.updateLayoutParams
import eu.kanade.tachiyomi.util.view.updatePaddingRelative
import eu.kanade.tachiyomi.util.view.visible
import eu.kanade.tachiyomi.widget.AutofitRecyclerView
import eu.kanade.tachiyomi.widget.IgnoreFirstSpinnerListener
import kotlinx.android.synthetic.main.filter_bottom_sheet.*
import kotlinx.android.synthetic.main.library_controller.*
import kotlinx.android.synthetic.main.main_activity.*
import rx.Subscription
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@ -68,12 +81,16 @@ class LibraryController(
bundle: Bundle? = null,
private val preferences: PreferencesHelper = Injekt.get()
) : BaseController(bundle),
TabbedController,
//TabbedController,
ActionMode.Callback,
ChangeMangaCategoriesDialog.Listener,
MigrationInterface,
DownloadServiceListener,
LibraryServiceListener {
LibraryServiceListener,
FlexibleAdapter.OnItemClickListener,
FlexibleAdapter.OnItemLongClickListener,
FlexibleAdapter.OnItemMoveListener,
LibraryCategoryAdapter.LibraryListener{
/**
* Position of the active category.
@ -135,14 +152,19 @@ class LibraryController(
/**
* Adapter of the view pager.
*/
private var adapter: LibraryAdapter? = null
private var pagerAdapter: LibraryAdapter? = null
private lateinit var adapter: LibraryCategoryAdapter
private lateinit var spinner: Spinner
private var lastClickPosition = -1
/**
* Drawer listener to allow swipe only for closing the drawer.
*/
private var tabsVisibilityRelay: BehaviorRelay<Boolean> = BehaviorRelay.create(false)
// private var tabsVisibilityRelay: BehaviorRelay<Boolean> = BehaviorRelay.create(false)
private var tabsVisibilitySubscription: Subscription? = null
// private var tabsVisibilitySubscription: Subscription? = null
private var observeLater:Boolean = false
@ -153,47 +175,131 @@ class LibraryController(
private var justStarted = true
private var updateScroll = true
private var spinnerAdapter: SpinnerAdapter? = null
var scrollLister = object : RecyclerView.OnScrollListener () {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val position =
(recycler.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()
val order = when (val item = adapter.getItem(position)) {
is LibraryHeaderItem -> item.category.order
is LibraryItem -> presenter.categories.find { it.id == item.manga.category }?.order
else -> null
}
if (order != null && order != activeCategory) {
preferences.lastUsedCategory().set(order)
activeCategory = order
val category = presenter.categories.find { it.order == order }
bottom_sheet.lastCategory = category
if (preferences.librarySortingMode().getOrDefault() == LibrarySort.DRAG_AND_DROP)
bottom_sheet.updateTitle()
// spinner.onItemSelectedListener = null
spinnerAdapter?.setCustomText(category?.name)
//spinner.view
//spinner.post { spinner.onItemSelectedListener = listener }
}
}
}
/**
* Recycler view of the list of manga.
*/
private lateinit var recycler: RecyclerView
var usePager = preferences.libraryUsingPager().getOrDefault()
init {
setHasOptionsMenu(true)
retainViewMode = RetainViewMode.RETAIN_DETACH
}
override fun getTitle(): String? {
return resources?.getString(R.string.label_library)
return null//if (title != null) null else resources?.getString(R.string.label_library)
}
private var title: String? = null
set(value) {
field = value
setTitle()
}
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
return inflater.inflate(R.layout.library_controller, container, false)
}
override fun onViewCreated(view: View) {
super.onViewCreated(view)
adapter = LibraryAdapter(this)
library_pager.adapter = adapter
library_pager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
override fun onPageSelected(position: Int) {
preferences.lastUsedCategory().set(position)
activeCategory = position
bottom_sheet.lastCategory = adapter?.categories?.getOrNull(position)
if (preferences.librarySortingMode().getOrDefault() == LibrarySort.DRAG_AND_DROP) bottom_sheet.updateTitle()
}
override fun onPageScrolled(
position: Int, positionOffset: Float, positionOffsetPixels: Int
) {
}
override fun onPageScrollStateChanged(state: Int) {}
})
mangaPerRow = getColumnsPreferenceForCurrentOrientation().getOrDefault()
if (usePager) {
pager_layout.visible()
fast_scroller.gone()
pagerAdapter = LibraryAdapter(this)
library_pager.adapter = pagerAdapter
library_pager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
override fun onPageSelected(position: Int) {
preferences.lastUsedCategory().set(position)
activeCategory = position
bottom_sheet.lastCategory = pagerAdapter?.categories?.getOrNull(position)
if (preferences.librarySortingMode().getOrDefault() == LibrarySort.DRAG_AND_DROP) bottom_sheet.updateTitle()
}
override fun onPageScrolled(
position: Int, positionOffset: Float, positionOffsetPixels: Int
) {
}
override fun onPageScrollStateChanged(state: Int) {}
})
}
else {
adapter = LibraryCategoryAdapter(this)
recycler = if (preferences.libraryLayout().getOrDefault() == 0) {
(swipe_refresh.inflate(R.layout.library_list_recycler) as RecyclerView).apply {
layoutManager = LinearLayoutManager(context)
}
} else {
(swipe_refresh.inflate(R.layout.library_grid_recycler) as AutofitRecyclerView).apply {
spanCount = mangaPerRow
manager.spanSizeLookup = (object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
val item = this@LibraryController.adapter.getItem(position)
return if (item is LibraryHeaderItem)
manager.spanCount else 1
}
})
}
}
recycler.setHasFixedSize(true)
recycler.adapter = adapter
//adapter.setStickyHeaders(true)
recycler_layout.addView(recycler)
adapter.fastScroller = fast_scroller
recycler.addOnScrollListener(scrollLister)
spinner = swipe_refresh.inflate(R.layout.library_spinner) as AppCompatSpinner
(activity as MainActivity).supportActionBar?.setDisplayShowCustomEnabled(true)
(activity as MainActivity).supportActionBar?.customView = spinner
spinnerAdapter = SpinnerAdapter(view.context, R.layout.library_spinner_textview,
arrayOf(resources!!.getString(R.string.label_library)))
spinner.adapter = spinnerAdapter
spinnerAdapter?.setCustomText(resources?.getString(R.string.label_library))
}
if (selectedMangas.isNotEmpty()) {
createActionModeIfNeeded()
}
bottom_sheet.onCreate(pager_layout)
//bottom_sheet.onCreate(pager_layout)
bottom_sheet.onCreate(if (usePager) pager_layout else swipe_refresh)
bottom_sheet.onGroupClicked = {
when (it) {
@ -211,6 +317,28 @@ class LibraryController(
router.pushController(DownloadController().withFadeTransaction())
}
// spinner.onItemSelectedListener = listener
/*spinner.onItemSelectedListener = IgnoreFirstSpinnerListener { position ->
if (!updateScroll) return@IgnoreFirstSpinnerListener
val headerPosition = adapter.indexOf(position) + 1
if (headerPosition > -1) (recycler.layoutManager as LinearLayoutManager)
.scrollToPositionWithOffset(position, 0)
}*/
val config = resources?.configuration
val phoneLandscape = (config?.orientation == Configuration.ORIENTATION_LANDSCAPE &&
(config.screenLayout.and(Configuration.SCREENLAYOUT_SIZE_MASK)) <
Configuration.SCREENLAYOUT_SIZE_LARGE)
// pad the recycler if the filter bottom sheet is visible
if (!usePager && !phoneLandscape) {
val height = view.context.resources.getDimensionPixelSize(R.dimen.rounder_radius) + 5.dpToPx
recycler.updatePaddingRelative(bottom = height)
fast_scroller.updateLayoutParams<CoordinatorLayout.LayoutParams> {
bottomMargin = height
}
}
if (presenter.isDownloading()) {
fab.scaleY = 1f
fab.scaleX = 1f
@ -219,9 +347,10 @@ class LibraryController(
}
presenter.onRestore()
val library = presenter.getAllManga()
if (library != null) onNextLibraryUpdate(presenter.categories, library)
if (library != null) presenter.updateViewBlocking() //onNextLibraryUpdate(presenter.categories, library)
else {
library_pager.alpha = 0f
swipe_refresh.alpha = 0f
presenter.getLibraryBlocking()
}
}
@ -229,12 +358,17 @@ class LibraryController(
override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) {
super.onChangeStarted(handler, type)
if (type.isEnter) {
activity?.tabs?.setupWithViewPager(library_pager)
if (usePager)
(activity as MainActivity).supportActionBar?.setDisplayShowCustomEnabled(true)
//activity?.tabs?.setupWithViewPager(library_pager)
presenter.getLibrary()
DownloadService.addListener(this)
DownloadService.callListeners()
LibraryUpdateService.setListener(this)
}
else if (type == ControllerChangeType.PUSH_EXIT) {
(activity as MainActivity).supportActionBar?.setDisplayShowCustomEnabled(false)
}
}
override fun onActivityResumed(activity: Activity) {
@ -251,18 +385,19 @@ class LibraryController(
}
override fun onDestroy() {
(activity as MainActivity).supportActionBar?.setDisplayShowCustomEnabled(false)
presenter.onDestroy()
super.onDestroy()
}
override fun onDestroyView(view: View) {
adapter?.onDestroy()
//adapter.onDestroy()
DownloadService.removeListener(this)
LibraryUpdateService.removeListener()
adapter = null
//adapter = null
actionMode = null
tabsVisibilitySubscription?.unsubscribe()
tabsVisibilitySubscription = null
//tabsVisibilitySubscription?.unsubscribe()
//tabsVisibilitySubscription = null
super.onDestroyView(view)
}
@ -288,7 +423,7 @@ class LibraryController(
super.onDetach(view)
}
override fun configureTabs(tabs: TabLayout) {
/*override fun configureTabs(tabs: TabLayout) {
with(tabs) {
tabGravity = TabLayout.GRAVITY_CENTER
tabMode = TabLayout.MODE_SCROLLABLE
@ -307,12 +442,53 @@ class LibraryController(
override fun cleanupTabs(tabs: TabLayout) {
tabsVisibilitySubscription?.unsubscribe()
tabsVisibilitySubscription = null
}*/
fun onNextLibraryUpdate(mangaMap: List<LibraryItem>, freshStart: Boolean = false) {
if (mangaMap.isNotEmpty()) {
empty_view.hide()
} else {
empty_view.show(R.drawable.ic_book_black_128dp, R.string.information_empty_library)
}
adapter.setItems(mangaMap)
val position = if (freshStart) adapter.indexOf(activeCategory) + 1 else null
spinner.onItemSelectedListener = null
spinnerAdapter = SpinnerAdapter(view!!.context, R.layout.library_spinner_textview,
presenter.categories.map { it.name }.toTypedArray())
spinner.adapter = spinnerAdapter
spinnerAdapter?.setCustomText(presenter.categories.find { it.order == activeCategory
}?.name ?: resources?.getString(R.string.label_library))
if (!freshStart) {
spinnerAdapter?.setCustomText(presenter.categories.find { it.order == activeCategory
}?.name ?: resources?.getString(R.string.label_library))
justStarted = false
if (swipe_refresh.alpha == 0f)
swipe_refresh.animate().alpha(1f).setDuration(500).start()
}else {
if (position != null)
(recycler.layoutManager as LinearLayoutManager)
.scrollToPositionWithOffset(position, 0)
}
spinner.onItemSelectedListener = IgnoreFirstSpinnerListener { pos ->
val headerPosition = adapter.indexOf(pos - 1) + 1
if (headerPosition > -1) {
(recycler.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(
headerPosition, 0
)
}
}
}
fun onNextLibraryUpdate(categories: List<Category>, mangaMap: Map<Int, List<LibraryItem>>,
freshStart: Boolean = false) {
val view = view ?: return
val adapter = adapter ?: return
val adapter = pagerAdapter ?: return
// Show empty view if needed
if (mangaMap.isNotEmpty()) {
@ -342,13 +518,13 @@ class LibraryController(
bottom_sheet.lastCategory = adapter.categories.getOrNull(activeCat)
bottom_sheet.updateTitle()
tabsVisibilityRelay.call(categories.size > 1)
//tabsVisibilityRelay.call(categories.size > 1)
if (freshStart || !justStarted) {
// Delay the scroll position to allow the view to be properly measured.
view.post {
if (isAttached) {
activity?.tabs?.setScrollPosition(library_pager.currentItem, 0f, true)
//activity?.tabs?.setScrollPosition(library_pager.currentItem, 0f, true)
}
}
@ -398,22 +574,50 @@ class LibraryController(
}
fun onCatSortChanged(id: Int? = null) {
val catId = id ?: adapter?.categories?.getOrNull(library_pager.currentItem)?.id ?: return
presenter.requestCatSortUpdate(catId)
val catId = id ?: presenter.categories.find { it.order == activeCategory }?.id ?: return
presenter.requestCatSortUpdate(catId)
//val catId = id ?: adapter?.categories?.getOrNull(library_pager.currentItem)?.id ?: return
// presenter.requestCatSortUpdate(catId)
}
/**
* Reattaches the adapter to the view pager to recreate fragments
*/
private fun reattachAdapter() {
val adapter = adapter ?: return
val position = (recycler.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()
if (recycler is AutofitRecyclerView && preferences.libraryLayout().getOrDefault() == 0 ||
recycler !is AutofitRecyclerView && preferences.libraryLayout().getOrDefault() > 0) {
recycler_layout.removeView(recycler)
recycler = if (preferences.libraryLayout().getOrDefault() == 0) {
(swipe_refresh.inflate(R.layout.library_list_recycler) as RecyclerView).apply {
layoutManager = LinearLayoutManager(context)
}
} else {
(swipe_refresh.inflate(R.layout.library_grid_recycler) as AutofitRecyclerView).apply {
spanCount = mangaPerRow
manager.spanSizeLookup = (object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
val item = this@LibraryController.adapter.getItem(position)
return if (item is LibraryHeaderItem)
manager.spanCount else 1
}
})
}
}
recycler.setHasFixedSize(true)
recycler.addOnScrollListener(scrollLister)
recycler_layout.addView(recycler)
}
recycler.adapter = adapter
(recycler.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(position, 0)
//val adapter = adapter ?: return
val position = library_pager.currentItem
/*val position = library_pager.currentItem
adapter.recycle = false
library_pager.adapter = adapter
library_pager.currentItem = position
adapter.recycle = true
adapter.recycle = true*/
}
/**
@ -533,8 +737,8 @@ class LibraryController(
mode.title = resources?.getString(R.string.label_selected, count)
if (preferences.librarySortingMode().getOrDefault() == LibrarySort.DRAG_AND_DROP) {
val catId = (selectedMangas.first() as? LibraryManga)?.category
val sameCat = (adapter?.categories?.getOrNull(library_pager.currentItem)?.id
== catId) && selectedMangas.all { (it as? LibraryManga)?.category == catId }
val sameCat = /*(adapter?.categories?.getOrNull(library_pager.currentItem)?.id
== catId) &&*/ selectedMangas.all { (it as? LibraryManga)?.category == catId }
menu.findItem(R.id.action_move_manga).isVisible = sameCat
}
else menu.findItem(R.id.action_move_manga).isVisible = false
@ -554,11 +758,11 @@ class LibraryController(
.negativeButton(android.R.string.no)
.show()
}
R.id.action_select_all -> {
/*R.id.action_select_all -> {
adapter?.categories?.getOrNull(library_pager.currentItem)?.id?.let {
selectAllRelay.call(it)
}
}
}*/
R.id.action_migrate -> {
router.pushController(
if (preferences.skipPreMigration().getOrDefault()) {
@ -573,12 +777,12 @@ class LibraryController(
.withFadeTransaction())
destroyActionModeIfNeeded()
}
R.id.action_to_top, R.id.action_to_bottom -> {
/*R.id.action_to_top, R.id.action_to_bottom -> {
adapter?.categories?.getOrNull(library_pager.currentItem)?.id?.let {
reorganizeRelay.call(it to if (item.itemId == R.id.action_to_top) -1 else -2)
}
destroyActionModeIfNeeded()
}
}*/
else -> return false
}
return true
@ -669,6 +873,19 @@ class LibraryController(
destroyActionModeIfNeeded()
}
override fun startReading(position: Int) {
val activity = activity ?: return
val manga = (adapter.getItem(position) as? LibraryItem)?.manga ?: return
val chapter = presenter.getFirstUnread(manga) ?: return
val intent = ReaderActivity.newIntent(activity, manga, chapter)
destroyActionModeIfNeeded()
startActivity(intent)
}
override fun onItemReleased(position: Int) {
}
fun startReading(manga: Manga) {
val activity = activity ?: return
val chapter = presenter.getFirstUnread(manga) ?: return
@ -676,6 +893,94 @@ class LibraryController(
destroyActionModeIfNeeded()
startActivity(intent)
}
override fun canDrag(): Boolean {
val sortingMode = preferences.librarySortingMode().getOrDefault()
val filterOff = preferences.filterCompleted().getOrDefault() +
preferences.filterTracked().getOrDefault() +
preferences.filterUnread().getOrDefault() +
preferences.filterCompleted().getOrDefault() == 0 &&
!preferences.hideCategories().getOrDefault()
return sortingMode == LibrarySort.DRAG_AND_DROP && filterOff &&
adapter.mode != SelectableAdapter.Mode.MULTI
}
/**
* Called when a manga is clicked.
*
* @param position the position of the element clicked.
* @return true if the item should be selected, false otherwise.
*/
override fun onItemClick(view: View?, position: Int): Boolean {
// If the action mode is created and the position is valid, toggle the selection.
val item = adapter.getItem(position) as? LibraryItem ?: return false
return if (adapter.mode == SelectableAdapter.Mode.MULTI) {
lastClickPosition = position
toggleSelection(position)
true
} else {
openManga(item.manga, 0f)
false
}
}
/**
* Called when a manga is long clicked.
*
* @param position the position of the element clicked.
*/
override fun onItemLongClick(position: Int) {
createActionModeIfNeeded()
when {
lastClickPosition == -1 -> setSelection(position)
lastClickPosition > position -> for (i in position until lastClickPosition)
setSelection(i)
lastClickPosition < position -> for (i in lastClickPosition + 1..position)
setSelection(i)
else -> setSelection(position)
}
lastClickPosition = position
}
override fun onActionStateChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
val position = viewHolder?.adapterPosition ?: return
if (actionState == 2) onItemLongClick(position)
}
/**
* Tells the presenter to toggle the selection for the given position.
*
* @param position the position to toggle.
*/
private fun toggleSelection(position: Int) {
val item = adapter.getItem(position) ?: return
setSelection((item as LibraryItem).manga, !adapter.isSelected(position))
invalidateActionMode()
}
/**
* Tells the presenter to set the selection for the given position.
*
* @param position the position to toggle.
*/
private fun setSelection(position: Int) {
val item = adapter.getItem(position) ?: return
setSelection((item as LibraryItem).manga, true)
invalidateActionMode()
}
override fun onItemMove(fromPosition: Int, toPosition: Int) { }
override fun shouldMoveItem(fromPosition: Int, toPosition: Int): Boolean {
if (adapter.selectedItemCount > 1)
return false
if (adapter.isSelected(fromPosition))
toggleSelection(fromPosition)
return true
}
}
object HeightTopWindowInsetsListener : View.OnApplyWindowInsetsListener {
@ -688,4 +993,30 @@ object HeightTopWindowInsetsListener : View.OnApplyWindowInsetsListener {
}
return insets
}
}
class SpinnerAdapter(context: Context, layoutId: Int, val array: Array<String>) :
ArrayAdapter<String>
(context, layoutId, array) {
private var mCustomText = ""
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val view = super.getView(position, convertView, parent)
val tv: TextView = view as TextView
tv.text = mCustomText
return view
}
fun setCustomText(customText: String?) {
// Call to set the text that must be shown in the spinner for the custom option.
val text = customText ?: return
mCustomText = text
notifyDataSetChanged()
}
override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View {
val view = parent.inflate(R.layout.library_spinner_entry_text) as TextView
view.text = array[position]
return view
}
}

View File

@ -7,13 +7,11 @@ import android.widget.FrameLayout
import androidx.constraintlayout.widget.ConstraintLayout
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.signature.ObjectKey
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.MangaImpl
import eu.kanade.tachiyomi.data.glide.GlideApp
import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.util.view.gone
import eu.kanade.tachiyomi.util.view.updatePaddingRelative
import eu.kanade.tachiyomi.util.view.visible
import kotlinx.android.synthetic.main.catalogue_grid_item.*
import kotlinx.android.synthetic.main.unread_download_badge.*

View File

@ -0,0 +1,64 @@
package eu.kanade.tachiyomi.ui.library
import android.view.View
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractHeaderItem
import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.viewholders.FlexibleViewHolder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Category
class LibraryHeaderItem(val category: Category) : AbstractHeaderItem<LibraryHeaderItem.Holder>() {
override fun getLayoutRes(): Int {
return R.layout.library_category_header_item
}
override fun createViewHolder(
view: View,
adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>
): Holder {
return Holder(view, adapter)
}
override fun bindViewHolder(
adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>,
holder: Holder,
position: Int,
payloads: MutableList<Any?>?
) {
holder.bind(this)
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other is LibraryHeaderItem) {
return category.id == other.category.id
}
return false
}
override fun isDraggable(): Boolean {
return false
}
override fun isSelectable(): Boolean {
return false
}
override fun hashCode(): Int {
return category.id!!
}
class Holder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>) :
FlexibleViewHolder(view, adapter, true) {
private val sectionText: TextView = view.findViewById(R.id.category_title)
fun bind(item: LibraryHeaderItem) {
sectionText.text = item.category.name
}
}
}

View File

@ -37,8 +37,4 @@ abstract class LibraryHolder(
super.onItemReleased(position)
(adapter as? LibraryCategoryAdapter)?.libraryListener?.onItemReleased(position)
}
protected fun convertColor(color: Int):String {
return Integer.toHexString(color and 0x00ffffff)
}
}

View File

@ -9,7 +9,7 @@ import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import com.f2prateek.rx.preferences.Preference
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.davidea.flexibleadapter.items.AbstractSectionableItem
import eu.davidea.flexibleadapter.items.IFilterable
import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.R
@ -21,8 +21,10 @@ import eu.kanade.tachiyomi.widget.AutofitRecyclerView
import kotlinx.android.synthetic.main.catalogue_grid_item.view.*
import uy.kohesive.injekt.injectLazy
class LibraryItem(val manga: LibraryManga, private val libraryLayout: Preference<Int>) :
AbstractFlexibleItem<LibraryHolder>(), IFilterable<String> {
class LibraryItem(val manga: LibraryManga,
private val libraryLayout: Preference<Int>,
header: LibraryHeaderItem) :
AbstractSectionableItem<LibraryHolder, LibraryHeaderItem>(header), IFilterable<String> {
var downloadCount = -1
var unreadType = 1
@ -51,7 +53,8 @@ class LibraryItem(val manga: LibraryManga, private val libraryLayout: Preference
cover_thumbnail.maxHeight = Integer.MAX_VALUE
constraint_layout.minHeight = 0
cover_thumbnail.adjustViewBounds = false
cover_thumbnail.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, coverHeight)
cover_thumbnail.layoutParams =
FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, coverHeight)
}
else {
constraint_layout.minHeight = coverHeight

View File

@ -17,7 +17,6 @@ import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.ui.manga.chapter.ChapterItem
import eu.kanade.tachiyomi.ui.migration.MigrationFlags
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import eu.kanade.tachiyomi.util.lang.removeArticles
@ -29,20 +28,16 @@ import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup.Comp
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import rx.Observable
import rx.Subscription
import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.util.ArrayList
import java.util.Calendar
import java.util.Collections
import java.util.Comparator
import java.util.Date
/**
* Class containing library information.
@ -109,7 +104,7 @@ class LibraryPresenter(
mangaMap
}
currentMangaMap = mangaMap
view.onNextLibraryUpdate(categories, mangaMap)
updateView(categories, mangaMap)
}
}
@ -124,7 +119,9 @@ class LibraryPresenter(
mangaMap
}()
currentMangaMap = mangaMap
view.onNextLibraryUpdate(categories, mangaMap, true)
launchUI {
updateView(categories, mangaMap, true)
}
}
fun getAllManga(): LibraryMap? {
@ -314,7 +311,6 @@ class LibraryPresenter(
}
val catListing by lazy {
val default = createDefaultCategory()
default.order = -1
listOf(default) + db.getCategories().executeAsBlocking()
}
@ -423,11 +419,38 @@ class LibraryPresenter(
var libraryManga = db.getLibraryMangas().executeAsBlocking()
if (!showCategories)
libraryManga = libraryManga.distinctBy { it.id }
val libraryMap = libraryManga.map { manga ->
/*val libraryMap = libraryManga.map { manga ->
LibraryItem(manga, libraryLayout).apply { unreadType = unreadBadgeType }
}.groupBy {
if (showCategories) it.manga.category else 0
}
}*/
val catItemMain = LibraryHeaderItem(categories.firstOrNull() ?: createDefaultCategory())
val libraryMap =
if (preferences.libraryUsingPager().getOrDefault()) {
libraryManga.map { manga ->
LibraryItem(manga, libraryLayout, catItemMain).apply { unreadType = unreadBadgeType }
}.groupBy {
if (showCategories) it.manga.category else 0
}
}
else {
libraryManga.groupBy { manga ->
if (showCategories) manga.category else 0
//LibraryItem(manga, libraryLayout).apply { unreadType = unreadBadgeType }
}.map { entry ->
val categoryItem = LibraryHeaderItem(categories.find { entry.key == it.id }
?: createDefaultCategory())
entry.value.map {
LibraryItem(
it, libraryLayout, categoryItem
).apply { unreadType = unreadBadgeType }
}
}.map {
val cat = if (showCategories) it.firstOrNull()?.manga?.category ?: 0 else 0
cat to it
//LibraryItem(manga, libraryLayout).apply { unreadType = unreadBadgeType }
}.toMap()
}
if (libraryMap.containsKey(0))
categories.add(0, createDefaultCategory())
@ -441,6 +464,7 @@ class LibraryPresenter(
private fun createDefaultCategory(): Category {
val default = Category.createDefault(context)
default.order = -1
val defOrder = preferences.defaultMangaOrder().getOrDefault()
if (defOrder.firstOrNull()?.isLetter() == true) default.mangaSort = defOrder.first()
else default.mangaOrder = defOrder.split("/").mapNotNull { it.toLongOrNull() }
@ -456,10 +480,61 @@ class LibraryPresenter(
mangaMap = withContext(Dispatchers.IO) { applyFilters(mangaMap) }
mangaMap = withContext(Dispatchers.IO) { applySort(mangaMap) }
currentMangaMap = mangaMap
view.onNextLibraryUpdate(categories, mangaMap)
updateView(categories, mangaMap)
}
}
suspend fun updateView(categories: List<Category>, mangaMap: LibraryMap, freshStart:Boolean
= false) {
/* val list = withContext(Dispatchers.IO) {
val showCategories = !preferences.hideCategories().getOrDefault()
val current = mangaMap.values.first()
current.groupBy {
if (showCategories) it.manga.category else 0
}.flatMap { it.value }
}*/
if (preferences.libraryUsingPager().getOrDefault()) {
view.onNextLibraryUpdate(categories, mangaMap, true)
}
else {
val mangaList = withContext(Dispatchers.IO) {
val list = mutableListOf<LibraryItem>()
val many = categories.size > 1
for (element in mangaMap.toSortedMap(compareBy { entry ->
categories.find { it.id == entry }?.order ?: -1
})) {
list.addAll(element.value)
}
list
}
view.onNextLibraryUpdate(mangaList, freshStart)
}
}
fun updateViewBlocking() {
/* val list = withContext(Dispatchers.IO) {
val showCategories = !preferences.hideCategories().getOrDefault()
val current = mangaMap.values.first()
current.groupBy {
if (showCategories) it.manga.category else 0
}.flatMap { it.value }
}*/
val mangaMap = currentMangaMap ?: return
if (preferences.libraryUsingPager().getOrDefault()) {
view.onNextLibraryUpdate(categories, mangaMap, true)
}
else {
val list = mutableListOf<LibraryItem>()
for (element in mangaMap?.toSortedMap(compareBy { entry ->
categories.find { it.id == entry }?.order ?: -1
})) {
list.addAll(element.value)
}
view.onNextLibraryUpdate(list, true)
}
}
/**
* Requests the library to have download badges added/removed.
*/
@ -471,7 +546,7 @@ class LibraryPresenter(
val current = currentMangaMap ?: return@launchUI
withContext(Dispatchers.IO) { setDownloadCount(current) }
currentMangaMap = current
view.onNextLibraryUpdate(categories, current)
updateView(categories, current)
}
}
@ -487,7 +562,7 @@ class LibraryPresenter(
val current = currentMangaMap ?: return@launchUI
withContext(Dispatchers.IO) { setUnreadBadge(current) }
currentMangaMap = current
view.onNextLibraryUpdate(categories, current)
updateView(categories, current)
}
}
@ -499,7 +574,7 @@ class LibraryPresenter(
var mangaMap = currentMangaMap ?: return@launchUI
mangaMap = withContext(Dispatchers.IO) { applySort(mangaMap) }
currentMangaMap = mangaMap
view.onNextLibraryUpdate(categories, mangaMap)
updateView(categories, mangaMap)
}
}
@ -508,7 +583,7 @@ class LibraryPresenter(
var mangaMap = currentMangaMap ?: return@launchUI
mangaMap = withContext(Dispatchers.IO) { applyCatSort(mangaMap, catId) }
currentMangaMap = mangaMap
view.onNextLibraryUpdate(categories, mangaMap)
updateView(categories, mangaMap)
}
}
@ -698,5 +773,6 @@ class LibraryPresenter(
private companion object {
var currentLibrary:Library? = null
var currentList:List<LibraryItem>? = null
}
}

View File

@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.ui.main
import android.app.SearchManager
import android.content.ComponentCallbacks2
import android.content.Intent
import android.content.res.Configuration
import android.graphics.Color
@ -48,7 +47,6 @@ import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchController
import eu.kanade.tachiyomi.ui.download.DownloadController
import eu.kanade.tachiyomi.ui.extension.ExtensionController
import eu.kanade.tachiyomi.ui.library.LibraryController
import eu.kanade.tachiyomi.ui.library.LibraryPresenter
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.ui.recent_updates.RecentChaptersController
import eu.kanade.tachiyomi.ui.recently_read.RecentlyReadController
@ -58,7 +56,6 @@ import eu.kanade.tachiyomi.util.system.launchUI
import eu.kanade.tachiyomi.util.view.gone
import eu.kanade.tachiyomi.util.view.updateLayoutParams
import eu.kanade.tachiyomi.util.view.updatePadding
import eu.kanade.tachiyomi.util.view.updatePaddingRelative
import eu.kanade.tachiyomi.util.view.visible
import kotlinx.android.synthetic.main.main_activity.*
import kotlinx.coroutines.Dispatchers
@ -186,6 +183,8 @@ open class MainActivity : BaseActivity(), DownloadServiceListener {
}
}
supportActionBar?.setDisplayShowCustomEnabled(true)
content.setOnApplyWindowInsetsListener { v, insets ->
// if device doesn't support light nav bar
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
@ -279,6 +278,10 @@ open class MainActivity : BaseActivity(), DownloadServiceListener {
})
if (router.backstackSize <= 1) {
tabAnimator.hide()
}
syncActivityViewWithController(router.backstack.lastOrNull()?.controller())
if (savedInstanceState == null) {

View File

@ -1,9 +1,9 @@
package eu.kanade.tachiyomi.ui.main
import android.animation.ObjectAnimator
import com.google.android.material.tabs.TabLayout
import android.view.ViewTreeObserver
import android.view.animation.DecelerateInterpolator
import com.google.android.material.tabs.TabLayout
class TabsAnimator(val tabs: TabLayout) {
@ -97,6 +97,11 @@ class TabsAnimator(val tabs: TabLayout) {
isLastStateShown = false
}
fun hide() {
setHeight(0)
isLastStateShown = false
}
/**
* Returns whether the tab layout has a known height.
*/

View File

@ -52,6 +52,13 @@ class SettingsLibraryController : SettingsController() {
}
}
switchPreference {
key = Keys.libraryUsingPager
titleRes = R.string.pref_remove_articles
summaryRes = R.string.pref_remove_articles_summary
defaultValue = false
}
switchPreference {
key = Keys.removeArticles
titleRes = R.string.pref_remove_articles

View File

@ -3,13 +3,12 @@ package eu.kanade.tachiyomi.widget
import android.content.Context
import android.util.AttributeSet
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlin.math.max
class AutofitRecyclerView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
androidx.recyclerview.widget.RecyclerView(context, attrs) {
private val manager = GridLayoutManager(context, 1)
val manager = GridLayoutManager(context, 1)
private var columnWidth = -1

View File

@ -1,153 +1,154 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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/constraint_layout"
<androidx.constraintlayout.widget.ConstraintLayout 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/constraint_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="@drawable/library_compact_grid_selector"
android:minHeight="200dp"
android:orientation="vertical">
<com.google.android.material.card.MaterialCardView
android:id="@+id/card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="@drawable/library_compact_grid_selector"
android:minHeight="200dp"
android:orientation="vertical">
android:layout_marginStart="8dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="8dp"
app:layout_constraintBottom_toTopOf="@+id/title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="1.0">
<com.google.android.material.card.MaterialCardView
android:id="@+id/card"
<ImageView
android:id="@+id/cover_thumbnail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_marginStart="8dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="8dp"
app:layout_constraintBottom_toTopOf="@+id/title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="1.0">
<ImageView
android:id="@+id/cover_thumbnail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:background="?android:attr/colorBackground"
android:maxHeight="250dp"
app:layout_constrainedHeight="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:background="?android:attr/colorBackground"
tools:ignore="ContentDescription"
tools:src="@mipmap/ic_launcher" />
<FrameLayout
android:id="@+id/play_layout"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_gravity="end"
android:clickable="true"
android:focusable="true"
android:visibility="gone"
tools:visibility="visible">
<ImageView
android:id="@+id/play_button"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="end"
android:layout_marginTop="6dp"
android:layout_marginBottom="6dp"
android:layout_marginEnd="6dp"
android:background="@drawable/round_play_background"
android:contentDescription="@string/start_reading"
android:padding="6dp"
android:src="@drawable/ic_start_reading_white_24dp"
android:tint="@android:color/white" />
</FrameLayout>
<View
android:id="@+id/gradient"
android:layout_width="match_parent"
android:layout_height="150dp"
android:layout_gravity="bottom"
android:background="@drawable/gradient_shape" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/compact_title"
style="@style/TextAppearance.Regular.Body1.SemiBold"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:layout_marginBottom="4dp"
android:ellipsize="end"
android:maxLines="2"
android:textColor="@android:color/white"
android:textSize="13sp"
tools:text="Sample name" />
<ProgressBar
android:id="@+id/progress"
style="?android:attr/progressBarStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</com.google.android.material.card.MaterialCardView>
<View
android:id="@+id/badge_guide"
android:layout_width="match_parent"
android:layout_height="10dp"
app:layout_constraintTop_toTopOf="@+id/card"/>
<include layout="@layout/unread_download_badge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="@id/badge_guide"/>
<com.google.android.material.textview.MaterialTextView
android:id="@+id/title"
style="@style/TextAppearance.Regular.Body1.Light"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:ellipsize="end"
android:maxLines="2"
android:textColor="?android:attr/textColorPrimary"
android:textSize="12sp"
app:layout_constraintBottom_toTopOf="@+id/subtitle"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:text="Sample name" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/subtitle"
style="@style/TextAppearance.Regular.Body1.Light"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:layout_marginTop="-1dp"
android:ellipsize="end"
android:singleLine="true"
android:textColor="?android:attr/textColorSecondary"
android:textSize="12sp"
android:adjustViewBounds="true"
android:background="?android:attr/colorBackground"
android:maxHeight="250dp"
app:layout_constrainedHeight="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:text="Sample artist" />
app:layout_constraintTop_toTopOf="parent"
tools:background="?android:attr/colorBackground"
tools:ignore="ContentDescription"
tools:src="@mipmap/ic_launcher" />
</androidx.constraintlayout.widget.ConstraintLayout>
<FrameLayout
android:id="@+id/play_layout"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_gravity="end"
android:clickable="true"
android:focusable="true"
android:visibility="gone"
tools:visibility="visible">
<ImageView
android:id="@+id/play_button"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="end"
android:layout_marginTop="6dp"
android:layout_marginEnd="6dp"
android:layout_marginBottom="6dp"
android:background="@drawable/round_play_background"
android:contentDescription="@string/start_reading"
android:padding="6dp"
android:src="@drawable/ic_start_reading_white_24dp"
android:tint="@android:color/white" />
</FrameLayout>
<View
android:id="@+id/gradient"
android:layout_width="match_parent"
android:layout_height="150dp"
android:layout_gravity="bottom"
android:background="@drawable/gradient_shape" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/compact_title"
style="@style/TextAppearance.Regular.Body1.SemiBold"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:layout_marginBottom="4dp"
android:ellipsize="end"
android:maxLines="2"
android:textColor="@android:color/white"
android:textSize="13sp"
tools:text="Sample name" />
<ProgressBar
android:id="@+id/progress"
style="?android:attr/progressBarStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</com.google.android.material.card.MaterialCardView>
<View
android:id="@+id/badge_guide"
android:layout_width="match_parent"
android:layout_height="10dp"
app:layout_constraintTop_toTopOf="@+id/card" />
<include
layout="@layout/unread_download_badge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="@id/badge_guide"
app:layout_constraintStart_toStartOf="parent" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/title"
style="@style/TextAppearance.Regular.Body1.Light"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:ellipsize="end"
android:maxLines="2"
android:textColor="?android:attr/textColorPrimary"
android:textSize="12sp"
app:layout_constraintBottom_toTopOf="@+id/subtitle"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:text="Sample name" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/subtitle"
style="@style/TextAppearance.Regular.Body1.Light"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="-1dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:ellipsize="end"
android:singleLine="true"
android:textColor="?android:attr/textColorSecondary"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:text="Sample artist" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="15dp"
android:paddingBottom="5dp"
android:gravity="center_vertical">
<TextView
android:id="@+id/category_title"
style="@style/TextAppearance.MaterialComponents.Headline5"
android:layout_width="0dp"
android:layout_height="match_parent"
android:gravity="center|start"
android:inputType="none"
android:layout_marginStart="10dp"
android:maxLines="1"
android:textColor="?attr/actionBarTintColor"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Title" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -2,11 +2,24 @@
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/library_layout"
android:layout_height="match_parent">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipe_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/recycler_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
</FrameLayout>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/pager_layout"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="match_parent">
@ -16,6 +29,14 @@
android:layout_height="match_parent" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<eu.davidea.fastscroller.FastScroller
android:id="@+id/fast_scroller"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="end"
app:fastScrollerBubbleEnabled="true"
tools:visibility="visible" />
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/snackbar_layout"
android:layout_width="match_parent"

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.AppCompatSpinner xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:entries="@array/color_filter_modes" />

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
style="@style/TextAppearance.AppCompat.Widget.DropDownItem"
android:textAllCaps="false"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="start"
android:textSize="16sp"
android:padding="10dip"
tools:text="Title"
tools:background="?attr/colorPrimary"/>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
style="@style/TextAppearance.AppCompat.Widget.ActionBar.Title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="start"
android:textSize="20sp"
tools:text="Title"
tools:background="?attr/colorPrimary"/>