From 15e9aaab26954d97349b0027102fe11bd8dd9bb7 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Thu, 18 Jan 2024 11:47:58 +0200 Subject: [PATCH 01/14] Fix pages list scrolling (cherry picked from commit a1120ea7093f85eb2c7dad1222248c0f202227d2) --- .../kotlin/org/koitharu/kotatsu/KotatsuApp.kt | 2 +- .../list/fastscroll/FastScrollRecyclerView.kt | 25 +++++++++++++++---- .../kotatsu/details/ui/DetailsActivity.kt | 2 +- .../ui/pager/chapters/ChaptersFragment.kt | 12 +++++++++ .../details/ui/pager/pages/PagesFragment.kt | 12 +++++++++ .../ui/list/FavouritesListFragment.kt | 7 ++++++ 6 files changed, 53 insertions(+), 7 deletions(-) diff --git a/app/src/debug/kotlin/org/koitharu/kotatsu/KotatsuApp.kt b/app/src/debug/kotlin/org/koitharu/kotatsu/KotatsuApp.kt index 69b456c85..93593fbda 100644 --- a/app/src/debug/kotlin/org/koitharu/kotatsu/KotatsuApp.kt +++ b/app/src/debug/kotlin/org/koitharu/kotatsu/KotatsuApp.kt @@ -36,7 +36,7 @@ class KotatsuApp : BaseApp() { FragmentStrictMode.defaultPolicy = FragmentStrictMode.Policy.Builder() .penaltyDeath() .detectFragmentReuse() - // .detectWrongFragmentContainer() FIXME: migrate to ViewPager2 + .detectWrongFragmentContainer() .detectRetainInstanceUsage() .detectSetUserVisibleHint() .detectFragmentTagUsage() diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/fastscroll/FastScrollRecyclerView.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/fastscroll/FastScrollRecyclerView.kt index 6dfb0aae5..4374dc6d2 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/fastscroll/FastScrollRecyclerView.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/fastscroll/FastScrollRecyclerView.kt @@ -17,7 +17,16 @@ class FastScrollRecyclerView @JvmOverloads constructor( ) : RecyclerView(context, attrs, defStyleAttr) { val fastScroller = FastScroller(context, attrs) - private var applyViewPager2Fix = false + var isVP2BugWorkaroundEnabled = false + set(value) { + field = value + if (value && isAttachedToWindow) { + checkIfInVP2() + } else if (!value) { + applyVP2Workaround = false + } + } + private var applyVP2Workaround = false var isFastScrollerEnabled: Boolean = true set(value) { @@ -46,23 +55,29 @@ class FastScrollRecyclerView @JvmOverloads constructor( override fun onAttachedToWindow() { super.onAttachedToWindow() fastScroller.attachRecyclerView(this) - applyViewPager2Fix = ancestors.any { it is ViewPager2 } == true + if (isVP2BugWorkaroundEnabled) { + checkIfInVP2() + } } override fun onDetachedFromWindow() { fastScroller.detachRecyclerView() super.onDetachedFromWindow() - applyViewPager2Fix = false + applyVP2Workaround = false } override fun isLayoutRequested(): Boolean { - return if (applyViewPager2Fix) false else super.isLayoutRequested() + return if (applyVP2Workaround) false else super.isLayoutRequested() } override fun requestLayout() { super.requestLayout() - if (applyViewPager2Fix && parent?.isLayoutRequested == true) { + if (applyVP2Workaround && parent?.isLayoutRequested == true) { parent?.requestLayout() } } + + private fun checkIfInVP2() { + applyVP2Workaround = ancestors.any { it is ViewPager2 } == true + } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsActivity.kt index 125185ea4..35f381102 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsActivity.kt @@ -363,8 +363,8 @@ class DetailsActivity : } private fun initPager() { - viewBinding.pager.recyclerView?.isNestedScrollingEnabled = false val adapter = DetailsPagerAdapter(this) + viewBinding.pager.recyclerView?.isNestedScrollingEnabled = false viewBinding.pager.offscreenPageLimit = 1 viewBinding.pager.adapter = adapter TabLayoutMediator(viewBinding.tabs, viewBinding.pager, adapter).attach() diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/chapters/ChaptersFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/chapters/ChaptersFragment.kt index 768fb7201..c78196d1b 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/chapters/ChaptersFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/chapters/ChaptersFragment.kt @@ -59,6 +59,7 @@ class ChaptersFragment : with(binding.recyclerViewChapters) { checkNotNull(selectionController).attachToRecyclerView(this) setHasFixedSize(true) + isNestedScrollingEnabled = false adapter = chaptersAdapter } viewModel.isLoading.observe(viewLifecycleOwner, this::onLoadingStateChanged) @@ -83,6 +84,17 @@ class ChaptersFragment : super.onDestroyView() } + override fun onPause() { + // required for BottomSheetBehavior + requireViewBinding().recyclerViewChapters.isNestedScrollingEnabled = false + super.onPause() + } + + override fun onResume() { + requireViewBinding().recyclerViewChapters.isNestedScrollingEnabled = true + super.onResume() + } + override fun onItemClick(item: ChapterListItem, view: View) { if (selectionController?.onItemClick(item.chapter.id) == true) { return diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/pages/PagesFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/pages/PagesFragment.kt index f6d0ffa70..1dd7d6ee8 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/pages/PagesFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/pages/PagesFragment.kt @@ -88,6 +88,7 @@ class PagesFragment : addItemDecoration(TypedListSpacingDecoration(context, false)) adapter = thumbnailsAdapter setHasFixedSize(true) + isNestedScrollingEnabled = false addOnLayoutChangeListener(spanResolver) spanResolver?.setGridSize(settings.gridSize / 100f, this) addOnScrollListener(ScrollListener().also { scrollListener = it }) @@ -112,6 +113,17 @@ class PagesFragment : super.onDestroyView() } + override fun onPause() { + // required for BottomSheetBehavior + requireViewBinding().recyclerView.isNestedScrollingEnabled = false + super.onPause() + } + + override fun onResume() { + requireViewBinding().recyclerView.isNestedScrollingEnabled = true + super.onResume() + } + override fun onWindowInsetsChanged(insets: Insets) = Unit override fun onItemClick(item: PageThumbnail, view: View) { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/list/FavouritesListFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/list/FavouritesListFragment.kt index 43b7dd4ff..7caf9e068 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/list/FavouritesListFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/list/FavouritesListFragment.kt @@ -1,5 +1,6 @@ package org.koitharu.kotatsu.favourites.ui.list +import android.os.Bundle import android.view.Menu import android.view.MenuItem import android.view.View @@ -12,6 +13,7 @@ import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.ui.list.ListSelectionController import org.koitharu.kotatsu.core.util.ext.sortedByOrdinal import org.koitharu.kotatsu.core.util.ext.withArgs +import org.koitharu.kotatsu.databinding.FragmentListBinding import org.koitharu.kotatsu.list.domain.ListSortOrder import org.koitharu.kotatsu.list.ui.MangaListFragment import org.koitharu.kotatsu.parsers.model.MangaSource @@ -26,6 +28,11 @@ class FavouritesListFragment : MangaListFragment(), PopupMenu.OnMenuItemClickLis val categoryId get() = viewModel.categoryId + override fun onViewBindingCreated(binding: FragmentListBinding, savedInstanceState: Bundle?) { + super.onViewBindingCreated(binding, savedInstanceState) + binding.recyclerView.isVP2BugWorkaroundEnabled = true + } + override fun onScrolledToEnd() = Unit override fun onFilterClick(view: View?) { From 971c683746b230959bb77ef7b6fab801b2799d78 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Thu, 18 Jan 2024 16:16:12 +0200 Subject: [PATCH 02/14] Show all favorites on categories screen (cherry picked from commit 2a5300a634b7c62d11a820e2f1c76ef2ddd9538e) --- .../kotatsu/favourites/data/FavouritesDao.kt | 22 +++- .../favourites/domain/FavouritesRepository.kt | 12 +- .../categories/FavouriteCategoriesActivity.kt | 16 ++- .../FavouriteCategoriesListListener.kt | 4 +- .../FavouritesCategoriesViewModel.kt | 69 ++++++++--- .../adapter/AllCategoriesListModel.kt | 15 +++ .../categories/adapter/CategoriesAdapter.kt | 1 + .../ui/categories/adapter/CategoryAD.kt | 66 +++++++++++ .../main/res/layout/item_categories_all.xml | 111 ++++++++++++++++++ 9 files changed, 290 insertions(+), 26 deletions(-) create mode 100644 app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/adapter/AllCategoriesListModel.kt create mode 100644 app/src/main/res/layout/item_categories_all.xml diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/data/FavouritesDao.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/data/FavouritesDao.kt index c4d8e8ed4..ba7c02b18 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/data/FavouritesDao.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/data/FavouritesDao.kt @@ -82,19 +82,35 @@ abstract class FavouritesDao { ) abstract suspend fun findAllManga(categoryId: Int): List - suspend fun findCovers(categoryId: Long, order: ListSortOrder): List { + suspend fun findCovers(categoryId: Long, order: ListSortOrder, limit: Int): List { val orderBy = getOrderBy(order) @Language("RoomSql") val query = SimpleSQLiteQuery( "SELECT manga.cover_url AS url, manga.source AS source FROM favourites " + "LEFT JOIN manga ON favourites.manga_id = manga.manga_id " + - "WHERE favourites.category_id = ? AND deleted_at = 0 ORDER BY $orderBy", - arrayOf(categoryId), + "WHERE favourites.category_id = ? AND deleted_at = 0 ORDER BY $orderBy LIMIT ?", + arrayOf(categoryId, limit), ) return findCoversImpl(query) } + suspend fun findCovers(order: ListSortOrder, limit: Int): List { + val orderBy = getOrderBy(order) + + @Language("RoomSql") + val query = SimpleSQLiteQuery( + "SELECT manga.cover_url AS url, manga.source AS source FROM favourites " + + "LEFT JOIN manga ON favourites.manga_id = manga.manga_id " + + "WHERE deleted_at = 0 GROUP BY manga.manga_id ORDER BY $orderBy LIMIT ?", + arrayOf(limit), + ) + return findCoversImpl(query) + } + + @Query("SELECT COUNT(DISTINCT manga_id) FROM favourites WHERE deleted_at = 0") + abstract fun observeMangaCount(): Flow + @Query("SELECT * FROM manga WHERE manga_id IN (SELECT manga_id FROM favourites WHERE deleted_at = 0)") abstract suspend fun findAllManga(): List diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/domain/FavouritesRepository.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/domain/FavouritesRepository.kt index 5b5fa094f..b3e4ec4f4 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/domain/FavouritesRepository.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/domain/FavouritesRepository.kt @@ -60,6 +60,11 @@ class FavouritesRepository @Inject constructor( .flatMapLatest { order -> observeAll(categoryId, order) } } + fun observeMangaCount(): Flow { + return db.getFavouritesDao().observeMangaCount() + .distinctUntilChanged() + } + fun observeCategories(): Flow> { return db.getFavouriteCategoriesDao().observeAll().mapItems { it.toFavouriteCategory() @@ -72,7 +77,7 @@ class FavouritesRepository @Inject constructor( }.distinctUntilChanged() } - fun observeCategoriesWithCovers(): Flow>> { + fun observeCategoriesWithCovers(coversLimit: Int): Flow>> { return db.getFavouriteCategoriesDao().observeAll() .map { db.withTransaction { @@ -82,6 +87,7 @@ class FavouritesRepository @Inject constructor( res[cat] = db.getFavouritesDao().findCovers( categoryId = cat.id, order = cat.order, + limit = coversLimit, ) } res @@ -89,6 +95,10 @@ class FavouritesRepository @Inject constructor( } } + suspend fun getAllFavoritesCovers(order: ListSortOrder, limit: Int): List { + return db.getFavouritesDao().findCovers(order, limit) + } + fun observeCategory(id: Long): Flow { return db.getFavouriteCategoriesDao().observe(id) .map { it?.toFavouriteCategory() } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/FavouriteCategoriesActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/FavouriteCategoriesActivity.kt index 5e1f0982f..534092ebd 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/FavouriteCategoriesActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/FavouriteCategoriesActivity.kt @@ -76,7 +76,13 @@ class FavouriteCategoriesActivity : } } - override fun onItemClick(item: FavouriteCategory, view: View) { + override fun onItemClick(item: FavouriteCategory?, view: View) { + if (item == null) { + if (selectionController.count == 0) { + startActivity(FavouritesActivity.newIntent(view.context)) + } + return + } if (selectionController.onItemClick(item.id)) { return } @@ -92,8 +98,12 @@ class FavouriteCategoriesActivity : startActivity(intent) } - override fun onItemLongClick(item: FavouriteCategory, view: View): Boolean { - return selectionController.onItemLongClick(item.id) + override fun onItemLongClick(item: FavouriteCategory?, view: View): Boolean { + return item != null && selectionController.onItemLongClick(item.id) + } + + override fun onShowAllClick(isChecked: Boolean) { + viewModel.setAllCategoriesVisible(isChecked) } override fun onDragHandleTouch(holder: RecyclerView.ViewHolder): Boolean { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/FavouriteCategoriesListListener.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/FavouriteCategoriesListListener.kt index e778d42f4..736a8f766 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/FavouriteCategoriesListListener.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/FavouriteCategoriesListListener.kt @@ -5,9 +5,11 @@ import androidx.recyclerview.widget.RecyclerView import org.koitharu.kotatsu.core.model.FavouriteCategory import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener -interface FavouriteCategoriesListListener : OnListItemClickListener { +interface FavouriteCategoriesListListener : OnListItemClickListener { fun onDragHandleTouch(holder: RecyclerView.ViewHolder): Boolean fun onEditClick(item: FavouriteCategory, view: View) + + fun onShowAllClick(isChecked: Boolean) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/FavouritesCategoriesViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/FavouritesCategoriesViewModel.kt index 27fa877ee..4ba9720dc 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/FavouritesCategoriesViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/FavouritesCategoriesViewModel.kt @@ -5,17 +5,21 @@ import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.cancelAndJoin +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.plus import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.model.FavouriteCategory import org.koitharu.kotatsu.core.prefs.AppSettings +import org.koitharu.kotatsu.core.prefs.observeAsFlow import org.koitharu.kotatsu.core.ui.BaseViewModel import org.koitharu.kotatsu.core.util.ext.requireValue import org.koitharu.kotatsu.favourites.domain.FavouritesRepository import org.koitharu.kotatsu.favourites.domain.model.Cover +import org.koitharu.kotatsu.favourites.ui.categories.adapter.AllCategoriesListModel import org.koitharu.kotatsu.favourites.ui.categories.adapter.CategoryListModel import org.koitharu.kotatsu.list.ui.model.EmptyState import org.koitharu.kotatsu.list.ui.model.ListModel @@ -30,9 +34,13 @@ class FavouritesCategoriesViewModel @Inject constructor( private var commitJob: Job? = null - val content = repository.observeCategoriesWithCovers() - .map { it.toUiList() } - .stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(LoadingState)) + val content = combine( + repository.observeCategoriesWithCovers(coversLimit = 3), + observeAllCategories(), + settings.observeAsFlow(AppSettings.KEY_ALL_FAVOURITES_VISIBLE) { isAllFavouritesVisible }, + ) { cats, all, showAll -> + cats.toUiList(all, showAll) + }.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(LoadingState)) fun deleteCategories(ids: Set) { launchJob(Dispatchers.Default) { @@ -74,21 +82,46 @@ class FavouritesCategoriesViewModel @Inject constructor( } } - private fun Map>.toUiList(): List = map { (category, covers) -> - CategoryListModel( - mangaCount = covers.size, - covers = covers.take(3), - category = category, - isTrackerEnabled = settings.isTrackerEnabled && AppSettings.TRACK_FAVOURITES in settings.trackSources, - ) - }.ifEmpty { - listOf( - EmptyState( - icon = R.drawable.ic_empty_favourites, - textPrimary = R.string.text_empty_holder_primary, - textSecondary = R.string.empty_favourite_categories, - actionStringRes = 0, + private fun Map>.toUiList( + allFavorites: Pair>, + showAll: Boolean + ): List { + if (isEmpty()) { + return listOf( + EmptyState( + icon = R.drawable.ic_empty_favourites, + textPrimary = R.string.text_empty_holder_primary, + textSecondary = R.string.empty_favourite_categories, + actionStringRes = 0, + ), + ) + } + val result = ArrayList(size + 1) + result.add( + AllCategoriesListModel( + mangaCount = allFavorites.first, + covers = allFavorites.second, + isVisible = showAll, ), ) + mapTo(result) { (category, covers) -> + CategoryListModel( + mangaCount = covers.size, + covers = covers.take(3), + category = category, + isTrackerEnabled = settings.isTrackerEnabled && AppSettings.TRACK_FAVOURITES in settings.trackSources, + ) + } + return result + } + + private fun observeAllCategories(): Flow>> { + return settings.observeAsFlow(AppSettings.KEY_FAVORITES_ORDER) { + allFavoritesSortOrder + }.mapLatest { order -> + repository.getAllFavoritesCovers(order, limit = 3) + }.combine(repository.observeMangaCount()) { covers, count -> + count to covers + } } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/adapter/AllCategoriesListModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/adapter/AllCategoriesListModel.kt new file mode 100644 index 000000000..c3f5cd47b --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/adapter/AllCategoriesListModel.kt @@ -0,0 +1,15 @@ +package org.koitharu.kotatsu.favourites.ui.categories.adapter + +import org.koitharu.kotatsu.favourites.domain.model.Cover +import org.koitharu.kotatsu.list.ui.model.ListModel + +data class AllCategoriesListModel( + val mangaCount: Int, + val covers: List, + val isVisible: Boolean, +) : ListModel { + + override fun areItemsTheSame(other: ListModel): Boolean { + return other is AllCategoriesListModel + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/adapter/CategoriesAdapter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/adapter/CategoriesAdapter.kt index 43658d3a3..8569744cd 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/adapter/CategoriesAdapter.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/adapter/CategoriesAdapter.kt @@ -19,6 +19,7 @@ class CategoriesAdapter( init { addDelegate(ListItemType.CATEGORY_LARGE, categoryAD(coil, lifecycleOwner, onItemClickListener)) + addDelegate(ListItemType.NAV_ITEM, allCategoriesAD(coil, lifecycleOwner, onItemClickListener)) addDelegate(ListItemType.STATE_EMPTY, emptyStateListAD(coil, lifecycleOwner, listListener)) addDelegate(ListItemType.STATE_LOADING, loadingStateAD()) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/adapter/CategoryAD.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/adapter/CategoryAD.kt index 253827e88..1e0ae79b7 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/adapter/CategoryAD.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/adapter/CategoryAD.kt @@ -21,6 +21,7 @@ import org.koitharu.kotatsu.core.util.ext.getAnimationDuration import org.koitharu.kotatsu.core.util.ext.getThemeColor import org.koitharu.kotatsu.core.util.ext.newImageRequest import org.koitharu.kotatsu.core.util.ext.source +import org.koitharu.kotatsu.databinding.ItemCategoriesAllBinding import org.koitharu.kotatsu.databinding.ItemCategoryBinding import org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesListListener import org.koitharu.kotatsu.list.ui.model.ListModel @@ -92,3 +93,68 @@ fun categoryAD( } } } + +fun allCategoriesAD( + coil: ImageLoader, + lifecycleOwner: LifecycleOwner, + clickListener: FavouriteCategoriesListListener, +) = adapterDelegateViewBinding( + { inflater, parent -> ItemCategoriesAllBinding.inflate(inflater, parent, false) }, +) { + val eventListener = OnClickListener { v -> + if (v.id == R.id.imageView_visible) { + clickListener.onShowAllClick(!item.isVisible) + } else { + clickListener.onItemClick(null, v) + } + } + val backgroundColor = context.getThemeColor(android.R.attr.colorBackground) + ImageViewCompat.setImageTintList( + binding.imageViewCover3, + ColorStateList.valueOf(ColorUtils.setAlphaComponent(backgroundColor, 153)), + ) + ImageViewCompat.setImageTintList( + binding.imageViewCover2, + ColorStateList.valueOf(ColorUtils.setAlphaComponent(backgroundColor, 76)), + ) + binding.imageViewCover2.backgroundTintList = + ColorStateList.valueOf(ColorUtils.setAlphaComponent(backgroundColor, 76)) + binding.imageViewCover3.backgroundTintList = + ColorStateList.valueOf(ColorUtils.setAlphaComponent(backgroundColor, 153)) + val fallback = ColorDrawable(Color.TRANSPARENT) + val coverViews = arrayOf(binding.imageViewCover1, binding.imageViewCover2, binding.imageViewCover3) + val crossFadeDuration = context.getAnimationDuration(R.integer.config_defaultAnimTime).toInt() + itemView.setOnClickListener(eventListener) + binding.imageViewVisible.setOnClickListener(eventListener) + + bind { + binding.textViewSubtitle.text = if (item.mangaCount == 0) { + getString(R.string.empty) + } else { + context.resources.getQuantityString( + R.plurals.items, + item.mangaCount, + item.mangaCount, + ) + } + binding.imageViewVisible.setImageResource( + if (item.isVisible) { + R.drawable.ic_eye + } else { + R.drawable.ic_eye_off + }, + ) + repeat(coverViews.size) { i -> + val cover = item.covers.getOrNull(i) + coverViews[i].newImageRequest(lifecycleOwner, cover?.url)?.run { + placeholder(R.drawable.ic_placeholder) + fallback(fallback) + source(cover?.mangaSource) + crossfade(crossFadeDuration * (i + 1)) + error(R.drawable.ic_error_placeholder) + allowRgb565(true) + enqueueWith(coil) + } + } + } +} diff --git a/app/src/main/res/layout/item_categories_all.xml b/app/src/main/res/layout/item_categories_all.xml new file mode 100644 index 000000000..f7825e4c7 --- /dev/null +++ b/app/src/main/res/layout/item_categories_all.xml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + From 91e3d2f5dbe5964d5be2361e84f2ffdf92416e9d Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sat, 20 Jan 2024 09:07:27 +0200 Subject: [PATCH 03/14] Skip unsupported sources in global search (cherry picked from commit d6887e2d75e409eef6a262042f2f4fe03a4856e4) --- .../kotatsu/search/ui/multi/MultiSearchViewModel.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/MultiSearchViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/MultiSearchViewModel.kt index fdf1e1b99..27c2e5001 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/MultiSearchViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/MultiSearchViewModel.kt @@ -113,10 +113,14 @@ class MultiSearchViewModel @Inject constructor( } val semaphore = Semaphore(MAX_PARALLELISM) for (source in sources) { + val repository = mangaRepositoryFactory.create(source) + if (!repository.isSearchSupported) { + continue + } launch { val item = runCatchingCancellable { semaphore.withPermit { - mangaRepositoryFactory.create(source).getList(offset = 0, filter = MangaListFilter.Search(q)) + repository.getList(offset = 0, filter = MangaListFilter.Search(q)) .toUi(ListMode.GRID, extraProvider) } }.fold( From 5990da587cbe8a6dcf7ef551d0dbd9c56a034be2 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sat, 20 Jan 2024 09:08:26 +0200 Subject: [PATCH 04/14] Update supported links domains (cherry picked from commit a98202e15e07c76fa099a2923ef99c76f987df2c) --- app/src/main/AndroidManifest.xml | 693 +++++++++++++++++++++---------- 1 file changed, 479 insertions(+), 214 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 97d9cefbb..84121cf11 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -371,47 +371,62 @@ - - - - - + + + + + + + + + + + + + - + - + - + - + + - + + + + + + + - + - - - + + - - - + + + + @@ -419,109 +434,163 @@ - + + - - - + + - + + + - - + + - + + + + + + + - + + + + + + + + + + + + + + - - + - + + + + + - + - - + + + + + + + + + + - + + - - + + + + + + - + + + + - + + - + + + - + + + - + - + - + + + - + + + + - - - - - + + + + + + + + + @@ -530,307 +599,399 @@ - - - + + + + + + - + - + + - + - + + - - - - + - - - + + + + + + + + + - - - - - + + - - - - + + + - + - + - + - - - + + + + - + - - + + + + + + - + + + + + + + + + + + - + + + - - - - - + + + - - - - + + + + + + - - - + + + + + + + + + + + + + - - + + + - + + + + + + + - - - - + + - - - + - + - - - + + + + - - - + - + + + + + + + - - + + + + + + + + + + + + + + - - - + - + + + - - - - - - + + + + + + + + + + - + + - + + + - + - + - + + - - + + + - + + + + + + + - + - + - + + - - - - + + + + + + - + + + - - - - + + + + + + - + - + + - + + - - + + + - + - + + + - + - + + - + + + + + + + + + - - - - - + + + + + + - + + + - - + + + + - + - + - - + + @@ -841,173 +1002,277 @@ - + + + + + - + - + + - - + + + + - - - + + + + + + - + + + - + + + - - + + - - + + - + + + + + - + + + - + - - + + + + + - + + + + + - + + + + - + + + + + - + + + - - - + + - - - + + + + + + + + + + - + + + - + - + + + + + + + - + + + + + + - + + + + + - + + + + + + + + + + - + + + + + + + + + + - + - - + + + + + + + + - + + + + + - - + + + + - + + + + + + - + + + + + + + - + + + + + + + - - - + From f6a510653e201407846e42ffc2783b537d1d72d7 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sat, 20 Jan 2024 09:16:06 +0200 Subject: [PATCH 05/14] Fix import dialog injection (cherry picked from commit af5716a8cebc5a27aad4eb89952dd3a3af5e7e37) --- .../org/koitharu/kotatsu/local/ui/ImportDialogFragment.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/local/ui/ImportDialogFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/local/ui/ImportDialogFragment.kt index 514b6ad7d..cd40efb5a 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/local/ui/ImportDialogFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/local/ui/ImportDialogFragment.kt @@ -9,6 +9,7 @@ import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts import androidx.fragment.app.FragmentManager import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.ui.AlertDialogFragment import org.koitharu.kotatsu.core.util.ext.tryLaunch @@ -16,6 +17,7 @@ import org.koitharu.kotatsu.databinding.DialogImportBinding import org.koitharu.kotatsu.local.data.LocalStorageManager import javax.inject.Inject +@AndroidEntryPoint class ImportDialogFragment : AlertDialogFragment(), View.OnClickListener { @Inject From 24505444545cc189ecb4025a30538b6422ad5f60 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sat, 20 Jan 2024 09:19:29 +0200 Subject: [PATCH 06/14] Update parsers (cherry picked from commit da2ad40adf68f29e37c753b05ca831495620b1e8) --- app/build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 9362ceff3..6249a6ede 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -16,8 +16,8 @@ android { applicationId 'org.koitharu.kotatsu' minSdk = 21 targetSdk = 34 - versionCode = 614 - versionName = '6.6.4' + versionCode = 615 + versionName = '6.6.5' generatedDensities = [] testInstrumentationRunner 'org.koitharu.kotatsu.HiltTestRunner' ksp { @@ -82,7 +82,7 @@ afterEvaluate { } dependencies { //noinspection GradleDependency - implementation('com.github.KotatsuApp:kotatsu-parsers:8e7d7e0bde') { + implementation('com.github.KotatsuApp:kotatsu-parsers:8852d1e22e') { exclude group: 'org.json', module: 'json' } From a37e8825b0916085ac500eb84f33ab0e667c285c Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sat, 20 Jan 2024 09:31:17 +0200 Subject: [PATCH 07/14] All favorites item change payload --- .../ui/categories/adapter/AllCategoriesListModel.kt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/adapter/AllCategoriesListModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/adapter/AllCategoriesListModel.kt index c3f5cd47b..d1dffc421 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/adapter/AllCategoriesListModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/adapter/AllCategoriesListModel.kt @@ -1,6 +1,7 @@ package org.koitharu.kotatsu.favourites.ui.categories.adapter import org.koitharu.kotatsu.favourites.domain.model.Cover +import org.koitharu.kotatsu.list.ui.ListModelDiffCallback import org.koitharu.kotatsu.list.ui.model.ListModel data class AllCategoriesListModel( @@ -12,4 +13,12 @@ data class AllCategoriesListModel( override fun areItemsTheSame(other: ListModel): Boolean { return other is AllCategoriesListModel } + + override fun getChangePayload(previousState: ListModel): Any? { + return if (previousState is AllCategoriesListModel && previousState.isVisible != isVisible) { + ListModelDiffCallback.PAYLOAD_CHECKED_CHANGED + } else { + super.getChangePayload(previousState) + } + } } From cdd76f723f838319448eb9e4f0fdee45c7f2f4f2 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sat, 20 Jan 2024 15:54:02 +0200 Subject: [PATCH 08/14] Fix favorites counters (cherry picked from commit d56fc674ab8f383325c31138202b5d0d501a8a52) --- .../org/koitharu/kotatsu/favourites/data/FavouritesDao.kt | 6 +++--- .../kotatsu/favourites/domain/FavouritesRepository.kt | 3 +-- .../ui/categories/FavouritesCategoriesViewModel.kt | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/data/FavouritesDao.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/data/FavouritesDao.kt index ba7c02b18..d569ba69a 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/data/FavouritesDao.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/data/FavouritesDao.kt @@ -82,15 +82,15 @@ abstract class FavouritesDao { ) abstract suspend fun findAllManga(categoryId: Int): List - suspend fun findCovers(categoryId: Long, order: ListSortOrder, limit: Int): List { + suspend fun findCovers(categoryId: Long, order: ListSortOrder): List { val orderBy = getOrderBy(order) @Language("RoomSql") val query = SimpleSQLiteQuery( "SELECT manga.cover_url AS url, manga.source AS source FROM favourites " + "LEFT JOIN manga ON favourites.manga_id = manga.manga_id " + - "WHERE favourites.category_id = ? AND deleted_at = 0 ORDER BY $orderBy LIMIT ?", - arrayOf(categoryId, limit), + "WHERE favourites.category_id = ? AND deleted_at = 0 ORDER BY $orderBy", + arrayOf(categoryId), ) return findCoversImpl(query) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/domain/FavouritesRepository.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/domain/FavouritesRepository.kt index b3e4ec4f4..7c6bd3c5b 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/domain/FavouritesRepository.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/domain/FavouritesRepository.kt @@ -77,7 +77,7 @@ class FavouritesRepository @Inject constructor( }.distinctUntilChanged() } - fun observeCategoriesWithCovers(coversLimit: Int): Flow>> { + fun observeCategoriesWithCovers(): Flow>> { return db.getFavouriteCategoriesDao().observeAll() .map { db.withTransaction { @@ -87,7 +87,6 @@ class FavouritesRepository @Inject constructor( res[cat] = db.getFavouritesDao().findCovers( categoryId = cat.id, order = cat.order, - limit = coversLimit, ) } res diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/FavouritesCategoriesViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/FavouritesCategoriesViewModel.kt index 4ba9720dc..4b680f8a8 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/FavouritesCategoriesViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/FavouritesCategoriesViewModel.kt @@ -35,7 +35,7 @@ class FavouritesCategoriesViewModel @Inject constructor( private var commitJob: Job? = null val content = combine( - repository.observeCategoriesWithCovers(coversLimit = 3), + repository.observeCategoriesWithCovers(), observeAllCategories(), settings.observeAsFlow(AppSettings.KEY_ALL_FAVOURITES_VISIBLE) { isAllFavouritesVisible }, ) { cats, all, showAll -> From ec53eb9c709925e238c1e04aca4e61c13b797616 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sun, 21 Jan 2024 10:10:20 +0200 Subject: [PATCH 09/14] Incognito mode indicator in reader (cherry picked from commit db1ddf539ce483092c62d2aad1f8a74a3b4a8cc4) --- .../koitharu/kotatsu/core/util/ext/Theme.kt | 6 +++ .../download/ui/list/DownloadsMenuProvider.kt | 13 ++--- .../categories/CategoriesSelectionCallback.kt | 4 +- .../FavouriteTabPopupMenuProvider.kt | 7 ++- .../kotatsu/history/data/HistoryRepository.kt | 3 +- .../history/ui/HistoryListMenuProvider.kt | 3 +- .../kotatsu/reader/ui/ReaderActivity.kt | 13 ++--- .../reader/ui/ReaderTopMenuProvider.kt | 48 +++++++++++++++++++ .../kotatsu/reader/ui/ReaderViewModel.kt | 6 +++ .../SearchSuggestionMenuProvider.kt | 3 +- .../kotatsu/settings/about/AppUpdateDialog.kt | 7 +-- app/src/main/res/menu/opt_reader_top.xml | 15 ++++-- app/src/main/res/values/strings.xml | 1 + 13 files changed, 94 insertions(+), 35 deletions(-) create mode 100644 app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderTopMenuProvider.kt diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Theme.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Theme.kt index e5b713d8f..0d3a8d738 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Theme.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Theme.kt @@ -8,9 +8,11 @@ import androidx.annotation.AttrRes import androidx.annotation.ColorInt import androidx.annotation.FloatRange import androidx.annotation.Px +import androidx.annotation.StyleRes import androidx.core.content.ContextCompat import androidx.core.content.res.use import androidx.core.graphics.ColorUtils +import com.google.android.material.R as materialR fun Context.getThemeDrawable( @AttrRes resId: Int, @@ -75,3 +77,7 @@ fun TypedArray.getDrawableCompat(context: Context, index: Int): Drawable? { val resId = getResourceId(index, 0) return if (resId != 0) ContextCompat.getDrawable(context, resId) else null } + +@get:StyleRes +val DIALOG_THEME_CENTERED: Int + inline get() = materialR.style.ThemeOverlay_Material3_MaterialAlertDialog_Centered diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadsMenuProvider.kt b/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadsMenuProvider.kt index 3429ee2c9..1c6b6e92b 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadsMenuProvider.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadsMenuProvider.kt @@ -7,6 +7,7 @@ import android.view.MenuItem import androidx.core.view.MenuProvider import com.google.android.material.dialog.MaterialAlertDialogBuilder import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.core.util.ext.DIALOG_THEME_CENTERED import org.koitharu.kotatsu.settings.SettingsActivity class DownloadsMenuProvider( @@ -41,10 +42,8 @@ class DownloadsMenuProvider( } private fun confirmCancelAll() { - MaterialAlertDialogBuilder( - context, - com.google.android.material.R.style.ThemeOverlay_Material3_MaterialAlertDialog_Centered, - ).setTitle(R.string.cancel_all) + MaterialAlertDialogBuilder(context, DIALOG_THEME_CENTERED) + .setTitle(R.string.cancel_all) .setMessage(R.string.cancel_all_downloads_confirm) .setIcon(R.drawable.ic_cancel_multiple) .setNegativeButton(android.R.string.cancel, null) @@ -54,10 +53,8 @@ class DownloadsMenuProvider( } private fun confirmRemoveCompleted() { - MaterialAlertDialogBuilder( - context, - com.google.android.material.R.style.ThemeOverlay_Material3_MaterialAlertDialog_Centered, - ).setTitle(R.string.remove_completed) + MaterialAlertDialogBuilder(context, DIALOG_THEME_CENTERED) + .setTitle(R.string.remove_completed) .setMessage(R.string.remove_completed_downloads_confirm) .setIcon(R.drawable.ic_clear_all) .setNegativeButton(android.R.string.cancel, null) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/CategoriesSelectionCallback.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/CategoriesSelectionCallback.kt index d629424f7..fc529b643 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/CategoriesSelectionCallback.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/CategoriesSelectionCallback.kt @@ -7,7 +7,7 @@ import androidx.recyclerview.widget.RecyclerView import com.google.android.material.dialog.MaterialAlertDialogBuilder import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.ui.list.ListSelectionController -import com.google.android.material.R as materialR +import org.koitharu.kotatsu.core.util.ext.DIALOG_THEME_CENTERED class CategoriesSelectionCallback( private val recyclerView: RecyclerView, @@ -75,7 +75,7 @@ class CategoriesSelectionCallback( private fun confirmDeleteCategories(ids: Set, mode: ActionMode) { val context = recyclerView.context - MaterialAlertDialogBuilder(context, materialR.style.ThemeOverlay_Material3_MaterialAlertDialog_Centered) + MaterialAlertDialogBuilder(context, DIALOG_THEME_CENTERED) .setMessage(R.string.categories_delete_confirm) .setTitle(R.string.remove_category) .setIcon(R.drawable.ic_delete) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouriteTabPopupMenuProvider.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouriteTabPopupMenuProvider.kt index 995d5c616..64a26395a 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouriteTabPopupMenuProvider.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouriteTabPopupMenuProvider.kt @@ -7,6 +7,7 @@ import android.view.MenuItem import androidx.core.view.MenuProvider import com.google.android.material.dialog.MaterialAlertDialogBuilder import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.core.util.ext.DIALOG_THEME_CENTERED import org.koitharu.kotatsu.favourites.ui.categories.edit.FavouritesCategoryEditActivity import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment.Companion.NO_ID @@ -40,10 +41,8 @@ class FavouriteTabPopupMenuProvider( } private fun confirmDelete() { - MaterialAlertDialogBuilder( - context, - com.google.android.material.R.style.ThemeOverlay_Material3_MaterialAlertDialog_Centered, - ).setMessage(R.string.categories_delete_confirm) + MaterialAlertDialogBuilder(context, DIALOG_THEME_CENTERED) + .setMessage(R.string.categories_delete_confirm) .setTitle(R.string.remove_category) .setIcon(R.drawable.ic_delete) .setNegativeButton(android.R.string.cancel, null) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/history/data/HistoryRepository.kt b/app/src/main/kotlin/org/koitharu/kotatsu/history/data/HistoryRepository.kt index 5aae882ae..7a9e5cf5e 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/history/data/HistoryRepository.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/history/data/HistoryRepository.kt @@ -15,6 +15,7 @@ import org.koitharu.kotatsu.core.db.entity.toMangaTags import org.koitharu.kotatsu.core.model.MangaHistory import org.koitharu.kotatsu.core.model.findById import org.koitharu.kotatsu.core.model.isLocal +import org.koitharu.kotatsu.core.model.isNsfw import org.koitharu.kotatsu.core.parser.MangaDataRepository import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.ui.util.ReversibleHandle @@ -161,7 +162,7 @@ class HistoryRepository @Inject constructor( } fun shouldSkip(manga: Manga): Boolean { - return manga.isNsfw && settings.isHistoryExcludeNsfw || settings.isIncognitoModeEnabled + return ((manga.source.isNsfw() || manga.isNsfw) && settings.isHistoryExcludeNsfw) || settings.isIncognitoModeEnabled } fun observeShouldSkip(manga: Manga): Flow { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/history/ui/HistoryListMenuProvider.kt b/app/src/main/kotlin/org/koitharu/kotatsu/history/ui/HistoryListMenuProvider.kt index 08b1f82f4..d2a5758f3 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/history/ui/HistoryListMenuProvider.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/history/ui/HistoryListMenuProvider.kt @@ -8,6 +8,7 @@ import androidx.core.view.MenuProvider import com.google.android.material.dialog.MaterialAlertDialogBuilder import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.ui.dialog.RememberSelectionDialogListener +import org.koitharu.kotatsu.core.util.ext.DIALOG_THEME_CENTERED import java.time.Instant import java.time.LocalDate import java.time.ZoneId @@ -36,7 +37,7 @@ class HistoryListMenuProvider( private fun showClearHistoryDialog() { val selectionListener = RememberSelectionDialogListener(2) - MaterialAlertDialogBuilder(context, materialR.style.ThemeOverlay_Material3_MaterialAlertDialog_Centered) + MaterialAlertDialogBuilder(context, DIALOG_THEME_CENTERED) .setTitle(R.string.clear_history) .setSingleChoiceItems( arrayOf( diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderActivity.kt index bee4837b6..535fe7f29 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderActivity.kt @@ -10,7 +10,6 @@ import android.transition.TransitionManager import android.transition.TransitionSet import android.view.Gravity import android.view.KeyEvent -import android.view.Menu import android.view.MenuItem import android.view.MotionEvent import android.view.View @@ -40,6 +39,7 @@ import org.koitharu.kotatsu.core.parser.MangaIntent import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.ReaderMode import org.koitharu.kotatsu.core.ui.BaseFullscreenActivity +import org.koitharu.kotatsu.core.ui.util.MenuInvalidator import org.koitharu.kotatsu.core.ui.widgets.ZoomControl import org.koitharu.kotatsu.core.util.GridTouchHelper import org.koitharu.kotatsu.core.util.IdlingDetector @@ -140,6 +140,7 @@ class ReaderActivity : viewModel.content.observe(this) { onLoadingStateChanged(viewModel.isLoading.value) } + viewModel.incognitoMode.observe(this, MenuInvalidator(this)) viewModel.isScreenshotsBlockEnabled.observe(this, this::setWindowSecure) viewModel.isKeepScreenOnEnabled.observe(this, this::setKeepScreenOn) viewModel.isInfoBarEnabled.observe(this, ::onReaderBarChanged) @@ -152,6 +153,7 @@ class ReaderActivity : viewModel.isZoomControlsEnabled.observe(this) { viewBinding.zoomControl.isVisible = it } + addMenuProvider(ReaderTopMenuProvider(this, viewModel)) } override fun getParentActivityIntent(): Intent? { @@ -190,21 +192,12 @@ class ReaderActivity : viewBinding.slider.isRtl = mode == ReaderMode.REVERSED } - override fun onCreateOptionsMenu(menu: Menu): Boolean { - menuInflater.inflate(R.menu.opt_reader_top, menu) - return super.onCreateOptionsMenu(menu) - } - override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { R.id.action_settings -> { startActivity(SettingsActivity.newReaderSettingsIntent(this)) } - R.id.action_chapters -> { - ChaptersSheet.show(supportFragmentManager) - } - R.id.action_pages_thumbs -> { val state = viewModel.getCurrentState() ?: return false PagesThumbnailsSheet.show( diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderTopMenuProvider.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderTopMenuProvider.kt new file mode 100644 index 000000000..867ecfc7f --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderTopMenuProvider.kt @@ -0,0 +1,48 @@ +package org.koitharu.kotatsu.reader.ui + +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import androidx.core.view.MenuProvider +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.core.util.ext.DIALOG_THEME_CENTERED + +class ReaderTopMenuProvider( + private val activity: ReaderActivity, + private val viewModel: ReaderViewModel, +) : MenuProvider { + + override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { + menuInflater.inflate(R.menu.opt_reader_top, menu) + } + + override fun onPrepareMenu(menu: Menu) { + menu.findItem(R.id.action_incognito)?.isVisible = viewModel.incognitoMode.value + } + + override fun onMenuItemSelected(menuItem: MenuItem): Boolean { + return when (menuItem.itemId) { + R.id.action_chapters -> { + ChaptersSheet.show(activity.supportFragmentManager) + true + } + + R.id.action_incognito -> { + showIncognitoModeDialog() + true + } + + else -> false + } + } + + private fun showIncognitoModeDialog() { + MaterialAlertDialogBuilder(activity, DIALOG_THEME_CENTERED) + .setIcon(R.drawable.ic_incognito) + .setTitle(R.string.incognito_mode) + .setMessage(R.string.incognito_mode_hint) + .setPositiveButton(R.string.got_it, null) + .show() + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt index 5ce6a348d..03473f94f 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt @@ -96,6 +96,12 @@ class ReaderViewModel @Inject constructor( val onShowToast = MutableEventFlow() val uiState = MutableStateFlow(null) + val incognitoMode = if (isIncognito) { + MutableStateFlow(true) + } else mangaFlow.map { + it != null && historyRepository.shouldSkip(it) + }.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, false) + val content = MutableStateFlow(ReaderContent(emptyList(), null)) val manga: MangaDetails? get() = mangaData.value diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionMenuProvider.kt b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionMenuProvider.kt index 12d58adf9..8483287c1 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionMenuProvider.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionMenuProvider.kt @@ -8,6 +8,7 @@ import androidx.activity.result.ActivityResultLauncher import androidx.core.view.MenuProvider import com.google.android.material.dialog.MaterialAlertDialogBuilder import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.core.util.ext.DIALOG_THEME_CENTERED import org.koitharu.kotatsu.core.util.ext.resolve import org.koitharu.kotatsu.core.util.ext.tryLaunch import com.google.android.material.R as materialR @@ -43,7 +44,7 @@ class SearchSuggestionMenuProvider( } private fun clearSearchHistory() { - MaterialAlertDialogBuilder(context, materialR.style.ThemeOverlay_Material3_MaterialAlertDialog_Centered) + MaterialAlertDialogBuilder(context, DIALOG_THEME_CENTERED) .setTitle(R.string.clear_search_history) .setIcon(R.drawable.ic_clear_all) .setMessage(R.string.text_clear_search_history_prompt) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/settings/about/AppUpdateDialog.kt b/app/src/main/kotlin/org/koitharu/kotatsu/settings/about/AppUpdateDialog.kt index 74bcff08a..a93bc1925 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/settings/about/AppUpdateDialog.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/settings/about/AppUpdateDialog.kt @@ -16,8 +16,8 @@ import io.noties.markwon.Markwon import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.github.AppVersion import org.koitharu.kotatsu.core.util.FileSize +import org.koitharu.kotatsu.core.util.ext.DIALOG_THEME_CENTERED import org.koitharu.kotatsu.core.util.ext.getDisplayMessage -import com.google.android.material.R as materialR class AppUpdateDialog(private val activity: AppCompatActivity) { @@ -43,10 +43,7 @@ class AppUpdateDialog(private val activity: AppCompatActivity) { appendLine() append(Markwon.create(activity).toMarkdown(version.description)) } - MaterialAlertDialogBuilder( - activity, - materialR.style.ThemeOverlay_Material3_MaterialAlertDialog_Centered, - ) + MaterialAlertDialogBuilder(activity, DIALOG_THEME_CENTERED) .setTitle(R.string.app_update_available) .setMessage(message) .setIcon(R.drawable.ic_app_update) diff --git a/app/src/main/res/menu/opt_reader_top.xml b/app/src/main/res/menu/opt_reader_top.xml index c2f02a4d3..fa5c3be7d 100644 --- a/app/src/main/res/menu/opt_reader_top.xml +++ b/app/src/main/res/menu/opt_reader_top.xml @@ -1,13 +1,22 @@ + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + tools:ignore="AlwaysShowAction"> - \ No newline at end of file + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a819a68b7..90ebedeb9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -559,4 +559,5 @@ Mark as completed Mark selected manga as completely read?\n\nWarning: current reading progress will be lost. This category was hidden from the main screen and is accessible through Menu → Manage categories + Your reading progress will not be saved From 850f6c2f3e89b1a05ee844615da61d0f415c784f Mon Sep 17 00:00:00 2001 From: Koitharu Date: Mon, 22 Jan 2024 13:13:14 +0200 Subject: [PATCH 10/14] Fix pages numbers (cherry picked from commit 83cb35fe6e169dd8a4214db377ad9d7972dd47ad) --- .../org/koitharu/kotatsu/reader/ui/pager/standard/PageHolder.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/standard/PageHolder.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/standard/PageHolder.kt index e70d7b950..c4b9d6b80 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/standard/PageHolder.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/standard/PageHolder.kt @@ -42,7 +42,6 @@ open class PageHolder( bindingInfo.buttonRetry.setOnClickListener(this) @Suppress("LeakingThis") bindingInfo.buttonErrorDetails.setOnClickListener(this) - binding.textViewNumber.isVisible = settings.isPagesNumbersEnabled } override fun onResume() { @@ -61,6 +60,7 @@ open class PageHolder( delegate.reload() } binding.ssiv.applyDownsampling(isResumed()) + binding.textViewNumber.isVisible = settings.isPagesNumbersEnabled } @SuppressLint("SetTextI18n") From 5ec2eab6b8dc6f9fb574381183fc8f8c0c5cd6af Mon Sep 17 00:00:00 2001 From: Koitharu Date: Wed, 24 Jan 2024 11:26:29 +0200 Subject: [PATCH 11/14] Fix webtoon scroll dispatching (cherry picked from commit a0a72b1192af695c25fe1aab0fc418fd04c17094) --- .../kotatsu/reader/ui/ReaderViewModel.kt | 11 +++-- .../pager/reversed/ReversedReaderFragment.kt | 3 +- .../ui/pager/standard/PagerReaderFragment.kt | 2 +- .../ui/pager/webtoon/WebtoonReaderFragment.kt | 28 +++++------ .../ui/pager/webtoon/WebtoonRecyclerView.kt | 47 +++++++++++-------- 5 files changed, 50 insertions(+), 41 deletions(-) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt index 03473f94f..899c837ce 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt @@ -262,7 +262,7 @@ class ReaderViewModel @Inject constructor( } @MainThread - fun onCurrentPageChanged(position: Int) { + fun onCurrentPageChanged(lowerPos: Int, upperPos: Int) { val prevJob = stateChangeJob val pages = content.value.pages // capture immediately stateChangeJob = launchJob(Dispatchers.Default) { @@ -271,7 +271,8 @@ class ReaderViewModel @Inject constructor( if (pages.size != content.value.pages.size) { return@launchJob // TODO } - pages.getOrNull(position)?.let { page -> + val centerPos = (lowerPos + upperPos) / 2 + pages.getOrNull(centerPos)?.let { page -> currentState.update { cs -> cs?.copy(chapterId = page.chapterId, page = page.index) } @@ -281,14 +282,14 @@ class ReaderViewModel @Inject constructor( return@launchJob } ensureActive() - if (position >= pages.lastIndex - BOUNDS_PAGE_OFFSET) { + if (upperPos >= pages.lastIndex - BOUNDS_PAGE_OFFSET) { loadPrevNextChapter(pages.last().chapterId, isNext = true) } - if (position <= BOUNDS_PAGE_OFFSET) { + if (lowerPos <= BOUNDS_PAGE_OFFSET) { loadPrevNextChapter(pages.first().chapterId, isNext = false) } if (pageLoader.isPrefetchApplicable()) { - pageLoader.prefetch(pages.trySublist(position + 1, position + PREFETCH_LIMIT)) + pageLoader.prefetch(pages.trySublist(upperPos + 1, upperPos + PREFETCH_LIMIT)) } } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/reversed/ReversedReaderFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/reversed/ReversedReaderFragment.kt index e9ccb68ab..313f2ba16 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/reversed/ReversedReaderFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/reversed/ReversedReaderFragment.kt @@ -172,7 +172,8 @@ class ReversedReaderFragment : BaseReaderFragment } private fun notifyPageChanged(page: Int) { - viewModel.onCurrentPageChanged(reversed(page)) + val pos = reversed(page) + viewModel.onCurrentPageChanged(pos, pos) } private fun reversed(position: Int): Int { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/standard/PagerReaderFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/standard/PagerReaderFragment.kt index 70a2cbc28..ebf7962a6 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/standard/PagerReaderFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/standard/PagerReaderFragment.kt @@ -170,7 +170,7 @@ class PagerReaderFragment : BaseReaderFragment(), } private fun notifyPageChanged(page: Int) { - viewModel.onCurrentPageChanged(page) + viewModel.onCurrentPageChanged(page, page) } companion object { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonReaderFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonReaderFragment.kt index f3c1d2f89..4531cb8b9 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonReaderFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonReaderFragment.kt @@ -24,7 +24,8 @@ import org.koitharu.kotatsu.reader.ui.pager.ReaderPage import javax.inject.Inject @AndroidEntryPoint -class WebtoonReaderFragment : BaseReaderFragment() { +class WebtoonReaderFragment : BaseReaderFragment(), + WebtoonRecyclerView.OnWebtoonScrollListener { @Inject lateinit var networkState: NetworkState @@ -46,7 +47,7 @@ class WebtoonReaderFragment : BaseReaderFragment() with(binding.recyclerView) { setHasFixedSize(true) adapter = readerAdapter - addOnPageScrollListener(PageScrollListener()) + addOnPageScrollListener(this@WebtoonReaderFragment) recyclerLifecycleDispatcher = RecyclerViewLifecycleDispatcher().also { addOnScrollListener(it) } @@ -70,6 +71,15 @@ class WebtoonReaderFragment : BaseReaderFragment() exceptionResolver = exceptionResolver, ) + override fun onScrollChanged( + recyclerView: WebtoonRecyclerView, + dy: Int, + firstVisiblePosition: Int, + lastVisiblePosition: Int, + ) { + viewModel.onCurrentPageChanged(firstVisiblePosition, lastVisiblePosition) + } + override suspend fun onPagesChanged(pages: List, pendingState: ReaderState?) = coroutineScope { val setItems = launch { requireAdapter().setItems(pages) @@ -91,7 +101,7 @@ class WebtoonReaderFragment : BaseReaderFragment() ?.restoreScroll(pendingState.scroll) } } - notifyPageChanged(position) + viewModel.onCurrentPageChanged(position, position) } else { Snackbar.make(requireView(), R.string.not_found_404, Snackbar.LENGTH_SHORT) .show() @@ -121,10 +131,6 @@ class WebtoonReaderFragment : BaseReaderFragment() viewBinding?.frame?.onZoomOut() } - private fun notifyPageChanged(page: Int) { - viewModel.onCurrentPageChanged(page) - } - override fun switchPageBy(delta: Int) { with(requireViewBinding().recyclerView) { if (isAnimationEnabled()) { @@ -147,12 +153,4 @@ class WebtoonReaderFragment : BaseReaderFragment() } return true } - - private inner class PageScrollListener : WebtoonRecyclerView.OnPageScrollListener() { - - override fun onPageChanged(recyclerView: WebtoonRecyclerView, index: Int) { - super.onPageChanged(recyclerView, index) - notifyPageChanged(index) - } - } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonRecyclerView.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonRecyclerView.kt index 788a11107..346ea2b5f 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonRecyclerView.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonRecyclerView.kt @@ -6,8 +6,8 @@ import android.view.View import androidx.core.view.ViewCompat.TYPE_TOUCH import androidx.core.view.forEach import androidx.core.view.iterator +import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import org.koitharu.kotatsu.core.util.ext.findCenterViewPosition import java.util.LinkedList import java.util.WeakHashMap @@ -15,7 +15,8 @@ class WebtoonRecyclerView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : RecyclerView(context, attrs, defStyleAttr) { - private var onPageScrollListeners: MutableList? = null + private var onPageScrollListeners = LinkedList() + private val scrollDispatcher = WebtoonScrollDispatcher() private val detachedViews = WeakHashMap() private var isFixingScroll: Boolean = false @@ -103,22 +104,20 @@ class WebtoonRecyclerView @JvmOverloads constructor( return 0 } - fun addOnPageScrollListener(listener: OnPageScrollListener) { - val list = onPageScrollListeners ?: LinkedList().also { onPageScrollListeners = it } - list.add(listener) + fun addOnPageScrollListener(listener: OnWebtoonScrollListener) { + onPageScrollListeners.add(listener) } - fun removeOnPageScrollListener(listener: OnPageScrollListener) { - onPageScrollListeners?.remove(listener) + fun removeOnPageScrollListener(listener: OnWebtoonScrollListener) { + onPageScrollListeners.remove(listener) } private fun notifyScrollChanged(dy: Int) { val listeners = onPageScrollListeners - if (listeners.isNullOrEmpty()) { + if (listeners.isEmpty()) { return } - val centerPosition = findCenterViewPosition() - listeners.forEach { it.dispatchScroll(this, dy, centerPosition) } + scrollDispatcher.dispatchScroll(this, dy) } fun relayoutChildren() { @@ -162,20 +161,30 @@ class WebtoonRecyclerView @JvmOverloads constructor( else -> false } - abstract class OnPageScrollListener { + private class WebtoonScrollDispatcher { - private var lastPosition = NO_POSITION + private var firstPos = NO_POSITION + private var lastPos = NO_POSITION - fun dispatchScroll(recyclerView: WebtoonRecyclerView, dy: Int, centerPosition: Int) { - onScroll(recyclerView, dy) - if (centerPosition != NO_POSITION && centerPosition != lastPosition) { - lastPosition = centerPosition - onPageChanged(recyclerView, centerPosition) + fun dispatchScroll(rv: WebtoonRecyclerView, dy: Int) { + val lm = rv.layoutManager as? LinearLayoutManager + if (lm == null) { + firstPos = NO_POSITION + lastPos = NO_POSITION + return + } + val newFirstPos = lm.findFirstVisibleItemPosition() + val newLastPos = lm.findLastVisibleItemPosition() + if (newFirstPos != firstPos || newLastPos != lastPos) { + firstPos = newFirstPos + lastPos = newLastPos + rv.onPageScrollListeners.forEach { it.onScrollChanged(rv, dy, newFirstPos, newLastPos) } } } + } - open fun onScroll(recyclerView: WebtoonRecyclerView, dy: Int) = Unit + interface OnWebtoonScrollListener { - open fun onPageChanged(recyclerView: WebtoonRecyclerView, index: Int) = Unit + fun onScrollChanged(recyclerView: WebtoonRecyclerView, dy: Int, firstVisiblePosition: Int, lastVisiblePosition: Int) } } From 39e21ff93cbcfe83eaab19d627a2b089781603df Mon Sep 17 00:00:00 2001 From: Koitharu Date: Wed, 24 Jan 2024 11:53:53 +0200 Subject: [PATCH 12/14] Last read order in favorites #705 (cherry picked from commit b1240e7efafd1961ee7cc08325d3f309e56ed58c) --- .../kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt | 2 +- .../org/koitharu/kotatsu/favourites/data/FavouritesDao.kt | 2 +- .../org/koitharu/kotatsu/history/data/HistoryDao.kt | 2 +- .../koitharu/kotatsu/history/ui/HistoryListViewModel.kt | 2 +- .../org/koitharu/kotatsu/list/domain/ListSortOrder.kt | 8 ++++---- app/src/main/res/values/strings.xml | 1 + 6 files changed, 9 insertions(+), 8 deletions(-) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt index 0afb79590..d87ffe78d 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt @@ -347,7 +347,7 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) { set(value) = prefs.edit { putEnumValue(KEY_LOCAL_LIST_ORDER, value) } var historySortOrder: ListSortOrder - get() = prefs.getEnumValue(KEY_HISTORY_ORDER, ListSortOrder.UPDATED) + get() = prefs.getEnumValue(KEY_HISTORY_ORDER, ListSortOrder.LAST_READ) set(value) = prefs.edit { putEnumValue(KEY_HISTORY_ORDER, value) } var allFavoritesSortOrder: ListSortOrder diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/data/FavouritesDao.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/data/FavouritesDao.kt index d569ba69a..ec9590951 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/data/FavouritesDao.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/data/FavouritesDao.kt @@ -193,8 +193,8 @@ abstract class FavouritesDao { ListSortOrder.ALPHABETIC -> "manga.title ASC" ListSortOrder.ALPHABETIC_REVERSE -> "manga.title DESC" ListSortOrder.NEW_CHAPTERS -> "IFNULL((SELECT chapters_new FROM tracks WHERE tracks.manga_id = manga.manga_id), 0) DESC" - ListSortOrder.UPDATED, // for legacy support ListSortOrder.PROGRESS -> "IFNULL((SELECT percent FROM history WHERE history.manga_id = manga.manga_id), 0) DESC" + ListSortOrder.LAST_READ -> "IFNULL((SELECT updated_at FROM history WHERE history.manga_id = manga.manga_id), 0) DESC" else -> throw IllegalArgumentException("Sort order $sortOrder is not supported") } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/history/data/HistoryDao.kt b/app/src/main/kotlin/org/koitharu/kotatsu/history/data/HistoryDao.kt index 34314f881..fc60e5103 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/history/data/HistoryDao.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/history/data/HistoryDao.kt @@ -35,7 +35,7 @@ abstract class HistoryDao { fun observeAll(order: ListSortOrder): Flow> { val orderBy = when (order) { - ListSortOrder.UPDATED -> "history.updated_at DESC" + ListSortOrder.LAST_READ -> "history.updated_at DESC" ListSortOrder.NEWEST -> "history.created_at DESC" ListSortOrder.PROGRESS -> "history.percent DESC" ListSortOrder.ALPHABETIC -> "manga.title" diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/history/ui/HistoryListViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/history/ui/HistoryListViewModel.kt index d3e0561fc..91c192ac4 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/history/ui/HistoryListViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/history/ui/HistoryListViewModel.kt @@ -172,7 +172,7 @@ class HistoryListViewModel @Inject constructor( } private fun MangaHistory.header(order: ListSortOrder): ListHeader? = when (order) { - ListSortOrder.UPDATED -> ListHeader(calculateTimeAgo(updatedAt)) + ListSortOrder.LAST_READ -> ListHeader(calculateTimeAgo(updatedAt)) ListSortOrder.NEWEST -> ListHeader(calculateTimeAgo(createdAt)) ListSortOrder.PROGRESS -> ListHeader( when (percent) { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/domain/ListSortOrder.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/domain/ListSortOrder.kt index 8142a97eb..7637cb600 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/domain/ListSortOrder.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/domain/ListSortOrder.kt @@ -9,7 +9,6 @@ enum class ListSortOrder( @StringRes val titleResId: Int, ) { - UPDATED(R.string.updated), NEWEST(R.string.order_added), PROGRESS(R.string.progress), ALPHABETIC(R.string.by_name), @@ -17,14 +16,15 @@ enum class ListSortOrder( RATING(R.string.by_rating), RELEVANCE(R.string.by_relevance), NEW_CHAPTERS(R.string.new_chapters), + LAST_READ(R.string.last_read), ; - fun isGroupingSupported() = this == UPDATED || this == NEWEST || this == PROGRESS + fun isGroupingSupported() = this == LAST_READ || this == NEWEST || this == PROGRESS companion object { - val HISTORY: Set = EnumSet.of(UPDATED, NEWEST, PROGRESS, ALPHABETIC, ALPHABETIC_REVERSE, NEW_CHAPTERS) - val FAVORITES: Set = EnumSet.of(ALPHABETIC, ALPHABETIC_REVERSE, NEWEST, RATING, NEW_CHAPTERS, PROGRESS) + val HISTORY: Set = EnumSet.of(LAST_READ, NEWEST, PROGRESS, ALPHABETIC, ALPHABETIC_REVERSE, NEW_CHAPTERS) + val FAVORITES: Set = EnumSet.of(ALPHABETIC, ALPHABETIC_REVERSE, NEWEST, RATING, NEW_CHAPTERS, PROGRESS, LAST_READ) val SUGGESTIONS: Set = EnumSet.of(RELEVANCE) operator fun invoke(value: String, fallback: ListSortOrder) = entries.find(value) ?: fallback diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 90ebedeb9..f96de9a8a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -560,4 +560,5 @@ Mark selected manga as completely read?\n\nWarning: current reading progress will be lost. This category was hidden from the main screen and is accessible through Menu → Manage categories Your reading progress will not be saved + Last read From 6eb2608f886ab99fb0d08b7d9901b75a1390f983 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Wed, 24 Jan 2024 11:56:04 +0200 Subject: [PATCH 13/14] Disable autofill for protect password fields #702 (cherry picked from commit e7c9d1943d5f8c3539b4b971f0f0a257e5f84d70) --- app/src/main/res/layout/activity_protect.xml | 3 ++- app/src/main/res/layout/activity_setup_protect.xml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/layout/activity_protect.xml b/app/src/main/res/layout/activity_protect.xml index db3f7c3f7..0fd6edfc6 100644 --- a/app/src/main/res/layout/activity_protect.xml +++ b/app/src/main/res/layout/activity_protect.xml @@ -52,6 +52,7 @@ android:layout_height="wrap_content" android:gravity="center_horizontal" android:imeOptions="actionDone" + android:importantForAutofill="no" android:inputType="textPassword" android:maxLength="24" android:singleLine="true" @@ -80,4 +81,4 @@ app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" /> - \ No newline at end of file + diff --git a/app/src/main/res/layout/activity_setup_protect.xml b/app/src/main/res/layout/activity_setup_protect.xml index 78c0e8e02..a373f1a69 100644 --- a/app/src/main/res/layout/activity_setup_protect.xml +++ b/app/src/main/res/layout/activity_setup_protect.xml @@ -53,6 +53,7 @@ android:layout_height="wrap_content" android:gravity="center_horizontal" android:imeOptions="actionDone" + android:importantForAutofill="no" android:inputType="textPassword" android:maxLength="24" android:singleLine="true" @@ -94,4 +95,4 @@ app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" /> - \ No newline at end of file + From cd0575a52468a06c00c4aea17b2233edf08255db Mon Sep 17 00:00:00 2001 From: Koitharu Date: Wed, 24 Jan 2024 12:28:50 +0200 Subject: [PATCH 14/14] Update parsers --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 6249a6ede..a62c13676 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -17,7 +17,7 @@ android { minSdk = 21 targetSdk = 34 versionCode = 615 - versionName = '6.6.5' + versionName = '6.6.6' generatedDensities = [] testInstrumentationRunner 'org.koitharu.kotatsu.HiltTestRunner' ksp { @@ -82,7 +82,7 @@ afterEvaluate { } dependencies { //noinspection GradleDependency - implementation('com.github.KotatsuApp:kotatsu-parsers:8852d1e22e') { + implementation('com.github.KotatsuApp:kotatsu-parsers:7c871edbca') { exclude group: 'org.json', module: 'json' }