More use of Snackbars

No longer double confirming to see if it's ok to remove manga in library and catalouge, snackbar has an undo button
This commit is contained in:
Jay 2019-10-29 19:22:54 -07:00
parent 25bf5602ba
commit a7e349b1b2
14 changed files with 162 additions and 76 deletions

View File

@ -1,7 +1,11 @@
package eu.kanade.tachiyomi.data.cache
import android.content.Context
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.util.DiskUtil
import eu.kanade.tachiyomi.util.launchUI
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import java.io.File
import java.io.IOException
import java.io.InputStream
@ -60,8 +64,30 @@ class CoverCache(private val context: Context) {
return false
// Remove file.
val file = getCoverFile(thumbnailUrl!!)
val file = getCoverFile(thumbnailUrl)
return file.exists() && file.delete()
}
/**
* Delete the cover file from the cache.
*
* @param thumbnailUrl the thumbnail url.
* @return status of deletion.
*/
fun deleteFromCache(manga: Manga, delayBy:Long) {
val thumbnailUrl = manga.thumbnail_url
// Check if url is empty.
if (thumbnailUrl.isNullOrEmpty()) return
launchUI {
if (delayBy > 0) {
delay(delayBy)
if (manga.favorite) cancel()
}
// Remove file.
val file = getCoverFile(thumbnailUrl)
if (file.exists())
file.delete()
}
}
}

View File

