Emoji flags in details
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
package org.koitharu.kotatsu.core.ui.image
|
||||
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.graphics.ColorFilter
|
||||
import android.graphics.Paint
|
||||
import android.graphics.PixelFormat
|
||||
import android.graphics.PointF
|
||||
import android.graphics.Rect
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.widget.TextView
|
||||
import androidx.core.graphics.PaintCompat
|
||||
|
||||
class TextDrawable(
|
||||
val text: String,
|
||||
) : Drawable() {
|
||||
|
||||
private val paint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.SUBPIXEL_TEXT_FLAG)
|
||||
private val textBounds = Rect()
|
||||
private val textPoint = PointF()
|
||||
|
||||
var textSize: Float
|
||||
get() = paint.textSize
|
||||
set(value) {
|
||||
paint.textSize = value
|
||||
measureTextBounds()
|
||||
}
|
||||
|
||||
var textColor: ColorStateList = ColorStateList.valueOf(Color.BLACK)
|
||||
set(value) {
|
||||
field = value
|
||||
onStateChange(state)
|
||||
}
|
||||
|
||||
init {
|
||||
measureTextBounds()
|
||||
}
|
||||
|
||||
override fun draw(canvas: Canvas) {
|
||||
canvas.drawText(text, textPoint.x, textPoint.y, paint)
|
||||
}
|
||||
|
||||
override fun setAlpha(alpha: Int) {
|
||||
paint.alpha = alpha
|
||||
}
|
||||
|
||||
override fun setColorFilter(colorFilter: ColorFilter?) {
|
||||
paint.setColorFilter(colorFilter)
|
||||
}
|
||||
|
||||
override fun getOpacity(): Int = when (paint.alpha) {
|
||||
0 -> PixelFormat.TRANSPARENT
|
||||
255 -> PixelFormat.OPAQUE
|
||||
else -> PixelFormat.TRANSLUCENT
|
||||
}
|
||||
|
||||
override fun onBoundsChange(bounds: Rect) {
|
||||
textPoint.set(
|
||||
bounds.exactCenterX() - textBounds.exactCenterX(),
|
||||
bounds.exactCenterY() - textBounds.exactCenterY(),
|
||||
)
|
||||
}
|
||||
|
||||
override fun getIntrinsicWidth(): Int = textBounds.width()
|
||||
|
||||
override fun getIntrinsicHeight(): Int = textBounds.height()
|
||||
|
||||
override fun setDither(dither: Boolean) {
|
||||
paint.isDither = dither
|
||||
}
|
||||
|
||||
override fun isStateful(): Boolean = textColor.isStateful
|
||||
|
||||
override fun hasFocusStateSpecified(): Boolean = textColor.getColorForState(
|
||||
intArrayOf(android.R.attr.state_focused),
|
||||
textColor.defaultColor,
|
||||
) != textColor.defaultColor
|
||||
|
||||
override fun onStateChange(state: IntArray): Boolean {
|
||||
val prevColor = paint.color
|
||||
paint.color = textColor.getColorForState(state, textColor.defaultColor)
|
||||
return paint.color != prevColor
|
||||
}
|
||||
|
||||
private fun measureTextBounds() {
|
||||
paint.getTextBounds(text, 0, text.length, textBounds)
|
||||
onBoundsChange(bounds)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun compound(textView: TextView, text: String): TextDrawable? {
|
||||
val drawable = TextDrawable(text)
|
||||
drawable.textSize = textView.textSize
|
||||
drawable.textColor = textView.textColors
|
||||
return drawable.takeIf {
|
||||
PaintCompat.hasGlyph(drawable.paint, text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package org.koitharu.kotatsu.core.util
|
||||
|
||||
import android.graphics.Paint
|
||||
import androidx.core.graphics.PaintCompat
|
||||
import org.koitharu.kotatsu.core.util.ext.ifNullOrEmpty
|
||||
import java.util.Locale
|
||||
|
||||
object LocaleUtils {
|
||||
|
||||
private val paint = Paint()
|
||||
|
||||
fun getEmojiFlag(locale: Locale): String? {
|
||||
val code = when (val c = locale.country.ifNullOrEmpty { locale.toLanguageTag() }.uppercase(Locale.ENGLISH)) {
|
||||
"EN" -> "GB"
|
||||
"JA" -> "JP"
|
||||
else -> c
|
||||
}
|
||||
val emoji = countryCodeToEmojiFlag(code)
|
||||
return if (PaintCompat.hasGlyph(paint, emoji)) {
|
||||
emoji
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun countryCodeToEmojiFlag(countryCode: String): String {
|
||||
return countryCode.map { char ->
|
||||
Character.codePointAt("$char", 0) - 0x41 + 0x1F1E6
|
||||
}.map { codePoint ->
|
||||
Character.toChars(codePoint)
|
||||
}.joinToString(separator = "") { charArray ->
|
||||
String(charArray)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,6 +59,7 @@ import org.koitharu.kotatsu.core.ui.BaseListAdapter
|
||||
import org.koitharu.kotatsu.core.ui.OnContextClickListenerCompat
|
||||
import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver
|
||||
import org.koitharu.kotatsu.core.ui.image.FaviconDrawable
|
||||
import org.koitharu.kotatsu.core.ui.image.TextDrawable
|
||||
import org.koitharu.kotatsu.core.ui.image.TextViewTarget
|
||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.core.ui.sheet.BottomSheetCollapseCallback
|
||||
@@ -66,9 +67,11 @@ import org.koitharu.kotatsu.core.ui.util.MenuInvalidator
|
||||
import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver
|
||||
import org.koitharu.kotatsu.core.ui.widgets.ChipsView
|
||||
import org.koitharu.kotatsu.core.util.FileSize
|
||||
import org.koitharu.kotatsu.core.util.LocaleUtils
|
||||
import org.koitharu.kotatsu.core.util.ext.crossfade
|
||||
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.ifNullOrEmpty
|
||||
import org.koitharu.kotatsu.core.util.ext.isTextTruncated
|
||||
@@ -173,7 +176,13 @@ class DetailsActivity :
|
||||
viewModel.isStatsAvailable.observe(this, menuInvalidator)
|
||||
viewModel.remoteManga.observe(this, menuInvalidator)
|
||||
viewModel.branches.observe(this) {
|
||||
infoBinding.textViewTranslation.textAndVisible = it.singleOrNull()?.name
|
||||
val branch = it.singleOrNull()
|
||||
infoBinding.textViewTranslation.textAndVisible = branch?.name
|
||||
infoBinding.textViewTranslation.drawableStart = branch?.locale?.let {
|
||||
LocaleUtils.getEmojiFlag(it)
|
||||
}?.let {
|
||||
TextDrawable.compound(infoBinding.textViewTranslation, it)
|
||||
}
|
||||
infoBinding.textViewTranslationLabel.isVisible = infoBinding.textViewTranslation.isVisible
|
||||
}
|
||||
viewModel.chapters.observe(this, PrefetchObserver(this))
|
||||
@@ -193,8 +202,6 @@ class DetailsActivity :
|
||||
|
||||
override fun onClick(v: View) {
|
||||
when (v.id) {
|
||||
// R.id.chip_branch -> showBranchPopupMenu(v)
|
||||
|
||||
R.id.textView_author -> {
|
||||
val manga = viewModel.manga.value ?: return
|
||||
router.openSearch(manga.source, manga.author ?: return)
|
||||
@@ -462,14 +469,19 @@ class DetailsActivity :
|
||||
}
|
||||
|
||||
private fun onHistoryChanged(info: HistoryInfo, isLoading: Boolean) = with(infoBinding) {
|
||||
textViewChapters.textAndVisible = if (isLoading) {
|
||||
null
|
||||
} else when {
|
||||
info.currentChapter >= 0 -> getString(R.string.chapter_d_of_d, info.currentChapter + 1, info.totalChapters)
|
||||
textViewChapters.text = when {
|
||||
isLoading -> getString(R.string.loading_)
|
||||
info.currentChapter >= 0 -> getString(
|
||||
R.string.chapter_d_of_d,
|
||||
info.currentChapter + 1,
|
||||
info.totalChapters,
|
||||
).withEstimatedTime(info.estimatedTime)
|
||||
|
||||
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)
|
||||
}.withEstimatedTime(info.estimatedTime)
|
||||
.withEstimatedTime(info.estimatedTime)
|
||||
}
|
||||
textViewProgress.textAndVisible = if (info.percent <= 0f) {
|
||||
null
|
||||
} else {
|
||||
@@ -482,8 +494,6 @@ class DetailsActivity :
|
||||
textViewProgressLabel.isVisible = info.history != null
|
||||
textViewProgress.isVisible = info.history != null
|
||||
progress.isVisible = info.history != null
|
||||
// buttonRead.setProgress(info.percent.coerceIn(0f, 1f), !isFirstCall)
|
||||
// buttonDownload?.isEnabled = info.isValid && info.canDownload
|
||||
}
|
||||
|
||||
private fun openReader(isIncognitoMode: Boolean) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.koitharu.kotatsu.details.ui.model
|
||||
|
||||
import org.koitharu.kotatsu.list.ui.ListModelDiffCallback
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import java.util.Locale
|
||||
|
||||
data class MangaBranch(
|
||||
val name: String?,
|
||||
@@ -10,6 +11,8 @@ data class MangaBranch(
|
||||
val isCurrent: Boolean,
|
||||
) : ListModel {
|
||||
|
||||
val locale: Locale? by lazy(::findAppropriateLocale)
|
||||
|
||||
override fun areItemsTheSame(other: ListModel): Boolean {
|
||||
return other is MangaBranch && other.name == name
|
||||
}
|
||||
@@ -25,4 +28,16 @@ data class MangaBranch(
|
||||
override fun toString(): String {
|
||||
return "$name: $count"
|
||||
}
|
||||
|
||||
private fun findAppropriateLocale(): Locale? {
|
||||
if (name.isNullOrEmpty()) {
|
||||
return null
|
||||
}
|
||||
return Locale.getAvailableLocales().find { lc ->
|
||||
name.contains(lc.getDisplayName(lc), ignoreCase = true) ||
|
||||
name.contains(lc.getDisplayName(Locale.ENGLISH), ignoreCase = true) ||
|
||||
name.contains(lc.getDisplayLanguage(lc), ignoreCase = true) ||
|
||||
name.contains(lc.getDisplayLanguage(Locale.ENGLISH), ignoreCase = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,6 +93,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/margin_normal"
|
||||
android:layout_marginEnd="@dimen/margin_normal"
|
||||
android:drawablePadding="4dp"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="?textAppearanceBodyMedium"
|
||||
app:layout_constraintBaseline_toBaselineOf="@id/textView_translation_label"
|
||||
|
||||
Reference in New Issue
Block a user