UI improvements and author search support
This commit is contained in:
@@ -4,6 +4,7 @@ import android.text.style.ForegroundColorSpan
|
|||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.text.buildSpannedString
|
import androidx.core.text.buildSpannedString
|
||||||
import androidx.core.text.inSpans
|
import androidx.core.text.inSpans
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import coil3.ImageLoader
|
import coil3.ImageLoader
|
||||||
import coil3.request.ImageRequest
|
import coil3.request.ImageRequest
|
||||||
@@ -51,7 +52,13 @@ fun alternativeAD(
|
|||||||
binding.chipSource.setOnClickListener(clickListener)
|
binding.chipSource.setOnClickListener(clickListener)
|
||||||
|
|
||||||
bind { payloads ->
|
bind { payloads ->
|
||||||
binding.textViewTitle.text = item.manga.title
|
binding.textViewTitle.text = item.mangaModel.title
|
||||||
|
with(binding.iconsView) {
|
||||||
|
clearIcons()
|
||||||
|
if (item.mangaModel.isSaved) addIcon(R.drawable.ic_storage)
|
||||||
|
if (item.mangaModel.isFavorite) addIcon(R.drawable.ic_heart_outline)
|
||||||
|
isVisible = iconsCount > 0
|
||||||
|
}
|
||||||
binding.textViewSubtitle.text = buildSpannedString {
|
binding.textViewSubtitle.text = buildSpannedString {
|
||||||
if (item.chaptersCount > 0) {
|
if (item.chaptersCount > 0) {
|
||||||
append(context.resources.getQuantityString(R.plurals.chapters, item.chaptersCount, item.chaptersCount))
|
append(context.resources.getQuantityString(R.plurals.chapters, item.chaptersCount, item.chaptersCount))
|
||||||
@@ -70,7 +77,7 @@ fun alternativeAD(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
binding.progressView.setProgress(item.progress, ListModelDiffCallback.PAYLOAD_PROGRESS_CHANGED in payloads)
|
binding.progressView.setProgress(item.mangaModel.progress, ListModelDiffCallback.PAYLOAD_PROGRESS_CHANGED in payloads)
|
||||||
binding.chipSource.also { chip ->
|
binding.chipSource.also { chip ->
|
||||||
chip.text = item.manga.source.getTitle(chip.context)
|
chip.text = item.manga.source.getTitle(chip.context)
|
||||||
ImageRequest.Builder(context)
|
ImageRequest.Builder(context)
|
||||||
|
|||||||
@@ -15,17 +15,17 @@ import org.koitharu.kotatsu.core.model.chaptersCount
|
|||||||
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
|
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
|
||||||
import org.koitharu.kotatsu.core.nav.AppRouter
|
import org.koitharu.kotatsu.core.nav.AppRouter
|
||||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
import org.koitharu.kotatsu.core.prefs.ListMode
|
||||||
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
||||||
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
|
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
|
||||||
import org.koitharu.kotatsu.core.util.ext.call
|
import org.koitharu.kotatsu.core.util.ext.call
|
||||||
import org.koitharu.kotatsu.core.util.ext.require
|
import org.koitharu.kotatsu.core.util.ext.require
|
||||||
import org.koitharu.kotatsu.history.data.HistoryRepository
|
import org.koitharu.kotatsu.list.domain.MangaListMapper
|
||||||
import org.koitharu.kotatsu.list.domain.ReadingProgress
|
|
||||||
import org.koitharu.kotatsu.list.ui.model.EmptyState
|
import org.koitharu.kotatsu.list.ui.model.EmptyState
|
||||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||||
import org.koitharu.kotatsu.list.ui.model.LoadingFooter
|
import org.koitharu.kotatsu.list.ui.model.LoadingFooter
|
||||||
import org.koitharu.kotatsu.list.ui.model.LoadingState
|
import org.koitharu.kotatsu.list.ui.model.LoadingState
|
||||||
|
import org.koitharu.kotatsu.list.ui.model.MangaGridModel
|
||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -36,8 +36,7 @@ class AlternativesViewModel @Inject constructor(
|
|||||||
private val mangaRepositoryFactory: MangaRepository.Factory,
|
private val mangaRepositoryFactory: MangaRepository.Factory,
|
||||||
private val alternativesUseCase: AlternativesUseCase,
|
private val alternativesUseCase: AlternativesUseCase,
|
||||||
private val migrateUseCase: MigrateUseCase,
|
private val migrateUseCase: MigrateUseCase,
|
||||||
private val historyRepository: HistoryRepository,
|
private val mangaListMapper: MangaListMapper,
|
||||||
private val settings: AppSettings,
|
|
||||||
) : BaseViewModel() {
|
) : BaseViewModel() {
|
||||||
|
|
||||||
val manga = savedStateHandle.require<ParcelableManga>(AppRouter.KEY_MANGA).manga
|
val manga = savedStateHandle.require<ParcelableManga>(AppRouter.KEY_MANGA).manga
|
||||||
@@ -55,8 +54,7 @@ class AlternativesViewModel @Inject constructor(
|
|||||||
alternativesUseCase(ref)
|
alternativesUseCase(ref)
|
||||||
.map {
|
.map {
|
||||||
MangaAlternativeModel(
|
MangaAlternativeModel(
|
||||||
manga = it,
|
mangaModel = mangaListMapper.toListModel(it, ListMode.GRID) as MangaGridModel,
|
||||||
progress = getProgress(it.id),
|
|
||||||
referenceChapters = refCount,
|
referenceChapters = refCount,
|
||||||
)
|
)
|
||||||
}.runningFold<MangaAlternativeModel, List<ListModel>>(listOf(LoadingState)) { acc, item ->
|
}.runningFold<MangaAlternativeModel, List<ListModel>>(listOf(LoadingState)) { acc, item ->
|
||||||
@@ -88,8 +86,4 @@ class AlternativesViewModel @Inject constructor(
|
|||||||
onMigrated.call(target)
|
onMigrated.call(target)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getProgress(mangaId: Long): ReadingProgress? {
|
|
||||||
return historyRepository.getProgress(mangaId, settings.progressIndicatorMode)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
package org.koitharu.kotatsu.alternatives.ui
|
package org.koitharu.kotatsu.alternatives.ui
|
||||||
|
|
||||||
import org.koitharu.kotatsu.core.model.chaptersCount
|
import org.koitharu.kotatsu.core.model.chaptersCount
|
||||||
import org.koitharu.kotatsu.list.domain.ReadingProgress
|
|
||||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||||
|
import org.koitharu.kotatsu.list.ui.model.MangaGridModel
|
||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
|
|
||||||
data class MangaAlternativeModel(
|
data class MangaAlternativeModel(
|
||||||
val manga: Manga,
|
val mangaModel: MangaGridModel,
|
||||||
val progress: ReadingProgress?,
|
|
||||||
private val referenceChapters: Int,
|
private val referenceChapters: Int,
|
||||||
) : ListModel {
|
) : ListModel {
|
||||||
|
|
||||||
|
val manga: Manga
|
||||||
|
get() = mangaModel.manga
|
||||||
|
|
||||||
val chaptersCount = manga.chaptersCount()
|
val chaptersCount = manga.chaptersCount()
|
||||||
|
|
||||||
val chaptersDiff: Int
|
val chaptersDiff: Int
|
||||||
@@ -19,4 +21,10 @@ data class MangaAlternativeModel(
|
|||||||
override fun areItemsTheSame(other: ListModel): Boolean {
|
override fun areItemsTheSame(other: ListModel): Boolean {
|
||||||
return other is MangaAlternativeModel && other.manga.id == manga.id
|
return other is MangaAlternativeModel && other.manga.id == manga.id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getChangePayload(previousState: ListModel): Any? = if (previousState is MangaAlternativeModel) {
|
||||||
|
mangaModel.getChangePayload(previousState.mangaModel)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,183 +0,0 @@
|
|||||||
package org.koitharu.kotatsu.core.ui.widgets
|
|
||||||
|
|
||||||
import android.animation.ValueAnimator
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.res.ColorStateList
|
|
||||||
import android.graphics.Canvas
|
|
||||||
import android.graphics.Color
|
|
||||||
import android.graphics.Outline
|
|
||||||
import android.graphics.Paint
|
|
||||||
import android.util.AttributeSet
|
|
||||||
import android.view.Gravity
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewOutlineProvider
|
|
||||||
import android.view.animation.AccelerateDecelerateInterpolator
|
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import androidx.appcompat.widget.LinearLayoutCompat
|
|
||||||
import androidx.core.content.withStyledAttributes
|
|
||||||
import androidx.core.graphics.ColorUtils
|
|
||||||
import androidx.core.view.children
|
|
||||||
import androidx.core.widget.TextViewCompat
|
|
||||||
import org.koitharu.kotatsu.R
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.getAnimationDuration
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.getThemeColorStateList
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.isAnimationsEnabled
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.resolveDp
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.setTextAndVisible
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.textAndVisible
|
|
||||||
import com.google.android.material.R as materialR
|
|
||||||
|
|
||||||
@Deprecated("")
|
|
||||||
class ProgressButton @JvmOverloads constructor(
|
|
||||||
context: Context,
|
|
||||||
attrs: AttributeSet? = null,
|
|
||||||
defStyleAttr: Int = 0,
|
|
||||||
) : LinearLayoutCompat(context, attrs, defStyleAttr), ValueAnimator.AnimatorUpdateListener {
|
|
||||||
|
|
||||||
private val textViewTitle = TextView(context)
|
|
||||||
private val textViewSubtitle = TextView(context)
|
|
||||||
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
|
|
||||||
|
|
||||||
private var progress = 0f
|
|
||||||
private var targetProgress = 0f
|
|
||||||
private var colorBase: ColorStateList = ColorStateList.valueOf(Color.TRANSPARENT)
|
|
||||||
private var colorProgress: ColorStateList = ColorStateList.valueOf(Color.TRANSPARENT)
|
|
||||||
private var progressAnimator: ValueAnimator? = null
|
|
||||||
|
|
||||||
private var colorBaseCurrent = colorProgress.defaultColor
|
|
||||||
private var colorProgressCurrent = colorProgress.defaultColor
|
|
||||||
|
|
||||||
var title: CharSequence?
|
|
||||||
get() = textViewTitle.textAndVisible
|
|
||||||
set(value) {
|
|
||||||
textViewTitle.textAndVisible = value
|
|
||||||
}
|
|
||||||
|
|
||||||
var subtitle: CharSequence?
|
|
||||||
get() = textViewSubtitle.textAndVisible
|
|
||||||
set(value) {
|
|
||||||
textViewSubtitle.textAndVisible = value
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
|
||||||
orientation = VERTICAL
|
|
||||||
outlineProvider = OutlineProvider()
|
|
||||||
clipToOutline = true
|
|
||||||
|
|
||||||
context.withStyledAttributes(attrs, R.styleable.ProgressButton, defStyleAttr) {
|
|
||||||
val textAppearanceFallback = androidx.appcompat.R.style.TextAppearance_AppCompat
|
|
||||||
TextViewCompat.setTextAppearance(
|
|
||||||
textViewTitle,
|
|
||||||
getResourceId(R.styleable.ProgressButton_titleTextAppearance, textAppearanceFallback),
|
|
||||||
)
|
|
||||||
TextViewCompat.setTextAppearance(
|
|
||||||
textViewSubtitle,
|
|
||||||
getResourceId(R.styleable.ProgressButton_subtitleTextAppearance, textAppearanceFallback),
|
|
||||||
)
|
|
||||||
textViewTitle.text = getText(R.styleable.ProgressButton_title)
|
|
||||||
textViewSubtitle.text = getText(R.styleable.ProgressButton_subtitle)
|
|
||||||
colorBase = getColorStateList(R.styleable.ProgressButton_baseColor)
|
|
||||||
?: context.getThemeColorStateList(materialR.attr.colorPrimaryContainer) ?: colorBase
|
|
||||||
colorProgress = getColorStateList(R.styleable.ProgressButton_progressColor)
|
|
||||||
?: context.getThemeColorStateList(materialR.attr.colorPrimary) ?: colorProgress
|
|
||||||
getColorStateList(R.styleable.ProgressButton_android_textColor)?.let { colorText ->
|
|
||||||
textViewTitle.setTextColor(colorText)
|
|
||||||
textViewSubtitle.setTextColor(colorText)
|
|
||||||
}
|
|
||||||
progress = getInt(R.styleable.ProgressButton_android_progress, 0).toFloat() /
|
|
||||||
getInt(R.styleable.ProgressButton_android_max, 100).toFloat()
|
|
||||||
}
|
|
||||||
|
|
||||||
addView(textViewTitle, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT))
|
|
||||||
addView(
|
|
||||||
textViewSubtitle,
|
|
||||||
LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT).also { lp ->
|
|
||||||
lp.topMargin = context.resources.resolveDp(2)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
paint.style = Paint.Style.FILL
|
|
||||||
applyGravity()
|
|
||||||
setWillNotDraw(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDraw(canvas: Canvas) {
|
|
||||||
super.onDraw(canvas)
|
|
||||||
canvas.drawColor(colorBaseCurrent)
|
|
||||||
if (progress > 0f) {
|
|
||||||
canvas.drawRect(0f, 0f, width * progress, height.toFloat(), paint)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun drawableStateChanged() {
|
|
||||||
super.drawableStateChanged()
|
|
||||||
val state = drawableState
|
|
||||||
colorBaseCurrent = colorBase.getColorForState(state, colorBase.defaultColor)
|
|
||||||
colorProgressCurrent = colorProgress.getColorForState(state, colorProgress.defaultColor)
|
|
||||||
colorProgressCurrent = ColorUtils.setAlphaComponent(colorProgressCurrent, 84 /* 255 * 0.33F */)
|
|
||||||
paint.color = colorProgressCurrent
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setGravity(gravity: Int) {
|
|
||||||
super.setGravity(gravity)
|
|
||||||
if (childCount != 0) {
|
|
||||||
applyGravity()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setEnabled(enabled: Boolean) {
|
|
||||||
super.setEnabled(enabled)
|
|
||||||
children.forEach { it.isEnabled = enabled }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAnimationUpdate(animation: ValueAnimator) {
|
|
||||||
if (animation === progressAnimator) {
|
|
||||||
progress = animation.animatedValue as Float
|
|
||||||
invalidate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setTitle(@StringRes titleResId: Int) {
|
|
||||||
textViewTitle.setTextAndVisible(titleResId)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setSubtitle(@StringRes titleResId: Int) {
|
|
||||||
textViewSubtitle.setTextAndVisible(titleResId)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setProgress(value: Float, animate: Boolean) {
|
|
||||||
val prevAnimator = progressAnimator
|
|
||||||
if (animate && context.isAnimationsEnabled) {
|
|
||||||
if (value == targetProgress) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
targetProgress = value
|
|
||||||
progressAnimator = ValueAnimator.ofFloat(progress, value).apply {
|
|
||||||
duration = context.getAnimationDuration(android.R.integer.config_mediumAnimTime)
|
|
||||||
interpolator = AccelerateDecelerateInterpolator()
|
|
||||||
addUpdateListener(this@ProgressButton)
|
|
||||||
}
|
|
||||||
progressAnimator?.start()
|
|
||||||
} else {
|
|
||||||
progressAnimator = null
|
|
||||||
progress = value
|
|
||||||
targetProgress = value
|
|
||||||
invalidate()
|
|
||||||
}
|
|
||||||
prevAnimator?.cancel()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun applyGravity() {
|
|
||||||
val value = (gravity and Gravity.HORIZONTAL_GRAVITY_MASK) or Gravity.CENTER_VERTICAL
|
|
||||||
textViewTitle.gravity = value
|
|
||||||
textViewSubtitle.gravity = value
|
|
||||||
}
|
|
||||||
|
|
||||||
private class OutlineProvider : ViewOutlineProvider() {
|
|
||||||
|
|
||||||
override fun getOutline(view: View, outline: Outline) {
|
|
||||||
outline.setRoundRect(0, 0, view.width, view.height, view.height / 2f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -92,6 +92,7 @@ import org.koitharu.kotatsu.details.ui.scrobbling.ScrobblingItemDecoration
|
|||||||
import org.koitharu.kotatsu.details.ui.scrobbling.ScrollingInfoAdapter
|
import org.koitharu.kotatsu.details.ui.scrobbling.ScrollingInfoAdapter
|
||||||
import org.koitharu.kotatsu.download.ui.worker.DownloadStartedObserver
|
import org.koitharu.kotatsu.download.ui.worker.DownloadStartedObserver
|
||||||
import org.koitharu.kotatsu.list.domain.MangaListMapper
|
import org.koitharu.kotatsu.list.domain.MangaListMapper
|
||||||
|
import org.koitharu.kotatsu.list.domain.ReadingProgress
|
||||||
import org.koitharu.kotatsu.list.ui.adapter.ListItemType
|
import org.koitharu.kotatsu.list.ui.adapter.ListItemType
|
||||||
import org.koitharu.kotatsu.list.ui.adapter.mangaGridItemAD
|
import org.koitharu.kotatsu.list.ui.adapter.mangaGridItemAD
|
||||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||||
@@ -101,6 +102,7 @@ import org.koitharu.kotatsu.parsers.model.Manga
|
|||||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
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.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
|
||||||
@@ -203,8 +205,8 @@ 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 manga = viewModel.manga.value ?: return
|
val author = viewModel.manga.value?.author ?: return
|
||||||
router.openSearch(manga.source, manga.author ?: return)
|
router.openSearch(author, SearchKind.AUTHOR)
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.textView_source -> {
|
R.id.textView_source -> {
|
||||||
@@ -484,7 +486,7 @@ class DetailsActivity :
|
|||||||
textViewProgress.textAndVisible = if (info.percent <= 0f) {
|
textViewProgress.textAndVisible = if (info.percent <= 0f) {
|
||||||
null
|
null
|
||||||
} else {
|
} else {
|
||||||
val displayPercent = if (info.percent >= 0.999999f) 100 else (info.percent * 100f).toInt()
|
val displayPercent = if (ReadingProgress.isCompleted(info.percent)) 100 else (info.percent * 100f).toInt()
|
||||||
getString(R.string.percent_string_pattern, displayPercent.toString())
|
getString(R.string.percent_string_pattern, displayPercent.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -287,6 +287,15 @@ class FilterCoordinator @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setAuthor(value: String?) {
|
||||||
|
currentListFilter.update { oldValue ->
|
||||||
|
oldValue.copy(
|
||||||
|
author = value,
|
||||||
|
query = oldValue.takeQueryIfSupported(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun setOriginalLocale(value: Locale?) {
|
fun setOriginalLocale(value: Locale?) {
|
||||||
currentListFilter.update { oldValue ->
|
currentListFilter.update { oldValue ->
|
||||||
oldValue.copy(
|
oldValue.copy(
|
||||||
|
|||||||
@@ -60,7 +60,11 @@ class FilterHeaderFragment : BaseFragment<FragmentFilterHeaderBinding>(), ChipsV
|
|||||||
|
|
||||||
override fun onChipCloseClick(chip: Chip, data: Any?) {
|
override fun onChipCloseClick(chip: Chip, data: Any?) {
|
||||||
when (data) {
|
when (data) {
|
||||||
is String -> filter.setQuery(null)
|
is String -> if (data == filter.snapshot().listFilter.author) {
|
||||||
|
filter.setAuthor(null)
|
||||||
|
} else {
|
||||||
|
filter.setQuery(null)
|
||||||
|
}
|
||||||
is ContentRating -> filter.toggleContentRating(data, false)
|
is ContentRating -> filter.toggleContentRating(data, false)
|
||||||
is Demographic -> filter.toggleDemographic(data, false)
|
is Demographic -> filter.toggleDemographic(data, false)
|
||||||
is ContentType -> filter.toggleContentType(data, false)
|
is ContentType -> filter.toggleContentType(data, false)
|
||||||
|
|||||||
@@ -135,6 +135,16 @@ class FilterHeaderProducer @Inject constructor(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (!snapshot.author.isNullOrEmpty()) {
|
||||||
|
result.addFirst(
|
||||||
|
ChipsView.ChipModel(
|
||||||
|
title = snapshot.author,
|
||||||
|
icon = R.drawable.ic_user,
|
||||||
|
isCloseable = true,
|
||||||
|
data = snapshot.author,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
val hasTags = result.any { it.data is MangaTag }
|
val hasTags = result.any { it.data is MangaTag }
|
||||||
if (hasTags) {
|
if (hasTags) {
|
||||||
result.addLast(moreTagsChip())
|
result.addLast(moreTagsChip())
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ class HistoryRepository @Inject constructor(
|
|||||||
|
|
||||||
suspend fun getProgress(mangaId: Long, mode: ProgressIndicatorMode): ReadingProgress? {
|
suspend fun getProgress(mangaId: Long, mode: ProgressIndicatorMode): ReadingProgress? {
|
||||||
val entity = db.getHistoryDao().find(mangaId) ?: return null
|
val entity = db.getHistoryDao().find(mangaId) ?: return null
|
||||||
val fixedPercent = if (entity.percent >= 0.999999f) 1f else entity.percent
|
val fixedPercent = if (ReadingProgress.isCompleted(entity.percent)) 1f else entity.percent
|
||||||
return ReadingProgress(
|
return ReadingProgress(
|
||||||
percent = fixedPercent,
|
percent = fixedPercent,
|
||||||
totalChapters = entity.chaptersCount,
|
totalChapters = entity.chaptersCount,
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import org.koitharu.kotatsu.list.domain.ListFilterOption
|
|||||||
import org.koitharu.kotatsu.list.domain.ListSortOrder
|
import org.koitharu.kotatsu.list.domain.ListSortOrder
|
||||||
import org.koitharu.kotatsu.list.domain.MangaListMapper
|
import org.koitharu.kotatsu.list.domain.MangaListMapper
|
||||||
import org.koitharu.kotatsu.list.domain.QuickFilterListener
|
import org.koitharu.kotatsu.list.domain.QuickFilterListener
|
||||||
|
import org.koitharu.kotatsu.list.domain.ReadingProgress
|
||||||
import org.koitharu.kotatsu.list.ui.MangaListViewModel
|
import org.koitharu.kotatsu.list.ui.MangaListViewModel
|
||||||
import org.koitharu.kotatsu.list.ui.model.EmptyState
|
import org.koitharu.kotatsu.list.ui.model.EmptyState
|
||||||
import org.koitharu.kotatsu.list.ui.model.InfoModel
|
import org.koitharu.kotatsu.list.ui.model.InfoModel
|
||||||
@@ -207,10 +208,10 @@ class HistoryListViewModel @Inject constructor(
|
|||||||
|
|
||||||
ListSortOrder.UNREAD,
|
ListSortOrder.UNREAD,
|
||||||
ListSortOrder.PROGRESS -> ListHeader(
|
ListSortOrder.PROGRESS -> ListHeader(
|
||||||
when (percent) {
|
when {
|
||||||
1f -> R.string.status_completed
|
ReadingProgress.isCompleted(percent) -> R.string.status_completed
|
||||||
in 0f..0.01f -> R.string.status_planned
|
percent in 0f..0.01f -> R.string.status_planned
|
||||||
in 0f..1f -> R.string.status_reading
|
percent in 0f..1f -> R.string.status_reading
|
||||||
else -> R.string.unknown
|
else -> R.string.unknown
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -39,9 +39,10 @@ data class ReadingProgress(
|
|||||||
|
|
||||||
const val PROGRESS_NONE = -1f
|
const val PROGRESS_NONE = -1f
|
||||||
const val PROGRESS_COMPLETED = 1f
|
const val PROGRESS_COMPLETED = 1f
|
||||||
|
private const val PROGRESS_COMPLETED_THRESHOLD = 0.99999f
|
||||||
|
|
||||||
fun isValid(percent: Float) = percent in 0f..1f
|
fun isValid(percent: Float) = percent in 0f..1f
|
||||||
|
|
||||||
fun isCompleted(percent: Float) = percent >= PROGRESS_COMPLETED
|
fun isCompleted(percent: Float) = percent >= PROGRESS_COMPLETED_THRESHOLD
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package org.koitharu.kotatsu.list.ui.adapter
|
||||||
|
|
||||||
|
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||||
|
import org.koitharu.kotatsu.databinding.ItemButtonFooterBinding
|
||||||
|
import org.koitharu.kotatsu.list.ui.model.ButtonFooter
|
||||||
|
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||||
|
|
||||||
|
fun buttonFooterAD(
|
||||||
|
listener: ListStateHolderListener,
|
||||||
|
) = adapterDelegateViewBinding<ButtonFooter, ListModel, ItemButtonFooterBinding>(
|
||||||
|
{ inflater, parent -> ItemButtonFooterBinding.inflate(inflater, parent, false) },
|
||||||
|
) {
|
||||||
|
|
||||||
|
binding.button.setOnClickListener {
|
||||||
|
listener.onFooterButtonClick()
|
||||||
|
}
|
||||||
|
|
||||||
|
bind {
|
||||||
|
binding.button.setText(item.textResId)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ import org.koitharu.kotatsu.list.ui.model.ErrorFooter
|
|||||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||||
|
|
||||||
fun errorFooterAD(
|
fun errorFooterAD(
|
||||||
listener: MangaListListener?,
|
listener: ListStateHolderListener?,
|
||||||
) = adapterDelegateViewBinding<ErrorFooter, ListModel, ItemErrorFooterBinding>(
|
) = adapterDelegateViewBinding<ErrorFooter, ListModel, ItemErrorFooterBinding>(
|
||||||
{ inflater, parent -> ItemErrorFooterBinding.inflate(inflater, parent, false) },
|
{ inflater, parent -> ItemErrorFooterBinding.inflate(inflater, parent, false) },
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ enum class ListItemType {
|
|||||||
MANGA_NESTED_GROUP,
|
MANGA_NESTED_GROUP,
|
||||||
FOOTER_LOADING,
|
FOOTER_LOADING,
|
||||||
FOOTER_ERROR,
|
FOOTER_ERROR,
|
||||||
|
FOOTER_BUTTON,
|
||||||
STATE_LOADING,
|
STATE_LOADING,
|
||||||
STATE_ERROR,
|
STATE_ERROR,
|
||||||
STATE_EMPTY,
|
STATE_EMPTY,
|
||||||
|
|||||||
@@ -7,4 +7,6 @@ interface ListStateHolderListener {
|
|||||||
fun onSecondaryErrorActionClick(error: Throwable) = Unit
|
fun onSecondaryErrorActionClick(error: Throwable) = Unit
|
||||||
|
|
||||||
fun onEmptyActionClick()
|
fun onEmptyActionClick()
|
||||||
|
|
||||||
|
fun onFooterButtonClick() = Unit
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,5 +27,6 @@ open class MangaListAdapter(
|
|||||||
addDelegate(ListItemType.QUICK_FILTER, quickFilterAD(listener))
|
addDelegate(ListItemType.QUICK_FILTER, quickFilterAD(listener))
|
||||||
addDelegate(ListItemType.TIP, tipAD(listener))
|
addDelegate(ListItemType.TIP, tipAD(listener))
|
||||||
addDelegate(ListItemType.INFO, infoAD())
|
addDelegate(ListItemType.INFO, infoAD())
|
||||||
|
addDelegate(ListItemType.FOOTER_BUTTON, buttonFooterAD(listener))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ class TypedListSpacingDecoration(
|
|||||||
|
|
||||||
ListItemType.FOOTER_LOADING,
|
ListItemType.FOOTER_LOADING,
|
||||||
ListItemType.FOOTER_ERROR,
|
ListItemType.FOOTER_ERROR,
|
||||||
|
ListItemType.FOOTER_BUTTON,
|
||||||
ListItemType.STATE_LOADING,
|
ListItemType.STATE_LOADING,
|
||||||
ListItemType.STATE_ERROR,
|
ListItemType.STATE_ERROR,
|
||||||
ListItemType.STATE_EMPTY,
|
ListItemType.STATE_EMPTY,
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package org.koitharu.kotatsu.list.ui.model
|
||||||
|
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
|
||||||
|
data class ButtonFooter(
|
||||||
|
@StringRes val textResId: Int,
|
||||||
|
) : ListModel {
|
||||||
|
|
||||||
|
override fun areItemsTheSame(other: ListModel): Boolean {
|
||||||
|
return other is ButtonFooter && textResId == other.textResId
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -121,20 +121,14 @@ class PreviewFragment : BaseFragment<FragmentPreviewBinding>(), View.OnClickList
|
|||||||
private fun onFooterUpdated(footer: PreviewViewModel.FooterInfo?) {
|
private fun onFooterUpdated(footer: PreviewViewModel.FooterInfo?) {
|
||||||
with(requireViewBinding()) {
|
with(requireViewBinding()) {
|
||||||
buttonRead.isEnabled = footer != null
|
buttonRead.isEnabled = footer != null
|
||||||
buttonRead.setTitle(if (footer?.isInProgress() == true) R.string._continue else R.string.read)
|
buttonRead.setText(
|
||||||
buttonRead.subtitle = when {
|
when {
|
||||||
footer == null -> getString(R.string.loading_)
|
footer == null -> R.string.loading_
|
||||||
footer.isIncognito -> getString(R.string.incognito_mode)
|
footer.isIncognito == true -> R.string.incognito
|
||||||
footer.currentChapter >= 0 -> getString(
|
footer.isInProgress() == true -> R.string._continue
|
||||||
R.string.chapter_d_of_d,
|
else -> R.string.read
|
||||||
footer.currentChapter + 1,
|
},
|
||||||
footer.totalChapters,
|
)
|
||||||
)
|
|
||||||
|
|
||||||
footer.totalChapters == 0 -> getString(R.string.no_chapters)
|
|
||||||
else -> resources.getQuantityString(R.plurals.chapters, footer.totalChapters, footer.totalChapters)
|
|
||||||
}
|
|
||||||
buttonRead.setProgress(footer?.percent?.coerceIn(0f, 1f) ?: 0f, true)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import android.view.ViewGroup.MarginLayoutParams
|
|||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.core.graphics.Insets
|
import androidx.core.graphics.Insets
|
||||||
|
import androidx.core.view.MenuHost
|
||||||
import androidx.core.view.OnApplyWindowInsetsListener
|
import androidx.core.view.OnApplyWindowInsetsListener
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import androidx.core.view.isGone
|
import androidx.core.view.isGone
|
||||||
@@ -98,6 +99,9 @@ class ReaderActivity :
|
|||||||
scrollTimer.isEnabled = value
|
scrollTimer.isEnabled = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val secondaryMenuHost: MenuHost
|
||||||
|
get() = viewBinding.toolbarBottom ?: this
|
||||||
|
|
||||||
private lateinit var scrollTimer: ScrollTimer
|
private lateinit var scrollTimer: ScrollTimer
|
||||||
private lateinit var pageSaveHelper: PageSaveHelper
|
private lateinit var pageSaveHelper: PageSaveHelper
|
||||||
private lateinit var touchHelper: TapGridDispatcher
|
private lateinit var touchHelper: TapGridDispatcher
|
||||||
@@ -150,7 +154,7 @@ class ReaderActivity :
|
|||||||
viewModel.isInfoBarTransparent.observe(this) { viewBinding.infoBar.drawBackground = !it }
|
viewModel.isInfoBarTransparent.observe(this) { viewBinding.infoBar.drawBackground = !it }
|
||||||
viewModel.isInfoBarEnabled.observe(this, ::onReaderBarChanged)
|
viewModel.isInfoBarEnabled.observe(this, ::onReaderBarChanged)
|
||||||
viewModel.isBookmarkAdded.observe(this, MenuInvalidator(this))
|
viewModel.isBookmarkAdded.observe(this, MenuInvalidator(this))
|
||||||
val bottomMenuInvalidator = MenuInvalidator(viewBinding.toolbarBottom)
|
val bottomMenuInvalidator = MenuInvalidator(secondaryMenuHost)
|
||||||
viewModel.isPagesSheetEnabled.observe(this, bottomMenuInvalidator)
|
viewModel.isPagesSheetEnabled.observe(this, bottomMenuInvalidator)
|
||||||
screenOrientationHelper.observeAutoOrientation().observe(this, bottomMenuInvalidator)
|
screenOrientationHelper.observeAutoOrientation().observe(this, bottomMenuInvalidator)
|
||||||
viewModel.onShowToast.observeEvent(this) { msgId ->
|
viewModel.onShowToast.observeEvent(this) { msgId ->
|
||||||
@@ -165,7 +169,7 @@ class ReaderActivity :
|
|||||||
viewBinding.zoomControl.isVisible = it
|
viewBinding.zoomControl.isVisible = it
|
||||||
}
|
}
|
||||||
addMenuProvider(ReaderMenuTopProvider(viewModel))
|
addMenuProvider(ReaderMenuTopProvider(viewModel))
|
||||||
viewBinding.toolbarBottom.addMenuProvider(
|
secondaryMenuHost.addMenuProvider(
|
||||||
ReaderMenuBottomProvider(this, readerManager, screenOrientationHelper, this, viewModel),
|
ReaderMenuBottomProvider(this, readerManager, screenOrientationHelper, this, viewModel),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -221,7 +225,7 @@ class ReaderActivity :
|
|||||||
} else {
|
} else {
|
||||||
viewBinding.toastView.hide()
|
viewBinding.toastView.hide()
|
||||||
}
|
}
|
||||||
viewBinding.toolbarBottom.invalidateMenu()
|
secondaryMenuHost.invalidateMenu()
|
||||||
invalidateOptionsMenu()
|
invalidateOptionsMenu()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,7 +246,7 @@ class ReaderActivity :
|
|||||||
rawX >= viewBinding.root.width - gestureInsets.right ||
|
rawX >= viewBinding.root.width - gestureInsets.right ||
|
||||||
rawY >= viewBinding.root.height - gestureInsets.bottom ||
|
rawY >= viewBinding.root.height - gestureInsets.bottom ||
|
||||||
viewBinding.appbarTop.hasGlobalPoint(rawX, rawY) ||
|
viewBinding.appbarTop.hasGlobalPoint(rawX, rawY) ||
|
||||||
viewBinding.appbarBottom.hasGlobalPoint(rawX, rawY) == true
|
viewBinding.appbarBottom?.hasGlobalPoint(rawX, rawY) == true
|
||||||
) {
|
) {
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
@@ -306,7 +310,7 @@ class ReaderActivity :
|
|||||||
buttonPrev.isVisible = ReaderControl.PREV_CHAPTER in controls
|
buttonPrev.isVisible = ReaderControl.PREV_CHAPTER in controls
|
||||||
buttonNext.isVisible = ReaderControl.NEXT_CHAPTER in controls
|
buttonNext.isVisible = ReaderControl.NEXT_CHAPTER in controls
|
||||||
slider.isVisible = ReaderControl.SLIDER in controls
|
slider.isVisible = ReaderControl.SLIDER in controls
|
||||||
toolbarBottom.invalidateMenu()
|
secondaryMenuHost.invalidateMenu()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setUiIsVisible(isUiVisible: Boolean) {
|
private fun setUiIsVisible(isUiVisible: Boolean) {
|
||||||
@@ -321,7 +325,7 @@ class ReaderActivity :
|
|||||||
}
|
}
|
||||||
val isFullscreen = settings.isReaderFullscreenEnabled
|
val isFullscreen = settings.isReaderFullscreenEnabled
|
||||||
viewBinding.appbarTop.isVisible = isUiVisible
|
viewBinding.appbarTop.isVisible = isUiVisible
|
||||||
viewBinding.appbarBottom.isVisible = isUiVisible
|
viewBinding.appbarBottom?.isVisible = isUiVisible
|
||||||
viewBinding.infoBar.isGone = isUiVisible || (!viewModel.isInfoBarEnabled.value)
|
viewBinding.infoBar.isGone = isUiVisible || (!viewModel.isInfoBarEnabled.value)
|
||||||
viewBinding.infoBar.isTimeVisible = isFullscreen
|
viewBinding.infoBar.isTimeVisible = isFullscreen
|
||||||
systemUiController.setSystemUiVisible(isUiVisible || !isFullscreen)
|
systemUiController.setSystemUiVisible(isUiVisible || !isFullscreen)
|
||||||
@@ -336,7 +340,7 @@ class ReaderActivity :
|
|||||||
right = systemBars.right,
|
right = systemBars.right,
|
||||||
left = systemBars.left,
|
left = systemBars.left,
|
||||||
)
|
)
|
||||||
viewBinding.appbarBottom.updateLayoutParams<MarginLayoutParams> {
|
viewBinding.appbarBottom?.updateLayoutParams<MarginLayoutParams> {
|
||||||
bottomMargin = systemBars.bottom + topMargin
|
bottomMargin = systemBars.bottom + topMargin
|
||||||
rightMargin = systemBars.right + topMargin
|
rightMargin = systemBars.right + topMargin
|
||||||
leftMargin = systemBars.left + topMargin
|
leftMargin = systemBars.left + topMargin
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import org.koitharu.kotatsu.databinding.FragmentListBinding
|
|||||||
import org.koitharu.kotatsu.filter.ui.FilterCoordinator
|
import org.koitharu.kotatsu.filter.ui.FilterCoordinator
|
||||||
import org.koitharu.kotatsu.list.ui.MangaListFragment
|
import org.koitharu.kotatsu.list.ui.MangaListFragment
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
|
import org.koitharu.kotatsu.search.domain.SearchKind
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class RemoteListFragment : MangaListFragment(), FilterCoordinator.Owner {
|
class RemoteListFragment : MangaListFragment(), FilterCoordinator.Owner {
|
||||||
@@ -72,6 +73,15 @@ class RemoteListFragment : MangaListFragment(), FilterCoordinator.Owner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onFooterButtonClick() {
|
||||||
|
val filter = filterCoordinator.snapshot().listFilter
|
||||||
|
when {
|
||||||
|
!filter.query.isNullOrEmpty() -> router.openSearch(filter.query.orEmpty(), SearchKind.SIMPLE)
|
||||||
|
!filter.author.isNullOrEmpty() -> router.openSearch(filter.author.orEmpty(), SearchKind.AUTHOR)
|
||||||
|
filter.tags.size == 1 -> router.openSearch(filter.tags.singleOrNull()?.title.orEmpty(), SearchKind.TAG)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onSecondaryErrorActionClick(error: Throwable) {
|
override fun onSecondaryErrorActionClick(error: Throwable) {
|
||||||
openInBrowser(error.getCauseUrl())
|
openInBrowser(error.getCauseUrl())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import org.koitharu.kotatsu.explore.domain.ExploreRepository
|
|||||||
import org.koitharu.kotatsu.filter.ui.FilterCoordinator
|
import org.koitharu.kotatsu.filter.ui.FilterCoordinator
|
||||||
import org.koitharu.kotatsu.list.domain.MangaListMapper
|
import org.koitharu.kotatsu.list.domain.MangaListMapper
|
||||||
import org.koitharu.kotatsu.list.ui.MangaListViewModel
|
import org.koitharu.kotatsu.list.ui.MangaListViewModel
|
||||||
|
import org.koitharu.kotatsu.list.ui.model.ButtonFooter
|
||||||
import org.koitharu.kotatsu.list.ui.model.EmptyState
|
import org.koitharu.kotatsu.list.ui.model.EmptyState
|
||||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||||
import org.koitharu.kotatsu.list.ui.model.LoadingFooter
|
import org.koitharu.kotatsu.list.ui.model.LoadingFooter
|
||||||
@@ -90,6 +91,7 @@ open class RemoteListViewModel @Inject constructor(
|
|||||||
when {
|
when {
|
||||||
error != null -> add(error.toErrorFooter())
|
error != null -> add(error.toErrorFooter())
|
||||||
hasNext -> add(LoadingFooter())
|
hasNext -> add(LoadingFooter())
|
||||||
|
else -> getFooter()?.let(::add)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -178,6 +180,18 @@ open class RemoteListViewModel @Inject constructor(
|
|||||||
mode: ListMode
|
mode: ListMode
|
||||||
) = mangaListMapper.toListModelList(destination, manga, mode)
|
) = mangaListMapper.toListModelList(destination, manga, mode)
|
||||||
|
|
||||||
|
protected open fun getFooter(): ButtonFooter? {
|
||||||
|
val filter = filterCoordinator.snapshot().listFilter
|
||||||
|
val hasQuery = !filter.query.isNullOrEmpty()
|
||||||
|
val hasAuthor = !filter.author.isNullOrEmpty()
|
||||||
|
val isOneTag = filter.tags.size == 1
|
||||||
|
return if ((hasQuery xor isOneTag xor hasAuthor) && !(hasQuery && isOneTag && hasAuthor)) {
|
||||||
|
ButtonFooter(R.string.global_search)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun openRandom() {
|
fun openRandom() {
|
||||||
if (randomJob?.isActive == true) {
|
if (randomJob?.isActive == true) {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
|||||||
import org.koitharu.kotatsu.core.util.ext.require
|
import org.koitharu.kotatsu.core.util.ext.require
|
||||||
import org.koitharu.kotatsu.core.util.ext.requireValue
|
import org.koitharu.kotatsu.core.util.ext.requireValue
|
||||||
import org.koitharu.kotatsu.history.data.HistoryRepository
|
import org.koitharu.kotatsu.history.data.HistoryRepository
|
||||||
|
import org.koitharu.kotatsu.list.domain.ReadingProgress
|
||||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||||
import org.koitharu.kotatsu.list.ui.model.LoadingFooter
|
import org.koitharu.kotatsu.list.ui.model.LoadingFooter
|
||||||
import org.koitharu.kotatsu.list.ui.model.LoadingState
|
import org.koitharu.kotatsu.list.ui.model.LoadingState
|
||||||
@@ -159,7 +160,7 @@ class ScrobblingSelectorViewModel @Inject constructor(
|
|||||||
rating = prevInfo?.rating ?: 0f,
|
rating = prevInfo?.rating ?: 0f,
|
||||||
status = prevInfo?.status ?: when {
|
status = prevInfo?.status ?: when {
|
||||||
history == null -> ScrobblingStatus.PLANNED
|
history == null -> ScrobblingStatus.PLANNED
|
||||||
history.percent == 1f -> ScrobblingStatus.COMPLETED
|
ReadingProgress.isCompleted(history.percent) -> ScrobblingStatus.COMPLETED
|
||||||
else -> ScrobblingStatus.READING
|
else -> ScrobblingStatus.READING
|
||||||
},
|
},
|
||||||
comment = prevInfo?.comment,
|
comment = prevInfo?.comment,
|
||||||
|
|||||||
@@ -38,8 +38,15 @@ class SearchV2Helper @AssistedInject constructor(
|
|||||||
|
|
||||||
private suspend fun MangaRepository.getFilter(query: String, kind: SearchKind): MangaListFilter? = when (kind) {
|
private suspend fun MangaRepository.getFilter(query: String, kind: SearchKind): MangaListFilter? = when (kind) {
|
||||||
SearchKind.SIMPLE,
|
SearchKind.SIMPLE,
|
||||||
SearchKind.TITLE,
|
SearchKind.TITLE -> if (filterCapabilities.isSearchSupported) {
|
||||||
SearchKind.AUTHOR -> if (filterCapabilities.isSearchSupported) { // TODO author support
|
MangaListFilter(query = query)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchKind.AUTHOR -> if (filterCapabilities.isAuthorSearchSupported) {
|
||||||
|
MangaListFilter(author = query)
|
||||||
|
} else if (filterCapabilities.isSearchSupported) {
|
||||||
MangaListFilter(query = query)
|
MangaListFilter(query = query)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ data class SearchResultsListModel(
|
|||||||
val source: MangaSource,
|
val source: MangaSource,
|
||||||
val listFilter: MangaListFilter?,
|
val listFilter: MangaListFilter?,
|
||||||
val sortOrder: SortOrder?,
|
val sortOrder: SortOrder?,
|
||||||
val hasMore: Boolean,
|
|
||||||
val list: List<MangaListModel>,
|
val list: List<MangaListModel>,
|
||||||
val error: Throwable?,
|
val error: Throwable?,
|
||||||
) : ListModel {
|
) : ListModel {
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ import org.koitharu.kotatsu.search.domain.SearchV2Helper
|
|||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
private const val MAX_PARALLELISM = 4
|
private const val MAX_PARALLELISM = 4
|
||||||
private const val MIN_HAS_MORE_ITEMS = 8
|
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class SearchViewModel @Inject constructor(
|
class SearchViewModel @Inject constructor(
|
||||||
@@ -132,7 +131,6 @@ class SearchViewModel @Inject constructor(
|
|||||||
SearchResultsListModel(
|
SearchResultsListModel(
|
||||||
titleResId = 0,
|
titleResId = 0,
|
||||||
source = source,
|
source = source,
|
||||||
hasMore = list.size > MIN_HAS_MORE_ITEMS,
|
|
||||||
list = list,
|
list = list,
|
||||||
error = null,
|
error = null,
|
||||||
listFilter = result.listFilter,
|
listFilter = result.listFilter,
|
||||||
@@ -142,7 +140,7 @@ class SearchViewModel @Inject constructor(
|
|||||||
},
|
},
|
||||||
onFailure = { error ->
|
onFailure = { error ->
|
||||||
error.printStackTraceDebug()
|
error.printStackTraceDebug()
|
||||||
SearchResultsListModel(0, source, null, null, true, emptyList(), error)
|
SearchResultsListModel(0, source, null, null, emptyList(), error)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if (item != null) {
|
if (item != null) {
|
||||||
@@ -163,7 +161,6 @@ class SearchViewModel @Inject constructor(
|
|||||||
SearchResultsListModel(
|
SearchResultsListModel(
|
||||||
titleResId = R.string.history,
|
titleResId = R.string.history,
|
||||||
source = UnknownMangaSource,
|
source = UnknownMangaSource,
|
||||||
hasMore = false,
|
|
||||||
list = mangaListMapper.toListModelList(manga = result, mode = ListMode.GRID),
|
list = mangaListMapper.toListModelList(manga = result, mode = ListMode.GRID),
|
||||||
error = null,
|
error = null,
|
||||||
listFilter = null,
|
listFilter = null,
|
||||||
@@ -177,7 +174,6 @@ class SearchViewModel @Inject constructor(
|
|||||||
SearchResultsListModel(
|
SearchResultsListModel(
|
||||||
titleResId = R.string.history,
|
titleResId = R.string.history,
|
||||||
source = UnknownMangaSource,
|
source = UnknownMangaSource,
|
||||||
hasMore = false,
|
|
||||||
list = emptyList(),
|
list = emptyList(),
|
||||||
error = error,
|
error = error,
|
||||||
listFilter = null,
|
listFilter = null,
|
||||||
@@ -196,7 +192,6 @@ class SearchViewModel @Inject constructor(
|
|||||||
SearchResultsListModel(
|
SearchResultsListModel(
|
||||||
titleResId = R.string.favourites,
|
titleResId = R.string.favourites,
|
||||||
source = UnknownMangaSource,
|
source = UnknownMangaSource,
|
||||||
hasMore = false,
|
|
||||||
list = mangaListMapper.toListModelList(
|
list = mangaListMapper.toListModelList(
|
||||||
manga = result,
|
manga = result,
|
||||||
mode = ListMode.GRID,
|
mode = ListMode.GRID,
|
||||||
@@ -214,7 +209,6 @@ class SearchViewModel @Inject constructor(
|
|||||||
SearchResultsListModel(
|
SearchResultsListModel(
|
||||||
titleResId = R.string.favourites,
|
titleResId = R.string.favourites,
|
||||||
source = UnknownMangaSource,
|
source = UnknownMangaSource,
|
||||||
hasMore = false,
|
|
||||||
list = emptyList(),
|
list = emptyList(),
|
||||||
error = error,
|
error = error,
|
||||||
listFilter = null,
|
listFilter = null,
|
||||||
@@ -233,7 +227,6 @@ class SearchViewModel @Inject constructor(
|
|||||||
SearchResultsListModel(
|
SearchResultsListModel(
|
||||||
titleResId = 0,
|
titleResId = 0,
|
||||||
source = LocalMangaSource,
|
source = LocalMangaSource,
|
||||||
hasMore = result.manga.size > MIN_HAS_MORE_ITEMS,
|
|
||||||
list = mangaListMapper.toListModelList(
|
list = mangaListMapper.toListModelList(
|
||||||
manga = result.manga,
|
manga = result.manga,
|
||||||
mode = ListMode.GRID,
|
mode = ListMode.GRID,
|
||||||
@@ -251,7 +244,6 @@ class SearchViewModel @Inject constructor(
|
|||||||
SearchResultsListModel(
|
SearchResultsListModel(
|
||||||
titleResId = 0,
|
titleResId = 0,
|
||||||
source = LocalMangaSource,
|
source = LocalMangaSource,
|
||||||
hasMore = true,
|
|
||||||
list = emptyList(),
|
list = emptyList(),
|
||||||
error = error,
|
error = error,
|
||||||
listFilter = null,
|
listFilter = null,
|
||||||
|
|||||||
139
app/src/main/res/layout-w600dp-land/activity_reader.xml
Normal file
139
app/src/main/res/layout-w600dp-land/activity_reader.xml
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
|
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">
|
||||||
|
|
||||||
|
<androidx.fragment.app.FragmentContainerView
|
||||||
|
android:id="@+id/container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:background="@color/grey" />
|
||||||
|
|
||||||
|
<org.koitharu.kotatsu.core.ui.widgets.ZoomControl
|
||||||
|
android:id="@+id/zoomControl"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="bottom|end"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:spacing="2dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_dodgeInsetEdges="bottom"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<org.koitharu.kotatsu.reader.ui.ReaderInfoBarView
|
||||||
|
android:id="@+id/infoBar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="top"
|
||||||
|
android:padding="6dp"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
|
android:id="@+id/appbar_top"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:elevation="@dimen/m3_card_elevated_elevation"
|
||||||
|
app:elevation="@dimen/m3_card_elevated_elevation"
|
||||||
|
app:liftOnScroll="false">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
|
android:id="@id/toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:elevation="@dimen/m3_card_elevated_elevation"
|
||||||
|
app:elevation="@dimen/m3_card_elevated_elevation"
|
||||||
|
app:popupTheme="@style/ThemeOverlay.Kotatsu"
|
||||||
|
tools:menu="@menu/opt_reader_top">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/layout_slider"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical|end"
|
||||||
|
android:layout_marginEnd="2dp"
|
||||||
|
android:gravity="center_vertical|end">
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/button_prev"
|
||||||
|
style="?actionButtonStyle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:contentDescription="@string/prev_chapter"
|
||||||
|
android:src="@drawable/ic_prev"
|
||||||
|
android:tooltipText="@string/prev_chapter" />
|
||||||
|
|
||||||
|
<com.google.android.material.slider.Slider
|
||||||
|
android:id="@+id/slider"
|
||||||
|
android:layout_width="260dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:stepSize="1.0"
|
||||||
|
android:valueFrom="0"
|
||||||
|
app:labelBehavior="floating"
|
||||||
|
tools:value="6"
|
||||||
|
tools:valueTo="20" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/button_next"
|
||||||
|
style="?actionButtonStyle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:contentDescription="@string/next_chapter"
|
||||||
|
android:src="@drawable/ic_next"
|
||||||
|
android:tooltipText="@string/next_chapter" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.MaterialToolbar>
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
<org.koitharu.kotatsu.reader.ui.ReaderToastView
|
||||||
|
android:id="@+id/toastView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="bottom|center_horizontal"
|
||||||
|
android:layout_marginBottom="20dp"
|
||||||
|
android:background="@drawable/bg_reader_indicator"
|
||||||
|
android:drawablePadding="6dp"
|
||||||
|
android:elevation="1000dp"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textAppearance="?attr/textAppearanceBodySmall"
|
||||||
|
android:theme="@style/ThemeOverlay.Material3.Dark"
|
||||||
|
app:layout_dodgeInsetEdges="bottom"
|
||||||
|
tools:text="@string/loading_" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/layout_loading"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:background="@drawable/bg_card"
|
||||||
|
android:backgroundTint="?colorSurfaceContainer"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:outlineProvider="background"
|
||||||
|
android:padding="@dimen/screen_padding">
|
||||||
|
|
||||||
|
<com.google.android.material.progressindicator.CircularProgressIndicator
|
||||||
|
android:id="@+id/progressBar"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:indeterminate="true" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/textView_loading"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:text="@string/loading_"
|
||||||
|
android:textAppearance="?attr/textAppearanceTitleMedium" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
android:id="@+id/scrollView"
|
android:id="@+id/scrollView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:background="@macro/m3_comp_filled_card_container_color">
|
tools:background="?colorBackgroundFloating">
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -122,50 +122,38 @@
|
|||||||
app:barrierMargin="8dp"
|
app:barrierMargin="8dp"
|
||||||
app:constraint_referenced_ids="imageView_cover,rating_bar" />
|
app:constraint_referenced_ids="imageView_cover,rating_bar" />
|
||||||
|
|
||||||
<org.koitharu.kotatsu.core.ui.widgets.ProgressButton
|
<Button
|
||||||
android:id="@+id/button_read"
|
android:id="@+id/button_read"
|
||||||
|
style="?materialButtonStyle"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="16dp"
|
android:layout_marginStart="16dp"
|
||||||
android:layout_marginTop="@dimen/margin_normal"
|
android:layout_marginTop="@dimen/margin_normal"
|
||||||
android:layout_marginEnd="12dp"
|
android:layout_marginEnd="12dp"
|
||||||
android:foreground="?selectableItemBackground"
|
|
||||||
android:gravity="center"
|
|
||||||
android:paddingHorizontal="6dp"
|
|
||||||
android:paddingVertical="8dp"
|
|
||||||
app:baseColor="@color/m3_chip_background_color"
|
|
||||||
app:layout_constraintEnd_toStartOf="@id/button_open"
|
app:layout_constraintEnd_toStartOf="@id/button_open"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/barrier_header"
|
app:layout_constraintTop_toBottomOf="@id/barrier_header"
|
||||||
app:progressColor="?colorControlNormal"
|
tools:text="@string/read" />
|
||||||
app:subtitleTextAppearance="?textAppearanceBodySmall"
|
|
||||||
app:titleTextAppearance="?textAppearanceButton"
|
|
||||||
tools:max="100"
|
|
||||||
tools:progress="40"
|
|
||||||
tools:subtitle="12 chapters"
|
|
||||||
tools:title="@string/read" />
|
|
||||||
|
|
||||||
<ImageView
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/button_open"
|
android:id="@+id/button_open"
|
||||||
|
style="?materialIconButtonStyle"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_marginEnd="16dp"
|
android:layout_marginEnd="16dp"
|
||||||
android:background="@drawable/bg_circle_button"
|
|
||||||
android:backgroundTint="@color/m3_chip_background_color"
|
|
||||||
android:contentDescription="@string/details"
|
android:contentDescription="@string/details"
|
||||||
android:scaleType="centerInside"
|
app:icon="@drawable/ic_expand"
|
||||||
app:layout_constraintBottom_toBottomOf="@id/button_read"
|
app:layout_constraintBottom_toBottomOf="@id/button_read"
|
||||||
app:layout_constraintDimensionRatio="1"
|
app:layout_constraintDimensionRatio="1"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="@id/button_read"
|
app:layout_constraintTop_toTopOf="@id/button_read" />
|
||||||
app:srcCompat="@drawable/ic_expand" />
|
|
||||||
|
|
||||||
<org.koitharu.kotatsu.core.ui.widgets.ChipsView
|
<org.koitharu.kotatsu.core.ui.widgets.ChipsView
|
||||||
android:id="@+id/chips_tags"
|
android:id="@+id/chips_tags"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="@dimen/margin_normal"
|
|
||||||
android:layout_marginHorizontal="@dimen/screen_padding"
|
android:layout_marginHorizontal="@dimen/screen_padding"
|
||||||
|
android:layout_marginTop="@dimen/margin_normal"
|
||||||
app:chipSpacingHorizontal="6dp"
|
app:chipSpacingHorizontal="6dp"
|
||||||
app:chipSpacingVertical="6dp"
|
app:chipSpacingVertical="6dp"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
|||||||
17
app/src/main/res/layout/item_button_footer.xml
Normal file
17
app/src/main/res/layout/item_button_footer.xml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FrameLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:padding="4dp">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/button"
|
||||||
|
style="?materialButtonOutlinedStyle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
tools:text="@string/globally" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
@@ -23,6 +23,19 @@
|
|||||||
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Cover"
|
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Cover"
|
||||||
tools:src="@tools:sample/backgrounds/scenic" />
|
tools:src="@tools:sample/backgrounds/scenic" />
|
||||||
|
|
||||||
|
<org.koitharu.kotatsu.core.ui.widgets.IconsView
|
||||||
|
android:id="@+id/iconsView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:background="@drawable/bg_list_icons"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="4dp"
|
||||||
|
app:iconSize="14dp"
|
||||||
|
app:iconSpacing="4dp"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/imageView_cover"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/imageView_cover" />
|
||||||
|
|
||||||
<org.koitharu.kotatsu.history.ui.util.ReadingProgressView
|
<org.koitharu.kotatsu.history.ui.util.ReadingProgressView
|
||||||
android:id="@+id/progressView"
|
android:id="@+id/progressView"
|
||||||
android:layout_width="@dimen/card_indicator_size"
|
android:layout_width="@dimen/card_indicator_size"
|
||||||
|
|||||||
@@ -803,4 +803,5 @@
|
|||||||
<string name="badges_in_lists">Badges in lists</string>
|
<string name="badges_in_lists">Badges in lists</string>
|
||||||
<string name="search_everywhere">Search everywhere</string>
|
<string name="search_everywhere">Search everywhere</string>
|
||||||
<string name="simple">Simple</string>
|
<string name="simple">Simple</string>
|
||||||
|
<string name="global_search">Global search</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ material = "1.13.0-alpha10"
|
|||||||
moshi = "1.15.2"
|
moshi = "1.15.2"
|
||||||
okhttp = "4.12.0"
|
okhttp = "4.12.0"
|
||||||
okio = "3.10.2"
|
okio = "3.10.2"
|
||||||
parsers = "198e859850"
|
parsers = "88ea5215c0"
|
||||||
preference = "1.2.1"
|
preference = "1.2.1"
|
||||||
recyclerview = "1.4.0"
|
recyclerview = "1.4.0"
|
||||||
room = "2.6.1"
|
room = "2.6.1"
|
||||||
|
|||||||
Reference in New Issue
Block a user