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:
parent
25bf5602ba
commit
a7e349b1b2
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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>) {
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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)
|
||||
|
@ -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>
|
@ -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>
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user