diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/AllBookmarksActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/AllBookmarksActivity.kt index 44d30e76b..7a3ccc818 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/AllBookmarksActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/AllBookmarksActivity.kt @@ -1,38 +1,5 @@ package org.koitharu.kotatsu.bookmarks.ui -import android.os.Bundle -import androidx.coordinatorlayout.widget.CoordinatorLayout -import androidx.fragment.app.commit -import com.google.android.material.appbar.AppBarLayout -import dagger.hilt.android.AndroidEntryPoint -import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.core.ui.BaseActivity -import org.koitharu.kotatsu.databinding.ActivityContainerBinding -import org.koitharu.kotatsu.main.ui.owners.AppBarOwner -import org.koitharu.kotatsu.main.ui.owners.SnackbarOwner +import org.koitharu.kotatsu.core.ui.FragmentContainerActivity -@AndroidEntryPoint -class AllBookmarksActivity : - BaseActivity(), - AppBarOwner, - SnackbarOwner { - - override val appBar: AppBarLayout - get() = viewBinding.appbar - - override val snackbarHost: CoordinatorLayout - get() = viewBinding.root - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(ActivityContainerBinding.inflate(layoutInflater)) - supportActionBar?.setDisplayHomeAsUpEnabled(true) - val fm = supportFragmentManager - if (fm.findFragmentById(R.id.container) == null) { - fm.commit { - setReorderingAllowed(true) - replace(R.id.container, AllBookmarksFragment::class.java, null) - } - } - } -} +class AllBookmarksActivity : FragmentContainerActivity(AllBookmarksFragment::class.java) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/nav/AppRouter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/nav/AppRouter.kt index f8349ac2d..ba152d1c0 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/nav/AppRouter.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/nav/AppRouter.kt @@ -370,6 +370,25 @@ class AppRouter private constructor( }.show() } + fun showAuthorDialog(author: String, source: MangaSource) { + buildAlertDialog(contextOrNull() ?: return) { + setTitle(author) + setItems( + arrayOf( + context.getString(R.string.search_on_s, source.getTitle(context)), + context.getString(R.string.search_everywhere), + ), + ) { _, which -> + when (which) { + 0 -> openList(source, MangaListFilter(author = author), null) + 1 -> openSearch(author, SearchKind.AUTHOR) + } + } + setNegativeButton(R.string.close, null) + setCancelable(true) + }.show() + } + fun showErrorDialog(error: Throwable, url: String? = null) { ErrorDetailsDialog().withArgs(2) { putSerializable(KEY_ERROR, error) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/external/ExternalPluginContentSource.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/external/ExternalPluginContentSource.kt index 743a96401..49170f18f 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/external/ExternalPluginContentSource.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/external/ExternalPluginContentSource.kt @@ -252,7 +252,7 @@ class ExternalPluginContentSource( publicUrl = getString(COLUMN_PUBLIC_URL), rating = getFloat(COLUMN_RATING), isNsfw = getBooleanOrDefault(COLUMN_IS_NSFW, false), - coverUrl = getString(COLUMN_COVER_URL), + coverUrl = getStringOrNull(COLUMN_COVER_URL), tags = getStringOrNull(COLUMN_TAGS)?.split(':')?.mapNotNullToSet { val parts = it.splitTwoParts('=') ?: return@mapNotNullToSet null MangaTag(key = parts.first, title = parts.second, source = source) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/external/ExternalPluginCursor.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/external/ExternalPluginCursor.kt index 33cab9511..807ea5b4b 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/external/ExternalPluginCursor.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/external/ExternalPluginCursor.kt @@ -20,7 +20,7 @@ class ExternalPluginCursor(private val source: ExternalMangaSource, cursor: Curs return when { columnIndex < 0 -> null isNull(columnIndex) -> null - else -> getString(columnIndex) + else -> getString(columnIndex).takeUnless { it == "null" } } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BasePreferenceFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BasePreferenceFragment.kt index aa9e2c666..03c54ebd5 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BasePreferenceFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BasePreferenceFragment.kt @@ -35,7 +35,7 @@ abstract class BasePreferenceFragment(@StringRes private val titleId: Int) : @Inject lateinit var settings: AppSettings - override val recyclerView: RecyclerView + override val recyclerView: RecyclerView? get() = listView override fun onAttach(context: Context) { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/FragmentContainerActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/FragmentContainerActivity.kt new file mode 100644 index 000000000..5ef60515f --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/FragmentContainerActivity.kt @@ -0,0 +1,40 @@ +package org.koitharu.kotatsu.core.ui + +import android.os.Bundle +import androidx.coordinatorlayout.widget.CoordinatorLayout +import androidx.fragment.app.Fragment +import androidx.fragment.app.commit +import com.google.android.material.appbar.AppBarLayout +import dagger.hilt.android.AndroidEntryPoint +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.databinding.ActivityContainerBinding +import org.koitharu.kotatsu.main.ui.owners.AppBarOwner +import org.koitharu.kotatsu.main.ui.owners.SnackbarOwner + +@AndroidEntryPoint +abstract class FragmentContainerActivity(private val fragmentClass: Class) : + BaseActivity(), + AppBarOwner, + SnackbarOwner { + + override val appBar: AppBarLayout + get() = viewBinding.appbar + + override val snackbarHost: CoordinatorLayout + get() = viewBinding.root + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(ActivityContainerBinding.inflate(layoutInflater)) + supportActionBar?.setDisplayHomeAsUpEnabled(true) + val fm = supportFragmentManager + if (fm.findFragmentById(R.id.container) == null) { + fm.commit { + setReorderingAllowed(true) + replace(R.id.container, fragmentClass, getFragmentExtras()) + } + } + } + + protected open fun getFragmentExtras(): Bundle? = intent.extras +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/InsetsToMarginsListener.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/InsetsToMarginsListener.kt index 8a8470fa8..1977cd239 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/InsetsToMarginsListener.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/InsetsToMarginsListener.kt @@ -4,6 +4,7 @@ import android.view.Gravity import android.view.View import android.view.ViewGroup import androidx.annotation.GravityInt +import androidx.core.graphics.Insets import androidx.core.view.OnApplyWindowInsetsListener import androidx.core.view.WindowInsetsCompat import androidx.core.view.updateLayoutParams @@ -14,19 +15,30 @@ import org.koitharu.kotatsu.core.util.ext.start class InsetsToMarginsListener( @GravityInt private val sides: Int, + private val baseMargins: Insets, ) : OnApplyWindowInsetsListener { + private val insetType = WindowInsetsCompat.Type.systemBars() + override fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat { - val barsInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()) + val barsInsets = insets.getInsets(insetType) v.updateLayoutParams { - if (sides and Gravity.START == Gravity.START) marginStart = barsInsets.start(v) - if (sides and Gravity.TOP == Gravity.TOP) topMargin = barsInsets.top - if (sides and Gravity.END == Gravity.END) marginEnd = barsInsets.end(v) - if (sides and Gravity.BOTTOM == Gravity.BOTTOM) bottomMargin = barsInsets.bottom + if (sides and Gravity.START == Gravity.START) { + marginStart = barsInsets.start(v) + baseMargins.start(v) + } + if (sides and Gravity.TOP == Gravity.TOP) { + topMargin = barsInsets.top + baseMargins.top + } + if (sides and Gravity.END == Gravity.END) { + marginEnd = barsInsets.end(v) + baseMargins.end(v) + } + if (sides and Gravity.BOTTOM == Gravity.BOTTOM) { + bottomMargin = barsInsets.bottom + baseMargins.bottom + } } return WindowInsetsCompat.Builder(insets) .setInsets( - WindowInsetsCompat.Type.systemBars(), + insetType, barsInsets.consumeRelative( v, start = sides and Gravity.START == Gravity.START, diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/InsetsToPaddingListener.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/InsetsToPaddingListener.kt index c6071294c..6650c049c 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/InsetsToPaddingListener.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/InsetsToPaddingListener.kt @@ -3,6 +3,7 @@ package org.koitharu.kotatsu.core.ui.util import android.view.Gravity import android.view.View import androidx.annotation.GravityInt +import androidx.core.graphics.Insets import androidx.core.view.OnApplyWindowInsetsListener import androidx.core.view.WindowInsetsCompat import org.koitharu.kotatsu.core.util.ext.consumeRelative @@ -12,19 +13,42 @@ import org.koitharu.kotatsu.core.util.ext.start class InsetsToPaddingListener( @GravityInt private val sides: Int, + private val basePaddings: Insets, ) : OnApplyWindowInsetsListener { + private val insetType = WindowInsetsCompat.Type.systemBars() + override fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat { - val barsInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()) + val barsInsets = insets.getInsets(insetType) v.setPaddingRelative( - /* start = */ if (sides and Gravity.START == Gravity.START) barsInsets.start(v) else v.paddingStart, - /* top = */ if (sides and Gravity.TOP == Gravity.TOP) barsInsets.top else v.paddingTop, - /* end = */ if (sides and Gravity.END == Gravity.END) barsInsets.end(v) else v.paddingEnd, - /* bottom = */ if (sides and Gravity.BOTTOM == Gravity.BOTTOM) barsInsets.bottom else v.paddingBottom, + /* start = */ + if (sides and Gravity.START == Gravity.START) { + barsInsets.start(v) + basePaddings.start(v) + } else { + v.paddingStart + }, + /* top = */ + if (sides and Gravity.TOP == Gravity.TOP) { + barsInsets.top + basePaddings.top + } else { + v.paddingTop + }, + /* end = */ + if (sides and Gravity.END == Gravity.END) { + barsInsets.end(v) + basePaddings.end(v) + } else { + v.paddingEnd + }, + /* bottom = */ + if (sides and Gravity.BOTTOM == Gravity.BOTTOM) { + barsInsets.bottom + basePaddings.bottom + } else { + v.paddingBottom + }, ) return WindowInsetsCompat.Builder(insets) .setInsets( - WindowInsetsCompat.Type.systemBars(), + insetType, barsInsets.consumeRelative( v, start = sides and Gravity.START == Gravity.START, diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/RecyclerViewOwner.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/RecyclerViewOwner.kt index f34963f15..0e30fd085 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/RecyclerViewOwner.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/RecyclerViewOwner.kt @@ -4,5 +4,5 @@ import androidx.recyclerview.widget.RecyclerView interface RecyclerViewOwner { - val recyclerView: RecyclerView + val recyclerView: RecyclerView? } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Coil.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Coil.kt index 6bfbffb75..763df98a1 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Coil.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Coil.kt @@ -1,10 +1,10 @@ package org.koitharu.kotatsu.core.util.ext import android.content.Context -import android.graphics.drawable.ColorDrawable import android.graphics.drawable.Drawable import android.widget.ImageView import androidx.core.graphics.ColorUtils +import androidx.core.graphics.drawable.toDrawable import androidx.lifecycle.LifecycleOwner import coil3.Extras import coil3.ImageLoader @@ -116,8 +116,8 @@ fun ImageRequest.Builder.defaultPlaceholders(context: Context): ImageRequest.Bui 0.25f, ) return placeholder(AnimatedPlaceholderDrawable(context)) - .fallback(ColorDrawable(context.getThemeColor(materialR.attr.colorSurfaceContainer))) - .error(ColorDrawable(errorColor)) + .fallback(context.getThemeColor(materialR.attr.colorSurfaceContainer).toDrawable()) + .error(errorColor.toDrawable()) } private fun ImageView.ScaleType.toCoilScale(): Scale = if (this == ImageView.ScaleType.CENTER_CROP) { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/RecyclerView.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/RecyclerView.kt index e2170048f..44c6bcb6d 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/RecyclerView.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/RecyclerView.kt @@ -1,6 +1,10 @@ package org.koitharu.kotatsu.core.util.ext +import android.util.DisplayMetrics +import androidx.core.view.doOnNextLayout +import androidx.core.view.isEmpty import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.LinearSmoothScroller import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.StaggeredGridLayoutManager import com.hannesdorfmann.adapterdelegates4.dsl.AdapterDelegateViewBindingViewHolder @@ -52,7 +56,7 @@ fun RecyclerView.ViewHolder.getItem(clazz: Class): T? { val RecyclerView.isScrolledToTop: Boolean get() { - if (childCount == 0) { + if (isEmpty()) { return true } val holder = findViewHolderForAdapterPosition(0) @@ -72,3 +76,35 @@ val RecyclerView.LayoutManager?.isLayoutReversed is StaggeredGridLayoutManager -> reverseLayout else -> false } + +// https://medium.com/flat-pack-tech/quickly-scroll-to-the-top-of-a-recyclerview-da15b717f3c4 +fun RecyclerView.smoothScrollToTop() { + val layoutManager = layoutManager as? LinearLayoutManager ?: return + + if (!context.isAnimationsEnabled) { + layoutManager.scrollToPositionWithOffset(0, 0) + return + } + + val smoothScroller = object : LinearSmoothScroller(context) { + init { + targetPosition = 0 + } + + override fun calculateSpeedPerPixel(displayMetrics: DisplayMetrics?) = + super.calculateSpeedPerPixel(displayMetrics) / DEFAULT_SPEED_FACTOR + } + + val jumpBeforeScroll = layoutManager.findFirstVisibleItemPosition() > DEFAULT_JUMP_THRESHOLD + if (jumpBeforeScroll) { + layoutManager.scrollToPositionWithOffset(DEFAULT_JUMP_THRESHOLD, 0) + doOnNextLayout { + layoutManager.startSmoothScroll(smoothScroller) + } + } else { + layoutManager.startSmoothScroll(smoothScroller) + } +} + +private const val DEFAULT_JUMP_THRESHOLD = 30 +private const val DEFAULT_SPEED_FACTOR = 1f diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/View.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/View.kt index a7aa254f2..e987e2267 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/View.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/View.kt @@ -9,10 +9,13 @@ import android.widget.Checkable import androidx.annotation.GravityInt import androidx.appcompat.widget.ActionMenuView import androidx.appcompat.widget.Toolbar +import androidx.core.graphics.Insets import androidx.core.view.ViewCompat import androidx.core.view.children import androidx.core.view.descendants import androidx.core.view.isVisible +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView.ViewHolder import androidx.swiperefreshlayout.widget.CircularProgressDrawable @@ -72,6 +75,11 @@ fun ViewPager2.findCurrentViewHolder(): ViewHolder? { return recyclerView?.findViewHolderForAdapterPosition(currentItem) } +fun FragmentManager.findCurrentPagerFragment(pager: ViewPager2): Fragment? { + val currentId = pager.adapter?.getItemId(pager.currentItem) ?: pager.currentItem + return findFragmentByTag("f$currentId") +} + fun View.resetTransformations() { alpha = 1f translationX = 0f @@ -187,12 +195,18 @@ fun Chip.setProgressIcon() { progressDrawable.start() } +private fun View.marginsSnapshot(): Insets = (layoutParams as? ViewGroup.MarginLayoutParams)?.let { lp -> + Insets.of(lp.leftMargin, lp.topMargin, lp.rightMargin, lp.bottomMargin) +} ?: Insets.NONE + +private fun View.paddingSnapshot(): Insets = Insets.of(paddingLeft, paddingTop, paddingRight, paddingBottom) + fun View.consumeInsetsAsPadding(@GravityInt sides: Int) = ViewCompat.setOnApplyWindowInsetsListener( this, - InsetsToPaddingListener(sides), + InsetsToPaddingListener(sides, paddingSnapshot()), ) fun View.consumeInsetsAsMargins(@GravityInt sides: Int) = ViewCompat.setOnApplyWindowInsetsListener( this, - InsetsToMarginsListener(sides), + InsetsToMarginsListener(sides, marginsSnapshot()), ) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/data/MangaDetails.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/data/MangaDetails.kt index 30c814b96..45a4cad93 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/data/MangaDetails.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/data/MangaDetails.kt @@ -4,6 +4,8 @@ import org.koitharu.kotatsu.core.model.isLocal import org.koitharu.kotatsu.local.domain.model.LocalManga import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.MangaChapter +import org.koitharu.kotatsu.parsers.util.ifNullOrEmpty +import org.koitharu.kotatsu.parsers.util.nullIfEmpty import org.koitharu.kotatsu.reader.data.filterChapters data class MangaDetails( @@ -29,6 +31,12 @@ data class MangaDetails( val local: LocalManga? get() = localManga ?: if (manga.isLocal) LocalManga(manga) else null + val coverUrl: String? + get() = manga.largeCoverUrl + .ifNullOrEmpty { manga.largeCoverUrl } + .ifNullOrEmpty { localManga?.manga?.coverUrl } + ?.nullIfEmpty() + fun toManga() = manga fun filterChapters(branch: String?) = MangaDetails( diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/domain/DetailsLoadUseCase.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/domain/DetailsLoadUseCase.kt index 7f7c96509..c35fae7d1 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/domain/DetailsLoadUseCase.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/domain/DetailsLoadUseCase.kt @@ -6,6 +6,7 @@ import android.text.Spanned import android.text.style.ForegroundColorSpan import androidx.core.text.getSpans import androidx.core.text.parseAsHtml +import coil3.request.CachePolicy import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.flow.Flow @@ -15,6 +16,7 @@ import kotlinx.coroutines.runInterruptible import okio.IOException import org.koitharu.kotatsu.core.model.isLocal import org.koitharu.kotatsu.core.nav.MangaIntent +import org.koitharu.kotatsu.core.parser.CachingMangaRepository import org.koitharu.kotatsu.core.parser.MangaDataRepository import org.koitharu.kotatsu.core.parser.MangaRepository import org.koitharu.kotatsu.core.util.ext.peek @@ -40,7 +42,7 @@ class DetailsLoadUseCase @Inject constructor( private val newChaptersUseCaseProvider: Provider, ) { - operator fun invoke(intent: MangaIntent): Flow = channelFlow { + operator fun invoke(intent: MangaIntent, force: Boolean): Flow = channelFlow { val manga = requireNotNull(mangaDataRepository.resolveIntent(intent)) { "Cannot resolve intent $intent" }.let { m -> @@ -59,7 +61,7 @@ class DetailsLoadUseCase @Inject constructor( null } try { - val details = getDetails(manga) + val details = getDetails(manga, force) launch { mangaDataRepository.updateChapters(details) } launch { updateTracker(details) } send( @@ -92,9 +94,13 @@ class DetailsLoadUseCase @Inject constructor( } } - private suspend fun getDetails(seed: Manga) = runCatchingCancellable { + private suspend fun getDetails(seed: Manga, force: Boolean) = runCatchingCancellable { val repository = mangaRepositoryFactory.create(seed.source) - repository.getDetails(seed) + if (repository is CachingMangaRepository) { + repository.getDetails(seed, if (force) CachePolicy.WRITE_ONLY else CachePolicy.ENABLED) + } else { + repository.getDetails(seed) + } }.recoverNotNull { e -> if (e is NotFoundException) { recoverUseCase(seed) 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 2486e7747..51ef4d467 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 @@ -111,7 +111,6 @@ import org.koitharu.kotatsu.parsers.model.MangaTag import org.koitharu.kotatsu.parsers.util.ifNullOrEmpty import org.koitharu.kotatsu.parsers.util.nullIfEmpty import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo -import org.koitharu.kotatsu.search.domain.SearchKind import javax.inject.Inject import kotlin.math.roundToInt import com.google.android.material.R as materialR @@ -171,6 +170,7 @@ class DetailsActivity : val appRouter = router viewModel.mangaDetails.filterNotNull().observe(this, ::onMangaUpdated) + viewModel.coverUrl.observe(this, ::loadCover) viewModel.onMangaRemoved.observeEvent(this, ::onMangaRemoved) viewModel.onError .filterNot { appRouter.isChapterPagesSheetShown() } @@ -217,8 +217,9 @@ class DetailsActivity : override fun onClick(v: View) { when (v.id) { R.id.textView_author -> { - val author = viewModel.manga.value?.author ?: return - router.openSearch(author, SearchKind.AUTHOR) + val manga = viewModel.manga.value + val author = manga?.author ?: return + router.showAuthorDialog(author, manga.source) } R.id.textView_source -> { @@ -239,7 +240,7 @@ class DetailsActivity : R.id.imageView_cover -> { val manga = viewModel.manga.value ?: return router.openImage( - url = manga.largeCoverUrl.ifNullOrEmpty { manga.coverUrl } ?: return, + url = viewModel.coverUrl.value ?: return, source = manga.source, anchor = v, ) @@ -443,7 +444,6 @@ class DetailsActivity : private fun onMangaUpdated(details: MangaDetails) { val manga = details.toManga() - loadCover(manga) with(viewBinding) { textViewTitle.text = manga.title textViewSubtitle.textAndVisible = manga.altTitle @@ -560,8 +560,7 @@ class DetailsActivity : viewBinding.chipsTags.setChips(listMapper.mapTags(manga.tags)) } - private fun loadCover(manga: Manga) { - val imageUrl = manga.largeCoverUrl.ifNullOrEmpty { manga.coverUrl } + private fun loadCover(imageUrl: String?) { val lastResult = CoilUtils.result(viewBinding.imageViewCover) if (lastResult is SuccessResult && lastResult.request.data == imageUrl) { return @@ -571,10 +570,9 @@ class DetailsActivity : .size(CoverSizeResolver(viewBinding.imageViewCover)) .scale(Scale.FILL) .data(imageUrl) - .mangaSourceExtra(manga.source) + .mangaSourceExtra(viewModel.getMangaOrNull()?.source) .crossfade(this) .lifecycle(this) - .placeholderMemoryCacheKey(manga.coverUrl) val previousDrawable = lastResult?.drawable if (previousDrawable != null) { request.fallback(previousDrawable) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt index 01acb1b19..0eb38cd40 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt @@ -176,7 +176,7 @@ class DetailsViewModel @Inject constructor( get() = selectedBranch.value init { - loadingJob = doLoad() + loadingJob = doLoad(force = false) launchJob(Dispatchers.Default) { val manga = mangaDetails.firstOrNull { !it?.chapters.isNullOrEmpty() } ?: return@launchJob val h = history.firstOrNull() @@ -192,7 +192,7 @@ class DetailsViewModel @Inject constructor( fun reload() { loadingJob.cancel() - loadingJob = doLoad() + loadingJob = doLoad(force = true) } fun updateScrobbling(index: Int, rating: Float, status: ScrobblingStatus?) { @@ -223,8 +223,8 @@ class DetailsViewModel @Inject constructor( } } - private fun doLoad() = launchLoadingJob(Dispatchers.Default) { - detailsLoadUseCase.invoke(intent) + private fun doLoad(force: Boolean) = launchLoadingJob(Dispatchers.Default) { + detailsLoadUseCase.invoke(intent, force) .onEachWhile { if (it.allChapters.isNotEmpty()) { val manga = it.toManga() diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/ChaptersPagesSheet.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/ChaptersPagesSheet.kt index adf6e5062..e62f420f9 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/ChaptersPagesSheet.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/ChaptersPagesSheet.kt @@ -6,6 +6,7 @@ import android.view.View import android.view.ViewGroup import androidx.appcompat.view.ActionMode import androidx.core.view.isVisible +import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayoutMediator import dagger.hilt.android.AndroidEntryPoint import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver @@ -20,13 +21,16 @@ import org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetCallback import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet import org.koitharu.kotatsu.core.ui.util.ActionModeListener import org.koitharu.kotatsu.core.ui.util.MenuInvalidator +import org.koitharu.kotatsu.core.ui.util.RecyclerViewOwner import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver import org.koitharu.kotatsu.core.util.ext.doOnPageChanged +import org.koitharu.kotatsu.core.util.ext.findCurrentPagerFragment import org.koitharu.kotatsu.core.util.ext.menuView import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observeEvent import org.koitharu.kotatsu.core.util.ext.recyclerView import org.koitharu.kotatsu.core.util.ext.setTabsEnabled +import org.koitharu.kotatsu.core.util.ext.smoothScrollToTop import org.koitharu.kotatsu.databinding.SheetChaptersPagesBinding import org.koitharu.kotatsu.details.ui.DetailsViewModel import org.koitharu.kotatsu.details.ui.ReadButtonDelegate @@ -34,7 +38,10 @@ import org.koitharu.kotatsu.download.ui.worker.DownloadStartedObserver import javax.inject.Inject @AndroidEntryPoint -class ChaptersPagesSheet : BaseAdaptiveSheet(), ActionModeListener, AdaptiveSheetCallback { +class ChaptersPagesSheet : BaseAdaptiveSheet(), + TabLayout.OnTabSelectedListener, + ActionModeListener, + AdaptiveSheetCallback { @Inject lateinit var settings: AppSettings @@ -63,6 +70,7 @@ class ChaptersPagesSheet : BaseAdaptiveSheet(), Actio binding.pager.adapter = adapter binding.pager.doOnPageChanged(::onPageChanged) TabLayoutMediator(binding.tabs, binding.pager, adapter).attach() + binding.tabs.addOnTabSelectedListener(this) binding.pager.setCurrentItem(defaultTab, false) binding.tabs.isVisible = adapter.itemCount > 1 @@ -109,6 +117,17 @@ class ChaptersPagesSheet : BaseAdaptiveSheet(), Actio viewBinding?.toolbar?.menuView?.isVisible = state != STATE_COLLAPSED } + override fun onTabSelected(tab: TabLayout.Tab?) = Unit + + override fun onTabUnselected(tab: TabLayout.Tab?) = Unit + + override fun onTabReselected(tab: TabLayout.Tab?) { + val f = childFragmentManager.findCurrentPagerFragment( + viewBinding?.pager ?: return, + ) as? RecyclerViewOwner ?: return + f.recyclerView?.smoothScrollToTop() + } + override fun expandAndLock() { super.expandAndLock() adjustLockState() diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/ChaptersPagesViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/ChaptersPagesViewModel.kt index e4ac4dfaf..8eab8ab7f 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/ChaptersPagesViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/ChaptersPagesViewModel.kt @@ -71,6 +71,10 @@ abstract class ChaptersPagesViewModel( .withErrorHandling() .stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, null) + val coverUrl = mangaDetails.map { x -> x?.coverUrl } + .withErrorHandling() + .stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, null) + val isChaptersReversed = settings.observeAsStateFlow( scope = viewModelScope + Dispatchers.Default, key = AppSettings.KEY_REVERSE_CHAPTERS, diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/bookmarks/BookmarksFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/bookmarks/BookmarksFragment.kt index 6ff8d82f7..fa88bf7ca 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/bookmarks/BookmarksFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/bookmarks/BookmarksFragment.kt @@ -11,6 +11,7 @@ import android.view.ViewGroup import androidx.appcompat.view.ActionMode import androidx.fragment.app.viewModels import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.RecyclerView import coil3.ImageLoader import dagger.hilt.android.AndroidEntryPoint import org.koitharu.kotatsu.R @@ -26,6 +27,7 @@ import org.koitharu.kotatsu.core.ui.BaseFragment import org.koitharu.kotatsu.core.ui.list.ListSelectionController import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.ui.util.PagerNestedScrollHelper +import org.koitharu.kotatsu.core.ui.util.RecyclerViewOwner import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver import org.koitharu.kotatsu.core.util.ext.consumeInsetsAsPadding import org.koitharu.kotatsu.core.util.ext.findAppCompatDelegate @@ -43,6 +45,7 @@ import javax.inject.Inject @AndroidEntryPoint class BookmarksFragment : BaseFragment(), OnListItemClickListener, + RecyclerViewOwner, ListSelectionController.Callback { private val activityViewModel by ChaptersPagesViewModel.ActivityVMLazy(this) @@ -54,6 +57,9 @@ class BookmarksFragment : BaseFragment(), @Inject lateinit var settings: AppSettings + override val recyclerView: RecyclerView? + get() = viewBinding?.recyclerView + private var bookmarksAdapter: BookmarksAdapter? = null private var spanResolver: GridSpanResolver? = null private var selectionController: ListSelectionController? = null 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 febd85a06..206040c3e 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 @@ -12,6 +12,7 @@ import androidx.core.view.isVisible import androidx.core.view.updatePadding import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView import com.google.android.material.chip.Chip import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.Dispatchers @@ -25,6 +26,7 @@ import org.koitharu.kotatsu.core.ui.BaseFragment import org.koitharu.kotatsu.core.ui.list.ListSelectionController import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.ui.util.PagerNestedScrollHelper +import org.koitharu.kotatsu.core.ui.util.RecyclerViewOwner import org.koitharu.kotatsu.core.ui.widgets.ChipsView import org.koitharu.kotatsu.core.util.RecyclerViewScrollCallback import org.koitharu.kotatsu.core.util.ext.findAppCompatDelegate @@ -48,6 +50,7 @@ class ChaptersFragment : BaseFragment(), OnListItemClickListener, OnApplyWindowInsetsListener, + RecyclerViewOwner, ChipsView.OnChipClickListener { private val viewModel by ChaptersPagesViewModel.ActivityVMLazy(this) @@ -55,6 +58,9 @@ class ChaptersFragment : private var chaptersAdapter: ChaptersAdapter? = null private var selectionController: ListSelectionController? = null + override val recyclerView: RecyclerView? + get() = viewBinding?.recyclerViewChapters + override fun onCreateViewBinding( inflater: LayoutInflater, container: ViewGroup?, 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 bcf02f15a..dfba6cab1 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 @@ -31,6 +31,7 @@ import org.koitharu.kotatsu.core.ui.list.BoundsScrollListener import org.koitharu.kotatsu.core.ui.list.ListSelectionController import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.ui.util.PagerNestedScrollHelper +import org.koitharu.kotatsu.core.ui.util.RecyclerViewOwner import org.koitharu.kotatsu.core.util.RecyclerViewScrollCallback import org.koitharu.kotatsu.core.util.ext.consumeInsetsAsPadding import org.koitharu.kotatsu.core.util.ext.findAppCompatDelegate @@ -55,6 +56,7 @@ import kotlin.math.roundToInt class PagesFragment : BaseFragment(), OnListItemClickListener, + RecyclerViewOwner, ListSelectionController.Callback { @Inject @@ -77,6 +79,9 @@ class PagesFragment : private val spanSizeLookup = SpanSizeLookup() + override val recyclerView: RecyclerView? + get() = viewBinding?.recyclerView + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) pageSaveHelper = pageSaveHelperFactory.create(this) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/related/RelatedMangaActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/related/RelatedMangaActivity.kt index beb88c6a3..6e02fc0cd 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/related/RelatedMangaActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/related/RelatedMangaActivity.kt @@ -1,30 +1,5 @@ package org.koitharu.kotatsu.details.ui.related -import android.os.Bundle -import androidx.fragment.app.commit -import com.google.android.material.appbar.AppBarLayout -import dagger.hilt.android.AndroidEntryPoint -import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.core.ui.BaseActivity -import org.koitharu.kotatsu.databinding.ActivityContainerBinding -import org.koitharu.kotatsu.main.ui.owners.AppBarOwner +import org.koitharu.kotatsu.core.ui.FragmentContainerActivity -@AndroidEntryPoint -class RelatedMangaActivity : BaseActivity(), AppBarOwner { - - override val appBar: AppBarLayout - get() = viewBinding.appbar - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(ActivityContainerBinding.inflate(layoutInflater)) - supportActionBar?.setDisplayHomeAsUpEnabled(true) - val fm = supportFragmentManager - if (fm.findFragmentById(R.id.container) == null) { - fm.commit { - setReorderingAllowed(true) - replace(R.id.container, RelatedListFragment::class.java, intent.extras) - } - } - } -} +class RelatedMangaActivity : FragmentContainerActivity(RelatedListFragment::class.java) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreFragment.kt index 16fbb6fa5..c7e8a14f6 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreFragment.kt @@ -60,8 +60,8 @@ class ExploreFragment : private var exploreAdapter: ExploreAdapter? = null private var sourceSelectionController: ListSelectionController? = null - override val recyclerView: RecyclerView - get() = requireViewBinding().recyclerView + override val recyclerView: RecyclerView? + get() = viewBinding?.recyclerView override fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentExploreBinding { return FragmentExploreBinding.inflate(inflater, container, false) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/FavouritesActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/FavouritesActivity.kt index d11a66266..694c082ff 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/FavouritesActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/FavouritesActivity.kt @@ -1,33 +1,18 @@ package org.koitharu.kotatsu.favourites.ui import android.os.Bundle -import androidx.fragment.app.commit import dagger.hilt.android.AndroidEntryPoint -import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.nav.AppRouter -import org.koitharu.kotatsu.core.ui.BaseActivity -import org.koitharu.kotatsu.databinding.ActivityContainerBinding +import org.koitharu.kotatsu.core.ui.FragmentContainerActivity import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment -import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment.Companion.NO_ID -@AndroidEntryPoint -class FavouritesActivity : BaseActivity() { +class FavouritesActivity : FragmentContainerActivity(FavouritesListFragment::class.java) { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(ActivityContainerBinding.inflate(layoutInflater)) - supportActionBar?.setDisplayHomeAsUpEnabled(true) val categoryTitle = intent.getStringExtra(AppRouter.KEY_TITLE) if (categoryTitle != null) { title = categoryTitle } - val fm = supportFragmentManager - if (fm.findFragmentById(R.id.container) == null) { - fm.commit { - setReorderingAllowed(true) - val fragment = FavouritesListFragment.newInstance(intent.getLongExtra(AppRouter.KEY_ID, NO_ID)) - replace(R.id.container, fragment) - } - } } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouritesContainerFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouritesContainerFragment.kt index 3d5c6573a..8bcd606b1 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouritesContainerFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouritesContainerFragment.kt @@ -8,7 +8,9 @@ import android.view.ViewStub import androidx.appcompat.view.ActionMode import androidx.core.view.isGone import androidx.core.view.isVisible +import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels +import androidx.recyclerview.widget.RecyclerView import coil3.ImageLoader import com.google.android.material.tabs.TabLayoutMediator import dagger.hilt.android.AndroidEntryPoint @@ -16,9 +18,11 @@ import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.nav.router import org.koitharu.kotatsu.core.ui.BaseFragment import org.koitharu.kotatsu.core.ui.util.ActionModeListener +import org.koitharu.kotatsu.core.ui.util.RecyclerViewOwner import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver import org.koitharu.kotatsu.core.util.ext.addMenuProvider import org.koitharu.kotatsu.core.util.ext.enqueueWith +import org.koitharu.kotatsu.core.util.ext.findCurrentPagerFragment import org.koitharu.kotatsu.core.util.ext.newImageRequest import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observeEvent @@ -30,14 +34,20 @@ import org.koitharu.kotatsu.databinding.ItemEmptyStateBinding import javax.inject.Inject @AndroidEntryPoint -class FavouritesContainerFragment : BaseFragment(), ActionModeListener, - ViewStub.OnInflateListener, View.OnClickListener { +class FavouritesContainerFragment : BaseFragment(), + ActionModeListener, + RecyclerViewOwner, + ViewStub.OnInflateListener, + View.OnClickListener { @Inject lateinit var coil: ImageLoader private val viewModel: FavouritesContainerViewModel by viewModels() + override val recyclerView: RecyclerView? + get() = (findCurrentFragment() as? RecyclerViewOwner)?.recyclerView + override fun onCreateViewBinding( inflater: LayoutInflater, container: ViewGroup?, @@ -103,4 +113,10 @@ class FavouritesContainerFragment : BaseFragment diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/history/ui/HistoryActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/history/ui/HistoryActivity.kt index 2b99205fe..0cddefd99 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/history/ui/HistoryActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/history/ui/HistoryActivity.kt @@ -1,33 +1,5 @@ package org.koitharu.kotatsu.history.ui -import android.os.Bundle -import androidx.fragment.app.commit -import com.google.android.material.appbar.AppBarLayout -import dagger.hilt.android.AndroidEntryPoint -import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.core.ui.BaseActivity -import org.koitharu.kotatsu.databinding.ActivityContainerBinding -import org.koitharu.kotatsu.main.ui.owners.AppBarOwner +import org.koitharu.kotatsu.core.ui.FragmentContainerActivity -@AndroidEntryPoint -class HistoryActivity : - BaseActivity(), - AppBarOwner { - - override val appBar: AppBarLayout - get() = viewBinding.appbar - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(ActivityContainerBinding.inflate(layoutInflater)) - supportActionBar?.setDisplayHomeAsUpEnabled(true) - val fm = supportFragmentManager - if (fm.findFragmentById(R.id.container) == null) { - fm.commit { - setReorderingAllowed(true) - val fragment = HistoryListFragment() - replace(R.id.container, fragment) - } - } - } -} +class HistoryActivity : FragmentContainerActivity(HistoryListFragment::class.java) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/MangaListFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/MangaListFragment.kt index 48ab7eddc..37199ef6c 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/MangaListFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/MangaListFragment.kt @@ -12,6 +12,7 @@ import androidx.annotation.CallSuper import androidx.appcompat.view.ActionMode import androidx.collection.ArraySet import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.RecyclerView import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import coil3.ImageLoader import dagger.hilt.android.AndroidEntryPoint @@ -31,6 +32,7 @@ import org.koitharu.kotatsu.core.ui.list.FitHeightLinearLayoutManager import org.koitharu.kotatsu.core.ui.list.ListSelectionController import org.koitharu.kotatsu.core.ui.list.PaginationScrollListener import org.koitharu.kotatsu.core.ui.list.fastscroll.FastScroller +import org.koitharu.kotatsu.core.ui.util.RecyclerViewOwner import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver import org.koitharu.kotatsu.core.ui.widgets.TipView import org.koitharu.kotatsu.core.util.ShareHelper @@ -62,6 +64,7 @@ abstract class MangaListFragment : BaseFragment(), PaginationScrollListener.Callback, MangaListListener, + RecyclerViewOwner, SwipeRefreshLayout.OnRefreshListener, ListSelectionController.Callback, FastScroller.FastScrollListener { @@ -87,6 +90,9 @@ abstract class MangaListFragment : protected val selectedItems: Set get() = collectSelectedItems() + override val recyclerView: RecyclerView? + get() = viewBinding?.recyclerView + override fun onCreateViewBinding( inflater: LayoutInflater, container: ViewGroup?, @@ -159,8 +165,7 @@ abstract class MangaListFragment : override fun onTagClick(manga: Manga, tag: MangaTag, view: View) { if (selectionController?.onItemClick(manga.id) != true) { - // TODO dialog - router.openList(tag) + router.showTagDialog(tag) } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainNavigationDelegate.kt b/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainNavigationDelegate.kt index 8d6c0df78..e26cb70c0 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainNavigationDelegate.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainNavigationDelegate.kt @@ -26,8 +26,7 @@ import org.koitharu.kotatsu.bookmarks.ui.AllBookmarksFragment import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.NavItem import org.koitharu.kotatsu.core.ui.util.RecyclerViewOwner -import org.koitharu.kotatsu.core.util.ext.firstVisibleItemPosition -import org.koitharu.kotatsu.core.util.ext.isAnimationsEnabled +import org.koitharu.kotatsu.core.util.ext.smoothScrollToTop import org.koitharu.kotatsu.explore.ui.ExploreFragment import org.koitharu.kotatsu.favourites.ui.container.FavouritesContainerFragment import org.koitharu.kotatsu.history.ui.HistoryListFragment @@ -64,15 +63,8 @@ class MainNavigationDelegate( override fun onNavigationItemReselected(item: MenuItem) { val fragment = fragmentManager.findFragmentByTag(TAG_PRIMARY) - if (fragment == null || fragment !is RecyclerViewOwner || fragment.view == null) { - return - } - val recyclerView = fragment.recyclerView - if (recyclerView.context.isAnimationsEnabled) { - recyclerView.smoothScrollToPosition(0) - } else { - recyclerView.firstVisibleItemPosition = 0 - } + val recyclerView = (fragment as? RecyclerViewOwner)?.recyclerView ?: return + recyclerView.smoothScrollToTop() } override fun handleOnBackPressed() { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/protect/ProtectActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/protect/ProtectActivity.kt index 5133d5450..c05462db3 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/protect/ProtectActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/protect/ProtectActivity.kt @@ -4,6 +4,7 @@ import android.content.Context import android.content.Intent import android.os.Bundle import android.text.Editable +import android.view.Gravity import android.view.KeyEvent import android.view.View import android.view.WindowManager @@ -20,6 +21,7 @@ import dagger.hilt.android.AndroidEntryPoint import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.ui.BaseActivity import org.koitharu.kotatsu.core.ui.util.DefaultTextWatcher +import org.koitharu.kotatsu.core.util.ext.consumeInsetsAsPadding import org.koitharu.kotatsu.core.util.ext.getDisplayMessage import org.koitharu.kotatsu.core.util.ext.getParcelableExtraCompat import org.koitharu.kotatsu.core.util.ext.observe @@ -41,6 +43,7 @@ class ProtectActivity : super.onCreate(savedInstanceState) window.addFlags(WindowManager.LayoutParams.FLAG_SECURE) setContentView(ActivityProtectBinding.inflate(layoutInflater)) + viewBinding.root.consumeInsetsAsPadding(Gravity.FILL) viewBinding.editPassword.setOnEditorActionListener(this) viewBinding.editPassword.addTextChangedListener(this) viewBinding.buttonNext.setOnClickListener(this) 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 bdce762ed..00dc714ea 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 @@ -394,7 +394,7 @@ class ReaderViewModel @Inject constructor( private fun loadImpl() { loadingJob = launchLoadingJob(Dispatchers.Default) { - val details = detailsLoadUseCase.invoke(intent).first { x -> x.isLoaded } + val details = detailsLoadUseCase.invoke(intent, force = false).first { x -> x.isLoaded } mangaDetails.value = details chaptersLoader.init(details) val manga = details.toManga() diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/kitsu/ui/KitsuAuthActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/kitsu/ui/KitsuAuthActivity.kt index 38bf3164b..d50b47e50 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/kitsu/ui/KitsuAuthActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/kitsu/ui/KitsuAuthActivity.kt @@ -4,11 +4,13 @@ import android.annotation.SuppressLint import android.content.Intent import android.os.Bundle import android.text.Editable +import android.view.Gravity import android.view.View import androidx.core.net.toUri import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.ui.BaseActivity import org.koitharu.kotatsu.core.ui.util.DefaultTextWatcher +import org.koitharu.kotatsu.core.util.ext.consumeInsetsAsPadding import org.koitharu.kotatsu.databinding.ActivityKitsuAuthBinding import org.koitharu.kotatsu.parsers.util.urlEncoded @@ -19,6 +21,7 @@ class KitsuAuthActivity : BaseActivity(), View.OnClick override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(ActivityKitsuAuthBinding.inflate(layoutInflater)) + viewBinding.root.consumeInsetsAsPadding(Gravity.FILL) viewBinding.buttonCancel.setOnClickListener(this) viewBinding.buttonDone.setOnClickListener(this) viewBinding.editEmail.addTextChangedListener(this) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/MangaListActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/MangaListActivity.kt index bf7bcc6d8..2204ed113 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/MangaListActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/MangaListActivity.kt @@ -218,7 +218,7 @@ class MangaListActivity : filterOwner.filterCoordinator.setSortOrder(sortOrder) } if (filter != null) { - filterOwner.filterCoordinator.set(filter) + filterOwner.filterCoordinator.setAdjusted(filter) } } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/settings/nav/NavConfigFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/settings/nav/NavConfigFragment.kt index 616f9b652..2baf0756d 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/settings/nav/NavConfigFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/settings/nav/NavConfigFragment.kt @@ -32,8 +32,8 @@ class NavConfigFragment : BaseFragment(), Recycl private var reorderHelper: ItemTouchHelper? = null private val viewModel by viewModels() - override val recyclerView: RecyclerView - get() = requireViewBinding().recyclerView + override val recyclerView: RecyclerView? + get() = viewBinding?.recyclerView override fun onCreateViewBinding( inflater: LayoutInflater, diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/settings/protect/ProtectSetupActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/settings/protect/ProtectSetupActivity.kt index 93eab4552..a2e0d5d82 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/settings/protect/ProtectSetupActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/settings/protect/ProtectSetupActivity.kt @@ -4,6 +4,7 @@ import android.content.pm.PackageManager import android.os.Build import android.os.Bundle import android.text.Editable +import android.view.Gravity import android.view.KeyEvent import android.view.View import android.view.WindowManager @@ -17,6 +18,7 @@ import dagger.hilt.android.AndroidEntryPoint import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.ui.BaseActivity import org.koitharu.kotatsu.core.ui.util.DefaultTextWatcher +import org.koitharu.kotatsu.core.util.ext.consumeInsetsAsPadding import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observeEvent import org.koitharu.kotatsu.databinding.ActivitySetupProtectBinding @@ -37,6 +39,7 @@ class ProtectSetupActivity : super.onCreate(savedInstanceState) window.addFlags(WindowManager.LayoutParams.FLAG_SECURE) setContentView(ActivitySetupProtectBinding.inflate(layoutInflater)) + viewBinding.root.consumeInsetsAsPadding(Gravity.FILL) viewBinding.editPassword.addTextChangedListener(this) viewBinding.editPassword.setOnEditorActionListener(this) viewBinding.buttonNext.setOnClickListener(this) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/settings/reader/ReaderTapGridConfigActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/settings/reader/ReaderTapGridConfigActivity.kt index 76201176c..fe907c48a 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/settings/reader/ReaderTapGridConfigActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/settings/reader/ReaderTapGridConfigActivity.kt @@ -39,7 +39,7 @@ class ReaderTapGridConfigActivity : BaseActivity() - override val recyclerView: RecyclerView - get() = requireViewBinding().recyclerView + override val recyclerView: RecyclerView? + get() = viewBinding?.recyclerView override fun onCreateViewBinding( inflater: LayoutInflater, diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/stats/data/StatsDao.kt b/app/src/main/kotlin/org/koitharu/kotatsu/stats/data/StatsDao.kt index f0e3cbe56..c2a2ac4d3 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/stats/data/StatsDao.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/stats/data/StatsDao.kt @@ -40,6 +40,7 @@ abstract class StatsDao { favouriteCategories: Set ): Map { val conditions = ArrayList() + conditions.add("(SELECT deleted_at FROM history WHERE history.manga_id = stats.manga_id) = 0") conditions.add("stats.started_at >= $fromDate") if (favouriteCategories.isNotEmpty()) { val ids = favouriteCategories.joinToString(",") diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/suggestions/ui/SuggestionsActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/suggestions/ui/SuggestionsActivity.kt index c26c4fb6b..80f2f9064 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/suggestions/ui/SuggestionsActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/suggestions/ui/SuggestionsActivity.kt @@ -1,32 +1,5 @@ package org.koitharu.kotatsu.suggestions.ui -import android.os.Bundle -import androidx.fragment.app.commit -import com.google.android.material.appbar.AppBarLayout -import dagger.hilt.android.AndroidEntryPoint -import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.core.ui.BaseActivity -import org.koitharu.kotatsu.databinding.ActivityContainerBinding -import org.koitharu.kotatsu.main.ui.owners.AppBarOwner +import org.koitharu.kotatsu.core.ui.FragmentContainerActivity -@AndroidEntryPoint -class SuggestionsActivity : - BaseActivity(), - AppBarOwner { - - override val appBar: AppBarLayout - get() = viewBinding.appbar - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(ActivityContainerBinding.inflate(layoutInflater)) - supportActionBar?.setDisplayHomeAsUpEnabled(true) - val fm = supportFragmentManager - if (fm.findFragmentById(R.id.container) == null) { - fm.commit { - setReorderingAllowed(true) - replace(R.id.container, SuggestionsFragment::class.java, null) - } - } - } -} +class SuggestionsActivity : FragmentContainerActivity(SuggestionsFragment::class.java) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/sync/ui/SyncAuthActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/sync/ui/SyncAuthActivity.kt index 7cca04e4e..94288ed29 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/sync/ui/SyncAuthActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/sync/ui/SyncAuthActivity.kt @@ -6,6 +6,7 @@ import android.accounts.AccountManager import android.os.Bundle import android.text.Editable import android.text.TextWatcher +import android.view.Gravity import android.view.View import android.widget.Button import android.widget.Toast @@ -20,6 +21,7 @@ import dagger.hilt.android.AndroidEntryPoint import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.ui.BaseActivity import org.koitharu.kotatsu.core.ui.util.DefaultTextWatcher +import org.koitharu.kotatsu.core.util.ext.consumeInsetsAsPadding import org.koitharu.kotatsu.core.util.ext.getDisplayMessage import org.koitharu.kotatsu.core.util.ext.getParcelableExtraCompat import org.koitharu.kotatsu.core.util.ext.observe @@ -40,6 +42,7 @@ class SyncAuthActivity : BaseActivity(), View.OnClickLi override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(ActivitySyncAuthBinding.inflate(layoutInflater)) + viewBinding.root.consumeInsetsAsPadding(Gravity.FILL) accountAuthenticatorResponse = intent.getParcelableExtraCompat(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE) accountAuthenticatorResponse?.onRequestContinued() diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/FeedFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/FeedFragment.kt index 827f430bb..dd11ad2fd 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/FeedFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/FeedFragment.kt @@ -18,6 +18,7 @@ import org.koitharu.kotatsu.core.ui.BaseFragment import org.koitharu.kotatsu.core.ui.list.PaginationScrollListener import org.koitharu.kotatsu.core.ui.list.RecyclerScrollKeeper import org.koitharu.kotatsu.core.ui.util.MenuInvalidator +import org.koitharu.kotatsu.core.ui.util.RecyclerViewOwner import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver import org.koitharu.kotatsu.core.ui.widgets.TipView import org.koitharu.kotatsu.core.util.ext.addMenuProvider @@ -38,13 +39,18 @@ import javax.inject.Inject class FeedFragment : BaseFragment(), PaginationScrollListener.Callback, - MangaListListener, SwipeRefreshLayout.OnRefreshListener { + RecyclerViewOwner, + MangaListListener, + SwipeRefreshLayout.OnRefreshListener { @Inject lateinit var coil: ImageLoader private val viewModel by viewModels() + override val recyclerView: RecyclerView? + get() = viewBinding?.recyclerView + override fun onCreateViewBinding( inflater: LayoutInflater, container: ViewGroup?, diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/updates/UpdatesActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/updates/UpdatesActivity.kt index 4d8612912..4004a9afe 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/updates/UpdatesActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/updates/UpdatesActivity.kt @@ -1,33 +1,5 @@ package org.koitharu.kotatsu.tracker.ui.updates -import android.os.Bundle -import androidx.fragment.app.commit -import com.google.android.material.appbar.AppBarLayout -import dagger.hilt.android.AndroidEntryPoint -import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.core.ui.BaseActivity -import org.koitharu.kotatsu.databinding.ActivityContainerBinding -import org.koitharu.kotatsu.main.ui.owners.AppBarOwner +import org.koitharu.kotatsu.core.ui.FragmentContainerActivity -@AndroidEntryPoint -class UpdatesActivity : - BaseActivity(), - AppBarOwner { - - override val appBar: AppBarLayout - get() = viewBinding.appbar - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(ActivityContainerBinding.inflate(layoutInflater)) - supportActionBar?.setDisplayHomeAsUpEnabled(true) - val fm = supportFragmentManager - if (fm.findFragmentById(R.id.container) == null) { - fm.commit { - setReorderingAllowed(true) - val fragment = UpdatesFragment.newInstance() - replace(R.id.container, fragment) - } - } - } -} +class UpdatesActivity : FragmentContainerActivity(UpdatesFragment::class.java) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/updates/UpdatesFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/updates/UpdatesFragment.kt index 01b4afb3c..e918aba0a 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/updates/UpdatesFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/updates/UpdatesFragment.kt @@ -38,9 +38,4 @@ class UpdatesFragment : MangaListFragment() { else -> super.onActionItemClicked(controller, mode, item) } } - - companion object { - - fun newInstance() = UpdatesFragment() - } } diff --git a/app/src/main/res/layout-land/item_empty_state.xml b/app/src/main/res/layout-land/item_empty_state.xml index f039c9cbc..660a4a677 100644 --- a/app/src/main/res/layout-land/item_empty_state.xml +++ b/app/src/main/res/layout-land/item_empty_state.xml @@ -5,7 +5,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" - android:orientation="vertical" + android:orientation="horizontal" android:paddingHorizontal="32dp"> - + android:orientation="vertical"> - + -