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" /> -