mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-11-11 17:10:48 +01:00
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:
parent
bc8753da85
commit
6a310bbaa9
10 changed files with 334 additions and 31 deletions
|
@ -37,6 +37,7 @@ class ChaptersController : NucleusController<ChaptersPresenter>(),
|
|||
SetDisplayModeDialog.Listener,
|
||||
SetSortingDialog.Listener,
|
||||
DownloadChaptersDialog.Listener,
|
||||
DownloadCustomChaptersDialog.Listener,
|
||||
DeleteChaptersDialog.Listener {
|
||||
|
||||
/**
|
||||
|
@ -210,7 +211,7 @@ class ChaptersController : NucleusController<ChaptersPresenter>(),
|
|||
}
|
||||
}
|
||||
|
||||
fun fetchChaptersFromSource() {
|
||||
private fun fetchChaptersFromSource() {
|
||||
swipe_refresh?.isRefreshing = true
|
||||
presenter.fetchChaptersFromSource()
|
||||
}
|
||||
|
@ -272,18 +273,18 @@ class ChaptersController : NucleusController<ChaptersPresenter>(),
|
|||
actionMode?.invalidate()
|
||||
}
|
||||
|
||||
fun getSelectedChapters(): List<ChapterItem> {
|
||||
private fun getSelectedChapters(): List<ChapterItem> {
|
||||
val adapter = adapter ?: return emptyList()
|
||||
return adapter.selectedPositions.mapNotNull { adapter.getItem(it) }
|
||||
}
|
||||
|
||||
fun createActionModeIfNeeded() {
|
||||
private fun createActionModeIfNeeded() {
|
||||
if (actionMode == null) {
|
||||
actionMode = (activity as? AppCompatActivity)?.startSupportActionMode(this)
|
||||
}
|
||||
}
|
||||
|
||||
fun destroyActionModeIfNeeded() {
|
||||
private fun destroyActionModeIfNeeded() {
|
||||
actionMode?.finish()
|
||||
}
|
||||
|
||||
|
@ -341,25 +342,25 @@ class ChaptersController : NucleusController<ChaptersPresenter>(),
|
|||
|
||||
// SELECTION MODE ACTIONS
|
||||
|
||||
fun selectAll() {
|
||||
private fun selectAll() {
|
||||
val adapter = adapter ?: return
|
||||
adapter.selectAll()
|
||||
selectedItems.addAll(adapter.items)
|
||||
actionMode?.invalidate()
|
||||
}
|
||||
|
||||
fun markAsRead(chapters: List<ChapterItem>) {
|
||||
private fun markAsRead(chapters: List<ChapterItem>) {
|
||||
presenter.markChaptersRead(chapters, true)
|
||||
if (presenter.preferences.removeAfterMarkedAsRead()) {
|
||||
deleteChapters(chapters)
|
||||
}
|
||||
}
|
||||
|
||||
fun markAsUnread(chapters: List<ChapterItem>) {
|
||||
private fun markAsUnread(chapters: List<ChapterItem>) {
|
||||
presenter.markChaptersRead(chapters, false)
|
||||
}
|
||||
|
||||
fun downloadChapters(chapters: List<ChapterItem>) {
|
||||
private fun downloadChapters(chapters: List<ChapterItem>) {
|
||||
val view = view
|
||||
destroyActionModeIfNeeded()
|
||||
presenter.downloadChapters(chapters)
|
||||
|
@ -372,6 +373,7 @@ class ChaptersController : NucleusController<ChaptersPresenter>(),
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
private fun showDeleteChaptersConfirmationDialog() {
|
||||
DeleteChaptersDialog(this).showDialog(router)
|
||||
}
|
||||
|
@ -380,7 +382,7 @@ class ChaptersController : NucleusController<ChaptersPresenter>(),
|
|||
deleteChapters(getSelectedChapters())
|
||||
}
|
||||
|
||||
fun markPreviousAsRead(chapter: ChapterItem) {
|
||||
private fun markPreviousAsRead(chapter: ChapterItem) {
|
||||
val adapter = adapter ?: return
|
||||
val chapters = if (presenter.sortDescending()) adapter.items.reversed() else adapter.items
|
||||
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()
|
||||
presenter.bookmarkChapters(chapters, bookmarked)
|
||||
}
|
||||
|
@ -412,7 +414,7 @@ class ChaptersController : NucleusController<ChaptersPresenter>(),
|
|||
Timber.e(error)
|
||||
}
|
||||
|
||||
fun dismissDeletingDialog() {
|
||||
private fun dismissDeletingDialog() {
|
||||
router.popControllerWithTag(DeletingChaptersDialog.TAG)
|
||||
}
|
||||
|
||||
|
@ -441,29 +443,44 @@ class ChaptersController : NucleusController<ChaptersPresenter>(),
|
|||
DownloadChaptersDialog(this).showDialog(router)
|
||||
}
|
||||
|
||||
override fun downloadChapters(choice: Int) {
|
||||
fun getUnreadChaptersSorted() = presenter.chapters
|
||||
.filter { !it.read && it.status == Download.NOT_DOWNLOADED }
|
||||
.distinctBy { it.name }
|
||||
.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()
|
||||
}
|
||||
private fun getUnreadChaptersSorted() = presenter.chapters
|
||||
.filter { !it.read && it.status == Download.NOT_DOWNLOADED }
|
||||
.distinctBy { it.name }
|
||||
.sortedByDescending { it.source_order }
|
||||
|
||||
override fun downloadCustomChapters(amount: Int) {
|
||||
val chaptersToDownload = getUnreadChaptersSorted().take(amount)
|
||||
if (chaptersToDownload.isNotEmpty()) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,12 +21,12 @@ class DownloadChaptersDialog<T>(bundle: Bundle? = null) : DialogController(bundl
|
|||
R.string.download_1,
|
||||
R.string.download_5,
|
||||
R.string.download_10,
|
||||
R.string.download_custom,
|
||||
R.string.download_unread,
|
||||
R.string.download_all
|
||||
).map { activity.getString(it) }
|
||||
|
||||
return MaterialDialog.Builder(activity)
|
||||
.title(R.string.manga_download)
|
||||
.negativeText(android.R.string.cancel)
|
||||
.items(choices)
|
||||
.itemsCallback { _, _, position, _ ->
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
9
app/src/main/res/drawable/ic_chevron_left_black_24dp.xml
Normal file
9
app/src/main/res/drawable/ic_chevron_left_black_24dp.xml
Normal 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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
55
app/src/main/res/layout/download_custom_amount.xml
Normal file
55
app/src/main/res/layout/download_custom_amount.xml
Normal 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>
|
|
@ -341,9 +341,12 @@
|
|||
<string name="sort_by_source">By source</string>
|
||||
<string name="sort_by_number">By chapter number</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_5">Download next 5 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_unread">Download unread</string>
|
||||
<string name="confirm_delete_chapters">Are you sure you want to delete selected chapters?</string>
|
||||
|
|
Loading…
Reference in a new issue