Download badge

This commit is contained in:
inorichi 2017-10-21 17:08:49 +02:00
parent ca30fd6088
commit d0567de4e6
13 changed files with 269 additions and 45 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,7 +5,12 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.glide.GlideApp 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 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. * 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( class LibraryGridHolder(
private val view: View, private val view: View,
private val adapter: FlexibleAdapter<*> private val adapter: FlexibleAdapter<*>
) : LibraryHolder(view, adapter) { ) : LibraryHolder(view, adapter) {
private val preferences: PreferencesHelper = Injekt.get()
/** /**
* Method called from [LibraryCategoryAdapter.onBindViewHolder]. It updates the data for this * 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 visibility = if (manga.unread > 0) View.VISIBLE else View.GONE
text = manga.unread.toString() 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. // Update the cover.
GlideApp.with(view.context).clear(view.thumbnail) GlideApp.with(view.context).clear(view.thumbnail)

View File

@ -5,7 +5,12 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.glide.GlideApp 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 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. * 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 view: View,
private val adapter: FlexibleAdapter<*> private val adapter: FlexibleAdapter<*>
) : LibraryHolder(view, adapter) { ) : LibraryHolder(view, adapter) {
private val preferences: PreferencesHelper = Injekt.get()
/** /**
* Method called from [LibraryCategoryAdapter.onBindViewHolder]. It updates the data for this * 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 visibility = if (manga.unread > 0) View.VISIBLE else View.GONE
text = manga.unread.toString() 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 // Create thumbnail onclick to simulate long click
itemView.thumbnail.setOnClickListener { itemView.thumbnail.setOnClickListener {

View File

@ -25,7 +25,7 @@ class LibraryNavigationView @JvmOverloads constructor(context: Context, attrs: A
/** /**
* List of groups shown in the view. * List of groups shown in the view.
*/ */
private val groups = listOf(FilterGroup(), SortGroup(), DisplayGroup()) private val groups = listOf(FilterGroup(), SortGroup(), DisplayGroup(), BadgeGroup())
/** /**
* Adapter instance. * 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. * Display group, to show the library as a list or a grid.
*/ */

View File

@ -53,6 +53,11 @@ class LibraryPresenter(
*/ */
private val filterTriggerRelay = BehaviorRelay.create(Unit) 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. * Relay used to apply the selected sorting method to the last emission of the library.
*/ */
@ -76,6 +81,8 @@ class LibraryPresenter(
librarySubscription = getLibraryObservable() librarySubscription = getLibraryObservable()
.combineLatest(filterTriggerRelay.observeOn(Schedulers.io()), .combineLatest(filterTriggerRelay.observeOn(Schedulers.io()),
{ lib, _ -> Pair(lib.first, applyFilters(lib.second)) }) { lib, _ -> Pair(lib.first, applyFilters(lib.second)) })
.combineLatest(downloadTriggerRelay.observeOn(Schedulers.io()),
{ lib, _ -> Pair(lib.first, addDownloadTotal(lib.second)) })
.combineLatest(sortTriggerRelay.observeOn(Schedulers.io()), .combineLatest(sortTriggerRelay.observeOn(Schedulers.io()),
{ lib, _ -> Pair(lib.first, applySort(lib.second)) }) { lib, _ -> Pair(lib.first, applySort(lib.second)) })
.map { Pair(it.first, it.second.mapValues { it.value.map(::LibraryItem) }) } .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) } 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. * Applies library sorting to the given map of manga.
* *
@ -236,6 +285,13 @@ class LibraryPresenter(
filterTriggerRelay.call(Unit) filterTriggerRelay.call(Unit)
} }
/**
* Requests the library to have download badges added.
*/
fun requestDownloadBadgesUpdate() {
downloadTriggerRelay.call(Unit)
}
/** /**
* Requests the library to be sorted. * Requests the library to be sorted.
*/ */

View File

@ -29,17 +29,63 @@
android:layout_gravity="bottom" android:layout_gravity="bottom"
android:background="@drawable/gradient_shape"/> android:background="@drawable/gradient_shape"/>
<TextView <android.support.constraint.ConstraintLayout
android:id="@+id/unread_text"
style="@style/TextAppearance.Regular.Caption.Light"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?attr/colorAccent" tools:layout_editor_absoluteY="7dp"
android:paddingBottom="1dp" tools:layout_editor_absoluteX="7dp">
android:paddingLeft="3dp" <TextView
android:paddingRight="3dp" android:id="@+id/unread_text"
android:paddingTop="1dp" style="@style/TextAppearance.Regular.Caption.Light"
android:visibility="gone"/> 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 <eu.kanade.tachiyomi.widget.PTSansTextView
android:id="@+id/title" android:id="@+id/title"

View File

@ -1,56 +1,112 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout <android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android" 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" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/material_component_lists_single_line_with_avatar_height" 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:layout_gravity="center_vertical"
android:paddingEnd="0dp" android:background="?attr/selectable_list_drawable"
android:paddingLeft="@dimen/material_component_lists_icon_left_padding" tools:layout_editor_absoluteY="25dp"
android:paddingRight="0dp" tools:layout_editor_absoluteX="0dp">
android:paddingStart="@dimen/material_component_lists_icon_left_padding"/> <ImageView
android:id="@+id/thumbnail"
<RelativeLayout android:layout_width="@dimen/material_component_lists_single_line_with_avatar_height"
android:layout_width="match_parent" 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_height="wrap_content"
android:layout_gravity="center_vertical" android:ellipsize="end"
android:paddingLeft="@dimen/material_component_lists_text_left_padding" android:maxLines="1"
android:paddingStart="@dimen/material_component_lists_text_left_padding" tools:text="Manga title"
android:paddingRight="@dimen/material_component_lists_right_padding" android:layout_marginStart="8dp"
android:paddingEnd="@dimen/material_component_lists_right_padding"> 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 <TextView
android:id="@+id/unread_text" android:id="@+id/unread_text"
style="@style/TextAppearance.Regular.Caption.Hint" style="@style/TextAppearance.Regular.Caption.Light"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="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_alignParentEnd="true"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:paddingStart="4dp"
android:paddingLeft="4dp"
android:maxLines="1" android:maxLines="1"
android:textAppearance="@style/TextAppearance.Regular.Caption.Hint"
android:visibility="gone" 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 <TextView
android:id="@+id/title" android:id="@+id/download_text"
style="@style/TextAppearance.Regular.SubHeading" style="@style/TextAppearance.Regular.Caption.Light"
android:layout_toLeftOf="@id/unread_text" android:layout_width="wrap_content"
android:layout_toStartOf="@id/unread_text"
android:layout_width="match_parent"
android:layout_height="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:maxLines="1"
android:ellipsize="end" android:visibility="gone"
tools:text="Manga title"/> 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>

View File

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

View File

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