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.util.chapter.syncChaptersWithSource
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.notificationManager
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import rx.Observable
import rx.Subscription
import rx.schedulers.Schedulers
@ -98,6 +99,13 @@ class LibraryUpdateService(
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.
*/
@ -127,11 +135,8 @@ class LibraryUpdateService(
*/
const val KEY_CATEGORY = "category"
private val mangaToUpdate = mutableListOf<LibraryManga>()
private val categoryIds = mutableSetOf<Int>()
fun categoryInQueue(id: Int?) = categoryIds.contains(id)
fun categoryInQueue(id: Int?) = instance?.categoryIds?.contains(id) ?: false
private var instance: LibraryUpdateService? = null
/**
* Key that defines what should be updated.
@ -141,11 +146,10 @@ class LibraryUpdateService(
/**
* Returns the status of the service.
*
* @param context the application context.
* @return true if the service is running, false otherwise.
*/
fun isRunning(context: Context): Boolean {
return context.isServiceRunning(LibraryUpdateService::class.java)
fun isRunning(): Boolean {
return instance != null
}
/**
@ -157,12 +161,11 @@ class LibraryUpdateService(
* @param target defines what should be updated.
*/
fun start(context: Context, category: Category? = null, target: Target = Target.CHAPTERS) {
if (!isRunning(context)) {
if (!isRunning()) {
val intent = Intent(context, LibraryUpdateService::class.java).apply {
putExtra(KEY_TARGET, target)
category?.id?.let { id ->
putExtra(KEY_CATEGORY, id)
categoryIds.add(id)
}
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
@ -173,22 +176,11 @@ class LibraryUpdateService(
}
else {
if (target == Target.CHAPTERS) category?.id?.let {
categoryIds.add(it)
val preferences: PreferencesHelper = Injekt.get()
val selectedScheme = preferences.libraryUpdatePrioritization().getOrDefault()
addManga(getMangaToUpdate(it, target).sortedWith(
rankingScheme[selectedScheme]
))
instance?.addCategory(it)
}
}
}
private fun addManga(mangaToAdd: List<LibraryManga>) {
for (manga in mangaToAdd) {
if (mangaToUpdate.none { it.id == manga.id }) mangaToUpdate.add(manga)
}
}
/**
* Stops the service.
*
@ -198,40 +190,6 @@ class LibraryUpdateService(
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
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
* the wake lock.
@ -255,22 +262,19 @@ class LibraryUpdateService(
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
* lock.
*/
override fun onDestroy() {
job?.cancel()
if (instance == this)
instance = null
subscription?.unsubscribe()
mangaToUpdate.clear()
categoryIds.clear()
if (wakeLock.isHeld) {
wakeLock.release()
}
listener?.onUpdateManga(LibraryManga())
super.onDestroy()
}
@ -291,23 +295,22 @@ class LibraryUpdateService(
*/
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
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.
job?.cancel()
subscription?.unsubscribe()
instance = this
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) {
updateChapters(
getMangaToUpdate(intent, target).sortedWith(rankingScheme[selectedScheme]), startId
)
updateChapters(mangaList, startId)
}
else {
// 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 {
when (target) {
Target.DETAILS -> updateDetails(mangaList)
@ -324,25 +327,26 @@ class LibraryUpdateService(
}
private fun updateChapters(mangaToAdd: List<LibraryManga>, startId: Int) {
addManga(mangaToAdd)
val handler = CoroutineExceptionHandler { _, 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)
}
job = GlobalScope.launch(handler) {
updateChaptersJob()
mangaToUpdate.clear()
categoryIds.clear()
stopSelf(startId)
updateChaptersJob(mangaToAdd)
}
job?.invokeOnCompletion { stopSelf(startId) }
}
private fun updateChaptersJob() {
// Initialize the variables holding the progress of the updates.
var count = 0
// List containing new updates
val newUpdates = ArrayList<Pair<LibraryManga, Array<Chapter>>>()
private suspend fun updateChaptersJob(mangaToAdd: List<LibraryManga>) {
// list containing failed updates
val failedUpdates = ArrayList<Manga>()
// List containing categories that get included in downloads.
@ -351,28 +355,37 @@ class LibraryUpdateService(
val downloadNew = preferences.downloadNew().getOrDefault()
// Boolean to determine if DownloadManager has downloads
var hasDownloads = false
withContext(Dispatchers.IO) {
// Initialize the variables holding the progress of the updates.
var count = 0
while (count < mangaToUpdate.size) {
if (job?.isCancelled == true || job == null) break
val manga = mangaToUpdate[count]
showProgressNotification(manga, count++, mangaToUpdate.size)
val source = sourceManager.get(manga.source) as? HttpSource ?: continue
val fetchedChapters = try { source.fetchChapterList(manga).toBlocking().single() }
catch(e: java.lang.Exception) {
failedUpdates.add(manga)
emptyList<SChapter>()
}
if (fetchedChapters.isNotEmpty()) {
val newChapters = syncChaptersWithSource(db, fetchedChapters, manga, source)
if (newChapters.first.isNotEmpty()) {
if (downloadNew && (categoriesToDownload.isEmpty() || manga.category in categoriesToDownload)) {
downloadChapters(manga, newChapters.first.sortedBy { it.chapter_number })
hasDownloads = true
mangaToUpdate.addAll(mangaToAdd)
while (count < mangaToUpdate.size) {
if (job?.isCancelled == true) break
val manga = mangaToUpdate[count]
showProgressNotification(manga, count++, mangaToUpdate.size)
val source = sourceManager.get(manga.source) as? HttpSource ?: continue
val fetchedChapters = try {
source.fetchChapterList(manga).toBlocking().single()
} catch (e: java.lang.Exception) {
failedUpdates.add(manga)
emptyList<SChapter>()
} ?: emptyList()
if (fetchedChapters.isNotEmpty()) {
val newChapters = syncChaptersWithSource(db, fetchedChapters, manga, source)
if (newChapters.first.isNotEmpty()) {
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()) {

View File

@ -120,7 +120,7 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
resources.getString(
when {
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
}, category.name))
if (!inQueue)

View File

@ -12,15 +12,10 @@ import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager
import android.widget.Spinner
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.SearchView
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 com.afollestad.materialdialogs.MaterialDialog
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.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
@ -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.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.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 kotlinx.coroutines.delay
import rx.Subscription
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.util.Locale
import kotlin.math.min
class LibraryController(
open class LibraryController(
bundle: Bundle? = null,
private val preferences: PreferencesHelper = Injekt.get()
protected val preferences: PreferencesHelper = Injekt.get()
) : BaseController(bundle), TabbedController,
ActionMode.Callback,
ChangeMangaCategoriesDialog.Listener,
MigrationInterface,
DownloadServiceListener,
LibraryServiceListener,
FlexibleAdapter.OnItemClickListener,
FlexibleAdapter.OnItemLongClickListener,
FlexibleAdapter.OnItemMoveListener,
LibraryCategoryAdapter.LibraryListener{
LibraryServiceListener {
/**
* Position of the active category.
*/
var activeCategory: Int = preferences.lastUsedCategory().getOrDefault()
private set
protected var activeCategory: Int = preferences.lastUsedCategory().getOrDefault()
/**
* Action mode for selections.
@ -107,7 +85,7 @@ class LibraryController(
/**
* Library search query.
*/
private var query = ""
protected var query = ""
/**
* Currently selected mangas.
@ -154,16 +132,11 @@ class LibraryController(
* Adapter of the view pager.
*/
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)
protected var tabsVisibilityRelay: BehaviorRelay<Boolean> = BehaviorRelay.create(false)
private var tabsVisibilitySubscription: Subscription? = null
@ -171,56 +144,24 @@ class LibraryController(
var snack: Snackbar? = null
var presenter = LibraryPresenter(this)
lateinit var presenter:LibraryPresenter
private set
private var justStarted = true
protected var justStarted = true
private var updateScroll = true
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()
var libraryLayout:Int = preferences.libraryLayout().getOrDefault()
private var usePager: Boolean = !preferences.libraryAsSingleList().getOrDefault()
open fun contentView():View = pager_layout
init {
setHasOptionsMenu(true)
retainViewMode = RetainViewMode.RETAIN_DETACH
}
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 {
@ -230,64 +171,17 @@ class LibraryController(
override fun onViewCreated(view: View) {
super.onViewCreated(view)
mangaPerRow = getColumnsPreferenceForCurrentOrientation().getOrDefault()
if (!::presenter.isInitialized)
presenter = LibraryPresenter(this)
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 = (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
}
layoutView(view)
if (selectedMangas.isNotEmpty()) {
createActionModeIfNeeded()
}
//bottom_sheet.onCreate(pager_layout)
bottom_sheet.onCreate(if (usePager) pager_layout else recycler_layout)
bottom_sheet.onCreate(contentView())
bottom_sheet.onGroupClicked = {
when (it) {
@ -305,50 +199,53 @@ class LibraryController(
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()) {
fab.scaleY = 1f
fab.scaleX = 1f
fab.isClickable = true
fab.isFocusable = true
}
presenter.onRestore()
val library = presenter.getAllManga()
if (library != null) presenter.updateViewBlocking() //onNextLibraryUpdate(presenter.categories, library)
else {
library_pager.alpha = 0f
recycler_layout.alpha = 0f
contentView().alpha = 0f
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) {
super.onChangeStarted(handler, type)
if (type.isEnter) {
if (!usePager)
(activity as MainActivity).supportActionBar?.setDisplayShowCustomEnabled(true)
else
if (library_pager != null)
activity?.tabs?.setupWithViewPager(library_pager)
presenter.getLibrary()
DownloadService.addListener(this)
DownloadService.callListeners()
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) {
@ -365,7 +262,6 @@ class LibraryController(
}
override fun onDestroy() {
(activity as MainActivity).supportActionBar?.setDisplayShowCustomEnabled(false)
presenter.onDestroy()
super.onDestroy()
}
@ -393,7 +289,7 @@ class LibraryController(
}
override fun onUpdateManga(manga: LibraryManga) {
presenter.updateManga(manga)
if (manga.id != null) presenter.updateManga(manga)
}
override fun onDetach(view: View) {
@ -424,56 +320,7 @@ class LibraryController(
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)
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
)
}
}
}
open fun onNextLibraryUpdate(mangaMap: List<LibraryItem>, freshStart: Boolean = false) { }
fun onNextLibraryUpdate(categories: List<Category>, mangaMap: Map<Int, List<LibraryItem>>,
freshStart: Boolean = false) {
@ -523,8 +370,8 @@ class LibraryController(
}
else if (!freshStart) {
justStarted = false
if (library_pager.alpha == 0f)
library_pager.animate().alpha(1f).setDuration(500).start()
if (pager_layout.alpha == 0f)
pager_layout.animate().alpha(1f).setDuration(500).start()
}
}
@ -563,10 +410,8 @@ class LibraryController(
destroyActionModeIfNeeded()
}
fun onCatSortChanged(id: Int? = null) {
val catId =
(if (usePager)(id ?: pagerAdapter?.categories?.getOrNull(library_pager.currentItem)?.id)
else (id ?: presenter.categories.find { it.order == activeCategory }?.id))
open fun onCatSortChanged(id: Int? = null) {
val catId = (id ?: pagerAdapter?.categories?.getOrNull(library_pager.currentItem)?.id)
?: return
presenter.requestCatSortUpdate(catId)
}
@ -574,26 +419,15 @@ class LibraryController(
/**
* Reattaches the adapter to the view pager to recreate fragments
*/
private fun reattachAdapter() {
if (usePager) {
val adapter = pagerAdapter ?: return
protected open fun reattachAdapter() {
val adapter = pagerAdapter ?: 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
}
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)
}
adapter.recycle = false
library_pager.adapter = adapter
library_pager.currentItem = position
adapter.recycle = true
}
/**
@ -643,8 +477,6 @@ class LibraryController(
setOnQueryTextChangeListener(searchView) {
query = it ?: ""
searchRelay.call(query)
adapter.setFilter(it)
adapter.performFilter()
true
}
searchItem.fixExpand(onExpand = { invalidateMenuOnExpand() })
@ -778,11 +610,6 @@ class LibraryController(
override fun onDestroyActionMode(mode: ActionMode?) {
// Clear all the manga selections and notify child views.
selectedMangas.clear()
adapter.mode = SelectableAdapter.Mode.SINGLE
adapter.clearSelection()
adapter.notifyDataSetChanged()
lastClickPosition = -1
adapter.isLongPressDragEnabled = canDrag()
selectionRelay.call(LibrarySelectionEvent.Cleared())
actionMode = null
}
@ -797,36 +624,14 @@ class LibraryController(
* @param manga the manga whose selection has changed.
* @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 (selectedMangas.add(manga)) {
if (usePager) 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()
}
selectionRelay.call(LibrarySelectionEvent.Selected(manga))
}
} else {
if (selectedMangas.remove(manga)) {
if (usePager) 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()
}
selectionRelay.call(LibrarySelectionEvent.Unselected(manga))
}
}
}
@ -878,16 +683,6 @@ class LibraryController(
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
fun startReading(manga: Manga) {
val activity = activity ?: return
@ -896,147 +691,4 @@ class LibraryController(
destroyActionModeIfNeeded()
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.library.LibraryUpdateService
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.visible
import kotlinx.android.synthetic.main.library_category_header_item.view.*
class LibraryHeaderItem(val category: Category) : AbstractHeaderItem<LibraryHeaderItem.Holder>() {
@ -63,7 +63,7 @@ class LibraryHeaderItem(val category: Category) : AbstractHeaderItem<LibraryHead
return -(category.id!!)
}
class Holder(view: View, private val adapter: LibraryCategoryAdapter) :
class Holder(val view: View, private val adapter: LibraryCategoryAdapter) :
FlexibleViewHolder(view, adapter, true) {
private val sectionText: TextView = view.findViewById(R.id.category_title)
@ -92,15 +92,15 @@ class LibraryHeaderItem(val category: Category) : AbstractHeaderItem<LibraryHead
when {
item.category.id == -1 -> {
catProgress.alpha = 1f
updateButton.invisible()
catProgress.gone()
}
LibraryUpdateService.categoryInQueue(item.category.id) -> {
catProgress.visible()
catProgress.alpha = 1f
updateButton.invisible()
}
else -> {
catProgress.gone()
catProgress.alpha = 0f
updateButton.visible()
}
}
@ -108,16 +108,15 @@ class LibraryHeaderItem(val category: Category) : AbstractHeaderItem<LibraryHead
private fun addCategoryToUpdate() {
if (adapter.libraryListener.updateCategory(adapterPosition)) {
catProgress.visible()
catProgress.alpha = 1f
updateButton.invisible()
}
}
private fun showCatSortOptions() {
val category =
(adapter.getItem(adapterPosition) as? LibraryHeaderItem)?.category ?: return
// 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
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()
val shadow2:View = (pagerView.parent as ViewGroup).findViewById(R.id.shadow2)
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 phoneLandscape = (isLandscape() && !isTablet())
if (phoneLandscape)
@ -167,7 +167,7 @@ class SortFilterBottomSheet @JvmOverloads constructor(context: Context, attrs: A
}
if (sheetBehavior?.state == BottomSheetBehavior.STATE_COLLAPSED) {
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)
}
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.extension.ExtensionController
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.recent_updates.RecentChaptersController
import eu.kanade.tachiyomi.ui.recently_read.RecentlyReadController
@ -131,7 +132,11 @@ open class MainActivity : BaseActivity(), DownloadServiceListener {
val currentRoot = router.backstack.firstOrNull()
if (currentRoot?.tag()?.toIntOrNull() != 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 -> {
if (preferences.showRecentUpdates().getOrDefault())
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.manga.MangaController
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.view.RecyclerWindowInsetsListener
import eu.kanade.tachiyomi.util.view.snack
import eu.kanade.tachiyomi.ui.recently_read.RecentlyReadController
import kotlinx.android.synthetic.main.recent_chapters_controller.*
import timber.log.Timber
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.refreshes().subscribeUntilDestroy {
if (!LibraryUpdateService.isRunning(view.context)) {
if (!LibraryUpdateService.isRunning()) {
LibraryUpdateService.start(view.context)
view.snack(R.string.updating_library)
}

View File

@ -2,8 +2,8 @@ package eu.kanade.tachiyomi.ui.setting
import android.app.Dialog
import android.os.Bundle
import androidx.preference.PreferenceScreen
import android.widget.Toast
import androidx.preference.PreferenceScreen
import com.afollestad.materialdialogs.MaterialDialog
import com.bluelinelabs.conductor.RouterTransaction
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.library.LibraryUpdateService
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.source.SourceManager
import eu.kanade.tachiyomi.ui.base.controller.DialogController
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.toast
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import eu.kanade.tachiyomi.util.system.toast
import rx.Observable
import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers
@ -154,7 +156,11 @@ class SettingsAdvancedController : SettingsController() {
private fun clearDatabase() {
// 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.setBackstack(newBackstack, FadeChangeHandler())

View File

@ -16,7 +16,6 @@
android:gravity="center|start"
android:inputType="none"
android:maxLines="1"
android:textColor="?attr/actionBarTintColor"
app:layout_constraintBottom_toBottomOf="@id/update_button"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintWidth_default="wrap"
@ -32,6 +31,7 @@
android:background="@drawable/square_ripple"
android:clickable="true"
android:layout_marginEnd="10dp"
android:drawableTint="@color/gray_button"
android:drawableEnd="@drawable/ic_sort_white_24dp"
android:drawablePadding="6dp"
android:focusable="true"
@ -65,10 +65,15 @@
<ProgressBar
android:id="@+id/cat_progress"
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"
app:layout_constraintBottom_toBottomOf="@+id/category_title"
app:layout_constraintStart_toEndOf="@+id/category_title"
app:layout_constraintTop_toTopOf="@+id/category_title" />
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintBottom_toBottomOf="@+id/update_button"
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>

View File

@ -6,16 +6,8 @@
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>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/pager_layout"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="match_parent">
@ -25,15 +17,6 @@
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:fastScrollerIgnoreTouchesOutsideHandle="true"
app:fastScrollerBubbleEnabled="false"
tools:visibility="visible" />
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/snackbar_layout"
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:gravity="start"
android:textSize="20sp"
android:textColor="?attr/actionBarTintColor"
tools:text="Title"
tools:background="?attr/colorPrimary"/>

View File

@ -21,7 +21,7 @@
</declare-styleable>
<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="tabBarIconInactive" format="reference|integer"/>
<attr name="selectable_list_drawable" format="reference|integer" />