Safe way to getQuantityString

This commit is contained in:
Koitharu
2025-02-25 08:53:45 +02:00
parent e98f5b9d54
commit 8d44ad8866
17 changed files with 66 additions and 33 deletions

View File

@@ -27,6 +27,7 @@ import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders
import org.koitharu.kotatsu.core.util.ext.enqueueWith
import org.koitharu.kotatsu.core.util.ext.getQuantityStringSafe
import org.koitharu.kotatsu.core.util.ext.mangaExtra
import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra
import org.koitharu.kotatsu.core.util.ext.newImageRequest
@@ -61,7 +62,13 @@ fun alternativeAD(
}
binding.textViewSubtitle.text = buildSpannedString {
if (item.chaptersCount > 0) {
append(context.resources.getQuantityString(R.plurals.chapters, item.chaptersCount, item.chaptersCount))
append(
context.resources.getQuantityStringSafe(
R.plurals.chapters,
item.chaptersCount,
item.chaptersCount,
),
)
} else {
append(context.getString(R.string.no_chapters))
}
@@ -77,7 +84,10 @@ fun alternativeAD(
}
}
}
binding.progressView.setProgress(item.mangaModel.progress, ListModelDiffCallback.PAYLOAD_PROGRESS_CHANGED in payloads)
binding.progressView.setProgress(
item.mangaModel.progress,
ListModelDiffCallback.PAYLOAD_PROGRESS_CHANGED in payloads,
)
binding.chipSource.also { chip ->
chip.text = item.manga.source.getTitle(chip.context)
ImageRequest.Builder(context)

View File

@@ -3,6 +3,7 @@ package org.koitharu.kotatsu.core.ui.model
import android.content.Context
import android.text.format.DateUtils
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.util.ext.getQuantityStringSafe
import org.koitharu.kotatsu.core.util.ext.toMillis
import java.time.LocalDate
@@ -22,7 +23,7 @@ sealed class DateTimeAgo {
data class MinutesAgo(val minutes: Int) : DateTimeAgo() {
override fun format(context: Context): String {
return context.resources.getQuantityString(
return context.resources.getQuantityStringSafe(
R.plurals.minutes_ago,
minutes,
minutes,
@@ -34,7 +35,7 @@ sealed class DateTimeAgo {
data class HoursAgo(val hours: Int) : DateTimeAgo() {
override fun format(context: Context): String {
return context.resources.getQuantityString(
return context.resources.getQuantityStringSafe(
R.plurals.hours_ago,
hours,
hours,
@@ -66,7 +67,7 @@ sealed class DateTimeAgo {
data class DaysAgo(val days: Int) : DateTimeAgo() {
override fun format(context: Context): String {
return context.resources.getQuantityString(R.plurals.days_ago, days, days)
return context.resources.getQuantityStringSafe(R.plurals.days_ago, days, days)
}
override fun toString() = "days_ago_$days"
@@ -77,7 +78,7 @@ sealed class DateTimeAgo {
return if (months == 0) {
context.getString(R.string.this_month)
} else {
context.resources.getQuantityString(
context.resources.getQuantityStringSafe(
R.plurals.months_ago,
months,
months,

View File

@@ -3,6 +3,7 @@ package org.koitharu.kotatsu.core.util.ext
import android.annotation.SuppressLint
import android.content.Context
import android.content.res.Resources
import androidx.annotation.PluralsRes
import androidx.annotation.Px
import androidx.core.util.TypedValueCompat
import kotlin.math.roundToInt
@@ -25,3 +26,11 @@ fun Context.getSystemBoolean(resName: String, fallback: Boolean): Boolean {
fallback
}
}
fun Resources.getQuantityStringSafe(@PluralsRes resId: Int, quantity: Int, vararg formatArgs: Any): String = try {
getQuantityString(resId, quantity, *formatArgs)
} catch (e: Resources.NotFoundException) {
e.report(silent = true)
e.printStackTraceDebug()
formatArgs.firstOrNull()?.toString() ?: quantity.toString()
}

View File

@@ -12,6 +12,7 @@ import okio.ProtocolException
import org.acra.ktx.sendSilentlyWithAcra
import org.acra.ktx.sendWithAcra
import org.jsoup.HttpStatusException
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.BadBackupFormatException
import org.koitharu.kotatsu.core.exceptions.CaughtException
@@ -207,10 +208,10 @@ fun Throwable.isNetworkError(): Boolean {
fun Throwable.report(silent: Boolean = false) {
val exception = CaughtException(this)
if (silent) {
exception.sendSilentlyWithAcra()
} else {
if (!silent) {
exception.sendWithAcra()
} else if (!BuildConfig.DEBUG) {
exception.sendSilentlyWithAcra()
}
}

View File

@@ -2,6 +2,7 @@ package org.koitharu.kotatsu.details.data
import android.content.res.Resources
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.util.ext.getQuantityStringSafe
data class ReadingTime(
val minutes: Int,
@@ -11,12 +12,12 @@ data class ReadingTime(
fun format(resources: Resources): String = when {
hours == 0 && minutes == 0 -> resources.getString(R.string.less_than_minute)
hours == 0 -> resources.getQuantityString(R.plurals.minutes, minutes, minutes)
minutes == 0 -> resources.getQuantityString(R.plurals.hours, hours, hours)
hours == 0 -> resources.getQuantityStringSafe(R.plurals.minutes, minutes, minutes)
minutes == 0 -> resources.getQuantityStringSafe(R.plurals.hours, hours, hours)
else -> resources.getString(
R.string.remaining_time_pattern,
resources.getQuantityString(R.plurals.hours, hours, hours),
resources.getQuantityString(R.plurals.minutes, minutes, minutes),
resources.getQuantityStringSafe(R.plurals.hours, hours, hours),
resources.getQuantityStringSafe(R.plurals.minutes, minutes, minutes),
)
}

View File

@@ -73,6 +73,7 @@ import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders
import org.koitharu.kotatsu.core.util.ext.drawable
import org.koitharu.kotatsu.core.util.ext.drawableStart
import org.koitharu.kotatsu.core.util.ext.enqueueWith
import org.koitharu.kotatsu.core.util.ext.getQuantityStringSafe
import org.koitharu.kotatsu.core.util.ext.isTextTruncated
import org.koitharu.kotatsu.core.util.ext.joinToStringWithLimit
import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra
@@ -477,7 +478,7 @@ class DetailsActivity :
info.totalChapters == 0 -> getString(R.string.no_chapters)
info.totalChapters == -1 -> getString(R.string.error_occurred)
else -> resources.getQuantityString(R.plurals.chapters, info.totalChapters, info.totalChapters)
else -> resources.getQuantityStringSafe(R.plurals.chapters, info.totalChapters, info.totalChapters)
.withEstimatedTime(info.estimatedTime)
}
textViewProgress.textAndVisible = if (info.percent <= 0f) {

View File

@@ -24,6 +24,7 @@ import org.koitharu.kotatsu.core.ui.AlertDialogFragment
import org.koitharu.kotatsu.core.ui.widgets.TwoLinesItemView
import org.koitharu.kotatsu.core.util.ext.findActivity
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
import org.koitharu.kotatsu.core.util.ext.getQuantityStringSafe
import org.koitharu.kotatsu.core.util.ext.joinToStringWithLimit
import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent
@@ -169,7 +170,7 @@ class DownloadDialogFragment : AlertDialogFragment<DialogDownloadBinding>(), Vie
with(viewBinding ?: return) {
// Whole manga
optionWholeManga.subtitle = if (options.wholeManga.chaptersCount > 0) {
resources.getQuantityString(
resources.getQuantityStringSafe(
R.plurals.chapters,
options.wholeManga.chaptersCount,
options.wholeManga.chaptersCount,
@@ -185,7 +186,7 @@ class DownloadDialogFragment : AlertDialogFragment<DialogDownloadBinding>(), Vie
it.selectedBranch,
)
optionWholeBranch.subtitle = if (it.chaptersCount > 0) {
resources.getQuantityString(
resources.getQuantityStringSafe(
R.plurals.chapters,
it.chaptersCount,
it.chaptersCount,
@@ -199,7 +200,7 @@ class DownloadDialogFragment : AlertDialogFragment<DialogDownloadBinding>(), Vie
options.firstChapters?.let {
optionFirstChapters.title = resources.getString(
R.string.download_option_first_n_chapters,
resources.getQuantityString(
resources.getQuantityStringSafe(
R.plurals.chapters,
it.chaptersCount,
it.chaptersCount,
@@ -215,7 +216,7 @@ class DownloadDialogFragment : AlertDialogFragment<DialogDownloadBinding>(), Vie
} else {
resources.getString(
R.string.download_option_next_unread_n_chapters,
resources.getQuantityString(
resources.getQuantityStringSafe(
R.plurals.chapters,
it.chaptersCount,
it.chaptersCount,

View File

@@ -21,6 +21,7 @@ import org.koitharu.kotatsu.core.ui.BaseListAdapter
import org.koitharu.kotatsu.core.ui.image.TrimTransformation
import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders
import org.koitharu.kotatsu.core.util.ext.enqueueWith
import org.koitharu.kotatsu.core.util.ext.getQuantityStringSafe
import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra
import org.koitharu.kotatsu.core.util.ext.newImageRequest
import org.koitharu.kotatsu.core.util.ext.textAndVisible
@@ -161,7 +162,7 @@ fun downloadItemAD(
binding.progressBar.isEnabled = true
binding.textViewPercent.isVisible = false
if (item.chaptersDownloaded > 0) {
binding.textViewDetails.text = context.resources.getQuantityString(
binding.textViewDetails.text = context.resources.getQuantityStringSafe(
R.plurals.chapters,
item.chaptersDownloaded,
item.chaptersDownloaded,

View File

@@ -24,6 +24,7 @@ import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.util.ext.enqueueWith
import org.koitharu.kotatsu.core.util.ext.getAnimationDuration
import org.koitharu.kotatsu.core.util.ext.getQuantityStringSafe
import org.koitharu.kotatsu.core.util.ext.getThemeColor
import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra
import org.koitharu.kotatsu.core.util.ext.newImageRequest
@@ -79,7 +80,7 @@ fun categoryAD(
binding.textViewSubtitle.text = if (item.mangaCount == 0) {
getString(R.string.empty)
} else {
context.resources.getQuantityString(
context.resources.getQuantityStringSafe(
R.plurals.items,
item.mangaCount,
item.mangaCount,
@@ -139,7 +140,7 @@ fun allCategoriesAD(
binding.textViewSubtitle.text = if (item.mangaCount == 0) {
getString(R.string.empty)
} else {
context.resources.getQuantityString(
context.resources.getQuantityStringSafe(
R.plurals.items,
item.mangaCount,
item.mangaCount,

View File

@@ -16,6 +16,7 @@ import org.koitharu.kotatsu.core.ui.AlertDialogFragment
import org.koitharu.kotatsu.core.ui.widgets.SegmentedBarView
import org.koitharu.kotatsu.core.util.FileSize
import org.koitharu.kotatsu.core.util.KotatsuColors
import org.koitharu.kotatsu.core.util.ext.getQuantityStringSafe
import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.core.util.ext.setProgressIcon
@@ -73,7 +74,7 @@ class LocalInfoDialog : AlertDialogFragment<DialogLocalInfoBinding>(), View.OnCl
} else {
c.getString(
R.string.chapters_deleted_pattern,
c.resources.getQuantityString(R.plurals.chapters, result.first, result.first),
c.resources.getQuantityStringSafe(R.plurals.chapters, result.first, result.first),
FileSize.BYTES.format(c, result.second),
)
}

View File

@@ -20,6 +20,7 @@ import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.DownloadFormat
import org.koitharu.kotatsu.core.prefs.TriStateOption
import org.koitharu.kotatsu.core.ui.BasePreferenceFragment
import org.koitharu.kotatsu.core.util.ext.getQuantityStringSafe
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
import org.koitharu.kotatsu.core.util.ext.resolveFile
import org.koitharu.kotatsu.core.util.ext.setDefaultValueCompat
@@ -145,7 +146,7 @@ class DownloadsSettingsFragment :
private fun Preference.bindDirectoriesCount() {
viewLifecycleScope.launch {
val dirs = storageManager.getReadableDirs().size
summary = resources.getQuantityString(R.plurals.items, dirs, dirs)
summary = resources.getQuantityStringSafe(R.plurals.items, dirs, dirs)
}
}

View File

@@ -12,6 +12,7 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.BasePreferenceFragment
import org.koitharu.kotatsu.core.util.ext.addMenuProvider
import org.koitharu.kotatsu.core.util.ext.getQuantityStringSafe
import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.settings.search.SettingsSearchMenuProvider
import org.koitharu.kotatsu.settings.search.SettingsSearchViewModel
@@ -42,7 +43,7 @@ class RootSettingsFragment : BasePreferenceFragment(0) {
pref.summary = if (it >= 0) {
getString(R.string.enabled_d_of_d, it, total)
} else {
resources.getQuantityString(R.plurals.items, total, total)
resources.getQuantityStringSafe(R.plurals.items, total, total)
}
}
}

View File

@@ -12,6 +12,7 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.nav.router
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.BasePreferenceFragment
import org.koitharu.kotatsu.core.util.ext.getQuantityStringSafe
import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.setDefaultValueCompat
import org.koitharu.kotatsu.explore.data.SourcesSortOrder
@@ -37,7 +38,7 @@ class SourcesSettingsFragment : BasePreferenceFragment(R.string.remote_sources),
findPreference<Preference>(AppSettings.KEY_REMOTE_SOURCES)?.let { pref ->
viewModel.enabledSourcesCount.observe(viewLifecycleOwner) {
pref.summary = if (it >= 0) {
resources.getQuantityString(R.plurals.items, it, it)
resources.getQuantityStringSafe(R.plurals.items, it, it)
} else {
null
}

View File

@@ -14,6 +14,7 @@ import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.BasePreferenceFragment
import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver
import org.koitharu.kotatsu.core.util.FileSize
import org.koitharu.kotatsu.core.util.ext.getQuantityStringSafe
import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.local.data.CacheDir
@@ -38,7 +39,7 @@ class StorageManageSettingsFragment : BasePreferenceFragment(R.string.storage_us
pref.summary = if (it < 0) {
view.context.getString(R.string.loading_)
} else {
pref.context.resources.getQuantityString(R.plurals.items, it, it)
pref.context.resources.getQuantityStringSafe(R.plurals.items, it, it)
}
}
}
@@ -47,7 +48,7 @@ class StorageManageSettingsFragment : BasePreferenceFragment(R.string.storage_us
pref.summary = if (it < 0) {
view.context.getString(R.string.loading_)
} else {
pref.context.resources.getQuantityString(R.plurals.items, it, it)
pref.context.resources.getQuantityStringSafe(R.plurals.items, it, it)
}
}
}
@@ -118,7 +119,7 @@ class StorageManageSettingsFragment : BasePreferenceFragment(R.string.storage_us
} else {
c.getString(
R.string.chapters_deleted_pattern,
c.resources.getQuantityString(R.plurals.chapters, result.first, result.first),
c.resources.getQuantityStringSafe(R.plurals.chapters, result.first, result.first),
FileSize.BYTES.format(c, result.second),
)
}

View File

@@ -58,6 +58,7 @@ import org.koitharu.kotatsu.core.util.ext.awaitUniqueWorkInfoByName
import org.koitharu.kotatsu.core.util.ext.awaitWorkInfosByTag
import org.koitharu.kotatsu.core.util.ext.checkNotificationPermission
import org.koitharu.kotatsu.core.util.ext.flatten
import org.koitharu.kotatsu.core.util.ext.getQuantityStringSafe
import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
import org.koitharu.kotatsu.core.util.ext.sanitize
@@ -311,7 +312,7 @@ class SuggestionsWorker @AssistedInject constructor(
appendLine()
bold {
append(
applicationContext.resources.getQuantityString(
applicationContext.resources.getQuantityStringSafe(
R.plurals.chapters,
chaptersCount,
chaptersCount,

View File

@@ -10,6 +10,7 @@ import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders
import org.koitharu.kotatsu.core.util.ext.drawableStart
import org.koitharu.kotatsu.core.util.ext.enqueueWith
import org.koitharu.kotatsu.core.util.ext.getQuantityStringSafe
import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra
import org.koitharu.kotatsu.core.util.ext.newImageRequest
import org.koitharu.kotatsu.databinding.ItemFeedBinding
@@ -37,7 +38,7 @@ fun feedItemAD(
enqueueWith(coil)
}
binding.textViewTitle.text = item.title
binding.textViewSummary.text = context.resources.getQuantityString(
binding.textViewSummary.text = context.resources.getQuantityStringSafe(
R.plurals.new_chapters,
item.count,
item.count,

View File

@@ -14,12 +14,12 @@ import androidx.core.app.PendingIntentCompat
import androidx.core.content.ContextCompat
import coil3.ImageLoader
import coil3.request.ImageRequest
import dagger.hilt.android.qualifiers.ApplicationContext
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.LocalizedAppContext
import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.util.ext.checkNotificationPermission
import org.koitharu.kotatsu.core.util.ext.getQuantityStringSafe
import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra
import org.koitharu.kotatsu.core.util.ext.toBitmapOrNull
import org.koitharu.kotatsu.parsers.model.Manga
@@ -55,7 +55,7 @@ class TrackerNotificationHelper @Inject constructor(
}
val id = manga.url.hashCode()
val builder = NotificationCompat.Builder(applicationContext, CHANNEL_ID)
val summary = applicationContext.resources.getQuantityString(
val summary = applicationContext.resources.getQuantityStringSafe(
R.plurals.new_chapters,
newChapters.size,
newChapters.size,
@@ -107,7 +107,7 @@ class TrackerNotificationHelper @Inject constructor(
val newChaptersCount = notifications.sumOf { it.newChapters }
val builder = NotificationCompat.Builder(applicationContext, CHANNEL_ID)
with(builder) {
val title = applicationContext.resources.getQuantityString(
val title = applicationContext.resources.getQuantityStringSafe(
R.plurals.new_chapters,
newChaptersCount,
newChaptersCount,