Added add to library dialog when downloading from catalogue (#618)

* Now show snackbar when adding from catalogue

* Code cleanup + added manga favorite event to update favorite drawable when added via snack

* Update SettingsAdvancedFragment.kt

Forgot to check optimize import. I think(hope) I got them all ;).

* Now uses PublishRelay. Manga favorite is now handled in info presenter

* Update MangaInfoFragment.kt
This commit is contained in:
Bram van de Kerkhof 2017-01-08 20:07:19 +01:00 committed by inorichi
parent 07cae4d684
commit 72f8c4d5e2
14 changed files with 134 additions and 49 deletions

View File

@ -6,6 +6,7 @@ import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.ui.manga.info.ChapterCountEvent import eu.kanade.tachiyomi.ui.manga.info.ChapterCountEvent
import eu.kanade.tachiyomi.ui.manga.info.MangaFavoriteEvent
import eu.kanade.tachiyomi.util.SharedData import eu.kanade.tachiyomi.util.SharedData
import rx.Observable import rx.Observable
import rx.Subscription import rx.Subscription
@ -38,6 +39,8 @@ class MangaPresenter : BasePresenter<MangaActivity>() {
// Prepare a subject to communicate the chapters and info presenters for the chapter count. // Prepare a subject to communicate the chapters and info presenters for the chapter count.
SharedData.put(ChapterCountEvent()) SharedData.put(ChapterCountEvent())
// Prepare a subject to communicate the chapters and info presenters for the chapter favorite.
SharedData.put(MangaFavoriteEvent())
} }
fun setMangaEvent(event: MangaEvent) { fun setMangaEvent(event: MangaEvent) {

View File

@ -4,6 +4,7 @@ import android.animation.Animator
import android.animation.AnimatorListenerAdapter import android.animation.AnimatorListenerAdapter
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.support.design.widget.Snackbar
import android.support.v4.app.DialogFragment import android.support.v4.app.DialogFragment
import android.support.v7.view.ActionMode import android.support.v7.view.ActionMode
import android.support.v7.widget.DividerItemDecoration import android.support.v7.widget.DividerItemDecoration
@ -20,6 +21,7 @@ import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment
import eu.kanade.tachiyomi.ui.manga.MangaActivity import eu.kanade.tachiyomi.ui.manga.MangaActivity
import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.util.getCoordinates import eu.kanade.tachiyomi.util.getCoordinates
import eu.kanade.tachiyomi.util.snack
import eu.kanade.tachiyomi.util.toast import eu.kanade.tachiyomi.util.toast
import eu.kanade.tachiyomi.widget.DeletingChaptersDialog import eu.kanade.tachiyomi.widget.DeletingChaptersDialog
import kotlinx.android.synthetic.main.fragment_manga_chapters.* import kotlinx.android.synthetic.main.fragment_manga_chapters.*
@ -370,6 +372,13 @@ class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callbac
fun downloadChapters(chapters: List<ChapterModel>) { fun downloadChapters(chapters: List<ChapterModel>) {
destroyActionModeIfNeeded() destroyActionModeIfNeeded()
presenter.downloadChapters(chapters) presenter.downloadChapters(chapters)
if (!presenter.manga.favorite){
recycler.snack(getString(R.string.snack_add_to_library), Snackbar.LENGTH_INDEFINITE) {
setAction(R.string.action_add) {
presenter.addToLibrary()
}
}
}
} }
fun bookmarkChapters(chapters: List<ChapterModel>, bookmarked: Boolean) { fun bookmarkChapters(chapters: List<ChapterModel>, bookmarked: Boolean) {

View File

@ -13,6 +13,7 @@ import eu.kanade.tachiyomi.data.source.SourceManager
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.ui.manga.MangaEvent import eu.kanade.tachiyomi.ui.manga.MangaEvent
import eu.kanade.tachiyomi.ui.manga.info.ChapterCountEvent import eu.kanade.tachiyomi.ui.manga.info.ChapterCountEvent
import eu.kanade.tachiyomi.ui.manga.info.MangaFavoriteEvent
import eu.kanade.tachiyomi.util.SharedData import eu.kanade.tachiyomi.util.SharedData
import eu.kanade.tachiyomi.util.syncChaptersWithSource import eu.kanade.tachiyomi.util.syncChaptersWithSource
import rx.Observable import rx.Observable
@ -68,7 +69,8 @@ class ChaptersPresenter : BasePresenter<ChaptersFragment>() {
/** /**
* Subject of list of chapters to allow updating the view without going to DB. * Subject of list of chapters to allow updating the view without going to DB.
*/ */
val chaptersSubject by lazy { PublishSubject.create<List<ChapterModel>>() } val chaptersSubject: PublishSubject<List<ChapterModel>>
by lazy { PublishSubject.create<List<ChapterModel>>() }
/** /**
* Whether the chapter list has been requested to the source. * Whether the chapter list has been requested to the source.
@ -100,23 +102,23 @@ class ChaptersPresenter : BasePresenter<ChaptersFragment>() {
// On each subject emission, apply filters and sort then update the view. // On each subject emission, apply filters and sort then update the view.
{ chaptersSubject { chaptersSubject
.flatMap { applyChapterFilters(it) } .flatMap { applyChapterFilters(it) }
.observeOn(AndroidSchedulers.mainThread()) }, .observeOn(AndroidSchedulers.mainThread())
{ view, chapters -> view.onNextChapters(chapters) }) }, ChaptersFragment::onNextChapters)
startableFirst(FETCH_CHAPTERS, startableFirst(FETCH_CHAPTERS,
{ getRemoteChaptersObservable() }, { getRemoteChaptersObservable() },
{ view, result -> view.onFetchChaptersDone() }, { view, result -> view.onFetchChaptersDone() },
{ view, error -> view.onFetchChaptersError(error) }) ChaptersFragment::onFetchChaptersError)
startableLatestCache(CHAPTER_STATUS_CHANGES, startableLatestCache(CHAPTER_STATUS_CHANGES,
{ getChapterStatusObservable() }, { getChapterStatusObservable() },
{ view, download -> view.onChapterStatusChange(download) }, ChaptersFragment::onChapterStatusChange,
{ view, error -> Timber.e(error) }) { view, error -> Timber.e(error) })
// Find the active manga from the shared data or return. // Find the active manga from the shared data or return.
manga = SharedData.get(MangaEvent::class.java)?.manga ?: return manga = SharedData.get(MangaEvent::class.java)?.manga ?: return
Observable.just(manga) Observable.just(manga)
.subscribeLatestCache({ view, manga -> view.onNextManga(manga) }) .subscribeLatestCache(ChaptersFragment::onNextManga)
// Find the source for this manga. // Find the source for this manga.
source = sourceManager.get(manga.source)!! source = sourceManager.get(manga.source)!!
@ -197,18 +199,20 @@ class ChaptersPresenter : BasePresenter<ChaptersFragment>() {
/** /**
* Returns an observable that updates the chapter list with the latest from the source. * Returns an observable that updates the chapter list with the latest from the source.
*/ */
fun getRemoteChaptersObservable() = Observable.defer { source.fetchChapterList(manga) } fun getRemoteChaptersObservable(): Observable<Pair<List<Chapter>, List<Chapter>>> =
.subscribeOn(Schedulers.io()) Observable.defer { source.fetchChapterList(manga) }
.map { syncChaptersWithSource(db, it, manga, source) } .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .map { syncChaptersWithSource(db, it, manga, source) }
.observeOn(AndroidSchedulers.mainThread())
/** /**
* Returns an observable that listens to download queue status changes. * Returns an observable that listens to download queue status changes.
*/ */
fun getChapterStatusObservable() = downloadManager.queue.getStatusObservable() fun getChapterStatusObservable(): Observable<Download> =
.observeOn(AndroidSchedulers.mainThread()) downloadManager.queue.getStatusObservable()
.filter { download -> download.manga.id == manga.id } .observeOn(AndroidSchedulers.mainThread())
.doOnNext { onDownloadStatusChange(it) } .filter { download -> download.manga.id == manga.id }
.doOnNext { onDownloadStatusChange(it) }
/** /**
* Applies the view filters to the list of chapters obtained from the database. * Applies the view filters to the list of chapters obtained from the database.
@ -231,11 +235,11 @@ class ChaptersPresenter : BasePresenter<ChaptersFragment>() {
} }
val sortFunction: (Chapter, Chapter) -> Int = when (manga.sorting) { val sortFunction: (Chapter, Chapter) -> Int = when (manga.sorting) {
Manga.SORTING_SOURCE -> when (sortDescending()) { Manga.SORTING_SOURCE -> when (sortDescending()) {
true -> { c1, c2 -> c1.source_order.compareTo(c2.source_order) } true -> { c1, c2 -> c1.source_order.compareTo(c2.source_order) }
false -> { c1, c2 -> c2.source_order.compareTo(c1.source_order) } false -> { c1, c2 -> c2.source_order.compareTo(c1.source_order) }
} }
Manga.SORTING_NUMBER -> when (sortDescending()) { Manga.SORTING_NUMBER -> when (sortDescending()) {
true -> { c1, c2 -> c2.chapter_number.compareTo(c1.chapter_number) } true -> { c1, c2 -> c2.chapter_number.compareTo(c1.chapter_number) }
false -> { c1, c2 -> c1.chapter_number.compareTo(c2.chapter_number) } false -> { c1, c2 -> c1.chapter_number.compareTo(c2.chapter_number) }
} }
else -> throw NotImplementedError("Unimplemented sorting method") else -> throw NotImplementedError("Unimplemented sorting method")
@ -325,9 +329,7 @@ class ChaptersPresenter : BasePresenter<ChaptersFragment>() {
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribeFirst({ view, result -> .subscribeFirst({ view, result ->
view.onChaptersDeleted() view.onChaptersDeleted()
}, { view, error -> }, ChaptersFragment::onChaptersDeletedError)
view.onChaptersDeletedError(error)
})
} }
/** /**
@ -401,6 +403,13 @@ class ChaptersPresenter : BasePresenter<ChaptersFragment>() {
refreshChapters() refreshChapters()
} }
/**
* Adds manga to library
*/
fun addToLibrary() {
SharedData.get(MangaFavoriteEvent::class.java)?.call(true)
}
/** /**
* Sets the active display mode. * Sets the active display mode.
* @param mode the mode to set. * @param mode the mode to set.

View File

@ -3,7 +3,7 @@ package eu.kanade.tachiyomi.ui.manga.info
import rx.Observable import rx.Observable
import rx.subjects.BehaviorSubject import rx.subjects.BehaviorSubject
class ChapterCountEvent() { class ChapterCountEvent {
private val subject = BehaviorSubject.create<Int>() private val subject = BehaviorSubject.create<Int>()

View File

@ -0,0 +1,16 @@
package eu.kanade.tachiyomi.ui.manga.info
import com.jakewharton.rxrelay.PublishRelay
import rx.Observable
class MangaFavoriteEvent {
private val subject = PublishRelay.create<Boolean>()
val observable: Observable<Boolean>
get() = subject
fun call(favorite: Boolean) {
subject.call(favorite)
}
}

View File

@ -77,11 +77,14 @@ class MangaInfoPresenter : BasePresenter<MangaInfoFragment>() {
refreshManga() refreshManga()
// Update chapter count // Update chapter count
SharedData.get(ChapterCountEvent::class.java)?.let { SharedData.get(ChapterCountEvent::class.java)?.observable
it.observable ?.observeOn(AndroidSchedulers.mainThread())
.observeOn(AndroidSchedulers.mainThread()) ?.subscribeLatestCache(MangaInfoFragment::setChapterCount)
.subscribeLatestCache({ view, count -> view.setChapterCount(count) })
} // Update favorite status
SharedData.get(MangaFavoriteEvent::class.java)?.observable
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe{setFavorite(it)}
} }
/** /**
@ -123,6 +126,12 @@ class MangaInfoPresenter : BasePresenter<MangaInfoFragment>() {
refreshManga() refreshManga()
} }
private fun setFavorite(favorite:Boolean){
if (manga.favorite == favorite)
return
toggleFavorite()
}
/** /**
* Refresh MangaInfo view. * Refresh MangaInfo view.
*/ */

View File

@ -7,7 +7,7 @@ import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.engine.DiskCacheStrategy
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.MangaChapterHistory import eu.kanade.tachiyomi.data.database.models.MangaChapterHistory
import kotlinx.android.synthetic.main.dialog_remove_recently.view.* import eu.kanade.tachiyomi.widget.DialogCheckboxView
import kotlinx.android.synthetic.main.item_recently_read.view.* import kotlinx.android.synthetic.main.item_recently_read.view.*
import java.text.DateFormat import java.text.DateFormat
import java.text.DecimalFormat import java.text.DecimalFormat
@ -24,7 +24,7 @@ import java.util.*
* @constructor creates a new recent chapter holder. * @constructor creates a new recent chapter holder.
*/ */
class RecentlyReadHolder(view: View, private val adapter: RecentlyReadAdapter) class RecentlyReadHolder(view: View, private val adapter: RecentlyReadAdapter)
: RecyclerView.ViewHolder(view) { : RecyclerView.ViewHolder(view) {
/** /**
* DecimalFormat used to display correct chapter number * DecimalFormat used to display correct chapter number
@ -66,14 +66,19 @@ class RecentlyReadHolder(view: View, private val adapter: RecentlyReadAdapter)
// Set remove clickListener // Set remove clickListener
itemView.remove.setOnClickListener { itemView.remove.setOnClickListener {
// Create custom view
val dialogCheckboxView = DialogCheckboxView(itemView.context).apply {
setDescription(R.string.dialog_with_checkbox_remove_description)
setOptionDescription(R.string.dialog_with_checkbox_reset)
}
MaterialDialog.Builder(itemView.context) MaterialDialog.Builder(itemView.context)
.title(R.string.action_remove) .title(R.string.action_remove)
.customView(R.layout.dialog_remove_recently, true) .customView(dialogCheckboxView, true)
.positiveText(R.string.action_remove) .positiveText(R.string.action_remove)
.negativeText(android.R.string.cancel) .negativeText(android.R.string.cancel)
.onPositive { materialDialog, dialogAction -> .onPositive { materialDialog, dialogAction ->
// Check if user wants all chapters reset // Check if user wants all chapters reset
if (materialDialog.customView?.removeAll?.isChecked as Boolean) { if (dialogCheckboxView.isChecked()) {
adapter.fragment.removeAllFromHistory(manga.id!!) adapter.fragment.removeAllFromHistory(manga.id!!)
} else { } else {
adapter.fragment.removeFromHistory(history) adapter.fragment.removeFromHistory(history)
@ -81,8 +86,7 @@ class RecentlyReadHolder(view: View, private val adapter: RecentlyReadAdapter)
} }
.onNegative { materialDialog, dialogAction -> .onNegative { materialDialog, dialogAction ->
materialDialog.dismiss() materialDialog.dismiss()
} }.show()
.show()
} }
// Set continue reading clickListener // Set continue reading clickListener

View File

@ -23,7 +23,7 @@ object ChapterRecognition {
* Regex used when manga title removed * Regex used when manga title removed
* Example: Solanin 028 Vol. 2 -> 028 Vol.2 -> 028Vol.2 -R> 028 * Example: Solanin 028 Vol. 2 -> 028 Vol.2 -> 028Vol.2 -R> 028
*/ */
private val withoutMange = Regex("""^([0-9]+)(\.[0-9]+)?(\.?[a-z]+)?""") private val withoutManga = Regex("""^([0-9]+)(\.[0-9]+)?(\.?[a-z]+)?""")
/** /**
* Regex used to remove unwanted tags * Regex used to remove unwanted tags
@ -77,7 +77,7 @@ object ChapterRecognition {
val nameWithoutManga = name.replace(manga.title.toLowerCase(), "").trim() val nameWithoutManga = name.replace(manga.title.toLowerCase(), "").trim()
// Check if first value is number after title remove. // Check if first value is number after title remove.
if (updateChapter(withoutMange.find(nameWithoutManga), chapter)) if (updateChapter(withoutManga.find(nameWithoutManga), chapter))
return return
// Take the first number encountered. // Take the first number encountered.
@ -123,7 +123,7 @@ object ChapterRecognition {
if (alpha.contains("special")) if (alpha.contains("special"))
return .97f return .97f
if (alpha[0].equals('.') ) { if (alpha[0] == '.') {
// Take value after (.) // Take value after (.)
return parseAlphaPostFix(alpha[1]) return parseAlphaPostFix(alpha[1])
} else { } else {

View File

@ -0,0 +1,28 @@
package eu.kanade.tachiyomi.widget
import android.content.Context
import android.support.annotation.StringRes
import android.util.AttributeSet
import android.widget.LinearLayout
import android.widget.RelativeLayout
import eu.kanade.tachiyomi.R
import kotlinx.android.synthetic.main.dialog_with_checkbox.view.*
class DialogCheckboxView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
LinearLayout(context, attrs) {
init {
RelativeLayout.inflate(context, R.layout.dialog_with_checkbox, this)
}
fun setDescription(@StringRes id: Int){
description.text = context.getString(id)
}
fun setOptionDescription(@StringRes id: Int){
checkbox_option.text = context.getString(id)
}
fun isChecked(): Boolean {
return checkbox_option.isChecked
}
}

View File

@ -2,21 +2,23 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical">
android:padding="@dimen/activity_vertical_margin">
<TextView <TextView
android:id="@+id/description"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/dialog_remove_recently_description"
android:textAppearance="@style/TextAppearance.Regular.Body1"/> android:textAppearance="@style/TextAppearance.Regular.Body1"/>
<CheckBox <CheckBox
android:id="@+id/removeAll" android:id="@+id/checkbox_option"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="18dp" android:layout_marginStart="-5dp"
android:text="@string/dialog_remove_recently_reset"/> android:layout_marginEnd="0dp"
android:layout_marginLeft="-5dp"
android:layout_marginRight="0dp"
android:layout_marginTop="@dimen/material_component_dialogs_padding_between_text_and_touch_target"/>
</LinearLayout> </LinearLayout>

View File

@ -237,8 +237,8 @@
<string name="chapters">Capítulos</string> <string name="chapters">Capítulos</string>
<!-- Dialog remove recently view --> <!-- Dialog remove recently view -->
<string name="dialog_remove_recently_description">Esto eliminará la fecha de lectura de este capítulo. ¿Estás seguro?</string> <string name="dialog_with_checkbox_remove_description">Esto eliminará la fecha de lectura de este capítulo. ¿Estás seguro?</string>
<string name="dialog_remove_recently_reset">Reiniciar todas los capítulos de este manga</string> <string name="dialog_with_checkbox_reset">Reiniciar todas los capítulos de este manga</string>
<!-- Reader activity --> <!-- Reader activity -->

View File

@ -285,8 +285,8 @@
<string name="error_category_exists">Esiste già una categoria con questo nome</string> <string name="error_category_exists">Esiste già una categoria con questo nome</string>
<!-- Dialog remove recently view --> <!-- Dialog remove recently view -->
<string name="dialog_remove_recently_description">Questo rimuoverà la data lettura di questo capitolo. Sei sicuro?</string> <string name="dialog_with_checkbox_remove_description">Questo rimuoverà la data lettura di questo capitolo. Sei sicuro?</string>
<string name="dialog_remove_recently_reset">Rimuovi per tutti i capitoli di questo manga</string> <string name="dialog_with_checkbox_reset">Rimuovi per tutti i capitoli di questo manga</string>
<!-- Image notifier --> <!-- Image notifier -->
<string name="picture_saved">Immagine salvata</string> <string name="picture_saved">Immagine salvata</string>

View File

@ -245,8 +245,8 @@
<string name="chapters">Capítulos</string> <string name="chapters">Capítulos</string>
<!-- Dialog remove recently view --> <!-- Dialog remove recently view -->
<string name="dialog_remove_recently_description">Esta ação irá remover a data de leitura deste capítulo. Continuar?</string> <string name="dialog_with_checkbox_remove_description">Esta ação irá remover a data de leitura deste capítulo. Continuar?</string>
<string name="dialog_remove_recently_reset">Repor todos os capítulos desta manga</string> <string name="dialog_with_checkbox_reset">Repor todos os capítulos desta manga</string>
<!-- Reader activity --> <!-- Reader activity -->
<string name="downloading">A transferir…</string> <string name="downloading">A transferir…</string>

View File

@ -38,6 +38,7 @@
<string name="action_update">Update</string> <string name="action_update">Update</string>
<string name="action_update_library">Update library</string> <string name="action_update_library">Update library</string>
<string name="action_edit">Edit</string> <string name="action_edit">Edit</string>
<string name="action_add">Add</string>
<string name="action_add_category">Add category</string> <string name="action_add_category">Add category</string>
<string name="action_edit_categories">Edit categories</string> <string name="action_edit_categories">Edit categories</string>
<string name="action_rename_category">Rename category</string> <string name="action_rename_category">Rename category</string>
@ -184,6 +185,7 @@
<string name="cache_delete_error">An error occurred while clearing cache</string> <string name="cache_delete_error">An error occurred while clearing cache</string>
<string name="pref_clear_cookies">Clear cookies</string> <string name="pref_clear_cookies">Clear cookies</string>
<string name="cookies_cleared">Cookies cleared</string> <string name="cookies_cleared">Cookies cleared</string>
<string name="choices_reset">Dialog choices reset</string>
<string name="pref_clear_database">Clear database</string> <string name="pref_clear_database">Clear database</string>
<string name="pref_clear_database_summary">Delete manga and chapters that are not in your library</string> <string name="pref_clear_database_summary">Delete manga and chapters that are not in your library</string>
<string name="clear_database_confirmation">Are you sure? Read chapters and progress of non-library manga will be lost</string> <string name="clear_database_confirmation">Are you sure? Read chapters and progress of non-library manga will be lost</string>
@ -285,9 +287,12 @@
<!-- Category activity --> <!-- Category activity -->
<string name="error_category_exists">A category with this name already exists!</string> <string name="error_category_exists">A category with this name already exists!</string>
<!-- Dialog remove recently view --> <!-- Dialog option with checkbox view -->
<string name="dialog_remove_recently_description">This will remove the read date of this chapter. Are you sure?</string> <string name="dialog_with_checkbox_remove_description">This will remove the read date of this chapter. Are you sure?</string>
<string name="dialog_remove_recently_reset">Reset all chapters for this manga</string> <string name="dialog_with_checkbox_reset">Reset all chapters for this manga</string>
<!-- SnackBar -->
<string name="snack_add_to_library">Add manga to library?</string>
<!-- Image notifier --> <!-- Image notifier -->
<string name="picture_saved">Picture saved</string> <string name="picture_saved">Picture saved</string>