Use A-C version of Resources.getSpanned (#12358)
parent
4f74f34dba
commit
65637e9e2b
|
@ -1,98 +0,0 @@
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
|
|
||||||
package org.mozilla.fenix.ext
|
|
||||||
|
|
||||||
import android.content.res.Configuration
|
|
||||||
import android.content.res.Resources
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.Build.VERSION.SDK_INT
|
|
||||||
import android.text.SpannableString
|
|
||||||
import android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import java.util.Formatter
|
|
||||||
|
|
||||||
// Credit to Michael Spitsin https://medium.com/@programmerr47/working-with-spans-in-android-ca4ab1327bc4
|
|
||||||
@Suppress("SpreadOperator")
|
|
||||||
fun Resources.getSpannable(@StringRes id: Int, spanParts: List<Pair<Any, Iterable<Any>>>): CharSequence {
|
|
||||||
val resultCreator = SpannableStringCreator()
|
|
||||||
Formatter(
|
|
||||||
SpannableAppendable(resultCreator, spanParts),
|
|
||||||
getLocale(configuration)
|
|
||||||
).format(getString(id), *spanParts.map { it.first }.toTypedArray())
|
|
||||||
return resultCreator.toSpannableString()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getLocale(configuration: Configuration) =
|
|
||||||
if (SDK_INT >= Build.VERSION_CODES.N) {
|
|
||||||
configuration.locales[0]
|
|
||||||
} else {
|
|
||||||
@Suppress("Deprecation")
|
|
||||||
configuration.locale
|
|
||||||
}
|
|
||||||
|
|
||||||
class SpannableStringCreator {
|
|
||||||
private val parts = ArrayList<CharSequence>()
|
|
||||||
private var length = 0
|
|
||||||
private val spanMap: MutableMap<IntRange, Iterable<Any>> = HashMap()
|
|
||||||
|
|
||||||
fun append(newText: CharSequence, spans: Iterable<Any>) = apply {
|
|
||||||
val end = newText.length
|
|
||||||
parts.add(newText)
|
|
||||||
spanMap[(length..length + end)] = spans
|
|
||||||
length += end
|
|
||||||
}
|
|
||||||
|
|
||||||
fun append(newText: CharSequence) = apply {
|
|
||||||
parts.add(newText)
|
|
||||||
length += newText.length
|
|
||||||
}
|
|
||||||
|
|
||||||
fun toSpannableString() = SpannableString(parts.joinToString("")).apply {
|
|
||||||
spanMap.forEach { entry ->
|
|
||||||
val range = entry.key
|
|
||||||
entry.value.forEach {
|
|
||||||
setSpan(it, range.first, range.last, SPAN_EXCLUSIVE_EXCLUSIVE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SpannableAppendable(
|
|
||||||
private val creator: SpannableStringCreator,
|
|
||||||
spanParts: List<Pair<Any, Iterable<Any>>>
|
|
||||||
) : Appendable {
|
|
||||||
|
|
||||||
private val spansMap = spanParts.toMap().mapKeys { entry -> entry.key.let { it as? CharSequence ?: it.toString() } }
|
|
||||||
|
|
||||||
override fun append(csq: CharSequence?) = apply { creator.appendSmart(csq, spansMap) }
|
|
||||||
|
|
||||||
override fun append(csq: CharSequence?, start: Int, end: Int) = apply {
|
|
||||||
if (csq != null) {
|
|
||||||
if (start in 0 until end && end <= csq.length) {
|
|
||||||
append(csq.subSequence(start, end))
|
|
||||||
} else {
|
|
||||||
throw IndexOutOfBoundsException("start " + start + ", end " + end + ", s.length() " + csq.length)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun append(c: Char) = apply { creator.append(c.toString()) }
|
|
||||||
|
|
||||||
private fun SpannableStringCreator.appendSmart(csq: CharSequence?, spanDict: Map<CharSequence, Iterable<Any>>) {
|
|
||||||
if (csq != null) {
|
|
||||||
if (csq in spanDict) {
|
|
||||||
append(csq, spanDict.getValue(csq))
|
|
||||||
} else {
|
|
||||||
val possibleMatchDict = spanDict.filter { it.key.toString() == csq }
|
|
||||||
if (possibleMatchDict.isNotEmpty()) {
|
|
||||||
val spanDictEntry = possibleMatchDict.entries.toList()[0]
|
|
||||||
append(spanDictEntry.key, spanDictEntry.value)
|
|
||||||
} else {
|
|
||||||
append(csq)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -40,6 +40,7 @@ import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
|
||||||
import mozilla.components.support.ktx.android.content.getColorFromAttr
|
import mozilla.components.support.ktx.android.content.getColorFromAttr
|
||||||
import mozilla.components.support.ktx.android.content.hasCamera
|
import mozilla.components.support.ktx.android.content.hasCamera
|
||||||
import mozilla.components.support.ktx.android.content.isPermissionGranted
|
import mozilla.components.support.ktx.android.content.isPermissionGranted
|
||||||
|
import mozilla.components.support.ktx.android.content.res.getSpanned
|
||||||
import mozilla.components.support.ktx.android.view.hideKeyboard
|
import mozilla.components.support.ktx.android.view.hideKeyboard
|
||||||
import mozilla.components.ui.autocomplete.InlineAutocompleteEditText
|
import mozilla.components.ui.autocomplete.InlineAutocompleteEditText
|
||||||
import org.mozilla.fenix.BrowserDirection
|
import org.mozilla.fenix.BrowserDirection
|
||||||
|
@ -50,7 +51,6 @@ import org.mozilla.fenix.components.metrics.Event
|
||||||
import org.mozilla.fenix.components.searchengine.CustomSearchEngineStore
|
import org.mozilla.fenix.components.searchengine.CustomSearchEngineStore
|
||||||
import org.mozilla.fenix.components.searchengine.FenixSearchEngineProvider
|
import org.mozilla.fenix.components.searchengine.FenixSearchEngineProvider
|
||||||
import org.mozilla.fenix.ext.components
|
import org.mozilla.fenix.ext.components
|
||||||
import org.mozilla.fenix.ext.getSpannable
|
|
||||||
import org.mozilla.fenix.ext.hideToolbar
|
import org.mozilla.fenix.ext.hideToolbar
|
||||||
import org.mozilla.fenix.ext.requireComponents
|
import org.mozilla.fenix.ext.requireComponents
|
||||||
import org.mozilla.fenix.ext.settings
|
import org.mozilla.fenix.ext.settings
|
||||||
|
@ -225,12 +225,10 @@ class SearchFragment : Fragment(), UserInteractionHandler {
|
||||||
search_scan_button.isChecked = false
|
search_scan_button.isChecked = false
|
||||||
activity?.let {
|
activity?.let {
|
||||||
AlertDialog.Builder(it).apply {
|
AlertDialog.Builder(it).apply {
|
||||||
val spannable = resources.getSpannable(
|
val spannable = resources.getSpanned(
|
||||||
R.string.qr_scanner_confirmation_dialog_message,
|
R.string.qr_scanner_confirmation_dialog_message,
|
||||||
listOf(
|
getString(R.string.app_name) to StyleSpan(BOLD),
|
||||||
getString(R.string.app_name) to listOf(StyleSpan(BOLD)),
|
result to StyleSpan(ITALIC)
|
||||||
result to listOf(StyleSpan(ITALIC))
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
setMessage(spannable)
|
setMessage(spannable)
|
||||||
setNegativeButton(R.string.qr_scanner_dialog_negative) { dialog: DialogInterface, _ ->
|
setNegativeButton(R.string.qr_scanner_dialog_negative) { dialog: DialogInterface, _ ->
|
||||||
|
|
Loading…
Reference in New Issue