Fixes and improvements batch

This commit is contained in:
Koitharu
2025-02-26 16:51:33 +02:00
parent b2f0da9245
commit 5e9dc87470
48 changed files with 475 additions and 266 deletions

View File

@@ -1,38 +1,5 @@
package org.koitharu.kotatsu.bookmarks.ui package org.koitharu.kotatsu.bookmarks.ui
import android.os.Bundle import org.koitharu.kotatsu.core.ui.FragmentContainerActivity
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
@AndroidEntryPoint class AllBookmarksActivity : FragmentContainerActivity(AllBookmarksFragment::class.java)
class AllBookmarksActivity :
BaseActivity<ActivityContainerBinding>(),
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)
}
}
}
}

View File

@@ -370,6 +370,25 @@ class AppRouter private constructor(
}.show() }.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) { fun showErrorDialog(error: Throwable, url: String? = null) {
ErrorDetailsDialog().withArgs(2) { ErrorDetailsDialog().withArgs(2) {
putSerializable(KEY_ERROR, error) putSerializable(KEY_ERROR, error)

View File

@@ -252,7 +252,7 @@ class ExternalPluginContentSource(
publicUrl = getString(COLUMN_PUBLIC_URL), publicUrl = getString(COLUMN_PUBLIC_URL),
rating = getFloat(COLUMN_RATING), rating = getFloat(COLUMN_RATING),
isNsfw = getBooleanOrDefault(COLUMN_IS_NSFW, false), isNsfw = getBooleanOrDefault(COLUMN_IS_NSFW, false),
coverUrl = getString(COLUMN_COVER_URL), coverUrl = getStringOrNull(COLUMN_COVER_URL),
tags = getStringOrNull(COLUMN_TAGS)?.split(':')?.mapNotNullToSet { tags = getStringOrNull(COLUMN_TAGS)?.split(':')?.mapNotNullToSet {
val parts = it.splitTwoParts('=') ?: return@mapNotNullToSet null val parts = it.splitTwoParts('=') ?: return@mapNotNullToSet null
MangaTag(key = parts.first, title = parts.second, source = source) MangaTag(key = parts.first, title = parts.second, source = source)

View File

@@ -20,7 +20,7 @@ class ExternalPluginCursor(private val source: ExternalMangaSource, cursor: Curs
return when { return when {
columnIndex < 0 -> null columnIndex < 0 -> null
isNull(columnIndex) -> null isNull(columnIndex) -> null
else -> getString(columnIndex) else -> getString(columnIndex).takeUnless { it == "null" }
} }
} }

View File

@@ -35,7 +35,7 @@ abstract class BasePreferenceFragment(@StringRes private val titleId: Int) :
@Inject @Inject
lateinit var settings: AppSettings lateinit var settings: AppSettings
override val recyclerView: RecyclerView override val recyclerView: RecyclerView?
get() = listView get() = listView
override fun onAttach(context: Context) { override fun onAttach(context: Context) {

View File

@@ -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<out Fragment>) :
BaseActivity<ActivityContainerBinding>(),
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
}

View File

@@ -4,6 +4,7 @@ import android.view.Gravity
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.annotation.GravityInt import androidx.annotation.GravityInt
import androidx.core.graphics.Insets
import androidx.core.view.OnApplyWindowInsetsListener import androidx.core.view.OnApplyWindowInsetsListener
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
@@ -14,19 +15,30 @@ import org.koitharu.kotatsu.core.util.ext.start
class InsetsToMarginsListener( class InsetsToMarginsListener(
@GravityInt @GravityInt
private val sides: Int, private val sides: Int,
private val baseMargins: Insets,
) : OnApplyWindowInsetsListener { ) : OnApplyWindowInsetsListener {
private val insetType = WindowInsetsCompat.Type.systemBars()
override fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat { override fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {
val barsInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()) val barsInsets = insets.getInsets(insetType)
v.updateLayoutParams<ViewGroup.MarginLayoutParams> { v.updateLayoutParams<ViewGroup.MarginLayoutParams> {
if (sides and Gravity.START == Gravity.START) marginStart = barsInsets.start(v) if (sides and Gravity.START == Gravity.START) {
if (sides and Gravity.TOP == Gravity.TOP) topMargin = barsInsets.top marginStart = barsInsets.start(v) + baseMargins.start(v)
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.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) return WindowInsetsCompat.Builder(insets)
.setInsets( .setInsets(
WindowInsetsCompat.Type.systemBars(), insetType,
barsInsets.consumeRelative( barsInsets.consumeRelative(
v, v,
start = sides and Gravity.START == Gravity.START, start = sides and Gravity.START == Gravity.START,

View File

@@ -3,6 +3,7 @@ package org.koitharu.kotatsu.core.ui.util
import android.view.Gravity import android.view.Gravity
import android.view.View import android.view.View
import androidx.annotation.GravityInt import androidx.annotation.GravityInt
import androidx.core.graphics.Insets
import androidx.core.view.OnApplyWindowInsetsListener import androidx.core.view.OnApplyWindowInsetsListener
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import org.koitharu.kotatsu.core.util.ext.consumeRelative import org.koitharu.kotatsu.core.util.ext.consumeRelative
@@ -12,19 +13,42 @@ import org.koitharu.kotatsu.core.util.ext.start
class InsetsToPaddingListener( class InsetsToPaddingListener(
@GravityInt @GravityInt
private val sides: Int, private val sides: Int,
private val basePaddings: Insets,
) : OnApplyWindowInsetsListener { ) : OnApplyWindowInsetsListener {
private val insetType = WindowInsetsCompat.Type.systemBars()
override fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat { override fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {
val barsInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()) val barsInsets = insets.getInsets(insetType)
v.setPaddingRelative( v.setPaddingRelative(
/* start = */ if (sides and Gravity.START == Gravity.START) barsInsets.start(v) else v.paddingStart, /* start = */
/* top = */ if (sides and Gravity.TOP == Gravity.TOP) barsInsets.top else v.paddingTop, if (sides and Gravity.START == Gravity.START) {
/* end = */ if (sides and Gravity.END == Gravity.END) barsInsets.end(v) else v.paddingEnd, barsInsets.start(v) + basePaddings.start(v)
/* bottom = */ if (sides and Gravity.BOTTOM == Gravity.BOTTOM) barsInsets.bottom else v.paddingBottom, } 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) return WindowInsetsCompat.Builder(insets)
.setInsets( .setInsets(
WindowInsetsCompat.Type.systemBars(), insetType,
barsInsets.consumeRelative( barsInsets.consumeRelative(
v, v,
start = sides and Gravity.START == Gravity.START, start = sides and Gravity.START == Gravity.START,

View File

@@ -4,5 +4,5 @@ import androidx.recyclerview.widget.RecyclerView
interface RecyclerViewOwner { interface RecyclerViewOwner {
val recyclerView: RecyclerView val recyclerView: RecyclerView?
} }

View File

@@ -1,10 +1,10 @@
package org.koitharu.kotatsu.core.util.ext package org.koitharu.kotatsu.core.util.ext
import android.content.Context import android.content.Context
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.widget.ImageView import android.widget.ImageView
import androidx.core.graphics.ColorUtils import androidx.core.graphics.ColorUtils
import androidx.core.graphics.drawable.toDrawable
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import coil3.Extras import coil3.Extras
import coil3.ImageLoader import coil3.ImageLoader
@@ -116,8 +116,8 @@ fun ImageRequest.Builder.defaultPlaceholders(context: Context): ImageRequest.Bui
0.25f, 0.25f,
) )
return placeholder(AnimatedPlaceholderDrawable(context)) return placeholder(AnimatedPlaceholderDrawable(context))
.fallback(ColorDrawable(context.getThemeColor(materialR.attr.colorSurfaceContainer))) .fallback(context.getThemeColor(materialR.attr.colorSurfaceContainer).toDrawable())
.error(ColorDrawable(errorColor)) .error(errorColor.toDrawable())
} }
private fun ImageView.ScaleType.toCoilScale(): Scale = if (this == ImageView.ScaleType.CENTER_CROP) { private fun ImageView.ScaleType.toCoilScale(): Scale = if (this == ImageView.ScaleType.CENTER_CROP) {

View File

@@ -1,6 +1,10 @@
package org.koitharu.kotatsu.core.util.ext 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.LinearLayoutManager
import androidx.recyclerview.widget.LinearSmoothScroller
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.StaggeredGridLayoutManager import androidx.recyclerview.widget.StaggeredGridLayoutManager
import com.hannesdorfmann.adapterdelegates4.dsl.AdapterDelegateViewBindingViewHolder import com.hannesdorfmann.adapterdelegates4.dsl.AdapterDelegateViewBindingViewHolder
@@ -52,7 +56,7 @@ fun <T> RecyclerView.ViewHolder.getItem(clazz: Class<T>): T? {
val RecyclerView.isScrolledToTop: Boolean val RecyclerView.isScrolledToTop: Boolean
get() { get() {
if (childCount == 0) { if (isEmpty()) {
return true return true
} }
val holder = findViewHolderForAdapterPosition(0) val holder = findViewHolderForAdapterPosition(0)
@@ -72,3 +76,35 @@ val RecyclerView.LayoutManager?.isLayoutReversed
is StaggeredGridLayoutManager -> reverseLayout is StaggeredGridLayoutManager -> reverseLayout
else -> false 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

View File

@@ -9,10 +9,13 @@ import android.widget.Checkable
import androidx.annotation.GravityInt import androidx.annotation.GravityInt
import androidx.appcompat.widget.ActionMenuView import androidx.appcompat.widget.ActionMenuView
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.core.graphics.Insets
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.children import androidx.core.view.children
import androidx.core.view.descendants import androidx.core.view.descendants
import androidx.core.view.isVisible 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
import androidx.recyclerview.widget.RecyclerView.ViewHolder import androidx.recyclerview.widget.RecyclerView.ViewHolder
import androidx.swiperefreshlayout.widget.CircularProgressDrawable import androidx.swiperefreshlayout.widget.CircularProgressDrawable
@@ -72,6 +75,11 @@ fun ViewPager2.findCurrentViewHolder(): ViewHolder? {
return recyclerView?.findViewHolderForAdapterPosition(currentItem) 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() { fun View.resetTransformations() {
alpha = 1f alpha = 1f
translationX = 0f translationX = 0f
@@ -187,12 +195,18 @@ fun Chip.setProgressIcon() {
progressDrawable.start() 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( fun View.consumeInsetsAsPadding(@GravityInt sides: Int) = ViewCompat.setOnApplyWindowInsetsListener(
this, this,
InsetsToPaddingListener(sides), InsetsToPaddingListener(sides, paddingSnapshot()),
) )
fun View.consumeInsetsAsMargins(@GravityInt sides: Int) = ViewCompat.setOnApplyWindowInsetsListener( fun View.consumeInsetsAsMargins(@GravityInt sides: Int) = ViewCompat.setOnApplyWindowInsetsListener(
this, this,
InsetsToMarginsListener(sides), InsetsToMarginsListener(sides, marginsSnapshot()),
) )

View File

@@ -4,6 +4,8 @@ import org.koitharu.kotatsu.core.model.isLocal
import org.koitharu.kotatsu.local.domain.model.LocalManga import org.koitharu.kotatsu.local.domain.model.LocalManga
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter 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 import org.koitharu.kotatsu.reader.data.filterChapters
data class MangaDetails( data class MangaDetails(
@@ -29,6 +31,12 @@ data class MangaDetails(
val local: LocalManga? val local: LocalManga?
get() = localManga ?: if (manga.isLocal) LocalManga(manga) else null 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 toManga() = manga
fun filterChapters(branch: String?) = MangaDetails( fun filterChapters(branch: String?) = MangaDetails(

View File

@@ -6,6 +6,7 @@ import android.text.Spanned
import android.text.style.ForegroundColorSpan import android.text.style.ForegroundColorSpan
import androidx.core.text.getSpans import androidx.core.text.getSpans
import androidx.core.text.parseAsHtml import androidx.core.text.parseAsHtml
import coil3.request.CachePolicy
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@@ -15,6 +16,7 @@ import kotlinx.coroutines.runInterruptible
import okio.IOException import okio.IOException
import org.koitharu.kotatsu.core.model.isLocal import org.koitharu.kotatsu.core.model.isLocal
import org.koitharu.kotatsu.core.nav.MangaIntent 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.MangaDataRepository
import org.koitharu.kotatsu.core.parser.MangaRepository import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.util.ext.peek import org.koitharu.kotatsu.core.util.ext.peek
@@ -40,7 +42,7 @@ class DetailsLoadUseCase @Inject constructor(
private val newChaptersUseCaseProvider: Provider<CheckNewChaptersUseCase>, private val newChaptersUseCaseProvider: Provider<CheckNewChaptersUseCase>,
) { ) {
operator fun invoke(intent: MangaIntent): Flow<MangaDetails> = channelFlow { operator fun invoke(intent: MangaIntent, force: Boolean): Flow<MangaDetails> = channelFlow {
val manga = requireNotNull(mangaDataRepository.resolveIntent(intent)) { val manga = requireNotNull(mangaDataRepository.resolveIntent(intent)) {
"Cannot resolve intent $intent" "Cannot resolve intent $intent"
}.let { m -> }.let { m ->
@@ -59,7 +61,7 @@ class DetailsLoadUseCase @Inject constructor(
null null
} }
try { try {
val details = getDetails(manga) val details = getDetails(manga, force)
launch { mangaDataRepository.updateChapters(details) } launch { mangaDataRepository.updateChapters(details) }
launch { updateTracker(details) } launch { updateTracker(details) }
send( 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) 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 -> }.recoverNotNull { e ->
if (e is NotFoundException) { if (e is NotFoundException) {
recoverUseCase(seed) recoverUseCase(seed)

View File

@@ -111,7 +111,6 @@ import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.parsers.util.ifNullOrEmpty import org.koitharu.kotatsu.parsers.util.ifNullOrEmpty
import org.koitharu.kotatsu.parsers.util.nullIfEmpty import org.koitharu.kotatsu.parsers.util.nullIfEmpty
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo
import org.koitharu.kotatsu.search.domain.SearchKind
import javax.inject.Inject import javax.inject.Inject
import kotlin.math.roundToInt import kotlin.math.roundToInt
import com.google.android.material.R as materialR import com.google.android.material.R as materialR
@@ -171,6 +170,7 @@ class DetailsActivity :
val appRouter = router val appRouter = router
viewModel.mangaDetails.filterNotNull().observe(this, ::onMangaUpdated) viewModel.mangaDetails.filterNotNull().observe(this, ::onMangaUpdated)
viewModel.coverUrl.observe(this, ::loadCover)
viewModel.onMangaRemoved.observeEvent(this, ::onMangaRemoved) viewModel.onMangaRemoved.observeEvent(this, ::onMangaRemoved)
viewModel.onError viewModel.onError
.filterNot { appRouter.isChapterPagesSheetShown() } .filterNot { appRouter.isChapterPagesSheetShown() }
@@ -217,8 +217,9 @@ class DetailsActivity :
override fun onClick(v: View) { override fun onClick(v: View) {
when (v.id) { when (v.id) {
R.id.textView_author -> { R.id.textView_author -> {
val author = viewModel.manga.value?.author ?: return val manga = viewModel.manga.value
router.openSearch(author, SearchKind.AUTHOR) val author = manga?.author ?: return
router.showAuthorDialog(author, manga.source)
} }
R.id.textView_source -> { R.id.textView_source -> {
@@ -239,7 +240,7 @@ class DetailsActivity :
R.id.imageView_cover -> { R.id.imageView_cover -> {
val manga = viewModel.manga.value ?: return val manga = viewModel.manga.value ?: return
router.openImage( router.openImage(
url = manga.largeCoverUrl.ifNullOrEmpty { manga.coverUrl } ?: return, url = viewModel.coverUrl.value ?: return,
source = manga.source, source = manga.source,
anchor = v, anchor = v,
) )
@@ -443,7 +444,6 @@ class DetailsActivity :
private fun onMangaUpdated(details: MangaDetails) { private fun onMangaUpdated(details: MangaDetails) {
val manga = details.toManga() val manga = details.toManga()
loadCover(manga)
with(viewBinding) { with(viewBinding) {
textViewTitle.text = manga.title textViewTitle.text = manga.title
textViewSubtitle.textAndVisible = manga.altTitle textViewSubtitle.textAndVisible = manga.altTitle
@@ -560,8 +560,7 @@ class DetailsActivity :
viewBinding.chipsTags.setChips(listMapper.mapTags(manga.tags)) viewBinding.chipsTags.setChips(listMapper.mapTags(manga.tags))
} }
private fun loadCover(manga: Manga) { private fun loadCover(imageUrl: String?) {
val imageUrl = manga.largeCoverUrl.ifNullOrEmpty { manga.coverUrl }
val lastResult = CoilUtils.result(viewBinding.imageViewCover) val lastResult = CoilUtils.result(viewBinding.imageViewCover)
if (lastResult is SuccessResult && lastResult.request.data == imageUrl) { if (lastResult is SuccessResult && lastResult.request.data == imageUrl) {
return return
@@ -571,10 +570,9 @@ class DetailsActivity :
.size(CoverSizeResolver(viewBinding.imageViewCover)) .size(CoverSizeResolver(viewBinding.imageViewCover))
.scale(Scale.FILL) .scale(Scale.FILL)
.data(imageUrl) .data(imageUrl)
.mangaSourceExtra(manga.source) .mangaSourceExtra(viewModel.getMangaOrNull()?.source)
.crossfade(this) .crossfade(this)
.lifecycle(this) .lifecycle(this)
.placeholderMemoryCacheKey(manga.coverUrl)
val previousDrawable = lastResult?.drawable val previousDrawable = lastResult?.drawable
if (previousDrawable != null) { if (previousDrawable != null) {
request.fallback(previousDrawable) request.fallback(previousDrawable)

View File

@@ -176,7 +176,7 @@ class DetailsViewModel @Inject constructor(
get() = selectedBranch.value get() = selectedBranch.value
init { init {
loadingJob = doLoad() loadingJob = doLoad(force = false)
launchJob(Dispatchers.Default) { launchJob(Dispatchers.Default) {
val manga = mangaDetails.firstOrNull { !it?.chapters.isNullOrEmpty() } ?: return@launchJob val manga = mangaDetails.firstOrNull { !it?.chapters.isNullOrEmpty() } ?: return@launchJob
val h = history.firstOrNull() val h = history.firstOrNull()
@@ -192,7 +192,7 @@ class DetailsViewModel @Inject constructor(
fun reload() { fun reload() {
loadingJob.cancel() loadingJob.cancel()
loadingJob = doLoad() loadingJob = doLoad(force = true)
} }
fun updateScrobbling(index: Int, rating: Float, status: ScrobblingStatus?) { fun updateScrobbling(index: Int, rating: Float, status: ScrobblingStatus?) {
@@ -223,8 +223,8 @@ class DetailsViewModel @Inject constructor(
} }
} }
private fun doLoad() = launchLoadingJob(Dispatchers.Default) { private fun doLoad(force: Boolean) = launchLoadingJob(Dispatchers.Default) {
detailsLoadUseCase.invoke(intent) detailsLoadUseCase.invoke(intent, force)
.onEachWhile { .onEachWhile {
if (it.allChapters.isNotEmpty()) { if (it.allChapters.isNotEmpty()) {
val manga = it.toManga() val manga = it.toManga()

View File

@@ -6,6 +6,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.view.ActionMode import androidx.appcompat.view.ActionMode
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator import com.google.android.material.tabs.TabLayoutMediator
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver 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.sheet.BaseAdaptiveSheet
import org.koitharu.kotatsu.core.ui.util.ActionModeListener import org.koitharu.kotatsu.core.ui.util.ActionModeListener
import org.koitharu.kotatsu.core.ui.util.MenuInvalidator 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.util.ReversibleActionObserver
import org.koitharu.kotatsu.core.util.ext.doOnPageChanged 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.menuView
import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.core.util.ext.recyclerView import org.koitharu.kotatsu.core.util.ext.recyclerView
import org.koitharu.kotatsu.core.util.ext.setTabsEnabled 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.databinding.SheetChaptersPagesBinding
import org.koitharu.kotatsu.details.ui.DetailsViewModel import org.koitharu.kotatsu.details.ui.DetailsViewModel
import org.koitharu.kotatsu.details.ui.ReadButtonDelegate import org.koitharu.kotatsu.details.ui.ReadButtonDelegate
@@ -34,7 +38,10 @@ import org.koitharu.kotatsu.download.ui.worker.DownloadStartedObserver
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
class ChaptersPagesSheet : BaseAdaptiveSheet<SheetChaptersPagesBinding>(), ActionModeListener, AdaptiveSheetCallback { class ChaptersPagesSheet : BaseAdaptiveSheet<SheetChaptersPagesBinding>(),
TabLayout.OnTabSelectedListener,
ActionModeListener,
AdaptiveSheetCallback {
@Inject @Inject
lateinit var settings: AppSettings lateinit var settings: AppSettings
@@ -63,6 +70,7 @@ class ChaptersPagesSheet : BaseAdaptiveSheet<SheetChaptersPagesBinding>(), Actio
binding.pager.adapter = adapter binding.pager.adapter = adapter
binding.pager.doOnPageChanged(::onPageChanged) binding.pager.doOnPageChanged(::onPageChanged)
TabLayoutMediator(binding.tabs, binding.pager, adapter).attach() TabLayoutMediator(binding.tabs, binding.pager, adapter).attach()
binding.tabs.addOnTabSelectedListener(this)
binding.pager.setCurrentItem(defaultTab, false) binding.pager.setCurrentItem(defaultTab, false)
binding.tabs.isVisible = adapter.itemCount > 1 binding.tabs.isVisible = adapter.itemCount > 1
@@ -109,6 +117,17 @@ class ChaptersPagesSheet : BaseAdaptiveSheet<SheetChaptersPagesBinding>(), Actio
viewBinding?.toolbar?.menuView?.isVisible = state != STATE_COLLAPSED 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() { override fun expandAndLock() {
super.expandAndLock() super.expandAndLock()
adjustLockState() adjustLockState()

View File

@@ -71,6 +71,10 @@ abstract class ChaptersPagesViewModel(
.withErrorHandling() .withErrorHandling()
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, null) .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( val isChaptersReversed = settings.observeAsStateFlow(
scope = viewModelScope + Dispatchers.Default, scope = viewModelScope + Dispatchers.Default,
key = AppSettings.KEY_REVERSE_CHAPTERS, key = AppSettings.KEY_REVERSE_CHAPTERS,

View File

@@ -11,6 +11,7 @@ import android.view.ViewGroup
import androidx.appcompat.view.ActionMode import androidx.appcompat.view.ActionMode
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import coil3.ImageLoader import coil3.ImageLoader
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R 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.ListSelectionController
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.ui.util.PagerNestedScrollHelper 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.ui.util.ReversibleActionObserver
import org.koitharu.kotatsu.core.util.ext.consumeInsetsAsPadding import org.koitharu.kotatsu.core.util.ext.consumeInsetsAsPadding
import org.koitharu.kotatsu.core.util.ext.findAppCompatDelegate import org.koitharu.kotatsu.core.util.ext.findAppCompatDelegate
@@ -43,6 +45,7 @@ import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
class BookmarksFragment : BaseFragment<FragmentMangaBookmarksBinding>(), class BookmarksFragment : BaseFragment<FragmentMangaBookmarksBinding>(),
OnListItemClickListener<Bookmark>, OnListItemClickListener<Bookmark>,
RecyclerViewOwner,
ListSelectionController.Callback { ListSelectionController.Callback {
private val activityViewModel by ChaptersPagesViewModel.ActivityVMLazy(this) private val activityViewModel by ChaptersPagesViewModel.ActivityVMLazy(this)
@@ -54,6 +57,9 @@ class BookmarksFragment : BaseFragment<FragmentMangaBookmarksBinding>(),
@Inject @Inject
lateinit var settings: AppSettings lateinit var settings: AppSettings
override val recyclerView: RecyclerView?
get() = viewBinding?.recyclerView
private var bookmarksAdapter: BookmarksAdapter? = null private var bookmarksAdapter: BookmarksAdapter? = null
private var spanResolver: GridSpanResolver? = null private var spanResolver: GridSpanResolver? = null
private var selectionController: ListSelectionController? = null private var selectionController: ListSelectionController? = null

View File

@@ -12,6 +12,7 @@ import androidx.core.view.isVisible
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.chip.Chip import com.google.android.material.chip.Chip
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers 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.ListSelectionController
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.ui.util.PagerNestedScrollHelper 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.ui.widgets.ChipsView
import org.koitharu.kotatsu.core.util.RecyclerViewScrollCallback import org.koitharu.kotatsu.core.util.RecyclerViewScrollCallback
import org.koitharu.kotatsu.core.util.ext.findAppCompatDelegate import org.koitharu.kotatsu.core.util.ext.findAppCompatDelegate
@@ -48,6 +50,7 @@ class ChaptersFragment :
BaseFragment<FragmentChaptersBinding>(), BaseFragment<FragmentChaptersBinding>(),
OnListItemClickListener<ChapterListItem>, OnListItemClickListener<ChapterListItem>,
OnApplyWindowInsetsListener, OnApplyWindowInsetsListener,
RecyclerViewOwner,
ChipsView.OnChipClickListener { ChipsView.OnChipClickListener {
private val viewModel by ChaptersPagesViewModel.ActivityVMLazy(this) private val viewModel by ChaptersPagesViewModel.ActivityVMLazy(this)
@@ -55,6 +58,9 @@ class ChaptersFragment :
private var chaptersAdapter: ChaptersAdapter? = null private var chaptersAdapter: ChaptersAdapter? = null
private var selectionController: ListSelectionController? = null private var selectionController: ListSelectionController? = null
override val recyclerView: RecyclerView?
get() = viewBinding?.recyclerViewChapters
override fun onCreateViewBinding( override fun onCreateViewBinding(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,

View File

@@ -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.ListSelectionController
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.ui.util.PagerNestedScrollHelper 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.RecyclerViewScrollCallback
import org.koitharu.kotatsu.core.util.ext.consumeInsetsAsPadding import org.koitharu.kotatsu.core.util.ext.consumeInsetsAsPadding
import org.koitharu.kotatsu.core.util.ext.findAppCompatDelegate import org.koitharu.kotatsu.core.util.ext.findAppCompatDelegate
@@ -55,6 +56,7 @@ import kotlin.math.roundToInt
class PagesFragment : class PagesFragment :
BaseFragment<FragmentPagesBinding>(), BaseFragment<FragmentPagesBinding>(),
OnListItemClickListener<PageThumbnail>, OnListItemClickListener<PageThumbnail>,
RecyclerViewOwner,
ListSelectionController.Callback { ListSelectionController.Callback {
@Inject @Inject
@@ -77,6 +79,9 @@ class PagesFragment :
private val spanSizeLookup = SpanSizeLookup() private val spanSizeLookup = SpanSizeLookup()
override val recyclerView: RecyclerView?
get() = viewBinding?.recyclerView
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
pageSaveHelper = pageSaveHelperFactory.create(this) pageSaveHelper = pageSaveHelperFactory.create(this)

View File

@@ -1,30 +1,5 @@
package org.koitharu.kotatsu.details.ui.related package org.koitharu.kotatsu.details.ui.related
import android.os.Bundle import org.koitharu.kotatsu.core.ui.FragmentContainerActivity
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
@AndroidEntryPoint class RelatedMangaActivity : FragmentContainerActivity(RelatedListFragment::class.java)
class RelatedMangaActivity : BaseActivity<ActivityContainerBinding>(), 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)
}
}
}
}

View File

@@ -60,8 +60,8 @@ class ExploreFragment :
private var exploreAdapter: ExploreAdapter? = null private var exploreAdapter: ExploreAdapter? = null
private var sourceSelectionController: ListSelectionController? = null private var sourceSelectionController: ListSelectionController? = null
override val recyclerView: RecyclerView override val recyclerView: RecyclerView?
get() = requireViewBinding().recyclerView get() = viewBinding?.recyclerView
override fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentExploreBinding { override fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentExploreBinding {
return FragmentExploreBinding.inflate(inflater, container, false) return FragmentExploreBinding.inflate(inflater, container, false)

View File

@@ -1,33 +1,18 @@
package org.koitharu.kotatsu.favourites.ui package org.koitharu.kotatsu.favourites.ui
import android.os.Bundle import android.os.Bundle
import androidx.fragment.app.commit
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.nav.AppRouter import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.ui.BaseActivity import org.koitharu.kotatsu.core.ui.FragmentContainerActivity
import org.koitharu.kotatsu.databinding.ActivityContainerBinding
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment.Companion.NO_ID
@AndroidEntryPoint class FavouritesActivity : FragmentContainerActivity(FavouritesListFragment::class.java) {
class FavouritesActivity : BaseActivity<ActivityContainerBinding>() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(ActivityContainerBinding.inflate(layoutInflater))
supportActionBar?.setDisplayHomeAsUpEnabled(true)
val categoryTitle = intent.getStringExtra(AppRouter.KEY_TITLE) val categoryTitle = intent.getStringExtra(AppRouter.KEY_TITLE)
if (categoryTitle != null) { if (categoryTitle != null) {
title = categoryTitle 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)
}
}
} }
} }

View File

@@ -8,7 +8,9 @@ import android.view.ViewStub
import androidx.appcompat.view.ActionMode import androidx.appcompat.view.ActionMode
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.RecyclerView
import coil3.ImageLoader import coil3.ImageLoader
import com.google.android.material.tabs.TabLayoutMediator import com.google.android.material.tabs.TabLayoutMediator
import dagger.hilt.android.AndroidEntryPoint 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.nav.router
import org.koitharu.kotatsu.core.ui.BaseFragment import org.koitharu.kotatsu.core.ui.BaseFragment
import org.koitharu.kotatsu.core.ui.util.ActionModeListener 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.ui.util.ReversibleActionObserver
import org.koitharu.kotatsu.core.util.ext.addMenuProvider import org.koitharu.kotatsu.core.util.ext.addMenuProvider
import org.koitharu.kotatsu.core.util.ext.enqueueWith 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.newImageRequest
import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent import org.koitharu.kotatsu.core.util.ext.observeEvent
@@ -30,14 +34,20 @@ import org.koitharu.kotatsu.databinding.ItemEmptyStateBinding
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
class FavouritesContainerFragment : BaseFragment<FragmentFavouritesContainerBinding>(), ActionModeListener, class FavouritesContainerFragment : BaseFragment<FragmentFavouritesContainerBinding>(),
ViewStub.OnInflateListener, View.OnClickListener { ActionModeListener,
RecyclerViewOwner,
ViewStub.OnInflateListener,
View.OnClickListener {
@Inject @Inject
lateinit var coil: ImageLoader lateinit var coil: ImageLoader
private val viewModel: FavouritesContainerViewModel by viewModels() private val viewModel: FavouritesContainerViewModel by viewModels()
override val recyclerView: RecyclerView?
get() = (findCurrentFragment() as? RecyclerViewOwner)?.recyclerView
override fun onCreateViewBinding( override fun onCreateViewBinding(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
@@ -103,4 +113,10 @@ class FavouritesContainerFragment : BaseFragment<FragmentFavouritesContainerBind
stubEmpty.isVisible = isEmpty stubEmpty.isVisible = isEmpty
} }
} }
private fun findCurrentFragment(): Fragment? {
return childFragmentManager.findCurrentPagerFragment(
viewBinding?.pager ?: return null,
)
}
} }

View File

@@ -11,6 +11,7 @@ import androidx.fragment.app.viewModels
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.ui.list.ListSelectionController import org.koitharu.kotatsu.core.ui.list.ListSelectionController
import org.koitharu.kotatsu.core.util.ext.sortedByOrdinal import org.koitharu.kotatsu.core.util.ext.sortedByOrdinal
import org.koitharu.kotatsu.core.util.ext.withArgs import org.koitharu.kotatsu.core.util.ext.withArgs
@@ -90,10 +91,9 @@ class FavouritesListFragment : MangaListFragment(), PopupMenu.OnMenuItemClickLis
companion object { companion object {
const val NO_ID = 0L const val NO_ID = 0L
const val ARG_CATEGORY_ID = "category_id"
fun newInstance(categoryId: Long) = FavouritesListFragment().withArgs(1) { fun newInstance(categoryId: Long) = FavouritesListFragment().withArgs(1) {
putLong(ARG_CATEGORY_ID, categoryId) putLong(AppRouter.KEY_ID, categoryId)
} }
} }
} }

View File

@@ -16,6 +16,7 @@ import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.plus import kotlinx.coroutines.plus
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ListMode import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.core.prefs.observeAsFlow import org.koitharu.kotatsu.core.prefs.observeAsFlow
@@ -25,7 +26,6 @@ import org.koitharu.kotatsu.core.util.ext.flattenLatest
import org.koitharu.kotatsu.download.ui.worker.DownloadWorker import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
import org.koitharu.kotatsu.favourites.domain.FavoritesListQuickFilter import org.koitharu.kotatsu.favourites.domain.FavoritesListQuickFilter
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment.Companion.ARG_CATEGORY_ID
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment.Companion.NO_ID import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment.Companion.NO_ID
import org.koitharu.kotatsu.history.domain.MarkAsReadUseCase import org.koitharu.kotatsu.history.domain.MarkAsReadUseCase
import org.koitharu.kotatsu.list.domain.ListFilterOption import org.koitharu.kotatsu.list.domain.ListFilterOption
@@ -54,7 +54,7 @@ class FavouritesListViewModel @Inject constructor(
downloadScheduler: DownloadWorker.Scheduler, downloadScheduler: DownloadWorker.Scheduler,
) : MangaListViewModel(settings, downloadScheduler), QuickFilterListener { ) : MangaListViewModel(settings, downloadScheduler), QuickFilterListener {
val categoryId: Long = savedStateHandle[ARG_CATEGORY_ID] ?: NO_ID val categoryId: Long = savedStateHandle[AppRouter.KEY_ID] ?: NO_ID
private val quickFilter = quickFilterFactory.create(categoryId) private val quickFilter = quickFilterFactory.create(categoryId)
private val refreshTrigger = MutableStateFlow(Any()) private val refreshTrigger = MutableStateFlow(Any())
private val limit = MutableStateFlow(PAGE_SIZE) private val limit = MutableStateFlow(PAGE_SIZE)

View File

@@ -267,6 +267,25 @@ class FilterCoordinator @Inject constructor(
currentListFilter.value = value currentListFilter.value = value
} }
fun setAdjusted(value: MangaListFilter) {
var newFilter = value
if (!newFilter.author.isNullOrEmpty() && !capabilities.isAuthorSearchSupported) {
newFilter = newFilter.copy(
query = newFilter.author,
author = null,
)
}
if (!capabilities.isSearchSupported && !newFilter.query.isNullOrEmpty()) {
newFilter = newFilter.copy(
query = null,
)
}
if (!newFilter.query.isNullOrEmpty() && !newFilter.hasNonSearchOptions() && !capabilities.isSearchWithFiltersSupported) {
newFilter = MangaListFilter(query = newFilter.query)
}
set(newFilter)
}
fun setQuery(value: String?) { fun setQuery(value: String?) {
val newQuery = value?.trim()?.nullIfEmpty() val newQuery = value?.trim()?.nullIfEmpty()
currentListFilter.update { oldValue -> currentListFilter.update { oldValue ->

View File

@@ -1,33 +1,5 @@
package org.koitharu.kotatsu.history.ui package org.koitharu.kotatsu.history.ui
import android.os.Bundle import org.koitharu.kotatsu.core.ui.FragmentContainerActivity
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
@AndroidEntryPoint class HistoryActivity : FragmentContainerActivity(HistoryListFragment::class.java)
class HistoryActivity :
BaseActivity<ActivityContainerBinding>(),
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)
}
}
}
}

View File

@@ -12,6 +12,7 @@ import androidx.annotation.CallSuper
import androidx.appcompat.view.ActionMode import androidx.appcompat.view.ActionMode
import androidx.collection.ArraySet import androidx.collection.ArraySet
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import coil3.ImageLoader import coil3.ImageLoader
import dagger.hilt.android.AndroidEntryPoint 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.ListSelectionController
import org.koitharu.kotatsu.core.ui.list.PaginationScrollListener import org.koitharu.kotatsu.core.ui.list.PaginationScrollListener
import org.koitharu.kotatsu.core.ui.list.fastscroll.FastScroller 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.util.ReversibleActionObserver
import org.koitharu.kotatsu.core.ui.widgets.TipView import org.koitharu.kotatsu.core.ui.widgets.TipView
import org.koitharu.kotatsu.core.util.ShareHelper import org.koitharu.kotatsu.core.util.ShareHelper
@@ -62,6 +64,7 @@ abstract class MangaListFragment :
BaseFragment<FragmentListBinding>(), BaseFragment<FragmentListBinding>(),
PaginationScrollListener.Callback, PaginationScrollListener.Callback,
MangaListListener, MangaListListener,
RecyclerViewOwner,
SwipeRefreshLayout.OnRefreshListener, SwipeRefreshLayout.OnRefreshListener,
ListSelectionController.Callback, ListSelectionController.Callback,
FastScroller.FastScrollListener { FastScroller.FastScrollListener {
@@ -87,6 +90,9 @@ abstract class MangaListFragment :
protected val selectedItems: Set<Manga> protected val selectedItems: Set<Manga>
get() = collectSelectedItems() get() = collectSelectedItems()
override val recyclerView: RecyclerView?
get() = viewBinding?.recyclerView
override fun onCreateViewBinding( override fun onCreateViewBinding(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
@@ -159,8 +165,7 @@ abstract class MangaListFragment :
override fun onTagClick(manga: Manga, tag: MangaTag, view: View) { override fun onTagClick(manga: Manga, tag: MangaTag, view: View) {
if (selectionController?.onItemClick(manga.id) != true) { if (selectionController?.onItemClick(manga.id) != true) {
// TODO dialog router.showTagDialog(tag)
router.openList(tag)
} }
} }

View File

@@ -26,8 +26,7 @@ import org.koitharu.kotatsu.bookmarks.ui.AllBookmarksFragment
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.NavItem import org.koitharu.kotatsu.core.prefs.NavItem
import org.koitharu.kotatsu.core.ui.util.RecyclerViewOwner import org.koitharu.kotatsu.core.ui.util.RecyclerViewOwner
import org.koitharu.kotatsu.core.util.ext.firstVisibleItemPosition import org.koitharu.kotatsu.core.util.ext.smoothScrollToTop
import org.koitharu.kotatsu.core.util.ext.isAnimationsEnabled
import org.koitharu.kotatsu.explore.ui.ExploreFragment import org.koitharu.kotatsu.explore.ui.ExploreFragment
import org.koitharu.kotatsu.favourites.ui.container.FavouritesContainerFragment import org.koitharu.kotatsu.favourites.ui.container.FavouritesContainerFragment
import org.koitharu.kotatsu.history.ui.HistoryListFragment import org.koitharu.kotatsu.history.ui.HistoryListFragment
@@ -64,15 +63,8 @@ class MainNavigationDelegate(
override fun onNavigationItemReselected(item: MenuItem) { override fun onNavigationItemReselected(item: MenuItem) {
val fragment = fragmentManager.findFragmentByTag(TAG_PRIMARY) val fragment = fragmentManager.findFragmentByTag(TAG_PRIMARY)
if (fragment == null || fragment !is RecyclerViewOwner || fragment.view == null) { val recyclerView = (fragment as? RecyclerViewOwner)?.recyclerView ?: return
return recyclerView.smoothScrollToTop()
}
val recyclerView = fragment.recyclerView
if (recyclerView.context.isAnimationsEnabled) {
recyclerView.smoothScrollToPosition(0)
} else {
recyclerView.firstVisibleItemPosition = 0
}
} }
override fun handleOnBackPressed() { override fun handleOnBackPressed() {

View File

@@ -4,6 +4,7 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.text.Editable import android.text.Editable
import android.view.Gravity
import android.view.KeyEvent import android.view.KeyEvent
import android.view.View import android.view.View
import android.view.WindowManager import android.view.WindowManager
@@ -20,6 +21,7 @@ import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.BaseActivity import org.koitharu.kotatsu.core.ui.BaseActivity
import org.koitharu.kotatsu.core.ui.util.DefaultTextWatcher 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.getDisplayMessage
import org.koitharu.kotatsu.core.util.ext.getParcelableExtraCompat import org.koitharu.kotatsu.core.util.ext.getParcelableExtraCompat
import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observe
@@ -41,6 +43,7 @@ class ProtectActivity :
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
window.addFlags(WindowManager.LayoutParams.FLAG_SECURE) window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
setContentView(ActivityProtectBinding.inflate(layoutInflater)) setContentView(ActivityProtectBinding.inflate(layoutInflater))
viewBinding.root.consumeInsetsAsPadding(Gravity.FILL)
viewBinding.editPassword.setOnEditorActionListener(this) viewBinding.editPassword.setOnEditorActionListener(this)
viewBinding.editPassword.addTextChangedListener(this) viewBinding.editPassword.addTextChangedListener(this)
viewBinding.buttonNext.setOnClickListener(this) viewBinding.buttonNext.setOnClickListener(this)

View File

@@ -394,7 +394,7 @@ class ReaderViewModel @Inject constructor(
private fun loadImpl() { private fun loadImpl() {
loadingJob = launchLoadingJob(Dispatchers.Default) { 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 mangaDetails.value = details
chaptersLoader.init(details) chaptersLoader.init(details)
val manga = details.toManga() val manga = details.toManga()

View File

@@ -4,11 +4,13 @@ import android.annotation.SuppressLint
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.text.Editable import android.text.Editable
import android.view.Gravity
import android.view.View import android.view.View
import androidx.core.net.toUri import androidx.core.net.toUri
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.BaseActivity import org.koitharu.kotatsu.core.ui.BaseActivity
import org.koitharu.kotatsu.core.ui.util.DefaultTextWatcher 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.databinding.ActivityKitsuAuthBinding
import org.koitharu.kotatsu.parsers.util.urlEncoded import org.koitharu.kotatsu.parsers.util.urlEncoded
@@ -19,6 +21,7 @@ class KitsuAuthActivity : BaseActivity<ActivityKitsuAuthBinding>(), View.OnClick
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(ActivityKitsuAuthBinding.inflate(layoutInflater)) setContentView(ActivityKitsuAuthBinding.inflate(layoutInflater))
viewBinding.root.consumeInsetsAsPadding(Gravity.FILL)
viewBinding.buttonCancel.setOnClickListener(this) viewBinding.buttonCancel.setOnClickListener(this)
viewBinding.buttonDone.setOnClickListener(this) viewBinding.buttonDone.setOnClickListener(this)
viewBinding.editEmail.addTextChangedListener(this) viewBinding.editEmail.addTextChangedListener(this)

View File

@@ -218,7 +218,7 @@ class MangaListActivity :
filterOwner.filterCoordinator.setSortOrder(sortOrder) filterOwner.filterCoordinator.setSortOrder(sortOrder)
} }
if (filter != null) { if (filter != null) {
filterOwner.filterCoordinator.set(filter) filterOwner.filterCoordinator.setAdjusted(filter)
} }
} }
} }

View File

@@ -32,8 +32,8 @@ class NavConfigFragment : BaseFragment<FragmentSettingsSourcesBinding>(), Recycl
private var reorderHelper: ItemTouchHelper? = null private var reorderHelper: ItemTouchHelper? = null
private val viewModel by viewModels<NavConfigViewModel>() private val viewModel by viewModels<NavConfigViewModel>()
override val recyclerView: RecyclerView override val recyclerView: RecyclerView?
get() = requireViewBinding().recyclerView get() = viewBinding?.recyclerView
override fun onCreateViewBinding( override fun onCreateViewBinding(
inflater: LayoutInflater, inflater: LayoutInflater,

View File

@@ -4,6 +4,7 @@ import android.content.pm.PackageManager
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.text.Editable import android.text.Editable
import android.view.Gravity
import android.view.KeyEvent import android.view.KeyEvent
import android.view.View import android.view.View
import android.view.WindowManager import android.view.WindowManager
@@ -17,6 +18,7 @@ import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.BaseActivity import org.koitharu.kotatsu.core.ui.BaseActivity
import org.koitharu.kotatsu.core.ui.util.DefaultTextWatcher 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.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.databinding.ActivitySetupProtectBinding import org.koitharu.kotatsu.databinding.ActivitySetupProtectBinding
@@ -37,6 +39,7 @@ class ProtectSetupActivity :
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
window.addFlags(WindowManager.LayoutParams.FLAG_SECURE) window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
setContentView(ActivitySetupProtectBinding.inflate(layoutInflater)) setContentView(ActivitySetupProtectBinding.inflate(layoutInflater))
viewBinding.root.consumeInsetsAsPadding(Gravity.FILL)
viewBinding.editPassword.addTextChangedListener(this) viewBinding.editPassword.addTextChangedListener(this)
viewBinding.editPassword.setOnEditorActionListener(this) viewBinding.editPassword.setOnEditorActionListener(this)
viewBinding.buttonNext.setOnClickListener(this) viewBinding.buttonNext.setOnClickListener(this)

View File

@@ -39,7 +39,7 @@ class ReaderTapGridConfigActivity : BaseActivity<ActivityReaderTapActionsBinding
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(ActivityReaderTapActionsBinding.inflate(layoutInflater)) setContentView(ActivityReaderTapActionsBinding.inflate(layoutInflater))
viewBinding.root.consumeInsetsAsPadding(Gravity.START or Gravity.END or Gravity.BOTTOM or Gravity.TOP) viewBinding.root.consumeInsetsAsPadding(Gravity.FILL)
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
controls[TapGridArea.TOP_LEFT] = viewBinding.textViewTopLeft controls[TapGridArea.TOP_LEFT] = viewBinding.textViewTopLeft
controls[TapGridArea.TOP_CENTER] = viewBinding.textViewTopCenter controls[TapGridArea.TOP_CENTER] = viewBinding.textViewTopCenter

View File

@@ -56,8 +56,8 @@ class SourcesManageFragment :
private var sourcesAdapter: SourceConfigAdapter? = null private var sourcesAdapter: SourceConfigAdapter? = null
private val viewModel by viewModels<SourcesManageViewModel>() private val viewModel by viewModels<SourcesManageViewModel>()
override val recyclerView: RecyclerView override val recyclerView: RecyclerView?
get() = requireViewBinding().recyclerView get() = viewBinding?.recyclerView
override fun onCreateViewBinding( override fun onCreateViewBinding(
inflater: LayoutInflater, inflater: LayoutInflater,

View File

@@ -40,6 +40,7 @@ abstract class StatsDao {
favouriteCategories: Set<Long> favouriteCategories: Set<Long>
): Map<MangaEntity, Long> { ): Map<MangaEntity, Long> {
val conditions = ArrayList<String>() val conditions = ArrayList<String>()
conditions.add("(SELECT deleted_at FROM history WHERE history.manga_id = stats.manga_id) = 0")
conditions.add("stats.started_at >= $fromDate") conditions.add("stats.started_at >= $fromDate")
if (favouriteCategories.isNotEmpty()) { if (favouriteCategories.isNotEmpty()) {
val ids = favouriteCategories.joinToString(",") val ids = favouriteCategories.joinToString(",")

View File

@@ -1,32 +1,5 @@
package org.koitharu.kotatsu.suggestions.ui package org.koitharu.kotatsu.suggestions.ui
import android.os.Bundle import org.koitharu.kotatsu.core.ui.FragmentContainerActivity
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
@AndroidEntryPoint class SuggestionsActivity : FragmentContainerActivity(SuggestionsFragment::class.java)
class SuggestionsActivity :
BaseActivity<ActivityContainerBinding>(),
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)
}
}
}
}

View File

@@ -6,6 +6,7 @@ import android.accounts.AccountManager
import android.os.Bundle import android.os.Bundle
import android.text.Editable import android.text.Editable
import android.text.TextWatcher import android.text.TextWatcher
import android.view.Gravity
import android.view.View import android.view.View
import android.widget.Button import android.widget.Button
import android.widget.Toast import android.widget.Toast
@@ -20,6 +21,7 @@ import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.BaseActivity import org.koitharu.kotatsu.core.ui.BaseActivity
import org.koitharu.kotatsu.core.ui.util.DefaultTextWatcher 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.getDisplayMessage
import org.koitharu.kotatsu.core.util.ext.getParcelableExtraCompat import org.koitharu.kotatsu.core.util.ext.getParcelableExtraCompat
import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observe
@@ -40,6 +42,7 @@ class SyncAuthActivity : BaseActivity<ActivitySyncAuthBinding>(), View.OnClickLi
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(ActivitySyncAuthBinding.inflate(layoutInflater)) setContentView(ActivitySyncAuthBinding.inflate(layoutInflater))
viewBinding.root.consumeInsetsAsPadding(Gravity.FILL)
accountAuthenticatorResponse = accountAuthenticatorResponse =
intent.getParcelableExtraCompat(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE) intent.getParcelableExtraCompat(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE)
accountAuthenticatorResponse?.onRequestContinued() accountAuthenticatorResponse?.onRequestContinued()

View File

@@ -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.PaginationScrollListener
import org.koitharu.kotatsu.core.ui.list.RecyclerScrollKeeper import org.koitharu.kotatsu.core.ui.list.RecyclerScrollKeeper
import org.koitharu.kotatsu.core.ui.util.MenuInvalidator 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.util.ReversibleActionObserver
import org.koitharu.kotatsu.core.ui.widgets.TipView import org.koitharu.kotatsu.core.ui.widgets.TipView
import org.koitharu.kotatsu.core.util.ext.addMenuProvider import org.koitharu.kotatsu.core.util.ext.addMenuProvider
@@ -38,13 +39,18 @@ import javax.inject.Inject
class FeedFragment : class FeedFragment :
BaseFragment<FragmentListBinding>(), BaseFragment<FragmentListBinding>(),
PaginationScrollListener.Callback, PaginationScrollListener.Callback,
MangaListListener, SwipeRefreshLayout.OnRefreshListener { RecyclerViewOwner,
MangaListListener,
SwipeRefreshLayout.OnRefreshListener {
@Inject @Inject
lateinit var coil: ImageLoader lateinit var coil: ImageLoader
private val viewModel by viewModels<FeedViewModel>() private val viewModel by viewModels<FeedViewModel>()
override val recyclerView: RecyclerView?
get() = viewBinding?.recyclerView
override fun onCreateViewBinding( override fun onCreateViewBinding(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,

View File

@@ -1,33 +1,5 @@
package org.koitharu.kotatsu.tracker.ui.updates package org.koitharu.kotatsu.tracker.ui.updates
import android.os.Bundle import org.koitharu.kotatsu.core.ui.FragmentContainerActivity
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
@AndroidEntryPoint class UpdatesActivity : FragmentContainerActivity(UpdatesFragment::class.java)
class UpdatesActivity :
BaseActivity<ActivityContainerBinding>(),
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)
}
}
}
}

View File

@@ -38,9 +38,4 @@ class UpdatesFragment : MangaListFragment() {
else -> super.onActionItemClicked(controller, mode, item) else -> super.onActionItemClicked(controller, mode, item)
} }
} }
companion object {
fun newInstance() = UpdatesFragment()
}
} }

View File

@@ -5,7 +5,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:gravity="center" android:gravity="center"
android:orientation="vertical" android:orientation="horizontal"
android:paddingHorizontal="32dp"> android:paddingHorizontal="32dp">
<ImageView <ImageView
@@ -16,32 +16,38 @@
android:scaleType="fitCenter" android:scaleType="fitCenter"
tools:src="@drawable/ic_empty_favourites" /> tools:src="@drawable/ic_empty_favourites" />
<TextView <LinearLayout
android:id="@+id/textPrimary"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_normal" android:orientation="vertical">
android:gravity="center"
android:textAppearance="?attr/textAppearanceTitleLarge"
tools:text="@tools:sample/lorem[3]" />
<TextView <TextView
android:id="@+id/textSecondary" android:id="@+id/textPrimary"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_normal" android:layout_marginTop="@dimen/margin_normal"
android:gravity="center" android:textAppearance="?attr/textAppearanceTitleLarge"
android:textAppearance="?attr/textAppearanceBodyMedium" tools:text="@tools:sample/lorem[3]" />
tools:text="@tools:sample/lorem[15]" />
<Button <TextView
android:id="@+id/button_retry" android:id="@+id/textSecondary"
style="@style/Widget.Material3.Button.TonalButton" android:layout_width="wrap_content"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_height="wrap_content" android:layout_marginTop="@dimen/margin_normal"
android:layout_marginTop="@dimen/margin_normal" android:maxWidth="320dp"
android:visibility="gone" android:textAppearance="?attr/textAppearanceBodyMedium"
tools:text="@string/try_again" tools:text="@tools:sample/lorem[15]" />
tools:visibility="visible" />
<Button
android:id="@+id/button_retry"
style="?materialButtonTonalStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_normal"
android:visibility="gone"
tools:text="@string/try_again"
tools:visibility="visible" />
</LinearLayout>
</LinearLayout> </LinearLayout>

View File

@@ -0,0 +1,115 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
app:layout_constraintEnd_toStartOf="@id/guideline_center"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.appbar.MaterialToolbar
android:id="@id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_scrollFlags="noScroll"
tools:title="@string/reading_stats" />
</com.google.android.material.appbar.AppBarLayout>
<HorizontalScrollView
android:id="@+id/scrollView_chips"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:paddingHorizontal="12dp"
android:scrollIndicators="start"
android:scrollbars="none"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/guideline_center"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.chip.ChipGroup
android:id="@+id/layout_chips"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<com.google.android.material.chip.Chip
android:id="@+id/chip_period"
style="@style/Widget.Kotatsu.Chip.Dropdown"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/week"
app:chipIcon="@drawable/ic_history" />
</com.google.android.material.chip.ChipGroup>
</HorizontalScrollView>
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/progressBar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:indeterminate="true"
android:visibility="gone"
app:hideAnimationBehavior="outward"
app:layout_constraintBottom_toBottomOf="@id/appbar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/appbar"
app:showAnimationBehavior="inward"
app:trackCornerRadius="0dp"
tools:visibility="visible" />
<org.koitharu.kotatsu.stats.ui.views.PieChartView
android:id="@+id/chart"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="24dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="1"
app:layout_constraintEnd_toStartOf="@id/guideline_center"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/appbar" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="0dp"
android:layout_height="0dp"
android:overScrollMode="ifContentScrolls"
android:scrollbars="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/guideline_center"
app:layout_constraintTop_toBottomOf="@id/appbar"
tools:itemCount="4"
tools:listitem="@layout/item_stats" />
<ViewStub
android:id="@+id/stub_empty"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout="@layout/item_empty_state"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/appbar"
tools:visibility="visible" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline_center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.5" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -37,6 +37,7 @@
<Button <Button
android:id="@+id/button_retry" android:id="@+id/button_retry"
style="?materialButtonTonalStyle"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_normal" android:layout_marginTop="@dimen/margin_normal"