Quellcode durchsuchen

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
Bram van de Kerkhof vor 7 Jahren
Ursprung
Commit
6a310bbaa9

+ 39 - 22
app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersController.kt

@@ -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 }
+    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 unread
-        // i = 4: Download all
+        // 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 -> presenter.chapters.filter { !it.read }
-            4 -> presenter.chapters
+            3 -> {
+                showCustomDownloadDialog()
+                return
+            }
+            4 -> presenter.chapters.filter { !it.read }
+            5 -> presenter.chapters
             else -> emptyList()
         }
-
         if (chaptersToDownload.isNotEmpty()) {
             downloadChapters(chaptersToDownload)
         }
     }
-
 }

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/DownloadChaptersDialog.kt

@@ -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, _ ->

+ 77 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/DownloadCustomChaptersDialog.kt

@@ -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"
+    }
+}

+ 109 - 0
app/src/main/java/eu/kanade/tachiyomi/widget/DialogCustomDownloadView.kt

@@ -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 - 0
app/src/main/res/drawable/ic_chevron_left_black_24dp.xml

@@ -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>

+ 12 - 0
app/src/main/res/drawable/ic_chevron_left_double_black_24dp.xml

@@ -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>

+ 9 - 0
app/src/main/res/drawable/ic_chevron_right_black_24dp.xml

@@ -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>

+ 12 - 0
app/src/main/res/drawable/ic_chevron_right_double_black_24dp.xml

@@ -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 - 0
app/src/main/res/layout/download_custom_amount.xml

@@ -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>

+ 3 - 0
app/src/main/res/values/strings.xml

@@ -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>