Browse Source

Download badge

inorichi 7 years ago
parent
commit
d0567de4e6

+ 2 - 0
app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.kt

@@ -18,6 +18,8 @@ interface Manga : SManga {
 
     var unread: Int
 
+    var downloadTotal: Int
+
     var category: Int
 
     fun setChapterOrder(order: Int) {

+ 2 - 0
app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaImpl.kt

@@ -34,6 +34,8 @@ class MangaImpl : Manga {
 
     @Transient override var unread: Int = 0
 
+    @Transient override var downloadTotal: Int = 0
+
     @Transient override var category: Int = 0
 
     override fun equals(other: Any?): Boolean {

+ 2 - 0
app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt

@@ -105,6 +105,8 @@ object PreferenceKeys {
 
     const val defaultCategory = "default_category"
 
+    const val downloadBadge = "pref_display_download_badge"
+
     fun sourceUsername(sourceId: Long) = "pref_source_username_$sourceId"
 
     fun sourcePassword(sourceId: Long) = "pref_source_password_$sourceId"

+ 2 - 0
app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt

@@ -141,6 +141,8 @@ class PreferencesHelper(val context: Context) {
 
     fun libraryAsList() = rxPrefs.getBoolean(Keys.libraryAsList, false)
 
+    fun downloadBadge() = rxPrefs.getBoolean(Keys.downloadBadge, false)
+
     fun filterDownloaded() = rxPrefs.getBoolean(Keys.filterDownloaded, false)
 
     fun filterUnread() = rxPrefs.getBoolean(Keys.filterUnread, false)

+ 5 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt

@@ -196,6 +196,7 @@ class LibraryController(
                 is LibraryNavigationView.FilterGroup -> onFilterChanged()
                 is LibraryNavigationView.SortGroup -> onSortChanged()
                 is LibraryNavigationView.DisplayGroup -> reattachAdapter()
+                is LibraryNavigationView.BadgeGroup -> onDownloadBadgeChanged()
             }
         }
 
@@ -285,6 +286,10 @@ class LibraryController(
         (activity as? AppCompatActivity)?.supportInvalidateOptionsMenu()
     }
 
+    private fun onDownloadBadgeChanged(){
+        presenter.requestDownloadBadgesUpdate()
+    }
+
     /**
      * Called when the sorting mode is changed.
      */

+ 16 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryGridHolder.kt

@@ -5,7 +5,12 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy
 import eu.davidea.flexibleadapter.FlexibleAdapter
 import eu.kanade.tachiyomi.data.database.models.Manga
 import eu.kanade.tachiyomi.data.glide.GlideApp
+import eu.kanade.tachiyomi.data.preference.PreferencesHelper
+import eu.kanade.tachiyomi.data.preference.getOrDefault
+import eu.kanade.tachiyomi.source.LocalSource
 import kotlinx.android.synthetic.main.catalogue_grid_item.view.*
+import uy.kohesive.injekt.Injekt
+import uy.kohesive.injekt.api.get
 
 /**
  * Class used to hold the displayed data of a manga in the library, like the cover or the title.
@@ -19,7 +24,9 @@ import kotlinx.android.synthetic.main.catalogue_grid_item.view.*
 class LibraryGridHolder(
         private val view: View,
         private val adapter: FlexibleAdapter<*>
+
 ) : LibraryHolder(view, adapter) {
+    private val preferences: PreferencesHelper = Injekt.get()
 
     /**
      * Method called from [LibraryCategoryAdapter.onBindViewHolder]. It updates the data for this
@@ -36,6 +43,15 @@ class LibraryGridHolder(
             visibility = if (manga.unread > 0) View.VISIBLE else View.GONE
             text = manga.unread.toString()
         }
+        // Update the download count and its visibility.
+        with(view.download_text) {
+            visibility = if (manga.downloadTotal > 0 && preferences.downloadBadge().getOrDefault()) View.VISIBLE else View.GONE
+            text = manga.downloadTotal.toString()
+        }
+        //set local visibility if its local manga
+        with(view.local_text){
+            visibility = if(manga.source == LocalSource.ID) View.VISIBLE else View.GONE
+        }
 
         // Update the cover.
         GlideApp.with(view.context).clear(view.thumbnail)

+ 16 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt

@@ -5,7 +5,12 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy
 import eu.davidea.flexibleadapter.FlexibleAdapter
 import eu.kanade.tachiyomi.data.database.models.Manga
 import eu.kanade.tachiyomi.data.glide.GlideApp
+import eu.kanade.tachiyomi.data.preference.PreferencesHelper
+import eu.kanade.tachiyomi.data.preference.getOrDefault
+import eu.kanade.tachiyomi.source.LocalSource
 import kotlinx.android.synthetic.main.catalogue_list_item.view.*
+import uy.kohesive.injekt.Injekt
+import uy.kohesive.injekt.api.get
 
 /**
  * Class used to hold the displayed data of a manga in the library, like the cover or the title.
@@ -21,6 +26,7 @@ class LibraryListHolder(
         private val view: View,
         private val adapter: FlexibleAdapter<*>
 ) : LibraryHolder(view, adapter) {
+    private val preferences: PreferencesHelper = Injekt.get()
 
     /**
      * Method called from [LibraryCategoryAdapter.onBindViewHolder]. It updates the data for this
@@ -37,6 +43,15 @@ class LibraryListHolder(
             visibility = if (manga.unread > 0) View.VISIBLE else View.GONE
             text = manga.unread.toString()
         }
+        // Update the download count and its visibility.
+        with(itemView.download_text) {
+            visibility = if (manga.downloadTotal > 0 && preferences.downloadBadge().getOrDefault()) View.VISIBLE else View.GONE
+            text = manga.downloadTotal.toString()
+        }
+        //show local text badge if local manga
+        with(itemView.local_text) {
+            visibility = if (manga.source == LocalSource.ID) View.VISIBLE else View.GONE
+        }
 
         // Create thumbnail onclick to simulate long click
         itemView.thumbnail.setOnClickListener {
@@ -55,4 +70,4 @@ class LibraryListHolder(
                 .into(itemView.thumbnail)
     }
 
-}
+}

+ 18 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryNavigationView.kt

@@ -25,7 +25,7 @@ class LibraryNavigationView @JvmOverloads constructor(context: Context, attrs: A
     /**
      * List of groups shown in the view.
      */
-    private val groups = listOf(FilterGroup(), SortGroup(),  DisplayGroup())
+    private val groups = listOf(FilterGroup(), SortGroup(),  DisplayGroup(), BadgeGroup())
 
     /**
      * Adapter instance.
@@ -166,6 +166,23 @@ class LibraryNavigationView @JvmOverloads constructor(context: Context, attrs: A
 
     }
 
+    inner class BadgeGroup : Group {
+        private val downloadBadge = Item.CheckboxGroup(R.string.action_display_download_badge, this)
+        override val header = null
+        override val footer= null
+        override val items = listOf(downloadBadge)
+        override fun initModels() {
+            downloadBadge.checked = preferences.downloadBadge().getOrDefault()
+        }
+
+        override fun onItemClicked(item: Item) {
+            item as Item.CheckboxGroup
+            item.checked = !item.checked
+            preferences.downloadBadge().set((item.checked))
+            adapter.notifyItemChanged(item)
+        }
+    }
+
     /**
      * Display group, to show the library as a list or a grid.
      */

+ 56 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt

@@ -53,6 +53,11 @@ class LibraryPresenter(
      */
     private val filterTriggerRelay = BehaviorRelay.create(Unit)
 
+    /**
+     * Relay used to apply the UI update to the last emission of the library.
+     */
+    private val downloadTriggerRelay = BehaviorRelay.create(Unit)
+
     /**
      * Relay used to apply the selected sorting method to the last emission of the library.
      */
@@ -76,6 +81,8 @@ class LibraryPresenter(
             librarySubscription = getLibraryObservable()
                     .combineLatest(filterTriggerRelay.observeOn(Schedulers.io()),
                             { lib, _ -> Pair(lib.first, applyFilters(lib.second)) })
+                    .combineLatest(downloadTriggerRelay.observeOn(Schedulers.io()),
+                            { lib, _ -> Pair(lib.first, addDownloadTotal(lib.second)) })
                     .combineLatest(sortTriggerRelay.observeOn(Schedulers.io()),
                             { lib, _ -> Pair(lib.first, applySort(lib.second)) })
                     .map { Pair(it.first, it.second.mapValues { it.value.map(::LibraryItem) }) }
@@ -141,6 +148,48 @@ class LibraryPresenter(
         return map.mapValues { entry -> entry.value.filter(filterFn) }
     }
 
+    /**
+     * Adds Downloaded chapter count to manga
+     *
+     * @param map the map to filter.
+     */
+    private fun addDownloadTotal(map: Map<Int, List<Manga>>): Map<Int, List<Manga>> {
+        // Cached list of downloaded manga directories given a source id.
+        if (preferences.downloadBadge().getOrDefault()) {
+            val mangaDirsForSource = mutableMapOf<Long, Map<String?, UniFile>>()
+
+            // Cached list of downloaded chapter directories for a manga.
+            val chapterDirectories = mutableMapOf<Long, Int>()
+
+            for ((key, mangaList) in map) {
+                for (manga in mangaList) {
+                    manga.downloadTotal = getDownloadedCountFromDirectory(manga, mangaDirsForSource, chapterDirectories)
+                }
+            }
+        }
+        return map;
+    }
+
+    //Get count of downloaded chapters for a manga
+    fun getDownloadedCountFromDirectory(manga: Manga, mangaDirsForSource: MutableMap<Long, Map<String?, UniFile>>, chapterDirectories: MutableMap<Long, Int>): Int {
+        val source = sourceManager.get(manga.source) ?: return 0;
+        // Get the directories for the source of the manga.
+        val dirsForSource = mangaDirsForSource.getOrPut(source.id) {
+            val sourceDir = downloadManager.findSourceDir(source)
+            sourceDir?.listFiles()?.associateBy { it.name }.orEmpty()
+        }
+        val mangaDirName = downloadManager.getMangaDirName(manga)
+        val mangaDir = dirsForSource[mangaDirName] ?: return 0
+
+        chapterDirectories.getOrPut(manga.id!!) {
+            if (mangaDir.listFiles()?.isNotEmpty() ?: false) {
+                return mangaDir.listFiles()!!.size
+            }
+            return 0;
+        }
+        return 0;
+    }
+
     /**
      * Applies library sorting to the given map of manga.
      *
@@ -236,6 +285,13 @@ class LibraryPresenter(
         filterTriggerRelay.call(Unit)
     }
 
+    /**
+     * Requests the library to have download badges added.
+     */
+    fun requestDownloadBadgesUpdate() {
+        downloadTriggerRelay.call(Unit)
+    }
+
     /**
      * Requests the library to be sorted.
      */

+ 55 - 9
app/src/main/res/layout/catalogue_grid_item.xml

@@ -29,17 +29,63 @@
             android:layout_gravity="bottom"
             android:background="@drawable/gradient_shape"/>
 
-        <TextView
-            android:id="@+id/unread_text"
-            style="@style/TextAppearance.Regular.Caption.Light"
+        <android.support.constraint.ConstraintLayout
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:background="?attr/colorAccent"
-            android:paddingBottom="1dp"
-            android:paddingLeft="3dp"
-            android:paddingRight="3dp"
-            android:paddingTop="1dp"
-            android:visibility="gone"/>
+            tools:layout_editor_absoluteY="7dp"
+            tools:layout_editor_absoluteX="7dp">
+            <TextView
+                android:id="@+id/unread_text"
+                style="@style/TextAppearance.Regular.Caption.Light"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:background="@color/colorAccentDark"
+                android:paddingBottom="1dp"
+                android:paddingLeft="3dp"
+                android:paddingRight="3dp"
+                android:paddingTop="1dp"
+                android:visibility="gone"
+                tools:visibility="visible"
+                tools:text="120"
+                app:layout_constraintLeft_toRightOf="@+id/download_text"
+                android:layout_marginLeft="4dp"
+                app:layout_constraintTop_toTopOf="parent"
+                android:layout_marginTop="4dp"/>
+            <TextView
+                android:id="@+id/download_text"
+                style="@style/TextAppearance.Regular.Caption.Light"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:background="@color/md_red_500"
+                android:paddingBottom="1dp"
+                android:paddingLeft="3dp"
+                android:paddingRight="3dp"
+                android:paddingTop="1dp"
+                android:visibility="gone"
+                tools:visibility="visible"
+                tools:text="120"
+                app:layout_constraintLeft_toRightOf="@+id/local_text"
+                android:layout_marginLeft="4dp"
+                app:layout_constraintTop_toTopOf="parent"
+                android:layout_marginTop="4dp"/>
+            <TextView
+                android:id="@+id/local_text"
+                style="@style/TextAppearance.Regular.Caption.Light"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:background="@color/md_teal_500"
+                android:paddingBottom="1dp"
+                android:paddingLeft="3dp"
+                android:paddingRight="3dp"
+                android:paddingTop="1dp"
+                android:visibility="gone"
+                tools:visibility="visible"
+                android:text="@string/local_source_badge"
+                android:layout_marginLeft="4dp"
+                app:layout_constraintLeft_toLeftOf="parent"
+                app:layout_constraintTop_toTopOf="parent"
+                android:layout_marginTop="4dp"/>
+        </android.support.constraint.ConstraintLayout>
 
         <eu.kanade.tachiyomi.widget.PTSansTextView
             android:id="@+id/title"

+ 90 - 34
app/src/main/res/layout/catalogue_list_item.xml

@@ -1,56 +1,112 @@
 <?xml version="1.0" encoding="utf-8"?>
-<FrameLayout
+<android.support.constraint.ConstraintLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="@dimen/material_component_lists_single_line_with_avatar_height"
-    android:background="?attr/selectable_list_drawable">
-
-    <ImageView
-        android:id="@+id/thumbnail"
-        android:layout_width="@dimen/material_component_lists_single_line_with_avatar_height"
-        android:layout_height="@dimen/material_component_lists_single_line_with_avatar_height"
         android:layout_gravity="center_vertical"
-        android:paddingEnd="0dp"
-        android:paddingLeft="@dimen/material_component_lists_icon_left_padding"
-        android:paddingRight="0dp"
-        android:paddingStart="@dimen/material_component_lists_icon_left_padding"/>
-
-    <RelativeLayout
-        android:layout_width="match_parent"
+    android:background="?attr/selectable_list_drawable"
+    tools:layout_editor_absoluteY="25dp"
+    tools:layout_editor_absoluteX="0dp">
+   <ImageView
+       android:id="@+id/thumbnail"
+       android:layout_width="@dimen/material_component_lists_single_line_with_avatar_height"
+       android:layout_height="@dimen/material_component_lists_single_line_with_avatar_height"
+       android:layout_gravity="center_vertical"
+       tools:src="@mipmap/ic_launcher"
+       app:layout_constraintLeft_toLeftOf="parent"
+       app:layout_constraintBottom_toBottomOf="parent"
+       app:layout_constraintTop_toTopOf="parent"
+       android:paddingTop="8dp"
+       android:paddingBottom="8dp"
+       android:layout_marginLeft="8dp"/>
+    <TextView
+        android:id="@+id/title"
+        style="@style/TextAppearance.Regular.SubHeading"
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_gravity="center_vertical"
-        android:paddingLeft="@dimen/material_component_lists_text_left_padding"
-        android:paddingStart="@dimen/material_component_lists_text_left_padding"
-        android:paddingRight="@dimen/material_component_lists_right_padding"
-        android:paddingEnd="@dimen/material_component_lists_right_padding">
+        android:ellipsize="end"
+        android:maxLines="1"
+        tools:text="Manga title"
+        android:layout_marginStart="8dp"
+        app:layout_constraintTop_toTopOf="parent"
+        android:layout_marginTop="8dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        android:layout_marginBottom="8dp"
+        app:layout_constraintLeft_toRightOf="@+id/thumbnail"
+        android:layout_marginLeft="8dp"
+        app:layout_constraintRight_toLeftOf="@+id/local_text"
+        android:layout_marginRight="8dp"
+        app:layout_constraintVertical_bias="0.523"
+        app:layout_constraintHorizontal_bias="0.007"/>
 
         <TextView
             android:id="@+id/unread_text"
-            style="@style/TextAppearance.Regular.Caption.Hint"
+            style="@style/TextAppearance.Regular.Caption.Light"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_alignParentRight="true"
+            android:background="@color/colorAccentLight"
+            android:paddingBottom="1dp"
+            android:paddingLeft="3dp"
+            android:paddingRight="3dp"
+            android:paddingTop="1dp"
             android:layout_alignParentEnd="true"
             android:layout_centerVertical="true"
-            android:paddingStart="4dp"
-            android:paddingLeft="4dp"
             android:maxLines="1"
-            android:textAppearance="@style/TextAppearance.Regular.Caption.Hint"
             android:visibility="gone"
-            tools:text="22"/>
+            tools:text="130"
+            tools:visibility="visible"
+            app:layout_constraintTop_toTopOf="parent"
+            android:layout_marginTop="8dp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            android:layout_marginBottom="8dp"
+            android:layout_marginRight="8dp"
+            app:layout_constraintRight_toRightOf="parent"/>
 
         <TextView
-            android:id="@+id/title"
-            style="@style/TextAppearance.Regular.SubHeading"
-            android:layout_toLeftOf="@id/unread_text"
-            android:layout_toStartOf="@id/unread_text"
-            android:layout_width="match_parent"
+            android:id="@+id/download_text"
+            style="@style/TextAppearance.Regular.Caption.Light"
+            android:layout_width="wrap_content"
             android:layout_height="wrap_content"
+            android:background="@color/md_red_500"
+            android:paddingBottom="1dp"
+            android:paddingLeft="3dp"
+            android:paddingRight="3dp"
+            android:paddingTop="1dp"
+            android:layout_centerVertical="true"
+            android:layout_toStartOf="@+id/unread_text"
             android:maxLines="1"
-            android:ellipsize="end"
-            tools:text="Manga title"/>
+            android:visibility="gone"
+            tools:text="122"
+            tools:visibility="visible"
+            android:layout_marginEnd="8dp"
+            app:layout_constraintRight_toLeftOf="@+id/unread_text"
+            app:layout_constraintTop_toTopOf="parent"
+            android:layout_marginTop="8dp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            android:layout_marginBottom="8dp"/>
+    <TextView
+        android:id="@+id/local_text"
+        style="@style/TextAppearance.Regular.Caption.Light"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:background="@color/md_teal_500"
+        android:paddingBottom="1dp"
+        android:paddingLeft="3dp"
+        android:paddingRight="3dp"
+        android:paddingTop="1dp"
+        android:layout_centerVertical="true"
+        android:maxLines="1"
+        android:text="@string/local_source_badge"
+        android:visibility="gone"
+        tools:visibility="visible"
+        android:layout_marginEnd="8dp"
+        app:layout_constraintRight_toLeftOf="@+id/download_text"
+        app:layout_constraintTop_toTopOf="parent"
+        android:layout_marginTop="8dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        android:layout_marginBottom="8dp"/>
 
-    </RelativeLayout>
+    </android.support.constraint.ConstraintLayout>
 
-</FrameLayout>

+ 2 - 0
app/src/main/res/values/colors.xml

@@ -78,4 +78,6 @@
 
     <color name="md_red_500">#F44336</color>
 
+    <color name="md_teal_500">#009688</color>
+
 </resources>

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

@@ -21,6 +21,7 @@
     <string name="label_selected">Selected: %1$d</string>
     <string name="label_backup">Backup</string>
 
+
     <!-- Actions -->
     <string name="action_settings">Settings</string>
     <string name="action_filter">Filter</string>
@@ -74,6 +75,7 @@
     <string name="action_display">Display</string>
     <string name="action_display_grid">Grid</string>
     <string name="action_display_list">List</string>
+    <string name="action_display_download_badge">Download Badges</string>
     <string name="action_set_filter">Set filter</string>
     <string name="action_cancel">Cancel</string>
     <string name="action_sort">Sort</string>
@@ -270,6 +272,7 @@
     <!-- Library fragment -->
     <string name="library_search_hint">Title or author…</string>
     <string name="updating_category">Updating category</string>
+    <string name="local_source_badge">Local</string>
     <string name="confirm_delete_manga">Are you sure you want to remove selected manga?</string>
     <string name="also_delete_chapters">Also delete downloaded chapters</string>