From d0def563c80453a086ea48ad189008a1dca8b898 Mon Sep 17 00:00:00 2001 From: Jay Date: Sat, 4 Apr 2020 13:47:32 -0400 Subject: [PATCH] New fast scroller for library Updated Drag handle icon Removed outline fron update button in extensions Moved "view options" to filter sheet as "display options" Removed freeform cover option from library :pensive: Added swipe refresh to recents page Removed a few overflows --- .../data/preference/PreferencesHelper.kt | 2 + .../ui/catalogue/CatalogueController.kt | 2 +- .../tachiyomi/ui/category/CategoryHolder.kt | 2 +- .../tachiyomi/ui/extension/ExtensionHolder.kt | 5 +- .../ui/library/DisplayBottomSheet.kt | 14 +- .../ui/library/LibraryCategoryAdapter.kt | 195 +++++++++---- .../tachiyomi/ui/library/LibraryController.kt | 256 ++++++++++++------ .../tachiyomi/ui/library/LibraryItem.kt | 2 +- .../tachiyomi/ui/library/LibraryPresenter.kt | 10 +- .../ui/library/filter/FilterBottomSheet.kt | 4 + .../tachiyomi/ui/manga/MangaHeaderHolder.kt | 9 +- .../tachiyomi/ui/recents/RecentsController.kt | 24 +- .../ui/setting/SettingsSourcesController.kt | 2 +- .../tachiyomi/util/view/ViewExtensions.kt | 13 + .../res/drawable/fast_scroll_background.xml | 27 ++ .../drawable/ic_drag_handle_black_24dp.xml | 6 + .../drawable/round_textview_background.xml | 2 +- app/src/main/res/layout/categories_item.xml | 2 +- .../main/res/layout/display_bottom_sheet.xml | 89 ++++-- app/src/main/res/layout/download_item.xml | 2 +- .../main/res/layout/extension_card_item.xml | 2 +- .../main/res/layout/filter_bottom_sheet.xml | 17 +- .../res/layout/library_list_controller.xml | 67 +++-- app/src/main/res/layout/main_activity.xml | 1 + app/src/main/res/layout/menu_counter.xml | 14 - .../main/res/layout/migration_source_item.xml | 2 +- .../main/res/layout/recents_controller.xml | 34 ++- app/src/main/res/menu/catalogue_list.xml | 3 +- app/src/main/res/menu/catalogue_main.xml | 6 +- app/src/main/res/menu/extension_main.xml | 11 +- app/src/main/res/menu/library.xml | 12 +- app/src/main/res/menu/library_selection.xml | 5 - app/src/main/res/menu/main_sort.xml | 10 +- app/src/main/res/menu/recents.xml | 3 +- app/src/main/res/values/colors.xml | 1 + app/src/main/res/values/strings.xml | 11 +- 36 files changed, 592 insertions(+), 275 deletions(-) create mode 100644 app/src/main/res/drawable/fast_scroll_background.xml create mode 100644 app/src/main/res/drawable/ic_drag_handle_black_24dp.xml delete mode 100644 app/src/main/res/layout/menu_counter.xml diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt index 3004c2ee20..b4e2f69dfd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt @@ -177,6 +177,8 @@ class PreferencesHelper(val context: Context) { fun gridSize() = rxPrefs.getInteger(Keys.gridSize, 1) + fun autoHideSeeker() = rxPrefs.getBoolean("auto_hide_seeker", true) + fun uniformGrid() = rxPrefs.getBoolean(Keys.uniformGrid, true) fun libraryAsSingleList() = rxPrefs.getBoolean(Keys.libraryAsSingleList, false) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueController.kt index 32520e9e52..a14d6ee580 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueController.kt @@ -91,7 +91,7 @@ class CatalogueController : NucleusController(), override fun getTitle(): String? { return if (showingExtenions) applicationContext?.getString(R.string.label_extensions) - else applicationContext?.getString(R.string.label_catalogues) + else applicationContext?.getString(R.string.pref_category_sources) } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryHolder.kt index 5e269fbd74..166bc5d96f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryHolder.kt @@ -60,7 +60,7 @@ class CategoryHolder(view: View, val adapter: CategoryAdapter) : BaseFlexibleVie } else { title.setTextColor(ContextCompat.getColor(itemView.context, R.color.textColorPrimary)) regularDrawable = ContextCompat.getDrawable(itemView.context, R.drawable - .ic_reorder_grey_24dp) + .ic_drag_handle_black_24dp) image.visible() edit_text.setText(title.text) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionHolder.kt index b7382d0d40..5aabfa50f3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionHolder.kt @@ -1,6 +1,7 @@ package eu.kanade.tachiyomi.ui.extension import android.content.res.ColorStateList +import android.graphics.Color import android.view.View import androidx.core.content.ContextCompat import eu.kanade.tachiyomi.R @@ -11,6 +12,7 @@ import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder import eu.kanade.tachiyomi.ui.base.holder.SlicedHolder import eu.kanade.tachiyomi.util.system.LocaleHelper import eu.kanade.tachiyomi.util.system.getResourceColor +import eu.kanade.tachiyomi.util.view.resetStrokeColor import io.github.mthli.slice.Slice import kotlinx.android.synthetic.main.extension_card_item.* @@ -64,8 +66,8 @@ class ExtensionHolder(view: View, override val adapter: ExtensionAdapter) : setTextColor(ContextCompat.getColorStateList(context, R.drawable.button_text_state)) backgroundTintList = ContextCompat.getColorStateList(context, android.R.color.transparent) + resetStrokeColor() val extension = item.extension - val installStep = item.installStep if (installStep != null) { setText(when (installStep) { @@ -85,6 +87,7 @@ class ExtensionHolder(view: View, override val adapter: ExtensionAdapter) : isActivated = true backgroundTintList = ColorStateList.valueOf( context.getResourceColor(R.attr.colorAccent)) + strokeColor = ColorStateList.valueOf(Color.TRANSPARENT) setText(R.string.ext_update) } extension.isObsolete -> { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/DisplayBottomSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/DisplayBottomSheet.kt index 220a639ffb..efef5bfac9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/DisplayBottomSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/DisplayBottomSheet.kt @@ -64,7 +64,7 @@ class DisplayBottomSheet(private val controller: LibraryController) : BottomShee override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) initGeneralPreferences() - setBottomEdge(unread_badge_group, activity) + setBottomEdge(hide_filters, activity) close_button.setOnClickListener { dismiss() true @@ -86,6 +86,9 @@ class DisplayBottomSheet(private val controller: LibraryController) : BottomShee uniform_grid.bindToPreference(preferences.uniformGrid()) { controller.reattachAdapter() } + autohide_seeker.bindToPreference(preferences.autoHideSeeker()) { + controller.updateAutoHideScrollbar(autohide_seeker.isChecked) + } grid_size_toggle_group.bindToPreference(preferences.gridSize()) { controller.reattachAdapter() } @@ -95,28 +98,29 @@ class DisplayBottomSheet(private val controller: LibraryController) : BottomShee unread_badge_group.bindToPreference(preferences.unreadBadgeType()) { controller.presenter.requestUnreadBadgesUpdate() } + hide_filters.bindToPreference(preferences.hideFiltersAtStart()) } /** * Binds a checkbox or switch view with a boolean preference. */ - private fun CompoundButton.bindToPreference(pref: Preference, block: () -> Unit) { + private fun CompoundButton.bindToPreference(pref: Preference, block: (() -> Unit)? = null) { isChecked = pref.getOrDefault() setOnCheckedChangeListener { _, isChecked -> pref.set(isChecked) - block() + block?.invoke() } } /** * Binds a radio group with a int preference. */ - private fun RadioGroup.bindToPreference(pref: Preference, block: () -> Unit) { + private fun RadioGroup.bindToPreference(pref: Preference, block: (() -> Unit)? = null) { (getChildAt(pref.getOrDefault()) as RadioButton).isChecked = true setOnCheckedChangeListener { _, checkedId -> val index = indexOfChild(findViewById(checkedId)) pref.set(index) - block() + block?.invoke() } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt index 7d86c5e162..2fbd915dfb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt @@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.ui.library import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.items.IFlexible +import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.preference.PreferencesHelper @@ -13,6 +14,7 @@ import java.text.SimpleDateFormat import java.util.Calendar import java.util.Date import java.util.Locale +import kotlin.math.max /** * Adapter storing a list of manga in a certain category. @@ -91,65 +93,152 @@ class LibraryCategoryAdapter(val libraryListener: LibraryListener) : isLongPressDragEnabled = libraryListener.canDrag() && s.isNullOrBlank() } - override fun onCreateBubbleText(position: Int): String { - return if (position < scrollableHeaders.size) { - "Top" - } else if (position >= itemCount - scrollableFooters.size) { - "Bottom" - } else { // Get and show the first character - val iFlexible: IFlexible<*>? = getItem(position) - if (iFlexible is LibraryHeaderItem) { - return iFlexible.category.name - } - val preferences: PreferencesHelper by injectLazy() - when (preferences.librarySortingMode().getOrDefault()) { - LibrarySort.DRAG_AND_DROP -> { - if (!preferences.hideCategories().getOrDefault()) { - val title = (iFlexible as LibraryItem).manga.title - if (preferences.removeArticles().getOrDefault()) - title.removeArticles().substring(0, 1).toUpperCase(Locale.US) - else title.substring(0, 1).toUpperCase(Locale.US) - } else { - val db: DatabaseHelper by injectLazy() - val category = db.getCategoriesForManga((iFlexible as LibraryItem).manga) - .executeAsBlocking().firstOrNull()?.name - category?.chop(10) ?: "Default" + fun getSectionText(position: Int): String? { + val preferences: PreferencesHelper by injectLazy() + val db: DatabaseHelper by injectLazy() + return when (val item: IFlexible<*>? = getItem(position)) { + is LibraryHeaderItem -> + if (preferences.hideCategories().getOrDefault()) null + else getFirstLetter(item.category.name) + + "\u200B".repeat(max(0, item.category.order)) + is LibraryItem -> { + when (preferences.librarySortingMode().getOrDefault()) { + LibrarySort.DRAG_AND_DROP -> { + val category = db.getCategoriesForManga(item.manga).executeAsBlocking() + .firstOrNull() + if (category == null) null + else getFirstLetter(category.name) + "\u200B".repeat(max(0, category.order)) + } + LibrarySort.LAST_READ -> { + val id = item.manga.id ?: return "" + val history = db.getHistoryByMangaId(id).executeAsBlocking() + val last = history.maxBy { it.last_read } + if (last != null && last.last_read > 100) getShorterDate(Date(last.last_read)) + else "*" + } + LibrarySort.TOTAL -> { + val unread = item.chapterCount + (unread / 100).toString() + } + LibrarySort.UNREAD -> { + val unread = item.manga.unread + if (unread > 0) (unread / 100).toString() + else "R" + } + LibrarySort.LATEST_CHAPTER -> { + val lastUpdate = item.manga.last_update + if (lastUpdate > 0) getShorterDate(Date(lastUpdate)) + else "*" + } + LibrarySort.DATE_ADDED -> { + val lastUpdate = item.manga.date_added + if (lastUpdate > 0) getShorterDate(Date(lastUpdate)) + else "*" + } + else -> { + val title = if (preferences.removeArticles() + .getOrDefault() + ) item.manga.title.removeArticles() + else item.manga.title + getFirstLetter(title) } } - LibrarySort.LAST_READ -> { - val db: DatabaseHelper by injectLazy() - val id = (iFlexible as LibraryItem).manga.id ?: return "" - val history = db.getHistoryByMangaId(id).executeAsBlocking() - val last = history.maxBy { it.last_read } - if (last != null) - getShortDate(Date(last.last_read)) - else - "N/A" - } - LibrarySort.UNREAD -> { - val unread = (iFlexible as LibraryItem).manga.unread - if (unread > 0) - unread.toString() - else - "Read" - } - LibrarySort.LATEST_CHAPTER -> { - val lastUpdate = (iFlexible as LibraryItem).manga.last_update - if (lastUpdate > 0) - getShortDate(Date(lastUpdate)) - else - "N/A" - } - else -> { - val title = (iFlexible as LibraryItem).manga.title - if (preferences.removeArticles().getOrDefault()) - title.removeArticles().substring(0, 1).toUpperCase(Locale.US) - else title.substring(0, 1).toUpperCase(Locale.US) + } + else -> "" + } + } + + private fun getFirstLetter(name: String): String { + val letter = name.first() + return if (letter.isLetter()) letter.toString() + .toUpperCase(Locale.ROOT) else "#" + } + + override fun onCreateBubbleText(position: Int): String { + val preferences: PreferencesHelper by injectLazy() + val db: DatabaseHelper by injectLazy() + return when (val iFlexible: IFlexible<*>? = getItem(position)) { + is LibraryHeaderItem -> iFlexible.category.name + is LibraryItem -> { + when (preferences.librarySortingMode().getOrDefault()) { + LibrarySort.DRAG_AND_DROP -> { + if (!preferences.hideCategories().getOrDefault()) { + val title = iFlexible.manga.title + if (preferences.removeArticles().getOrDefault()) title.removeArticles() + .substring(0, 1).toUpperCase(Locale.US) + else title.substring(0, 1).toUpperCase(Locale.US) + } else { + val category = db.getCategoriesForManga(iFlexible.manga) + .executeAsBlocking().firstOrNull()?.name + category?.chop(10) + ?: recyclerView.context.getString(R.string.default_category) + } + } + LibrarySort.LAST_READ -> { + val id = iFlexible.manga.id ?: return "" + val history = db.getHistoryByMangaId(id).executeAsBlocking() + val last = history.maxBy { it.last_read } + if (last != null && last.last_read > 100) getShortDate(Date(last.last_read)) + else "N/A" + } + LibrarySort.UNREAD -> { + val unread = iFlexible.manga.unread + if (unread > 0) getRange(unread) + else recyclerView.context.getString(R.string.action_filter_read) + } + LibrarySort.TOTAL -> { + val total = iFlexible.chapterCount + if (total > 0) getRange(total) + else "N/A" + } + LibrarySort.LATEST_CHAPTER -> { + val lastUpdate = iFlexible.manga.last_update + if (lastUpdate > 0) getShortDate(Date(lastUpdate)) + else "N/A" + } + LibrarySort.DATE_ADDED -> { + val lastUpdate = iFlexible.manga.date_added + if (lastUpdate > 0) getShortDate(Date(lastUpdate)) + else "N/A" + } + else -> getSectionText(position) ?: "" } } + else -> "" } } + private fun getRange(value: Int): String { + return when (value) { + in 1..99 -> "< 100" + in 100..199 -> "100-199" + in 200..299 -> "200-299" + in 300..399 -> "300-399" + in 400..499 -> "400-499" + in 500..599 -> "500-599" + in 600..699 -> "600-699" + in 700..799 -> "700-799" + in 800..899 -> "800-899" + in 900..Int.MAX_VALUE -> "900+" + else -> "None" + } + } + + private fun getShorterDate(date: Date): String { + val cal = Calendar.getInstance() + cal.time = Date() + + val yearNow = cal.get(Calendar.YEAR) + val cal2 = Calendar.getInstance() + cal2.time = date + val yearThen = cal2.get(Calendar.YEAR) + + return if (yearNow == yearThen) + SimpleDateFormat("M", Locale.getDefault()).format(date) + else + SimpleDateFormat("''yy", Locale.getDefault()).format(date) + } + private fun getShortDate(date: Date): String { val cal = Calendar.getInstance() cal.time = Date() @@ -160,7 +249,7 @@ class LibraryCategoryAdapter(val libraryListener: LibraryListener) : val yearThen = cal2.get(Calendar.YEAR) return if (yearNow == yearThen) - SimpleDateFormat("MMM", Locale.getDefault()).format(date) + SimpleDateFormat("MMMM", Locale.getDefault()).format(date) else SimpleDateFormat("yyyy", Locale.getDefault()).format(date) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt index e680f1b215..d5175cc230 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt @@ -5,6 +5,7 @@ import android.animation.AnimatorSet import android.animation.ValueAnimator import android.app.Activity import android.content.Context +import android.content.res.ColorStateList import android.graphics.Rect import android.os.Bundle import android.util.TypedValue @@ -15,11 +16,13 @@ import android.view.MenuItem import android.view.MotionEvent import android.view.View import android.view.ViewGroup +import android.view.ViewPropertyAnimator import android.view.inputmethod.InputMethodManager import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.view.ActionMode import androidx.appcompat.widget.SearchView +import androidx.core.content.ContextCompat import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager @@ -33,6 +36,8 @@ import com.bluelinelabs.conductor.ControllerChangeType import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.snackbar.BaseTransientBottomBar import com.google.android.material.snackbar.Snackbar +import com.reddit.indicatorfastscroll.FastScrollItemIndicator +import com.reddit.indicatorfastscroll.FastScrollerView import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.SelectableAdapter import eu.davidea.flexibleadapter.items.IFlexible @@ -51,7 +56,6 @@ import eu.kanade.tachiyomi.ui.library.filter.FilterBottomSheet import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.main.OnTouchEventInterface import eu.kanade.tachiyomi.ui.main.RootSearchInterface -import eu.kanade.tachiyomi.ui.main.SpinnerTitleInterface import eu.kanade.tachiyomi.ui.main.SwipeGestureInterface import eu.kanade.tachiyomi.ui.manga.MangaDetailsController import eu.kanade.tachiyomi.ui.migration.manga.design.PreMigrationController @@ -59,6 +63,7 @@ import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationListController import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationProcedureConfig import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.util.system.dpToPx +import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.system.launchUI import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.view.applyWindowInsetsForRootController @@ -90,7 +95,7 @@ class LibraryController( ChangeMangaCategoriesDialog.Listener, FlexibleAdapter.OnItemClickListener, FlexibleAdapter.OnItemLongClickListener, FlexibleAdapter.OnItemMoveListener, LibraryCategoryAdapter.LibraryListener, - SpinnerTitleInterface, OnTouchEventInterface, SwipeGestureInterface, + OnTouchEventInterface, SwipeGestureInterface, RootSearchInterface, LibraryServiceListener { init { @@ -112,6 +117,8 @@ class LibraryController( private var libraryLayout: Int = preferences.libraryLayout().getOrDefault() + private var singleCategory: Boolean = false + /** * Library search query. */ @@ -155,11 +162,15 @@ class LibraryController( private var isDragging = false private val scrollDistanceTilHidden = 1000.dpToPx + private var textAnim: ViewPropertyAnimator? = null + private var scrollAnim: ViewPropertyAnimator? = null + private var autoHideScroller: Boolean = preferences.autoHideSeeker().getOrDefault() + override fun getTitle(): String? { return if (view != null && presenter.categories.size > 1) presenter.categories.find { it.order == activeCategory - }?.name ?: super.getTitle() - else super.getTitle() + }?.name ?: view?.context?.getString(R.string.label_library) + else view?.context?.getString(R.string.label_library) } private var scrollListener = object : RecyclerView.OnScrollListener() { @@ -179,14 +190,145 @@ class LibraryController( setTitle() } } + + override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { + super.onScrollStateChanged(recyclerView, newState) + if (!autoHideScroller) return + when (newState) { + RecyclerView.SCROLL_STATE_DRAGGING -> { + scrollAnim?.cancel() + if (fast_scroller.translationX != 0f) { + fast_scroller.animate().setStartDelay(0).setDuration(100).translationX(0f) + .start() + } + } + RecyclerView.SCROLL_STATE_IDLE -> { + scrollAnim = fast_scroller.animate().setStartDelay(1000).setDuration(250) + .translationX(22f.dpToPx) + scrollAnim?.start() + } + } + } + } + + private fun hideScroller() { + if (!autoHideScroller) return + scrollAnim = fast_scroller.animate() + .setStartDelay(1000) + .setDuration(250) + .translationX(22f.dpToPx) + scrollAnim?.start() + } + + private fun setFastScrollBackground() { + val context = activity ?: return + fast_scroller.background = if (autoHideScroller) ContextCompat.getDrawable( + context, R.drawable.fast_scroll_background + ) else null + fast_scroller.textColor = ColorStateList.valueOf( + context.getResourceColor( + if (autoHideScroller) android.R.attr.textColorPrimaryInverse else android.R.attr.textColorPrimary + ) + ) } override fun onViewCreated(view: View) { super.onViewCreated(view) view.applyWindowInsetsForRootController(activity!!.bottom_nav) if (!::presenter.isInitialized) presenter = LibraryPresenter(this) + fast_scroller.translationX = 22f.dpToPx + setFastScrollBackground() - layoutView(view) + adapter = LibraryCategoryAdapter(this) + setRecyclerLayout() + recycler.manager.spanSizeLookup = (object : GridLayoutManager.SpanSizeLookup() { + override fun getSpanSize(position: Int): Int { + if (libraryLayout == 0) return 1 + val item = this@LibraryController.adapter.getItem(position) + return if (item is LibraryHeaderItem) recycler.manager.spanCount + else if (item is LibraryItem && item.manga.isBlank()) recycler.manager.spanCount + else 1 + } + }) + recycler.setHasFixedSize(true) + recycler.adapter = adapter + fast_scroller.setupWithRecyclerView( + recycler, { position -> + val letter = adapter.getSectionText(position) + if (!singleCategory && !adapter.isHeader(adapter.getItem(position))) null + else if (letter != null) FastScrollItemIndicator.Text(letter) + else FastScrollItemIndicator.Icon(R.drawable.star) + }) + fast_scroller.useDefaultScroller = false + fast_scroller.itemIndicatorSelectedCallbacks += object : FastScrollerView.ItemIndicatorSelectedCallback { + override fun onItemIndicatorSelected( + indicator: FastScrollItemIndicator, + indicatorCenterY: Int, + itemPosition: Int + ) { + fast_scroller.translationX = 0f + hideScroller() + + textAnim?.cancel() + textAnim = text_view_m.animate().alpha(0f).setDuration(250L).setStartDelay(1000) + textAnim?.start() + + text_view_m.translationY = indicatorCenterY.toFloat() - text_view_m.height / 2 + text_view_m.alpha = 1f + text_view_m.text = adapter.onCreateBubbleText(itemPosition) + val appbar = activity?.appbar + appbar?.y = 0f + (recycler.layoutManager as LinearLayoutManager).scrollToPositionWithOffset( + itemPosition, + if (singleCategory) 0 else (if (itemPosition == 0) 0 else (-40).dpToPx) + ) + } + } + recycler.addOnScrollListener(scrollListener) + + val tv = TypedValue() + activity!!.theme.resolveAttribute(R.attr.actionBarTintColor, tv, true) + + scrollViewWith(recycler, swipeRefreshLayout = swipe_refresh) { insets -> + fast_scroller.updateLayoutParams { + topMargin = insets.systemWindowInsetTop + } + } + + swipe_refresh.setDistanceToTriggerSync(150.dpToPx) + swipe_refresh.setOnRefreshListener { + swipe_refresh.isRefreshing = false + if (!LibraryUpdateService.isRunning()) { + when { + presenter.allCategories.size <= 1 -> updateLibrary() + preferences.updateOnRefresh().getOrDefault() == -1 -> { + MaterialDialog(activity!!).title(R.string.what_should_update) + .negativeButton(android.R.string.cancel) + .listItemsSingleChoice(items = listOf( + view.context.getString( + R.string.top_category, presenter.allCategories.first().name + ), view.context.getString( + R.string.categories_in_global_update + ) + ), selection = { _, index, _ -> + preferences.updateOnRefresh().set(index) + when (index) { + 0 -> updateLibrary(presenter.allCategories.first()) + else -> updateLibrary() + } + }) + .positiveButton(R.string.action_update) + .show() + } + else -> { + when (preferences.updateOnRefresh().getOrDefault()) { + 0 -> updateLibrary(presenter.allCategories.first()) + else -> updateLibrary() + } + } + } + } + } if (selectedMangas.isNotEmpty()) { createActionModeIfNeeded() @@ -201,6 +343,7 @@ class LibraryController( FilterBottomSheet.ACTION_HIDE_FILTER_TIP -> activity?.toast( R.string.hide_filters_tip, Toast.LENGTH_LONG ) + FilterBottomSheet.ACTION_DISPLAY -> DisplayBottomSheet(this).show() } } @@ -342,7 +485,7 @@ class LibraryController( } private fun resetScrollingValues() { - swipe_refresh.isEnabled = true + swipe_refresh.isEnabled = !isDragging startPosX = null startPosY = null nextCategory = null @@ -351,8 +494,17 @@ class LibraryController( lockedY = false } + fun updateAutoHideScrollbar(autoHide: Boolean) { + autoHideScroller = autoHide + setFastScrollBackground() + scrollAnim?.cancel() + if (autoHide) hideScroller() + else fast_scroller.translationX = 0f + setRecyclerLayout() + } + private fun resetRecyclerY(animated: Boolean = false, time: Long = 100) { - swipe_refresh.isEnabled = true + swipe_refresh.isEnabled = !isDragging moved = false lockedRecycler = false if (animated) { @@ -387,67 +539,6 @@ class LibraryController( return inflater.inflate(R.layout.library_list_controller, container, false) } - private fun layoutView(view: View) { - adapter = LibraryCategoryAdapter(this) - setRecyclerLayout() - recycler.manager.spanSizeLookup = (object : GridLayoutManager.SpanSizeLookup() { - override fun getSpanSize(position: Int): Int { - if (libraryLayout == 0) return 1 - val item = this@LibraryController.adapter.getItem(position) - return if (item is LibraryHeaderItem) recycler.manager.spanCount - else if (item is LibraryItem && item.manga.isBlank()) recycler.manager.spanCount - else 1 - } - }) - recycler.setHasFixedSize(true) - recycler.adapter = adapter - adapter.fastScroller = fast_scroller - recycler.addOnScrollListener(scrollListener) - - val tv = TypedValue() - activity!!.theme.resolveAttribute(R.attr.actionBarTintColor, tv, true) - - scrollViewWith(recycler, swipeRefreshLayout = swipe_refresh) { insets -> - fast_scroller.updateLayoutParams { - topMargin = insets.systemWindowInsetTop - } - } - - swipe_refresh.setOnRefreshListener { - swipe_refresh.isRefreshing = false - if (!LibraryUpdateService.isRunning()) { - when { - presenter.allCategories.size <= 1 -> updateLibrary() - preferences.updateOnRefresh().getOrDefault() == -1 -> { - MaterialDialog(activity!!).title(R.string.what_should_update) - .negativeButton(android.R.string.cancel) - .listItemsSingleChoice(items = listOf( - view.context.getString( - R.string.top_category, presenter.allCategories.first().name - ), view.context.getString( - R.string.categories_in_global_update - ) - ), selection = { _, index, _ -> - preferences.updateOnRefresh().set(index) - when (index) { - 0 -> updateLibrary(presenter.allCategories.first()) - else -> updateLibrary() - } - }) - .positiveButton(R.string.action_update) - .show() - } - else -> { - when (preferences.updateOnRefresh().getOrDefault()) { - 0 -> updateLibrary(presenter.allCategories.first()) - else -> updateLibrary() - } - } - } - } - } - } - private fun updateLibrary(category: Category? = null) { val view = view ?: return LibraryUpdateService.start(view.context, category) @@ -459,21 +550,24 @@ class LibraryController( private fun setRecyclerLayout() { if (libraryLayout == 0) { recycler.spanCount = 1 - recycler.updatePaddingRelative(start = 0, end = 0) + recycler.updatePaddingRelative(start = 0, end = if (!autoHideScroller) 10.dpToPx else 0) } else { recycler.columnWidth = (90 + (preferences.gridSize().getOrDefault() * 30)).dpToPx - recycler.updatePaddingRelative(start = 5.dpToPx, end = 5.dpToPx) + recycler.updatePaddingRelative( + start = (if (!autoHideScroller) 2 else 5).dpToPx, + end = (if (!autoHideScroller) 12 else 5).dpToPx + ) } } override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) { super.onChangeStarted(handler, type) if (type.isEnter) { - if (presenter.categories.size > 1) { + /*if (presenter.categories.size > 1) { activity?.toolbar?.showSpinner() } else { activity?.toolbar?.removeSpinner() - } + }*/ presenter.getLibrary() DownloadService.callListeners() LibraryUpdateService.setListener(this) @@ -521,8 +615,10 @@ class LibraryController( ) } adapter.setItems(mangaMap) + singleCategory = presenter.categories.size <= 1 - val isCurrentController = router?.backstack?.lastOrNull()?.controller() == this + fast_scroller.translationX = 0f + hideScroller() setTitle() updateScroll = false @@ -537,21 +633,21 @@ class LibraryController( } adapter.isLongPressDragEnabled = canDrag() - val popupMenu = if (presenter.categories.size > 1 && isCurrentController) { + /*val popupMenu = if (presenter.categories.size > 1 && isCurrentController) { activity?.toolbar?.showSpinner() } else { activity?.toolbar?.removeSpinner() null - } + }*/ - presenter.categories.forEach { category -> + /*presenter.categories.forEach { category -> popupMenu?.menu?.add(0, category.order, max(0, category.order), category.name) } popupMenu?.setOnMenuItemClickListener { item -> scrollToHeader(item.itemId) true - } + }*/ } private fun scrollToHeader(pos: Int) { @@ -736,6 +832,7 @@ class LibraryController( override fun onActionStateChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) { val position = viewHolder?.adapterPosition ?: return + swipe_refresh.isEnabled = actionState != ItemTouchHelper.ACTION_STATE_DRAG if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) { isDragging = true activity?.appbar?.y = 0f @@ -995,9 +1092,6 @@ class LibraryController( searchView.clearFocus() } - // Mutate the filter icon because it needs to be tinted and the resource is shared. - menu.findItem(R.id.action_library_filter).icon.mutate() - setOnQueryTextChangeListener(searchView) { search(it) } searchItem.fixExpand(onExpand = { invalidateMenuOnExpand() }) } @@ -1043,8 +1137,6 @@ class LibraryController( override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean { mode.menuInflater.inflate(R.menu.library_selection, menu) - val selectItem = menu.findItem(R.id.action_select_all) - selectItem.isVisible = false return true } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt index fe9b438fdf..da6845dd12 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt @@ -47,7 +47,7 @@ class LibraryItem( val parent = adapter.recyclerView return if (parent is AutofitRecyclerView) { val libraryLayout = libraryLayout.getOrDefault() - val isFixedSize = fixedSize.getOrDefault() + val isFixedSize = true // fixedSize.getOrDefault() if (libraryLayout == 0 || manga.isBlank()) { LibraryListHolder(view, adapter as LibraryCategoryAdapter) } else { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt index cb49102167..a9f2354b91 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt @@ -346,7 +346,7 @@ class LibraryPresenter( manga1TotalChapter.compareTo(mange2TotalChapter) } sortingMode == LibrarySort.DATE_ADDED -> { - i1.manga.date_added.compareTo(i2.manga.date_added) + i2.manga.date_added.compareTo(i1.manga.date_added) } else -> 0 } @@ -421,13 +421,13 @@ class LibraryPresenter( i2.chapterCount = totalChapters!![i2.manga.id!!] ?: 0 manga1TotalChapter.compareTo(mange2TotalChapter) } - LibrarySort.DATE_ADDED -> i1.manga.date_added.compareTo(i2.manga.date_added) + LibrarySort.DATE_ADDED -> i2.manga.date_added.compareTo(i1.manga.date_added) else -> sortAlphabetical(i1, i2) } if (!category.isAscending()) sort *= -1 sort } - category?.mangaOrder?.isEmpty() == false -> { + category.mangaOrder.isNotEmpty() -> { val order = category.mangaOrder val index1 = order.indexOf(i1.manga.id!!) val index2 = order.indexOf(i2.manga.id!!) @@ -441,7 +441,7 @@ class LibraryPresenter( else -> 0 } if (compare == 0) { - if (category?.isAscending() != false) sortAlphabetical(i1, i2) + if (category.isAscending()) sortAlphabetical(i1, i2) else sortAlphabetical(i2, i1) } else compare } else { @@ -560,7 +560,7 @@ class LibraryPresenter( } } - suspend fun updateView( + private suspend fun updateView( categories: List, mangaMap: LibraryMap, freshStart: Boolean = diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/filter/FilterBottomSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/filter/FilterBottomSheet.kt index af8c6abc89..b89eb9a1b1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/filter/FilterBottomSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/filter/FilterBottomSheet.kt @@ -98,6 +98,9 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri if (isChecked) onGroupClicked(ACTION_HIDE_FILTER_TIP) } + view_options.setOnClickListener { + onGroupClicked(ACTION_DISPLAY) + } val activeFilters = hasActiveFiltersFromPref() sheetBehavior?.isHideable = !activeFilters @@ -334,6 +337,7 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri const val ACTION_REFRESH = 0 const val ACTION_FILTER = 1 const val ACTION_HIDE_FILTER_TIP = 2 + const val ACTION_DISPLAY = 3 var FILTER_TRACKER = "" private set } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaHeaderHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaHeaderHolder.kt index d616cc597a..015ab6c38f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaHeaderHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaHeaderHolder.kt @@ -21,6 +21,7 @@ import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.view.gone import eu.kanade.tachiyomi.util.view.invisible +import eu.kanade.tachiyomi.util.view.resetStrokeColor import eu.kanade.tachiyomi.util.view.updateLayoutParams import eu.kanade.tachiyomi.util.view.visible import eu.kanade.tachiyomi.util.view.visibleIf @@ -233,13 +234,7 @@ class MangaHeaderHolder( ) strokeColor = ColorStateList.valueOf(Color.TRANSPARENT) } else { - strokeColor = ColorStateList.valueOf( - ColorUtils.setAlphaComponent( - itemView.context.getResourceColor( - R.attr.colorOnSurface - ), 31 - ) - ) + resetStrokeColor() backgroundTintList = ContextCompat.getColorStateList(context, android.R.color.transparent) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsController.kt index 0d561d0b8c..ab0289c12a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsController.kt @@ -11,6 +11,7 @@ import android.view.ViewGroup import androidx.appcompat.widget.SearchView import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView import com.bluelinelabs.conductor.Controller import com.bluelinelabs.conductor.ControllerChangeHandler import com.bluelinelabs.conductor.ControllerChangeType @@ -51,6 +52,7 @@ import kotlin.math.max class RecentsController(bundle: Bundle? = null) : BaseController(bundle), RecentMangaAdapter.RecentsInterface, FlexibleAdapter.OnItemClickListener, + FlexibleAdapter.OnItemMoveListener, RootSearchInterface { init { @@ -101,7 +103,7 @@ class RecentsController(bundle: Bundle? = null) : BaseController(bundle), val array = view.context.obtainStyledAttributes(attrsArray) val appBarHeight = array.getDimensionPixelSize(0, 0) array.recycle() - scrollViewWith(recycler, skipFirstSnap = true) { + scrollViewWith(recycler, skipFirstSnap = true, swipeRefreshLayout = swipe_refresh) { headerHeight = it.systemWindowInsetTop + appBarHeight } @@ -154,12 +156,32 @@ class RecentsController(bundle: Bundle? = null) : BaseController(bundle), } }) + swipe_refresh.setOnRefreshListener { + if (!LibraryUpdateService.isRunning()) { + LibraryUpdateService.start(view.context) + snack = view.snack(R.string.updating_library) { + anchorView = (activity as? MainActivity)?.bottom_nav + } + } + } + if (showingDownloads) { dl_bottom_sheet.sheetBehavior?.state = BottomSheetBehavior.STATE_EXPANDED } setPadding(dl_bottom_sheet.sheetBehavior?.isHideable == true) } + override fun onItemMove(fromPosition: Int, toPosition: Int) { + } + + override fun shouldMoveItem(fromPosition: Int, toPosition: Int): Boolean { + return true + } + + override fun onActionStateChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) { + swipe_refresh.isEnabled = actionState != ItemTouchHelper.ACTION_STATE_SWIPE + } + override fun handleRootBack(): Boolean { if (dl_bottom_sheet.sheetBehavior?.state == BottomSheetBehavior.STATE_EXPANDED) { dl_bottom_sheet.dismiss() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsSourcesController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsSourcesController.kt index 057610d0b3..61e1505e8a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsSourcesController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsSourcesController.kt @@ -39,7 +39,7 @@ class SettingsSourcesController : SettingsController(), private var sorting = SourcesSort.Alpha override fun setupPreferenceScreen(screen: PreferenceScreen) = with(screen) { - titleRes = R.string.pref_category_sources + titleRes = R.string.action_filter sorting = SourcesSort.from(preferences.sourceSorting().getOrDefault()) ?: SourcesSort.Alpha activity?.invalidateOptionsMenu() // Get the list of active language codes. diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt index 602bf4a067..50edcb5418 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt @@ -4,6 +4,7 @@ package eu.kanade.tachiyomi.util.view import android.app.Activity import android.content.Context +import android.content.res.ColorStateList import android.content.res.Configuration import android.graphics.Color import android.graphics.Point @@ -20,6 +21,7 @@ import android.widget.ImageView import android.widget.TextView import androidx.annotation.Px import androidx.appcompat.widget.SearchView +import androidx.core.graphics.ColorUtils import androidx.core.math.MathUtils.clamp import androidx.core.view.ViewCompat import androidx.recyclerview.widget.LinearLayoutManager @@ -29,6 +31,7 @@ import com.amulyakhare.textdrawable.TextDrawable import com.amulyakhare.textdrawable.util.ColorGenerator import com.bluelinelabs.conductor.Controller import com.google.android.material.bottomsheet.BottomSheetDialog +import com.google.android.material.button.MaterialButton import com.google.android.material.snackbar.Snackbar import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.preference.PreferencesHelper @@ -436,3 +439,13 @@ fun setBottomEdge(view: View, activity: Activity) { bottomMargin = marginB + activity.window.decorView.rootWindowInsets.systemWindowInsetBottom } } + +fun MaterialButton.resetStrokeColor() { + strokeColor = ColorStateList.valueOf( + ColorUtils.setAlphaComponent( + context.getResourceColor( + R.attr.colorOnSurface + ), 31 + ) + ) +} diff --git a/app/src/main/res/drawable/fast_scroll_background.xml b/app/src/main/res/drawable/fast_scroll_background.xml new file mode 100644 index 0000000000..a7d6ae84cd --- /dev/null +++ b/app/src/main/res/drawable/fast_scroll_background.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_drag_handle_black_24dp.xml b/app/src/main/res/drawable/ic_drag_handle_black_24dp.xml new file mode 100644 index 0000000000..94417b8d78 --- /dev/null +++ b/app/src/main/res/drawable/ic_drag_handle_black_24dp.xml @@ -0,0 +1,6 @@ + + + diff --git a/app/src/main/res/drawable/round_textview_background.xml b/app/src/main/res/drawable/round_textview_background.xml index 867628b7f3..698ec6ff0e 100644 --- a/app/src/main/res/drawable/round_textview_background.xml +++ b/app/src/main/res/drawable/round_textview_background.xml @@ -1,7 +1,7 @@ - + diff --git a/app/src/main/res/layout/categories_item.xml b/app/src/main/res/layout/categories_item.xml index 775f76ca42..33894e2bfc 100644 --- a/app/src/main/res/layout/categories_item.xml +++ b/app/src/main/res/layout/categories_item.xml @@ -17,7 +17,7 @@ app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" - app:srcCompat="@drawable/ic_reorder_grey_24dp" /> + app:srcCompat="@drawable/ic_drag_handle_black_24dp" /> @@ -32,23 +32,25 @@ android:id="@+id/display_group" android:layout_width="match_parent" android:layout_height="wrap_content" - android:orientation="vertical" + android:orientation="horizontal" android:paddingStart="12dp" android:paddingEnd="12dp"> @@ -90,6 +92,7 @@ + android:text="@string/action_display_unread_badge" /> + + + + + + + + + + + - - - - - - - - + android:layout_marginStart="12dp" + android:layout_marginEnd="12dp" + android:text="@string/auto_hide_category_seeker" /> + @@ -144,10 +175,10 @@ android:id="@+id/close_button" android:layout_width="32dp" android:layout_height="32dp" - android:background="@drawable/round_ripple" android:layout_gravity="end" android:layout_marginTop="12dp" android:layout_marginEnd="12dp" + android:background="@drawable/round_ripple" android:clickable="true" android:contentDescription="@string/action_close" android:focusable="true" diff --git a/app/src/main/res/layout/download_item.xml b/app/src/main/res/layout/download_item.xml index a4697ac1be..3091f2f8b6 100644 --- a/app/src/main/res/layout/download_item.xml +++ b/app/src/main/res/layout/download_item.xml @@ -57,7 +57,7 @@ app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" - app:srcCompat="@drawable/ic_reorder_grey_24dp" /> + app:srcCompat="@drawable/ic_drag_handle_black_24dp" /> -