Added custom download option (#1185)

* Added custom download option

* Implemented new design. TODO comments (like always...)

* W00t comments

* Implemented code review.

* Fixed commit breaking mistake :O

* Small design fix
This commit is contained in:
Bram van de Kerkhof 2018-01-23 21:18:55 +01:00 committed by GitHub
parent bc8753da85
commit 6a310bbaa9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 334 additions and 31 deletions

View file

@ -37,6 +37,7 @@ class ChaptersController : NucleusController<ChaptersPresenter>(),
SetDisplayModeDialog.Listener, SetDisplayModeDialog.Listener,
SetSortingDialog.Listener, SetSortingDialog.Listener,
DownloadChaptersDialog.Listener, DownloadChaptersDialog.Listener,
DownloadCustomChaptersDialog.Listener,
DeleteChaptersDialog.Listener { DeleteChaptersDialog.Listener {
/** /**
@ -210,7 +211,7 @@ class ChaptersController : NucleusController<ChaptersPresenter>(),
} }
} }
fun fetchChaptersFromSource() { private fun fetchChaptersFromSource() {
swipe_refresh?.isRefreshing = true swipe_refresh?.isRefreshing = true
presenter.fetchChaptersFromSource() presenter.fetchChaptersFromSource()
} }
@ -272,18 +273,18 @@ class ChaptersController : NucleusController<ChaptersPresenter>(),
actionMode?.invalidate() actionMode?.invalidate()
} }
fun getSelectedChapters(): List<ChapterItem> { private fun getSelectedChapters(): List<ChapterItem> {
val adapter = adapter ?: return emptyList() val adapter = adapter ?: return emptyList()
return adapter.selectedPositions.mapNotNull { adapter.getItem(it) } return adapter.selectedPositions.mapNotNull { adapter.getItem(it) }
} }
fun createActionModeIfNeeded() { private fun createActionModeIfNeeded() {
if (actionMode == null) { if (actionMode == null) {
actionMode = (activity as? AppCompatActivity)?.startSupportActionMode(this) actionMode = (activity as? AppCompatActivity)?.startSupportActionMode(this)
} }
} }
fun destroyActionModeIfNeeded() { private fun destroyActionModeIfNeeded() {
actionMode?.finish() actionMode?.finish()
} }
@ -341,25 +342,25 @@ class ChaptersController : NucleusController<ChaptersPresenter>(),
// SELECTION MODE ACTIONS // SELECTION MODE ACTIONS
fun selectAll() { private fun selectAll() {
val adapter = adapter ?: return val adapter = adapter ?: return
adapter.selectAll() adapter.selectAll()
selectedItems.addAll(adapter.items) selectedItems.addAll(adapter.items)
actionMode?.invalidate() actionMode?.invalidate()
} }
fun markAsRead(chapters: List<ChapterItem>) { private fun markAsRead(chapters: List<ChapterItem>) {
presenter.markChaptersRead(chapters, true) presenter.markChaptersRead(chapters, true)
if (presenter.preferences.removeAfterMarkedAsRead()) { if (presenter.preferences.removeAfterMarkedAsRead()) {
deleteChapters(chapters) deleteChapters(chapters)
} }
} }
fun markAsUnread(chapters: List<ChapterItem>) { private fun markAsUnread(chapters: List<ChapterItem>) {
presenter.markChaptersRead(chapters, false) presenter.markChaptersRead(chapters, false)
} }
fun downloadChapters(chapters: List<ChapterItem>) { private fun downloadChapters(chapters: List<ChapterItem>) {
val view = view val view = view
destroyActionModeIfNeeded() destroyActionModeIfNeeded()
presenter.downloadChapters(chapters) presenter.downloadChapters(chapters)
@ -372,6 +373,7 @@ class ChaptersController : NucleusController<ChaptersPresenter>(),
} }
} }
private fun showDeleteChaptersConfirmationDialog() { private fun showDeleteChaptersConfirmationDialog() {
DeleteChaptersDialog(this).showDialog(router) DeleteChaptersDialog(this).showDialog(router)
} }
@ -380,7 +382,7 @@ class ChaptersController : NucleusController<ChaptersPresenter>(),
deleteChapters(getSelectedChapters()) deleteChapters(getSelectedChapters())
} }
fun markPreviousAsRead(chapter: ChapterItem) { private fun markPreviousAsRead(chapter: ChapterItem) {
val adapter = adapter ?: return val adapter = adapter ?: return
val chapters = if (presenter.sortDescending()) adapter.items.reversed() else adapter.items val chapters = if (presenter.sortDescending()) adapter.items.reversed() else adapter.items
val chapterPos = chapters.indexOf(chapter) val chapterPos = chapters.indexOf(chapter)
@ -389,7 +391,7 @@ class ChaptersController : NucleusController<ChaptersPresenter>(),
} }
} }
fun bookmarkChapters(chapters: List<ChapterItem>, bookmarked: Boolean) { private fun bookmarkChapters(chapters: List<ChapterItem>, bookmarked: Boolean) {
destroyActionModeIfNeeded() destroyActionModeIfNeeded()
presenter.bookmarkChapters(chapters, bookmarked) presenter.bookmarkChapters(chapters, bookmarked)
} }
@ -412,7 +414,7 @@ class ChaptersController : NucleusController<ChaptersPresenter>(),
Timber.e(error) Timber.e(error)
} }
fun dismissDeletingDialog() { private fun dismissDeletingDialog() {
router.popControllerWithTag(DeletingChaptersDialog.TAG) router.popControllerWithTag(DeletingChaptersDialog.TAG)
} }
@ -441,29 +443,44 @@ class ChaptersController : NucleusController<ChaptersPresenter>(),
DownloadChaptersDialog(this).showDialog(router) DownloadChaptersDialog(this).showDialog(router)
} }
override fun downloadChapters(choice: Int) { private fun getUnreadChaptersSorted() = presenter.chapters
fun getUnreadChaptersSorted() = presenter.chapters .filter { !it.read && it.status == Download.NOT_DOWNLOADED }
.filter { !it.read && it.status == Download.NOT_DOWNLOADED } .distinctBy { it.name }
.distinctBy { it.name } .sortedByDescending { it.source_order }
.sortedByDescending { it.source_order }
// i = 0: Download 1
// i = 1: Download 5
// i = 2: Download 10
// i = 3: Download unread
// i = 4: Download all
val chaptersToDownload = when (choice) {
0 -> getUnreadChaptersSorted().take(1)
1 -> getUnreadChaptersSorted().take(5)
2 -> getUnreadChaptersSorted().take(10)
3 -> presenter.chapters.filter { !it.read }
4 -> presenter.chapters
else -> emptyList()
}
override fun downloadCustomChapters(amount: Int) {
val chaptersToDownload = getUnreadChaptersSorted().take(amount)
if (chaptersToDownload.isNotEmpty()) { if (chaptersToDownload.isNotEmpty()) {
downloadChapters(chaptersToDownload) downloadChapters(chaptersToDownload)
} }
} }
private fun showCustomDownloadDialog() {
DownloadCustomChaptersDialog(this, presenter.chapters.size).showDialog(router)
}
override fun downloadChapters(choice: Int) {
// i = 0: Download 1
// i = 1: Download 5
// i = 2: Download 10
// i = 3: Download x
// i = 4: Download unread
// i = 5: Download all
val chaptersToDownload = when (choice) {
0 -> getUnreadChaptersSorted().take(1)
1 -> getUnreadChaptersSorted().take(5)
2 -> getUnreadChaptersSorted().take(10)
3 -> {
showCustomDownloadDialog()
return
}
4 -> presenter.chapters.filter { !it.read }
5 -> presenter.chapters
else -> emptyList()
}
if (chaptersToDownload.isNotEmpty()) {
downloadChapters(chaptersToDownload)
}
}
} }

View file

@ -21,12 +21,12 @@ class DownloadChaptersDialog<T>(bundle: Bundle? = null) : DialogController(bundl
R.string.download_1, R.string.download_1,
R.string.download_5, R.string.download_5,
R.string.download_10, R.string.download_10,
R.string.download_custom,
R.string.download_unread, R.string.download_unread,
R.string.download_all R.string.download_all
).map { activity.getString(it) } ).map { activity.getString(it) }
return MaterialDialog.Builder(activity) return MaterialDialog.Builder(activity)
.title(R.string.manga_download)
.negativeText(android.R.string.cancel) .negativeText(android.R.string.cancel)
.items(choices) .items(choices)
.itemsCallback { _, _, position, _ -> .itemsCallback { _, _, position, _ ->

View file

@ -0,0 +1,77 @@
package eu.kanade.tachiyomi.ui.manga.chapter
import android.app.Dialog
import android.os.Bundle
import com.afollestad.materialdialogs.MaterialDialog
import com.bluelinelabs.conductor.Controller
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.widget.DialogCustomDownloadView
/**
* Dialog used to let user select amount of chapters to download.
*/
class DownloadCustomChaptersDialog<T> : DialogController
where T : Controller, T : DownloadCustomChaptersDialog.Listener {
/**
* Maximum number of chapters to download in download chooser.
*/
private val maxChapters: Int
/**
* Initialize dialog.
* @param maxChapters maximal number of chapters that user can download.
*/
constructor(target: T, maxChapters: Int) : super(Bundle().apply {
// Add maximum number of chapters to download value to bundle.
putInt(KEY_ITEM_MAX, maxChapters)
}) {
targetController = target
this.maxChapters = maxChapters
}
/**
* Restore dialog.
* @param bundle bundle containing data from state restore.
*/
@Suppress("unused")
constructor(bundle: Bundle) : super(bundle) {
// Get maximum chapters to download from bundle
val maxChapters = bundle.getInt(KEY_ITEM_MAX, 0)
this.maxChapters = maxChapters
}
/**
* Called when dialog is being created.
*/
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
val activity = activity!!
// Initialize view that lets user select number of chapters to download.
val view = DialogCustomDownloadView(activity).apply {
setMinMax(0, maxChapters)
}
// Build dialog.
// when positive dialog is pressed call custom listener.
return MaterialDialog.Builder(activity)
.title(R.string.custom_download)
.customView(view, true)
.positiveText(android.R.string.ok)
.negativeText(android.R.string.cancel)
.onPositive { _, _ ->
(targetController as? Listener)?.downloadCustomChapters(view.amount)
}
.build()
}
interface Listener {
fun downloadCustomChapters(amount: Int)
}
private companion object {
// Key to retrieve max chapters from bundle on process death.
const val KEY_ITEM_MAX = "DownloadCustomChaptersDialog.int.maxChapters"
}
}

View file

@ -0,0 +1,109 @@
package eu.kanade.tachiyomi.widget
import android.content.Context
import android.text.SpannableStringBuilder
import android.util.AttributeSet
import android.view.View
import android.widget.LinearLayout
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.inflate
import kotlinx.android.synthetic.main.download_custom_amount.view.*
import timber.log.Timber
/**
* Custom dialog to select how many chapters to download.
*/
class DialogCustomDownloadView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
LinearLayout(context, attrs) {
/**
* Current amount of custom download chooser.
*/
var amount: Int = 0
private set
/**
* Minimal value of custom download chooser.
*/
private var min = 0
/**
* Maximal value of custom download chooser.
*/
private var max = 0
init {
// Add view to stack
addView(inflate(R.layout.download_custom_amount))
}
/**
* Called when view is added
*
* @param child
*/
override fun onViewAdded(child: View) {
super.onViewAdded(child)
// Set download count to 0.
myNumber.text = SpannableStringBuilder(getAmount(0).toString())
// When user presses button decrease amount by 10.
btn_decrease_10.setOnClickListener {
myNumber.text = SpannableStringBuilder(getAmount(amount - 10).toString())
}
// When user presses button increase amount by 10.
btn_increase_10.setOnClickListener {
myNumber.text = SpannableStringBuilder(getAmount(amount + 10).toString())
}
// When user presses button decrease amount by 1.
btn_decrease.setOnClickListener {
myNumber.text = SpannableStringBuilder(getAmount(amount - 1).toString())
}
// When user presses button increase amount by 1.
btn_increase.setOnClickListener {
myNumber.text = SpannableStringBuilder(getAmount(amount + 1).toString())
}
// When user inputs custom number set amount equal to input.
myNumber.addTextChangedListener(object : SimpleTextWatcher() {
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
try {
amount = getAmount(Integer.parseInt(s.toString()))
} catch (error: NumberFormatException) {
// Catch NumberFormatException to prevent parse exception when input is empty.
Timber.e(error)
}
}
})
}
/**
* Set min max of custom download amount chooser.
* @param min minimal downloads
* @param max maximal downloads
*/
fun setMinMax(min: Int, max: Int) {
this.min = min
this.max = max
}
/**
* Returns amount to download.
* if minimal downloads is less than input return minimal downloads.
* if Maximal downloads is more than input return maximal downloads.
*
* @return amount to download.
*/
private fun getAmount(input: Int): Int {
return when {
input > max -> max
input < min -> min
else -> input
}
}
}

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M15.41,16.58L10.83,12L15.41,7.41L14,6L8,12L14,18L15.41,16.58Z" />
</vector>

View file

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:pathData="M11.9,16.6l-4.6,-4.6l4.6,-4.6l-1.4,-1.4l-6,6l6,6z"
android:fillColor="#FF000000" />
<path
android:pathData="M18.9,16.6l-4.6,-4.6l4.6,-4.6l-1.4,-1.4l-6,6l6,6z"
android:fillColor="#FF000000" />
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M10,6L8.59,7.41 13.17,12l-4.58,4.59L10,18l6,-6z" />
</vector>

View file

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:pathData="M12.1,16.6l4.6,-4.6l-4.6,-4.6l1.4,-1.4l6,6l-6,6z"
android:fillColor="#FF000000" />
<path
android:pathData="M5.1,16.6l4.6,-4.6l-4.6,-4.6l1.4,-1.4l6,6l-6,6z"
android:fillColor="#FF000000" />
</vector>

View file

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal">
<android.support.v7.widget.AppCompatImageButton
android:id="@+id/btn_decrease_10"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="?selectable_list_drawable"
android:padding="8dp"
android:tint="?colorAccent"
app:srcCompat="@drawable/ic_chevron_left_double_black_24dp" />
<android.support.v7.widget.AppCompatImageButton
android:id="@+id/btn_decrease"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="?selectable_list_drawable"
android:tint="?colorAccent"
android:padding="8dp"
app:srcCompat="@drawable/ic_chevron_left_black_24dp" />
<EditText
android:id="@+id/myNumber"
android:digits="0123456789"
android:inputType="number"
android:layout_height="wrap_content"
android:textStyle="bold"
android:padding="8dp"
android:layout_width="wrap_content" />
<android.support.v7.widget.AppCompatImageButton
android:id="@+id/btn_increase"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="?selectable_list_drawable"
android:tint="?colorAccent"
android:padding="8dp"
app:srcCompat="@drawable/ic_chevron_right_black_24dp" />
<android.support.v7.widget.AppCompatImageButton
android:id="@+id/btn_increase_10"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="?selectable_list_drawable"
android:tint="?colorAccent"
android:padding="8dp"
app:srcCompat="@drawable/ic_chevron_right_double_black_24dp" />
</LinearLayout>

View file

@ -341,9 +341,12 @@
<string name="sort_by_source">By source</string> <string name="sort_by_source">By source</string>
<string name="sort_by_number">By chapter number</string> <string name="sort_by_number">By chapter number</string>
<string name="manga_download">Download</string> <string name="manga_download">Download</string>
<string name="custom_download">Download custom amount</string>
<string name="custom_hint">amount</string>
<string name="download_1">Download next chapter</string> <string name="download_1">Download next chapter</string>
<string name="download_5">Download next 5 chapters</string> <string name="download_5">Download next 5 chapters</string>
<string name="download_10">Download next 10 chapters</string> <string name="download_10">Download next 10 chapters</string>
<string name="download_custom">Download custom</string>
<string name="download_all">Download all</string> <string name="download_all">Download all</string>
<string name="download_unread">Download unread</string> <string name="download_unread">Download unread</string>
<string name="confirm_delete_chapters">Are you sure you want to delete selected chapters?</string> <string name="confirm_delete_chapters">Are you sure you want to delete selected chapters?</string>