Fixed Update notifcation + Moved single list library to extended class

Fixing progress bar for category header and stop spinning after update
This commit is contained in:
Jay 2020-02-19 21:29:45 -08:00
parent 9768b5ae25
commit 49d95e2dd0
14 changed files with 723 additions and 546 deletions

View File

@ -37,13 +37,14 @@ import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import eu.kanade.tachiyomi.util.lang.chop import eu.kanade.tachiyomi.util.lang.chop
import eu.kanade.tachiyomi.util.system.isServiceRunning
import eu.kanade.tachiyomi.util.system.notification import eu.kanade.tachiyomi.util.system.notification
import eu.kanade.tachiyomi.util.system.notificationManager import eu.kanade.tachiyomi.util.system.notificationManager
import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import rx.Observable import rx.Observable
import rx.Subscription import rx.Subscription
import rx.schedulers.Schedulers import rx.schedulers.Schedulers
@ -98,6 +99,13 @@ class LibraryUpdateService(
private var job:Job? = null private var job:Job? = null
private val mangaToUpdate = mutableListOf<LibraryManga>()
private val categoryIds = mutableSetOf<Int>()
// List containing new updates
private val newUpdates = ArrayList<Pair<LibraryManga, Array<Chapter>>>()
/** /**
* Cached progress notification to avoid creating a lot. * Cached progress notification to avoid creating a lot.
*/ */
@ -127,11 +135,8 @@ class LibraryUpdateService(
*/ */
const val KEY_CATEGORY = "category" const val KEY_CATEGORY = "category"
private val mangaToUpdate = mutableListOf<LibraryManga>() fun categoryInQueue(id: Int?) = instance?.categoryIds?.contains(id) ?: false
private var instance: LibraryUpdateService? = null
private val categoryIds = mutableSetOf<Int>()
fun categoryInQueue(id: Int?) = categoryIds.contains(id)
/** /**
* Key that defines what should be updated. * Key that defines what should be updated.
@ -141,11 +146,10 @@ class LibraryUpdateService(
/** /**
* Returns the status of the service. * Returns the status of the service.
* *
* @param context the application context.
* @return true if the service is running, false otherwise. * @return true if the service is running, false otherwise.
*/ */
fun isRunning(context: Context): Boolean { fun isRunning(): Boolean {
return context.isServiceRunning(LibraryUpdateService::class.java) return instance != null
} }
/** /**
@ -157,12 +161,11 @@ class LibraryUpdateService(
* @param target defines what should be updated. * @param target defines what should be updated.
*/ */
fun start(context: Context, category: Category? = null, target: Target = Target.CHAPTERS) { fun start(context: Context, category: Category? = null, target: Target = Target.CHAPTERS) {
if (!isRunning(context)) { if (!isRunning()) {
val intent = Intent(context, LibraryUpdateService::class.java).apply { val intent = Intent(context, LibraryUpdateService::class.java).apply {
putExtra(KEY_TARGET, target) putExtra(KEY_TARGET, target)
category?.id?.let { id -> category?.id?.let { id ->
putExtra(KEY_CATEGORY, id) putExtra(KEY_CATEGORY, id)
categoryIds.add(id)
} }
} }
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
@ -173,22 +176,11 @@ class LibraryUpdateService(
} }
else { else {
if (target == Target.CHAPTERS) category?.id?.let { if (target == Target.CHAPTERS) category?.id?.let {
categoryIds.add(it) instance?.addCategory(it)
val preferences: PreferencesHelper = Injekt.get()
val selectedScheme = preferences.libraryUpdatePrioritization().getOrDefault()
addManga(getMangaToUpdate(it, target).sortedWith(
rankingScheme[selectedScheme]
))
} }
} }
} }
private fun addManga(mangaToAdd: List<LibraryManga>) {
for (manga in mangaToAdd) {
if (mangaToUpdate.none { it.id == manga.id }) mangaToUpdate.add(manga)
}
}
/** /**
* Stops the service. * Stops the service.
* *
@ -198,40 +190,6 @@ class LibraryUpdateService(
context.stopService(Intent(context, LibraryUpdateService::class.java)) context.stopService(Intent(context, LibraryUpdateService::class.java))
} }
/**
* Returns the list of manga to be updated.
*
* @param intent the update intent.
* @param target the target to update.
* @return a list of manga to update
*/
private fun getMangaToUpdate(categoryId: Int, target: Target): List<LibraryManga> {
val preferences: PreferencesHelper = Injekt.get()
val db: DatabaseHelper = Injekt.get()
var listToUpdate = if (categoryId != -1)
db.getLibraryMangas().executeAsBlocking().filter { it.category == categoryId }
else {
val categoriesToUpdate = preferences.libraryUpdateCategories().getOrDefault().map(String::toInt)
categoryIds.addAll(categoriesToUpdate)
if (categoriesToUpdate.isNotEmpty())
db.getLibraryMangas().executeAsBlocking()
.filter { it.category in categoriesToUpdate }
.distinctBy { it.id }
else
db.getLibraryMangas().executeAsBlocking().distinctBy { it.id }
}
if (target == Target.CHAPTERS && preferences.updateOnlyNonCompleted()) {
listToUpdate = listToUpdate.filter { it.status != SManga.COMPLETED }
}
return listToUpdate
}
private fun getMangaToUpdate(intent: Intent, target: Target): List<LibraryManga> {
val categoryId = intent.getIntExtra(KEY_CATEGORY, -1)
return getMangaToUpdate(categoryId, target)
}
private var listener:LibraryServiceListener? = null private var listener:LibraryServiceListener? = null
fun setListener(listener: LibraryServiceListener) { fun setListener(listener: LibraryServiceListener) {
@ -243,6 +201,55 @@ class LibraryUpdateService(
} }
} }
private fun addManga(mangaToAdd: List<LibraryManga>) {
for (manga in mangaToAdd) {
if (mangaToUpdate.none { it.id == manga.id }) mangaToUpdate.add(manga)
}
}
private fun addCategory(categoryId: Int) {
val selectedScheme = preferences.libraryUpdatePrioritization().getOrDefault()
val mangas =
getMangaToUpdate(categoryId, Target.CHAPTERS).sortedWith(
rankingScheme[selectedScheme])
categoryIds.add(categoryId)
addManga(mangas)
}
/**
* Returns the list of manga to be updated.
*
* @param intent the update intent.
* @param target the target to update.
* @return a list of manga to update
*/
private fun getMangaToUpdate(categoryId: Int, target: Target): List<LibraryManga> {
var listToUpdate = if (categoryId != -1) {
categoryIds.add(categoryId)
db.getLibraryMangas().executeAsBlocking().filter { it.category == categoryId }
}
else {
val categoriesToUpdate = preferences.libraryUpdateCategories().getOrDefault().map(String::toInt)
categoryIds.addAll(categoriesToUpdate)
if (categoriesToUpdate.isNotEmpty())
db.getLibraryMangas().executeAsBlocking()
.filter { it.category in categoriesToUpdate }
.distinctBy { it.id }
else
db.getLibraryMangas().executeAsBlocking().distinctBy { it.id }
}
if (target == Target.CHAPTERS && preferences.updateOnlyNonCompleted()) {
listToUpdate = listToUpdate.filter { it.status != SManga.COMPLETED }
}
return listToUpdate
}
private fun getMangaToUpdate(intent: Intent, target: Target): List<LibraryManga> {
val categoryId = intent.getIntExtra(KEY_CATEGORY, -1)
return getMangaToUpdate(categoryId, target)
}
/** /**
* Method called when the service is created. It injects dagger dependencies and acquire * Method called when the service is created. It injects dagger dependencies and acquire
* the wake lock. * the wake lock.
@ -255,22 +262,19 @@ class LibraryUpdateService(
wakeLock.acquire(TimeUnit.MINUTES.toMillis(30)) wakeLock.acquire(TimeUnit.MINUTES.toMillis(30))
} }
override fun stopService(name: Intent?): Boolean {
job?.cancel()
return super.stopService(name)
}
/** /**
* Method called when the service is destroyed. It destroys subscriptions and releases the wake * Method called when the service is destroyed. It destroys subscriptions and releases the wake
* lock. * lock.
*/ */
override fun onDestroy() { override fun onDestroy() {
job?.cancel()
if (instance == this)
instance = null
subscription?.unsubscribe() subscription?.unsubscribe()
mangaToUpdate.clear()
categoryIds.clear()
if (wakeLock.isHeld) { if (wakeLock.isHeld) {
wakeLock.release() wakeLock.release()
} }
listener?.onUpdateManga(LibraryManga())
super.onDestroy() super.onDestroy()
} }
@ -291,23 +295,22 @@ class LibraryUpdateService(
*/ */
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (intent == null) return START_NOT_STICKY if (intent == null) return START_NOT_STICKY
val target = intent.getSerializableExtra(KEY_TARGET) as? Target ?: return START_NOT_STICKY val target = intent.getSerializableExtra(KEY_TARGET) as? Target
?: return START_NOT_STICKY
// Unsubscribe from any previous subscription if needed. // Unsubscribe from any previous subscription if needed.
job?.cancel()
subscription?.unsubscribe() subscription?.unsubscribe()
instance = this
val selectedScheme = preferences.libraryUpdatePrioritization().getOrDefault() val selectedScheme = preferences.libraryUpdatePrioritization().getOrDefault()
val mangaList =
getMangaToUpdate(intent, target).sortedWith(rankingScheme[selectedScheme])
// Update favorite manga. Destroy service when completed or in case of an error.
if (target == Target.CHAPTERS) { if (target == Target.CHAPTERS) {
updateChapters( updateChapters(mangaList, startId)
getMangaToUpdate(intent, target).sortedWith(rankingScheme[selectedScheme]), startId
)
} }
else { else {
// Update either chapter list or manga details. // Update either chapter list or manga details.
// Update favorite manga. Destroy service when completed or in case of an error.
val mangaList =
getMangaToUpdate(intent, target).sortedWith(rankingScheme[selectedScheme])
subscription = Observable.defer { subscription = Observable.defer {
when (target) { when (target) {
Target.DETAILS -> updateDetails(mangaList) Target.DETAILS -> updateDetails(mangaList)
@ -324,25 +327,26 @@ class LibraryUpdateService(
} }
private fun updateChapters(mangaToAdd: List<LibraryManga>, startId: Int) { private fun updateChapters(mangaToAdd: List<LibraryManga>, startId: Int) {
addManga(mangaToAdd)
val handler = CoroutineExceptionHandler { _, exception -> val handler = CoroutineExceptionHandler { _, exception ->
Timber.e(exception) Timber.e(exception)
// Boolean to determine if user wants to automatically download new chapters.
val downloadNew = preferences.downloadNew().getOrDefault()
if (newUpdates.isNotEmpty()) {
showResultNotification(newUpdates)
if (downloadNew && downloadManager.queue.isNotEmpty()) {
DownloadService.start(this)
}
}
stopSelf(startId) stopSelf(startId)
} }
job = GlobalScope.launch(handler) { job = GlobalScope.launch(handler) {
updateChaptersJob() updateChaptersJob(mangaToAdd)
mangaToUpdate.clear()
categoryIds.clear()
stopSelf(startId)
} }
job?.invokeOnCompletion { stopSelf(startId) }
} }
private fun updateChaptersJob() { private suspend fun updateChaptersJob(mangaToAdd: List<LibraryManga>) {
// Initialize the variables holding the progress of the updates.
var count = 0
// List containing new updates
val newUpdates = ArrayList<Pair<LibraryManga, Array<Chapter>>>()
// list containing failed updates // list containing failed updates
val failedUpdates = ArrayList<Manga>() val failedUpdates = ArrayList<Manga>()
// List containing categories that get included in downloads. // List containing categories that get included in downloads.
@ -351,28 +355,37 @@ class LibraryUpdateService(
val downloadNew = preferences.downloadNew().getOrDefault() val downloadNew = preferences.downloadNew().getOrDefault()
// Boolean to determine if DownloadManager has downloads // Boolean to determine if DownloadManager has downloads
var hasDownloads = false var hasDownloads = false
withContext(Dispatchers.IO) {
// Initialize the variables holding the progress of the updates.
var count = 0
while (count < mangaToUpdate.size) { mangaToUpdate.addAll(mangaToAdd)
if (job?.isCancelled == true || job == null) break while (count < mangaToUpdate.size) {
val manga = mangaToUpdate[count] if (job?.isCancelled == true) break
showProgressNotification(manga, count++, mangaToUpdate.size) val manga = mangaToUpdate[count]
val source = sourceManager.get(manga.source) as? HttpSource ?: continue showProgressNotification(manga, count++, mangaToUpdate.size)
val fetchedChapters = try { source.fetchChapterList(manga).toBlocking().single() } val source = sourceManager.get(manga.source) as? HttpSource ?: continue
catch(e: java.lang.Exception) { val fetchedChapters = try {
failedUpdates.add(manga) source.fetchChapterList(manga).toBlocking().single()
emptyList<SChapter>() } catch (e: java.lang.Exception) {
} failedUpdates.add(manga)
if (fetchedChapters.isNotEmpty()) { emptyList<SChapter>()
val newChapters = syncChaptersWithSource(db, fetchedChapters, manga, source) } ?: emptyList()
if (newChapters.first.isNotEmpty()) { if (fetchedChapters.isNotEmpty()) {
if (downloadNew && (categoriesToDownload.isEmpty() || manga.category in categoriesToDownload)) { val newChapters = syncChaptersWithSource(db, fetchedChapters, manga, source)
downloadChapters(manga, newChapters.first.sortedBy { it.chapter_number }) if (newChapters.first.isNotEmpty()) {
hasDownloads = true if (downloadNew && (categoriesToDownload.isEmpty() || manga.category in categoriesToDownload)) {
downloadChapters(
manga,
newChapters.first.sortedBy { it.chapter_number })
hasDownloads = true
}
newUpdates.add(manga to newChapters.first.sortedBy { it.chapter_number }.toTypedArray())
} }
newUpdates.add(manga to newChapters.first.sortedBy { it.chapter_number }.toTypedArray()) if (newChapters.first.size + newChapters.second.size > 0) listener?.onUpdateManga(
manga
)
} }
if (newChapters.first.size + newChapters.second.size > 0)
listener?.onUpdateManga(manga)
} }
} }
if (newUpdates.isNotEmpty()) { if (newUpdates.isNotEmpty()) {

View File

@ -120,7 +120,7 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
resources.getString( resources.getString(
when { when {
inQueue -> R.string.category_already_in_queue inQueue -> R.string.category_already_in_queue
LibraryUpdateService.isRunning(context) -> R.string.adding_category_to_queue LibraryUpdateService.isRunning() -> R.string.adding_category_to_queue
else -> R.string.updating_category_x else -> R.string.updating_category_x
}, category.name)) }, category.name))
if (!inQueue) if (!inQueue)

View File

@ -12,15 +12,10 @@ import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import android.widget.Spinner
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.core.graphics.drawable.DrawableCompat import androidx.core.graphics.drawable.DrawableCompat
import androidx.core.math.MathUtils.clamp
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager.widget.ViewPager import androidx.viewpager.widget.ViewPager
import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.MaterialDialog
import com.bluelinelabs.conductor.ControllerChangeHandler import com.bluelinelabs.conductor.ControllerChangeHandler
@ -32,8 +27,6 @@ import com.google.android.material.snackbar.Snackbar
import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayout
import com.jakewharton.rxrelay.BehaviorRelay import com.jakewharton.rxrelay.BehaviorRelay
import com.jakewharton.rxrelay.PublishRelay import com.jakewharton.rxrelay.PublishRelay
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.SelectableAdapter
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.data.database.models.LibraryManga import eu.kanade.tachiyomi.data.database.models.LibraryManga
@ -58,46 +51,31 @@ 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.MigrationListController
import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationProcedureConfig import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationProcedureConfig
import eu.kanade.tachiyomi.ui.reader.ReaderActivity 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.getResourceColor
import eu.kanade.tachiyomi.util.system.launchUI 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.setOnQueryTextChangeListener
import eu.kanade.tachiyomi.util.view.snack import eu.kanade.tachiyomi.util.view.snack
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.filter_bottom_sheet.*
import kotlinx.android.synthetic.main.library_controller.* import kotlinx.android.synthetic.main.library_controller.*
import kotlinx.android.synthetic.main.main_activity.* import kotlinx.android.synthetic.main.main_activity.*
import kotlinx.coroutines.delay
import rx.Subscription import rx.Subscription
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.util.Locale
import kotlin.math.min
class LibraryController( open class LibraryController(
bundle: Bundle? = null, bundle: Bundle? = null,
private val preferences: PreferencesHelper = Injekt.get() protected val preferences: PreferencesHelper = Injekt.get()
) : BaseController(bundle), TabbedController, ) : BaseController(bundle), TabbedController,
ActionMode.Callback, ActionMode.Callback,
ChangeMangaCategoriesDialog.Listener, ChangeMangaCategoriesDialog.Listener,
MigrationInterface, MigrationInterface,
DownloadServiceListener, DownloadServiceListener,
LibraryServiceListener, LibraryServiceListener {
FlexibleAdapter.OnItemClickListener,
FlexibleAdapter.OnItemLongClickListener,
FlexibleAdapter.OnItemMoveListener,
LibraryCategoryAdapter.LibraryListener{
/** /**
* Position of the active category. * Position of the active category.
*/ */
var activeCategory: Int = preferences.lastUsedCategory().getOrDefault() protected var activeCategory: Int = preferences.lastUsedCategory().getOrDefault()
private set
/** /**
* Action mode for selections. * Action mode for selections.
@ -107,7 +85,7 @@ class LibraryController(
/** /**
* Library search query. * Library search query.
*/ */
private var query = "" protected var query = ""
/** /**
* Currently selected mangas. * Currently selected mangas.
@ -154,16 +132,11 @@ class LibraryController(
* Adapter of the view pager. * Adapter of the view pager.
*/ */
private var pagerAdapter: 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. * Drawer listener to allow swipe only for closing the drawer.
*/ */
private var tabsVisibilityRelay: BehaviorRelay<Boolean> = BehaviorRelay.create(false) protected var tabsVisibilityRelay: BehaviorRelay<Boolean> = BehaviorRelay.create(false)
private var tabsVisibilitySubscription: Subscription? = null private var tabsVisibilitySubscription: Subscription? = null
@ -171,56 +144,24 @@ class LibraryController(
var snack: Snackbar? = null var snack: Snackbar? = null
var presenter = LibraryPresenter(this) lateinit var presenter:LibraryPresenter
private set private set
private var justStarted = true protected var justStarted = true
private var updateScroll = true var libraryLayout:Int = preferences.libraryLayout().getOrDefault()
private var spinnerAdapter: SpinnerAdapter? = null
private var scrollListener = 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
bottom_sheet.updateTitle()
if (spinner.selectedItemPosition != order + 1) {
updateScroll = true
spinner.setSelection(order + 1, true)
}
}
}
}
/**
* Recycler view of the list of manga.
*/
private lateinit var recycler: RecyclerView
var libraryLayout = preferences.libraryLayout().getOrDefault()
private var usePager: Boolean = !preferences.libraryAsSingleList().getOrDefault() private var usePager: Boolean = !preferences.libraryAsSingleList().getOrDefault()
open fun contentView():View = pager_layout
init { init {
setHasOptionsMenu(true) setHasOptionsMenu(true)
retainViewMode = RetainViewMode.RETAIN_DETACH retainViewMode = RetainViewMode.RETAIN_DETACH
} }
override fun getTitle(): String? { override fun getTitle(): String? {
return if (usePager) resources?.getString(R.string.label_library) else null return resources?.getString(R.string.label_library)
} }
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
@ -230,64 +171,17 @@ class LibraryController(
override fun onViewCreated(view: View) { override fun onViewCreated(view: View) {
super.onViewCreated(view) super.onViewCreated(view)
mangaPerRow = getColumnsPreferenceForCurrentOrientation().getOrDefault() mangaPerRow = getColumnsPreferenceForCurrentOrientation().getOrDefault()
if (!::presenter.isInitialized)
presenter = LibraryPresenter(this)
if (usePager) { layoutView(view)
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 = (recycler_layout.inflate(R.layout.library_grid_recycler) as
AutofitRecyclerView).apply {
spanCount = if (libraryLayout == 0) 1 else mangaPerRow
manager.spanSizeLookup = (object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
if (libraryLayout == 0) return 1
val item = this@LibraryController.adapter.getItem(position)
return if (item is LibraryHeaderItem) manager.spanCount else 1
}
})
}
recycler.setHasFixedSize(true)
recycler.adapter = adapter
recycler_layout.addView(recycler)
adapter.fastScroller = fast_scroller
recycler.addOnScrollListener(scrollListener)
spinner = ReSpinner(view.context)
(activity as MainActivity).supportActionBar?.customView = spinner
(activity as MainActivity).supportActionBar?.setDisplayShowCustomEnabled(true)
spinnerAdapter = SpinnerAdapter(view.context, R.layout.library_spinner_textview,
arrayOf(resources!!.getString(R.string.label_library)))
spinnerAdapter?.setDropDownViewResource(R.layout.library_spinner_entry_text)
spinner.adapter = spinnerAdapter
}
if (selectedMangas.isNotEmpty()) { if (selectedMangas.isNotEmpty()) {
createActionModeIfNeeded() createActionModeIfNeeded()
} }
//bottom_sheet.onCreate(pager_layout) //bottom_sheet.onCreate(pager_layout)
bottom_sheet.onCreate(if (usePager) pager_layout else recycler_layout) bottom_sheet.onCreate(contentView())
bottom_sheet.onGroupClicked = { bottom_sheet.onGroupClicked = {
when (it) { when (it) {
@ -305,50 +199,53 @@ class LibraryController(
router.pushController(DownloadController().withFadeTransaction()) router.pushController(DownloadController().withFadeTransaction())
} }
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) + 4.dpToPx
recycler.updatePaddingRelative(bottom = height)
}
if (presenter.isDownloading()) { if (presenter.isDownloading()) {
fab.scaleY = 1f fab.scaleY = 1f
fab.scaleX = 1f fab.scaleX = 1f
fab.isClickable = true fab.isClickable = true
fab.isFocusable = true fab.isFocusable = true
} }
presenter.onRestore() presenter.onRestore()
val library = presenter.getAllManga() val library = presenter.getAllManga()
if (library != null) presenter.updateViewBlocking() //onNextLibraryUpdate(presenter.categories, library) if (library != null) presenter.updateViewBlocking() //onNextLibraryUpdate(presenter.categories, library)
else { else {
library_pager.alpha = 0f contentView().alpha = 0f
recycler_layout.alpha = 0f
presenter.getLibraryBlocking() presenter.getLibraryBlocking()
} }
} }
open fun layoutView(view: View) {
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) {}
})
}
override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) { override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) {
super.onChangeStarted(handler, type) super.onChangeStarted(handler, type)
if (type.isEnter) { if (type.isEnter) {
if (!usePager) if (library_pager != null)
(activity as MainActivity).supportActionBar?.setDisplayShowCustomEnabled(true)
else
activity?.tabs?.setupWithViewPager(library_pager) activity?.tabs?.setupWithViewPager(library_pager)
presenter.getLibrary() presenter.getLibrary()
DownloadService.addListener(this) DownloadService.addListener(this)
DownloadService.callListeners() DownloadService.callListeners()
LibraryUpdateService.setListener(this) LibraryUpdateService.setListener(this)
} }
else if (type == ControllerChangeType.PUSH_EXIT) {
(activity as MainActivity).toolbar.menu.findItem(R.id
.action_search)?.collapseActionView()
(activity as MainActivity).supportActionBar?.setDisplayShowCustomEnabled(false)
}
} }
override fun onActivityResumed(activity: Activity) { override fun onActivityResumed(activity: Activity) {
@ -365,7 +262,6 @@ class LibraryController(
} }
override fun onDestroy() { override fun onDestroy() {
(activity as MainActivity).supportActionBar?.setDisplayShowCustomEnabled(false)
presenter.onDestroy() presenter.onDestroy()
super.onDestroy() super.onDestroy()
} }
@ -393,7 +289,7 @@ class LibraryController(
} }
override fun onUpdateManga(manga: LibraryManga) { override fun onUpdateManga(manga: LibraryManga) {
presenter.updateManga(manga) if (manga.id != null) presenter.updateManga(manga)
} }
override fun onDetach(view: View) { override fun onDetach(view: View) {
@ -424,56 +320,7 @@ class LibraryController(
tabsVisibilitySubscription = null tabsVisibilitySubscription = null
} }
fun onNextLibraryUpdate(mangaMap: List<LibraryItem>, freshStart: Boolean = false) { open 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)
spinner.onItemSelectedListener = null
spinnerAdapter = SpinnerAdapter(view!!.context, R.layout.library_spinner_textview,
presenter.categories.map { it.name }.toTypedArray())
spinnerAdapter?.setDropDownViewResource(R.layout.library_spinner_entry_text)
spinner.adapter = spinnerAdapter
spinner.setSelection(min(presenter.categories.size - 1, activeCategory + 1))
if (!freshStart) {
justStarted = false
if (recycler_layout.alpha == 0f)
recycler_layout.animate().alpha(1f).setDuration(500).start()
}else {
val position = if (freshStart) adapter.indexOf(activeCategory) else null
if (position != null)
(recycler.layoutManager as LinearLayoutManager)
.scrollToPositionWithOffset(position, (-30).dpToPx)
}
adapter.isLongPressDragEnabled = canDrag()
tabsVisibilityRelay.call(false)
bottom_sheet.lastCategory = presenter.categories[clamp(activeCategory,
0,
presenter.categories.size - 1)]
bottom_sheet.updateTitle()
updateScroll = false
spinner.onItemSelectedListener = IgnoreFirstSpinnerListener { pos ->
if (updateScroll) {
updateScroll = false
return@IgnoreFirstSpinnerListener
}
val headerPosition = adapter.indexOf(pos - 1)
if (headerPosition > -1) {
(recycler.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(
headerPosition, (-30).dpToPx
)
}
}
}
fun onNextLibraryUpdate(categories: List<Category>, mangaMap: Map<Int, List<LibraryItem>>, fun onNextLibraryUpdate(categories: List<Category>, mangaMap: Map<Int, List<LibraryItem>>,
freshStart: Boolean = false) { freshStart: Boolean = false) {
@ -523,8 +370,8 @@ class LibraryController(
} }
else if (!freshStart) { else if (!freshStart) {
justStarted = false justStarted = false
if (library_pager.alpha == 0f) if (pager_layout.alpha == 0f)
library_pager.animate().alpha(1f).setDuration(500).start() pager_layout.animate().alpha(1f).setDuration(500).start()
} }
} }
@ -563,10 +410,8 @@ class LibraryController(
destroyActionModeIfNeeded() destroyActionModeIfNeeded()
} }
fun onCatSortChanged(id: Int? = null) { open fun onCatSortChanged(id: Int? = null) {
val catId = val catId = (id ?: pagerAdapter?.categories?.getOrNull(library_pager.currentItem)?.id)
(if (usePager)(id ?: pagerAdapter?.categories?.getOrNull(library_pager.currentItem)?.id)
else (id ?: presenter.categories.find { it.order == activeCategory }?.id))
?: return ?: return
presenter.requestCatSortUpdate(catId) presenter.requestCatSortUpdate(catId)
} }
@ -574,26 +419,15 @@ class LibraryController(
/** /**
* Reattaches the adapter to the view pager to recreate fragments * Reattaches the adapter to the view pager to recreate fragments
*/ */
private fun reattachAdapter() { protected open fun reattachAdapter() {
if (usePager) { val adapter = pagerAdapter ?: return
val adapter = pagerAdapter ?: return
val position = library_pager.currentItem val position = library_pager.currentItem
adapter.recycle = false adapter.recycle = false
library_pager.adapter = adapter library_pager.adapter = adapter
library_pager.currentItem = position library_pager.currentItem = position
adapter.recycle = true adapter.recycle = true
}
else {
val position =
(recycler.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()
libraryLayout = preferences.libraryLayout().getOrDefault()
recycler.adapter = adapter
(recycler as? AutofitRecyclerView)?.spanCount = if (libraryLayout == 0) 1 else mangaPerRow
(recycler.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(position, 0)
}
} }
/** /**
@ -643,8 +477,6 @@ class LibraryController(
setOnQueryTextChangeListener(searchView) { setOnQueryTextChangeListener(searchView) {
query = it ?: "" query = it ?: ""
searchRelay.call(query) searchRelay.call(query)
adapter.setFilter(it)
adapter.performFilter()
true true
} }
searchItem.fixExpand(onExpand = { invalidateMenuOnExpand() }) searchItem.fixExpand(onExpand = { invalidateMenuOnExpand() })
@ -778,11 +610,6 @@ class LibraryController(
override fun onDestroyActionMode(mode: ActionMode?) { override fun onDestroyActionMode(mode: ActionMode?) {
// Clear all the manga selections and notify child views. // Clear all the manga selections and notify child views.
selectedMangas.clear() selectedMangas.clear()
adapter.mode = SelectableAdapter.Mode.SINGLE
adapter.clearSelection()
adapter.notifyDataSetChanged()
lastClickPosition = -1
adapter.isLongPressDragEnabled = canDrag()
selectionRelay.call(LibrarySelectionEvent.Cleared()) selectionRelay.call(LibrarySelectionEvent.Cleared())
actionMode = null actionMode = null
} }
@ -797,36 +624,14 @@ class LibraryController(
* @param manga the manga whose selection has changed. * @param manga the manga whose selection has changed.
* @param selected whether it's now selected or not. * @param selected whether it's now selected or not.
*/ */
fun setSelection(manga: Manga, selected: Boolean) { open fun setSelection(manga: Manga, selected: Boolean) {
if (selected) { if (selected) {
if (selectedMangas.add(manga)) { if (selectedMangas.add(manga)) {
if (usePager) selectionRelay.call(LibrarySelectionEvent.Selected(manga)) selectionRelay.call(LibrarySelectionEvent.Selected(manga))
else {
val position = adapter.indexOf(manga)
if (adapter.mode != SelectableAdapter.Mode.MULTI) {
adapter.mode = SelectableAdapter.Mode.MULTI
}
launchUI {
delay(100)
adapter.isLongPressDragEnabled = false
}
adapter.toggleSelection(position)
(recycler.findViewHolderForItemId(manga.id!!) as? LibraryHolder)?.toggleActivation()
}
} }
} else { } else {
if (selectedMangas.remove(manga)) { if (selectedMangas.remove(manga)) {
if (usePager) selectionRelay.call(LibrarySelectionEvent.Unselected(manga)) selectionRelay.call(LibrarySelectionEvent.Unselected(manga))
else {
val position = adapter.indexOf(manga)
lastClickPosition = -1
if (selectedMangas.isEmpty()) {
adapter.mode = SelectableAdapter.Mode.SINGLE
adapter.isLongPressDragEnabled = canDrag()
}
adapter.toggleSelection(position)
(recycler.findViewHolderForItemId(manga.id!!) as? LibraryHolder)?.toggleActivation()
}
} }
} }
} }
@ -878,16 +683,6 @@ class LibraryController(
destroyActionModeIfNeeded() destroyActionModeIfNeeded()
} }
/// Method for single list
override fun startReading(position: Int) {
if (adapter.mode == SelectableAdapter.Mode.MULTI) {
toggleSelection(position)
return
}
val manga = (adapter.getItem(position) as? LibraryItem)?.manga ?: return
startReading(manga)
}
/// Method for the category view /// Method for the category view
fun startReading(manga: Manga) { fun startReading(manga: Manga) {
val activity = activity ?: return val activity = activity ?: return
@ -896,147 +691,4 @@ class LibraryController(
destroyActionModeIfNeeded() destroyActionModeIfNeeded()
startActivity(intent) startActivity(intent)
} }
override fun canDrag(): Boolean {
val filterOff = preferences.filterCompleted().getOrDefault() +
preferences.filterTracked().getOrDefault() +
preferences.filterUnread().getOrDefault() +
preferences.filterMangaType().getOrDefault() +
preferences.filterCompleted().getOrDefault() == 0 &&
!preferences.hideCategories().getOrDefault()
return 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, null)
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) as? LibraryItem ?: return
setSelection(item.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) as? LibraryItem ?: return
setSelection(item.manga, true)
invalidateActionMode()
}
override fun onItemMove(fromPosition: Int, toPosition: Int) { }
override fun onItemReleased(position: Int) {
if (adapter.selectedItemCount > 0) return
val item = adapter.getItem(position) as? LibraryItem ?: return
val newHeader = adapter.getSectionHeader(position) as? LibraryHeaderItem
val libraryItems = adapter.getSectionItems(adapter.getSectionHeader(position))
.filterIsInstance<LibraryItem>()
val mangaIds = libraryItems.mapNotNull { (it as? LibraryItem)?.manga?.id }
if (newHeader?.category?.id == item.manga.category) {
presenter.rearrangeCategory(item.manga.category, mangaIds)
} else {
if (newHeader?.category?.mangaSort == null) {
presenter.moveMangaToCategory(item, newHeader?.category?.id, mangaIds, true)
} else {
MaterialDialog(activity!!).message(R.string.switch_to_dnd)
.positiveButton(R.string.action_switch) {
presenter.moveMangaToCategory(item, newHeader.category.id, mangaIds, true)
}.negativeButton(
text = resources?.getString(
R.string.keep_current_sort,
resources!!.getString(newHeader.category.sortRes()).toLowerCase
(Locale.getDefault())
)
) {
presenter.moveMangaToCategory(
item, newHeader.category.id, mangaIds, false
)
}
.cancelOnTouchOutside(false)
.show()
}
}
}
override fun shouldMoveItem(fromPosition: Int, toPosition: Int): Boolean {
if (adapter.selectedItemCount > 1)
return false
if (adapter.isSelected(fromPosition))
toggleSelection(fromPosition)
return true
}
override fun updateCategory(catId: Int): Boolean {
val category = (adapter.getItem(catId) as? LibraryHeaderItem)?.category ?:
return false
val inQueue = LibraryUpdateService.categoryInQueue(category.id)
snack?.dismiss()
snack = snackbar_layout.snack(resources!!.getString(
when {
inQueue -> R.string.category_already_in_queue
LibraryUpdateService.isRunning(view!!.context) ->
R.string.adding_category_to_queue
else -> R.string.updating_category_x
}, category.name))
if (!inQueue)
LibraryUpdateService.start(view!!.context, category)
return true
}
override fun sortCategory(catId: Int, sortBy: Int): String {
presenter.sortCategory(catId, sortBy)
return ""
}
} }

View File

@ -17,9 +17,9 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.data.library.LibraryUpdateService import eu.kanade.tachiyomi.data.library.LibraryUpdateService
import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.view.gone
import eu.kanade.tachiyomi.util.view.invisible import eu.kanade.tachiyomi.util.view.invisible
import eu.kanade.tachiyomi.util.view.visible import eu.kanade.tachiyomi.util.view.visible
import kotlinx.android.synthetic.main.library_category_header_item.view.*
class LibraryHeaderItem(val category: Category) : AbstractHeaderItem<LibraryHeaderItem.Holder>() { class LibraryHeaderItem(val category: Category) : AbstractHeaderItem<LibraryHeaderItem.Holder>() {
@ -63,7 +63,7 @@ class LibraryHeaderItem(val category: Category) : AbstractHeaderItem<LibraryHead
return -(category.id!!) return -(category.id!!)
} }
class Holder(view: View, private val adapter: LibraryCategoryAdapter) : class Holder(val view: View, private val adapter: LibraryCategoryAdapter) :
FlexibleViewHolder(view, adapter, true) { FlexibleViewHolder(view, adapter, true) {
private val sectionText: TextView = view.findViewById(R.id.category_title) private val sectionText: TextView = view.findViewById(R.id.category_title)
@ -92,15 +92,15 @@ class LibraryHeaderItem(val category: Category) : AbstractHeaderItem<LibraryHead
when { when {
item.category.id == -1 -> { item.category.id == -1 -> {
catProgress.alpha = 1f
updateButton.invisible() updateButton.invisible()
catProgress.gone()
} }
LibraryUpdateService.categoryInQueue(item.category.id) -> { LibraryUpdateService.categoryInQueue(item.category.id) -> {
catProgress.visible() catProgress.alpha = 1f
updateButton.invisible() updateButton.invisible()
} }
else -> { else -> {
catProgress.gone() catProgress.alpha = 0f
updateButton.visible() updateButton.visible()
} }
} }
@ -108,16 +108,15 @@ class LibraryHeaderItem(val category: Category) : AbstractHeaderItem<LibraryHead
private fun addCategoryToUpdate() { private fun addCategoryToUpdate() {
if (adapter.libraryListener.updateCategory(adapterPosition)) { if (adapter.libraryListener.updateCategory(adapterPosition)) {
catProgress.visible() catProgress.alpha = 1f
updateButton.invisible() updateButton.invisible()
} }
} }
private fun showCatSortOptions() { private fun showCatSortOptions() {
val category = val category =
(adapter.getItem(adapterPosition) as? LibraryHeaderItem)?.category ?: return (adapter.getItem(adapterPosition) as? LibraryHeaderItem)?.category ?: return
// Create a PopupMenu, giving it the clicked view for an anchor // Create a PopupMenu, giving it the clicked view for an anchor
val popup = PopupMenu(itemView.context, sortText) val popup = PopupMenu(itemView.context, view.category_sort)
// Inflate our menu resource into the PopupMenu's Menu // Inflate our menu resource into the PopupMenu's Menu
popup.menuInflater.inflate( popup.menuInflater.inflate(

View File

@ -0,0 +1,444 @@
package eu.kanade.tachiyomi.ui.library
import android.content.res.Configuration
import android.os.Bundle
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Spinner
import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.SearchView
import androidx.core.content.ContextCompat
import androidx.core.math.MathUtils
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.afollestad.materialdialogs.MaterialDialog
import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.ControllerChangeType
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.SelectableAdapter
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.LibraryManga
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.library.LibraryUpdateService
import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.util.system.launchUI
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.updatePaddingRelative
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_list_controller.*
import kotlinx.android.synthetic.main.main_activity.*
import kotlinx.coroutines.delay
import java.util.Locale
import kotlin.math.min
class LibraryListController(bundle: Bundle? = null) : LibraryController(bundle),
FlexibleAdapter.OnItemClickListener,
FlexibleAdapter.OnItemLongClickListener,
FlexibleAdapter.OnItemMoveListener,
LibraryCategoryAdapter.LibraryListener{
private lateinit var adapter: LibraryCategoryAdapter
private lateinit var spinner: Spinner
private var lastClickPosition = -1
private var updateScroll = true
private var spinnerAdapter: SpinnerAdapter? = null
/**
* Recycler view of the list of manga.
*/
private lateinit var recycler: RecyclerView
override fun contentView():View = recycler_layout
override fun getTitle(): String? {
return null
}
private var scrollListener = 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
bottom_sheet.updateTitle()
if (spinner.selectedItemPosition != order + 1) {
updateScroll = true
spinner.setSelection(order + 1, true)
}
}
}
}
override fun onViewCreated(view: View) {
super.onViewCreated(view)
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 (!phoneLandscape) {
val height = view.context.resources.getDimensionPixelSize(R.dimen.rounder_radius) + 4.dpToPx
recycler.updatePaddingRelative(bottom = height)
}
}
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
return inflater.inflate(R.layout.library_list_controller, container, false)
}
override fun layoutView(view: View) {
adapter = LibraryCategoryAdapter(this)
recycler =
(recycler_layout.inflate(R.layout.library_grid_recycler) as AutofitRecyclerView).apply {
spanCount = if (libraryLayout == 0) 1 else mangaPerRow
manager.spanSizeLookup = (object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
if (libraryLayout == 0) return 1
val item = this@LibraryListController.adapter.getItem(position)
return if (item is LibraryHeaderItem) manager.spanCount else 1
}
})
}
recycler.setHasFixedSize(true)
recycler.adapter = adapter
recycler_layout.addView(recycler, 0)
adapter.fastScroller = fast_scroller
recycler.addOnScrollListener(scrollListener)
spinner = ReSpinner(view.context)
val tv = TypedValue()
activity!!.theme.resolveAttribute(R.attr.actionBarTintColor, tv, true)
spinner.backgroundTintList = ContextCompat.getColorStateList(
view.context, tv.resourceId
)
(activity as MainActivity).supportActionBar?.customView = spinner
(activity as MainActivity).supportActionBar?.setDisplayShowCustomEnabled(true)
spinnerAdapter = SpinnerAdapter(
view.context,
R.layout.library_spinner_textview,
arrayOf(resources!!.getString(R.string.label_library))
)
spinnerAdapter?.setDropDownViewResource(R.layout.library_spinner_entry_text)
spinner.adapter = spinnerAdapter
}
override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) {
super.onChangeStarted(handler, type)
if (type.isEnter) {
(activity as MainActivity).supportActionBar?.setDisplayShowCustomEnabled(true)
}
else if (type == ControllerChangeType.PUSH_EXIT) {
(activity as MainActivity).toolbar.menu.findItem(R.id
.action_search)?.collapseActionView()
(activity as MainActivity).supportActionBar?.setDisplayShowCustomEnabled(false)
}
}
override fun onDestroy() {
(activity as MainActivity).supportActionBar?.setDisplayShowCustomEnabled(false)
super.onDestroy()
}
override fun onNextLibraryUpdate(mangaMap: List<LibraryItem>, freshStart: Boolean) {
if (mangaMap.isNotEmpty()) {
empty_view?.hide()
} else {
empty_view?.show(R.drawable.ic_book_black_128dp, R.string.information_empty_library)
}
adapter.setItems(mangaMap)
spinner.onItemSelectedListener = null
spinnerAdapter = SpinnerAdapter(view!!.context, R.layout.library_spinner_textview,
presenter.categories.map { it.name }.toTypedArray())
spinnerAdapter?.setDropDownViewResource(R.layout.library_spinner_entry_text)
spinner.adapter = spinnerAdapter
spinner.setSelection(min(presenter.categories.size - 1, activeCategory + 1))
if (!freshStart) {
justStarted = false
if (recycler_layout.alpha == 0f)
recycler_layout.animate().alpha(1f).setDuration(500).start()
}else {
val position = if (freshStart) adapter.indexOf(activeCategory) else null
if (position != null)
(recycler.layoutManager as LinearLayoutManager)
.scrollToPositionWithOffset(position, (-30).dpToPx)
}
adapter.isLongPressDragEnabled = canDrag()
tabsVisibilityRelay.call(false)
bottom_sheet.lastCategory = presenter.categories[MathUtils.clamp(
activeCategory, 0, presenter.categories.size - 1
)]
bottom_sheet.updateTitle()
updateScroll = false
spinner.onItemSelectedListener = IgnoreFirstSpinnerListener { pos ->
if (updateScroll) {
updateScroll = false
return@IgnoreFirstSpinnerListener
}
val headerPosition = adapter.indexOf(pos - 1)
if (headerPosition > -1) {
(recycler.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(
headerPosition, (-30).dpToPx
)
}
}
}
override fun reattachAdapter() {
val position =
(recycler.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()
libraryLayout = preferences.libraryLayout().getOrDefault()
recycler.adapter = adapter
(recycler as? AutofitRecyclerView)?.spanCount = if (libraryLayout == 0) 1 else mangaPerRow
(recycler.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(position, 0)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
val searchItem = menu.findItem(R.id.action_search)
val searchView = searchItem.actionView as SearchView
setOnQueryTextChangeListener(searchView) {
query = it ?: ""
adapter.setFilter(it)
adapter.performFilter()
true
}
}
override fun onCatSortChanged(id: Int?) {
val catId = (id ?: presenter.categories.find { it.order == activeCategory }?.id)
?: return
presenter.requestCatSortUpdate(catId)
}
override fun onDestroyActionMode(mode: ActionMode?) {
super.onDestroyActionMode(mode)
adapter.mode = SelectableAdapter.Mode.SINGLE
adapter.clearSelection()
adapter.notifyDataSetChanged()
lastClickPosition = -1
adapter.isLongPressDragEnabled = canDrag()
}
override fun setSelection(manga: Manga, selected: Boolean) {
if (selected) {
if (selectedMangas.add(manga)) {
val position = adapter.indexOf(manga)
if (adapter.mode != SelectableAdapter.Mode.MULTI) {
adapter.mode = SelectableAdapter.Mode.MULTI
}
launchUI {
delay(100)
adapter.isLongPressDragEnabled = false
}
adapter.toggleSelection(position)
(recycler.findViewHolderForItemId(manga.id!!) as? LibraryHolder)?.toggleActivation()
}
} else {
if (selectedMangas.remove(manga)) {
val position = adapter.indexOf(manga)
lastClickPosition = -1
if (selectedMangas.isEmpty()) {
adapter.mode = SelectableAdapter.Mode.SINGLE
adapter.isLongPressDragEnabled = canDrag()
}
adapter.toggleSelection(position)
(recycler.findViewHolderForItemId(manga.id!!) as? LibraryHolder)?.toggleActivation()
}
}
}
/// Method for single list
override fun startReading(position: Int) {
if (adapter.mode == SelectableAdapter.Mode.MULTI) {
toggleSelection(position)
return
}
val manga = (adapter.getItem(position) as? LibraryItem)?.manga ?: return
startReading(manga)
}
/**
* 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) as? LibraryItem ?: return
setSelection(item.manga, !adapter.isSelected(position))
invalidateActionMode()
}
override fun canDrag(): Boolean {
val filterOff = preferences.filterCompleted().getOrDefault() +
preferences.filterTracked().getOrDefault() +
preferences.filterUnread().getOrDefault() +
preferences.filterMangaType().getOrDefault() +
preferences.filterCompleted().getOrDefault() == 0 &&
!preferences.hideCategories().getOrDefault()
return 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, null)
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)
}
override fun onUpdateManga(manga: LibraryManga) {
if (manga.id == null) adapter.notifyDataSetChanged()
else super.onUpdateManga(manga)
}
/**
* 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) as? LibraryItem ?: return
setSelection(item.manga, true)
invalidateActionMode()
}
override fun onItemMove(fromPosition: Int, toPosition: Int) { }
override fun onItemReleased(position: Int) {
if (adapter.selectedItemCount > 0) return
val item = adapter.getItem(position) as? LibraryItem ?: return
val newHeader = adapter.getSectionHeader(position) as? LibraryHeaderItem
val libraryItems = adapter.getSectionItems(adapter.getSectionHeader(position))
.filterIsInstance<LibraryItem>()
val mangaIds = libraryItems.mapNotNull { (it as? LibraryItem)?.manga?.id }
if (newHeader?.category?.id == item.manga.category) {
presenter.rearrangeCategory(item.manga.category, mangaIds)
} else {
if (newHeader?.category?.mangaSort == null) {
presenter.moveMangaToCategory(item, newHeader?.category?.id, mangaIds, true)
} else {
MaterialDialog(activity!!).message(R.string.switch_to_dnd)
.positiveButton(R.string.action_switch) {
presenter.moveMangaToCategory(item, newHeader.category.id, mangaIds, true)
}.negativeButton(
text = resources?.getString(
R.string.keep_current_sort,
resources!!.getString(newHeader.category.sortRes()).toLowerCase
(Locale.getDefault())
)
) {
presenter.moveMangaToCategory(
item, newHeader.category.id, mangaIds, false
)
}
.cancelOnTouchOutside(false)
.show()
}
}
}
override fun shouldMoveItem(fromPosition: Int, toPosition: Int): Boolean {
if (adapter.selectedItemCount > 1)
return false
if (adapter.isSelected(fromPosition))
toggleSelection(fromPosition)
return true
}
override fun updateCategory(catId: Int): Boolean {
val category = (adapter.getItem(catId) as? LibraryHeaderItem)?.category ?:
return false
val inQueue = LibraryUpdateService.categoryInQueue(category.id)
snack?.dismiss()
snack = snackbar_layout.snack(resources!!.getString(
when {
inQueue -> R.string.category_already_in_queue
LibraryUpdateService.isRunning() ->
R.string.adding_category_to_queue
else -> R.string.updating_category_x
}, category.name))
if (!inQueue)
LibraryUpdateService.start(view!!.context, category)
return true
}
override fun sortCategory(catId: Int, sortBy: Int): String {
presenter.sortCategory(catId, sortBy)
return ""
}
}

View File

@ -121,7 +121,7 @@ class SortFilterBottomSheet @JvmOverloads constructor(context: Context, attrs: A
updateTitle() updateTitle()
val shadow2:View = (pagerView.parent as ViewGroup).findViewById(R.id.shadow2) val shadow2:View = (pagerView.parent as ViewGroup).findViewById(R.id.shadow2)
val shadow:View = (pagerView.parent as ViewGroup).findViewById(R.id.shadow) val shadow:View = (pagerView.parent as ViewGroup).findViewById(R.id.shadow)
val fastScroller:View = (pagerView.parent as ViewGroup).findViewById(R.id.fast_scroller) val fastScroller:View? = (pagerView.parent as ViewGroup).findViewById(R.id.fast_scroller)
val coordLayout:View = (pagerView.parent as ViewGroup).findViewById(R.id.snackbar_layout) val coordLayout:View = (pagerView.parent as ViewGroup).findViewById(R.id.snackbar_layout)
val phoneLandscape = (isLandscape() && !isTablet()) val phoneLandscape = (isLandscape() && !isTablet())
if (phoneLandscape) if (phoneLandscape)
@ -167,7 +167,7 @@ class SortFilterBottomSheet @JvmOverloads constructor(context: Context, attrs: A
} }
if (sheetBehavior?.state == BottomSheetBehavior.STATE_COLLAPSED) { if (sheetBehavior?.state == BottomSheetBehavior.STATE_COLLAPSED) {
val height = context.resources.getDimensionPixelSize(R.dimen.rounder_radius) val height = context.resources.getDimensionPixelSize(R.dimen.rounder_radius)
fastScroller.updateLayoutParams<CoordinatorLayout.LayoutParams> { fastScroller?.updateLayoutParams<CoordinatorLayout.LayoutParams> {
bottomMargin = if (phoneLandscape) 0 else (top_bar.height - height) bottomMargin = if (phoneLandscape) 0 else (top_bar.height - height)
} }
pager?.setPadding(0, 0, 0, if (phoneLandscape) 0 else pager?.setPadding(0, 0, 0, if (phoneLandscape) 0 else

View File

@ -47,6 +47,7 @@ import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchController
import eu.kanade.tachiyomi.ui.download.DownloadController import eu.kanade.tachiyomi.ui.download.DownloadController
import eu.kanade.tachiyomi.ui.extension.ExtensionController import eu.kanade.tachiyomi.ui.extension.ExtensionController
import eu.kanade.tachiyomi.ui.library.LibraryController import eu.kanade.tachiyomi.ui.library.LibraryController
import eu.kanade.tachiyomi.ui.library.LibraryListController
import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.ui.recent_updates.RecentChaptersController import eu.kanade.tachiyomi.ui.recent_updates.RecentChaptersController
import eu.kanade.tachiyomi.ui.recently_read.RecentlyReadController import eu.kanade.tachiyomi.ui.recently_read.RecentlyReadController
@ -131,7 +132,11 @@ open class MainActivity : BaseActivity(), DownloadServiceListener {
val currentRoot = router.backstack.firstOrNull() val currentRoot = router.backstack.firstOrNull()
if (currentRoot?.tag()?.toIntOrNull() != id) { if (currentRoot?.tag()?.toIntOrNull() != id) {
when (id) { when (id) {
R.id.nav_library -> setRoot(LibraryController(), id) R.id.nav_library -> setRoot(
if (preferences.libraryAsSingleList().getOrDefault())
LibraryListController()
else
LibraryController(), id)
R.id.nav_recents -> { R.id.nav_recents -> {
if (preferences.showRecentUpdates().getOrDefault()) if (preferences.showRecentUpdates().getOrDefault())
setRoot(RecentChaptersController(), id) setRoot(RecentChaptersController(), id)

View File

@ -26,10 +26,10 @@ import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.ui.recently_read.RecentlyReadController
import eu.kanade.tachiyomi.util.system.notificationManager import eu.kanade.tachiyomi.util.system.notificationManager
import eu.kanade.tachiyomi.util.view.RecyclerWindowInsetsListener import eu.kanade.tachiyomi.util.view.RecyclerWindowInsetsListener
import eu.kanade.tachiyomi.util.view.snack import eu.kanade.tachiyomi.util.view.snack
import eu.kanade.tachiyomi.ui.recently_read.RecentlyReadController
import kotlinx.android.synthetic.main.recent_chapters_controller.* import kotlinx.android.synthetic.main.recent_chapters_controller.*
import timber.log.Timber import timber.log.Timber
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
@ -98,7 +98,7 @@ class RecentChaptersController : NucleusController<RecentChaptersPresenter>(),
swipe_refresh.setDistanceToTriggerSync((2 * 64 * view.resources.displayMetrics.density).toInt()) swipe_refresh.setDistanceToTriggerSync((2 * 64 * view.resources.displayMetrics.density).toInt())
swipe_refresh.refreshes().subscribeUntilDestroy { swipe_refresh.refreshes().subscribeUntilDestroy {
if (!LibraryUpdateService.isRunning(view.context)) { if (!LibraryUpdateService.isRunning()) {
LibraryUpdateService.start(view.context) LibraryUpdateService.start(view.context)
view.snack(R.string.updating_library) view.snack(R.string.updating_library)
} }

View File

@ -2,8 +2,8 @@ package eu.kanade.tachiyomi.ui.setting
import android.app.Dialog import android.app.Dialog
import android.os.Bundle import android.os.Bundle
import androidx.preference.PreferenceScreen
import android.widget.Toast import android.widget.Toast
import androidx.preference.PreferenceScreen
import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.MaterialDialog
import com.bluelinelabs.conductor.RouterTransaction import com.bluelinelabs.conductor.RouterTransaction
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler import com.bluelinelabs.conductor.changehandler.FadeChangeHandler
@ -13,17 +13,19 @@ import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.library.LibraryUpdateService import eu.kanade.tachiyomi.data.library.LibraryUpdateService
import eu.kanade.tachiyomi.data.library.LibraryUpdateService.Target import eu.kanade.tachiyomi.data.library.LibraryUpdateService.Target
import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.ui.base.controller.DialogController import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.ui.library.LibraryController import eu.kanade.tachiyomi.ui.library.LibraryController
import eu.kanade.tachiyomi.ui.library.LibraryListController
import eu.kanade.tachiyomi.util.system.launchUI import eu.kanade.tachiyomi.util.system.launchUI
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import eu.kanade.tachiyomi.util.system.toast
import rx.Observable import rx.Observable
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers import rx.schedulers.Schedulers
@ -154,7 +156,11 @@ class SettingsAdvancedController : SettingsController() {
private fun clearDatabase() { private fun clearDatabase() {
// Avoid weird behavior by going back to the library. // Avoid weird behavior by going back to the library.
val newBackstack = listOf(RouterTransaction.with(LibraryController())) + val newBackstack = listOf(RouterTransaction.with(
if (preferences.libraryAsSingleList().getOrDefault())
LibraryListController()
else
LibraryController())) +
router.backstack.drop(1) router.backstack.drop(1)
router.setBackstack(newBackstack, FadeChangeHandler()) router.setBackstack(newBackstack, FadeChangeHandler())

View File

@ -16,7 +16,6 @@
android:gravity="center|start" android:gravity="center|start"
android:inputType="none" android:inputType="none"
android:maxLines="1" android:maxLines="1"
android:textColor="?attr/actionBarTintColor"
app:layout_constraintBottom_toBottomOf="@id/update_button" app:layout_constraintBottom_toBottomOf="@id/update_button"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintWidth_default="wrap" app:layout_constraintWidth_default="wrap"
@ -32,6 +31,7 @@
android:background="@drawable/square_ripple" android:background="@drawable/square_ripple"
android:clickable="true" android:clickable="true"
android:layout_marginEnd="10dp" android:layout_marginEnd="10dp"
android:drawableTint="@color/gray_button"
android:drawableEnd="@drawable/ic_sort_white_24dp" android:drawableEnd="@drawable/ic_sort_white_24dp"
android:drawablePadding="6dp" android:drawablePadding="6dp"
android:focusable="true" android:focusable="true"
@ -65,10 +65,15 @@
<ProgressBar <ProgressBar
android:id="@+id/cat_progress" android:id="@+id/cat_progress"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="0dp" android:indeterminate="true"
android:layout_height="30dp"
android:alpha="0.0"
tools:alpha="1.0"
android:layout_marginStart="0dp" android:layout_marginStart="0dp"
app:layout_constraintBottom_toBottomOf="@+id/category_title" app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@+id/category_title" app:layout_constraintBottom_toBottomOf="@+id/update_button"
app:layout_constraintTop_toTopOf="@+id/category_title" /> app:layout_constraintStart_toStartOf="@+id/update_button"
app:layout_constraintEnd_toEndOf="@id/update_button"
app:layout_constraintTop_toTopOf="@+id/update_button" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -6,16 +6,8 @@
android:id="@+id/library_layout" android:id="@+id/library_layout"
android:layout_height="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.coordinatorlayout.widget.CoordinatorLayout <androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/pager_layout" android:id="@+id/pager_layout"
android:visibility="gone"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
@ -25,15 +17,6 @@
android:layout_height="match_parent" /> android:layout_height="match_parent" />
</androidx.coordinatorlayout.widget.CoordinatorLayout> </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:fastScrollerIgnoreTouchesOutsideHandle="true"
app:fastScrollerBubbleEnabled="false"
tools:visibility="visible" />
<androidx.coordinatorlayout.widget.CoordinatorLayout <androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/snackbar_layout" android:id="@+id/snackbar_layout"
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<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">
<FrameLayout
android:id="@+id/recycler_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
</FrameLayout>
<eu.davidea.fastscroller.FastScroller
android:id="@+id/fast_scroller"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="end"
app:fastScrollerIgnoreTouchesOutsideHandle="true"
app:fastScrollerBubbleEnabled="false"
tools:visibility="visible" />
<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"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone" />
<View
android:id="@+id/shadow"
android:layout_width="match_parent"
android:layout_height="24dp"
android:alpha="0.5"
android:background="@drawable/shape_gradient_top_shadow"
android:paddingBottom="10dp"
app:layout_anchorGravity="top"
app:layout_anchor="@id/bottom_sheet" />
<!-- Adding bottom sheet after main content -->
<include layout="@layout/filter_bottom_sheet"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
style="@style/Theme.Widget.FABFixed"
android:layout_gravity="center"
android:clickable="false"
android:focusable="false"
android:scaleX="0"
android:scaleY="0"
app:layout_anchor="@id/bottom_sheet"
app:layout_anchorGravity="end|top"
app:srcCompat="@drawable/ic_file_download_white_24dp" />
<View
android:id="@+id/shadow2"
android:layout_width="match_parent"
android:layout_height="8dp"
android:layout_gravity="bottom"
android:alpha="0.25"
android:background="@drawable/shape_gradient_top_shadow" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -7,5 +7,6 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="start" android:gravity="start"
android:textSize="20sp" android:textSize="20sp"
android:textColor="?attr/actionBarTintColor"
tools:text="Title" tools:text="Title"
tools:background="?attr/colorPrimary"/> tools:background="?attr/colorPrimary"/>

View File

@ -21,7 +21,7 @@
</declare-styleable> </declare-styleable>
<attr name="navigation_view_theme" format="reference"/> <attr name="navigation_view_theme" format="reference"/>
<attr name="actionBarTintColor" format="reference|integer"/> <attr name="actionBarTintColor" format="color"/>
<attr name="tabBarIconColor" format="reference|integer"/> <attr name="tabBarIconColor" format="reference|integer"/>
<attr name="tabBarIconInactive" format="reference|integer"/> <attr name="tabBarIconInactive" format="reference|integer"/>
<attr name="selectable_list_drawable" format="reference|integer" /> <attr name="selectable_list_drawable" format="reference|integer" />