@ -9,6 +9,9 @@ import eu.kanade.tachiyomi.data.download.model.DownloadQueue
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.util.launchNow
import eu.kanade.tachiyomi.util.launchUI
import kotlinx.coroutines.delay
import rx.Observable
import uy.kohesive.injekt.injectLazy
@ -181,11 +184,28 @@ class DownloadManager(context: Context) {
* @param source the source of the manga.
*/
fun deleteManga(manga: Manga, source: Source) {
downloader.clearQueue(manga, true)
queue.remove(manga)
provider.findMangaDir(manga, source)?.delete()
cache.removeManga(manga)
}
/**
* Deletes the directory of a downloaded manga.
*
* @param manga the manga to delete.
* @param source the source of the manga.
*/
fun deleteManga(manga: Manga, source: Source, delayBy: Long) {
launchUI {
delay(delayBy)
if (!manga.favorite) {
deleteManga(manga, source)
}
}
}
/**
* Adds a list of chapters to be deleted later.
*

View File

@ -90,6 +90,22 @@ class DownloadPendingDeleter(context: Context) {
}
}
/**
* Returns the list of chapters to be deleted grouped by its manga.
*
* Note: the returned list of manga and chapters only contain basic information needed by the
* downloader, so don't use them for anything else.
*/
@Synchronized
fun getPendingChapters(manga: Manga): List<Chapter>? {
val entries = decodeAll()
prefs.edit().clear().apply()
lastAddedEntry = null
val entry = entries.find { it.manga.id == manga.id }
return entry?.chapters?.map { it.toModel() }
}
/**
* Decodes all the chapters from preferences.
*/

View File

@ -157,6 +157,26 @@ class Downloader(
notifier.dismiss()
}
/**
* Removes everything from the queue for a certain manga
*
* @param isNotification value that determines if status is set (needed for view updates)
*/
fun clearQueue(manga: Manga, isNotification: Boolean = false) {
//Needed to update the chapter view
if (isNotification) {
queue
.filter { it.status == Download.QUEUE && it.manga.id == manga.id }
.forEach { it.status = Download.NOT_DOWNLOADED }
}
queue.remove(manga)
if (queue.isEmpty()) {
DownloadService.stop(context)
stop()
}
notifier.dismiss()
}
/**
* Prepares the subscriptions to start downloading.
*/

View File

@ -503,38 +503,40 @@ open class BrowseCatalogueController(bundle: Bundle) :
override fun onItemLongClick(position: Int) {
val activity = activity ?: return
val manga = (adapter?.getItem(position) as? CatalogueItem?)?.manga ?: return
snack?.dismiss()
if (manga.favorite) {
MaterialDialog.Builder(activity)
.items(activity.getString(R.string.remove_from_library))
.itemsCallback { _, _, which, _ ->
when (which) {
0 -> {
presenter.changeMangaFavorite(manga)
adapter?.notifyItemChanged(position)
activity?.toast(activity?.getString(R.string.manga_removed_library))
}
}
}.show()
} else {
presenter.changeMangaFavorite(manga)
adapter?.notifyItemChanged(position)
snack =
catalogue_view?.snack(activity.getString(R.string.manga_removed_library), 5000) {
setAction(R.string.action_undo) {
if (!manga.favorite) addManga(manga, position)
}
}
} else {
addManga(manga, position)
snack =
catalogue_view?.snack(activity.getString(R.string.manga_added_library), Snackbar.LENGTH_SHORT)
}
}
val categories = presenter.getCategories()
val defaultCategory = categories.find { it.id == preferences.defaultCategory() }
if (defaultCategory != null) {
presenter.moveMangaToCategory(manga, defaultCategory)
} else if (categories.size <= 1) { // default or the one from the user
presenter.moveMangaToCategory(manga, categories.firstOrNull())
} else {
val ids = presenter.getMangaCategoryIds(manga)
val preselected = ids.mapNotNull { id ->
categories.indexOfFirst { it.id == id }.takeIf { it != -1 }
}.toTypedArray()
private fun addManga(manga: Manga, position: Int) {
presenter.changeMangaFavorite(manga)
adapter?.notifyItemChanged(position)
ChangeMangaCategoriesDialog(this, listOf(manga), categories, preselected)
.showDialog(router)
}
activity?.toast(activity?.getString(R.string.manga_added_library))
val categories = presenter.getCategories()
val defaultCategory = categories.find { it.id == preferences.defaultCategory() }
if (defaultCategory != null) {
presenter.moveMangaToCategory(manga, defaultCategory)
} else if (categories.size <= 1) { // default or the one from the user
presenter.moveMangaToCategory(manga, categories.firstOrNull())
} else {
val ids = presenter.getMangaCategoryIds(manga)
val preselected = ids.mapNotNull { id ->
categories.indexOfFirst { it.id == id }.takeIf { it != -1 }
}.toTypedArray()
ChangeMangaCategoriesDialog(this, listOf(manga), categories, preselected).showDialog(router)
}
}

View File

@ -8,6 +8,7 @@ import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.MangaCategory
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.SourceManager
@ -254,7 +255,9 @@ open class BrowseCataloguePresenter(
fun changeMangaFavorite(manga: Manga) {
manga.favorite = !manga.favorite
if (!manga.favorite) {
coverCache.deleteFromCache(manga.thumbnail_url)
coverCache.deleteFromCache(manga, 5000)
val downloadManager: DownloadManager = Injekt.get()
downloadManager.deleteManga(manga,source,5000)
}
db.insertManga(manga).executeAsBlocking()
}

View File

@ -8,11 +8,11 @@ import android.net.Uri
import android.os.Bundle
import com.google.android.material.tabs.TabLayout
import androidx.core.graphics.drawable.DrawableCompat
import androidx.drawerlayout.widget.DrawerLayout
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.SearchView
import android.view.*
import androidx.core.view.GravityCompat
import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.ControllerChangeType
import com.f2prateek.rx.preferences.Preference
@ -177,7 +177,8 @@ class LibraryController(
override fun createSecondaryDrawer(drawer: androidx.drawerlayout.widget.DrawerLayout): ViewGroup {
val view = drawer.inflate(R.layout.library_drawer) as LibraryNavigationView
navView = view
drawer.setDrawerLockMode(androidx.drawerlayout.widget.DrawerLayout.LOCK_MODE_UNLOCKED, Gravity.END)
drawer.setDrawerLockMode(androidx.drawerlayout.widget.DrawerLayout.LOCK_MODE_UNLOCKED,
GravityCompat.END)
navView?.onGroupClicked = { group ->
when (group) {
@ -365,7 +366,7 @@ class LibraryController(
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.action_filter -> {
navView?.let { activity?.drawer?.openDrawer(Gravity.END) }
navView?.let { activity?.drawer?.openDrawer(GravityCompat.END) }
}
R.id.action_update_library -> {
activity?.let { LibraryUpdateService.start(it) }
@ -413,7 +414,7 @@ class LibraryController(
destroyActionModeIfNeeded()
}
R.id.action_move_to_category -> showChangeMangaCategoriesDialog()
R.id.action_delete -> showDeleteMangaDialog()
R.id.action_delete -> deleteMangasFromLibrary()
else -> return false
}
return true
@ -470,8 +471,15 @@ class LibraryController(
.showDialog(router)
}
private fun showDeleteMangaDialog() {
DeleteLibraryMangasDialog(this, selectedMangas.toList()).showDialog(router)
private fun deleteMangasFromLibrary() {
val mangas = selectedMangas.toList()
presenter.removeMangaFromLibrary(mangas, true)
destroyActionModeIfNeeded()
view?.snack(activity?.getString(R.string.remove_from_library) ?: "", 5000) {
setAction(R.string.action_undo) {
presenter.addMangas(mangas)
}
}
}
override fun updateCategoriesForMangas(mangas: List<Manga>, categories: List<Category>) {

View File

@ -222,16 +222,13 @@ class LibraryPresenter(
* @return an observable of the categories and its manga.
*/
private fun getLibraryObservable(): Observable<Library> {
return Observable.combineLatest(getCategoriesObservable(), getLibraryMangasObservable(),
{ dbCategories, libraryManga ->
val categories = if (libraryManga.containsKey(0))
arrayListOf(Category.createDefault()) + dbCategories
else
dbCategories
return Observable.combineLatest(getCategoriesObservable(), getLibraryMangasObservable()) { dbCategories, libraryManga ->
val categories = if (libraryManga.containsKey(0)) arrayListOf(Category.createDefault()) + dbCategories
else dbCategories
this.categories = categories
Library(categories, libraryManga)
})
this.categories = categories
Library(categories, libraryManga)
}
}
/**
@ -316,11 +313,11 @@ class LibraryPresenter(
Observable.fromCallable {
mangaToDelete.forEach { manga ->
coverCache.deleteFromCache(manga.thumbnail_url)
coverCache.deleteFromCache(manga, 5000)
if (deleteChapters) {
val source = sourceManager.get(manga.source) as? HttpSource
if (source != null) {
downloadManager.deleteManga(manga, source)
downloadManager.deleteManga(manga, source, 5000)
}
}
}
@ -329,6 +326,17 @@ class LibraryPresenter(
.subscribe()
}
fun addMangas(mangas: List<Manga>) {
val mangaToAdd = mangas.distinctBy { it.id }
mangaToAdd.forEach { it.favorite = true }
Observable.fromCallable { db.insertMangas(mangaToAdd).executeAsBlocking() }
.onErrorResumeNext { Observable.empty() }
.subscribeOn(Schedulers.io())
.subscribe()
mangaToAdd.forEach { db.insertManga(it).executeAsBlocking() }
}
/**
* Move the given list of manga to categories.
*

View File

@ -421,24 +421,18 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
private fun showAddedSnack() {
val view = container
snack?.dismiss()
snack = view?.snack(view.context.getString(R.string.manga_added_library), Snackbar
.LENGTH_SHORT)
snack = view?.snack(view.context.getString(R.string.manga_added_library), Snackbar.LENGTH_SHORT)
}
private fun showRemovedSnack() {
val view = container
val hasDownloads = presenter.hasDownloads()
snack?.dismiss()
if (view != null) {
val message = view.context.getString(R.string.manga_removed_library) +
(if (hasDownloads) "\n" + view.context.getString(R.string
.delete_downloads_for_manga) else "")
snack = view.snack(message, (if (hasDownloads) Snackbar.LENGTH_INDEFINITE
else Snackbar.LENGTH_SHORT)) {
if (hasDownloads) setAction(R.string.action_delete) {
presenter.deleteDownloads()
}
snack = view.snack(view.context.getString(R.string.manga_removed_library), 5000) {
setAction(R.string.action_undo) {
presenter.setFavorite(true)
}
}
}
}

View File

@ -101,34 +101,21 @@ class MangaInfoPresenter(
fun toggleFavorite(): Boolean {
manga.favorite = !manga.favorite
if (!manga.favorite) {
coverCache.deleteFromCache(manga.thumbnail_url)
coverCache.deleteFromCache(manga, 5000)
downloadManager.deleteManga(manga, source, 5000)
}
db.insertManga(manga).executeAsBlocking()
sendMangaToView()
return manga.favorite
}
private fun setFavorite(favorite: Boolean) {
fun setFavorite(favorite: Boolean) {
if (manga.favorite == favorite) {
return
}
toggleFavorite()
}
/**
* Returns true if the manga has any downloads.
*/
fun hasDownloads(): Boolean {
return downloadManager.getDownloadCount(manga) > 0
}
/**
* Deletes all the downloads for the manga.
*/
fun deleteDownloads() {
downloadManager.deleteManga(manga, source)
}
/**
* Get the default, and user categories.
*

View File

@ -40,7 +40,7 @@ fun View.snack(message: String, length: Int = Snackbar.LENGTH_LONG, f: (Snackbar
Unit)? = null): Snackbar {
val snack = Snackbar.make(this, message, length)
val textView: TextView = snack.view.findViewById(com.google.android.material.R.id.snackbar_text)
textView.setTextColor(Color.WHITE)
textView.setTextColor(context.getResourceColor(android.R.attr.textColorPrimaryInverse))
when {
Build.VERSION.SDK_INT >= 23 -> snack.config(context, rootWindowInsets.systemWindowInsetBottom)
else -> snack.config(context)

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#323232" />
<corners android:radius="4dp" />
<solid android:color="@color/snackbarBackground"/>
<corners android:radius="4dp"/>
</shape>

View File

@ -3,5 +3,6 @@
<color name="drawerHighlight">@color/md_white_1000_12</color>
<color name="drawerPrimary">@color/colorAccentDark</color>
<color name="oldNavBarBackground">#B3000000</color>
<color name="snackbarBackground">#FFFFFF</color>
<color name="cardBackground">@color/colorDarkPrimary</color>
</resources>

View File

@ -6,6 +6,7 @@
<color name="drawerHighlight">@color/md_black_1000_12</color>
<color name="drawerPrimary">@color/colorPrimary</color>
<color name="cardBackground">#FFFFFF</color>
<color name="snackbarBackground">#323232</color>
<!-- Dark Application Colors -->
<color name="colorDarkPrimary">#212121</color>
<color name="colorDarkPrimaryDark">#212121</color>