1
0
Fork 0

For #1955: Add prompt before loading QR code (#2096)

master
Colin Lee 2019-04-28 01:00:55 -05:00 committed by GitHub
parent e6c34f7045
commit 5b7e297adf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 125 additions and 4 deletions

View File

@ -40,6 +40,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- #974 - Added telemetry for bookmarks - #974 - Added telemetry for bookmarks
- #113 - Added QR code scanner - #113 - Added QR code scanner
- #975 - Added telemetry for preference switches - #975 - Added telemetry for preference switches
- #1955 - Added a confirmation dialog for QR code and barcode searches
### Changed ### Changed
- #1429 - Updated site permissions ui for MVP - #1429 - Updated site permissions ui for MVP

View File

@ -0,0 +1,92 @@
/* 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.Resources
import android.os.Build
import android.text.SpannableString
import android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
import androidx.annotation.StringRes
import java.util.Formatter
import kotlin.collections.ArrayList
import kotlin.collections.HashMap
// 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),
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
configuration.locales[0]
} else configuration.locale
).format(getString(id), *spanParts.map { it.first }.toTypedArray())
return resultCreator.toSpannableString()
}
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.start, range.endInclusive, 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)
}
}
}
}
}

View File

@ -6,13 +6,17 @@ package org.mozilla.fenix.search
import android.Manifest import android.Manifest
import android.content.Context import android.content.Context
import android.content.DialogInterface
import android.graphics.Typeface.BOLD
import android.graphics.Typeface.ITALIC
import android.os.Bundle import android.os.Bundle
import android.text.style.StyleSpan
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import kotlinx.android.synthetic.main.component_search.*
import kotlinx.android.synthetic.main.fragment_search.* import kotlinx.android.synthetic.main.fragment_search.*
import kotlinx.android.synthetic.main.fragment_search.view.* import kotlinx.android.synthetic.main.fragment_search.view.*
import mozilla.components.browser.search.SearchEngine import mozilla.components.browser.search.SearchEngine
@ -33,6 +37,7 @@ import org.mozilla.fenix.components.toolbar.SearchState
import org.mozilla.fenix.components.toolbar.ToolbarComponent import org.mozilla.fenix.components.toolbar.ToolbarComponent
import org.mozilla.fenix.components.toolbar.ToolbarUIView import org.mozilla.fenix.components.toolbar.ToolbarUIView
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.getSpannable
import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.mvi.ActionBusFactory import org.mozilla.fenix.mvi.ActionBusFactory
import org.mozilla.fenix.mvi.getAutoDisposeObservable import org.mozilla.fenix.mvi.getAutoDisposeObservable
@ -90,9 +95,28 @@ class SearchFragment : Fragment(), BackHandler {
requestPermissions(permissions, REQUEST_CODE_CAMERA_PERMISSIONS) requestPermissions(permissions, REQUEST_CODE_CAMERA_PERMISSIONS)
}, },
onScanResult = { result -> onScanResult = { result ->
(activity as HomeActivity) activity?.let {
.openToBrowserAndLoad(result, from = BrowserDirection.FromSearch) AlertDialog.Builder(it).apply {
// TODO add metrics, also should we have confirmation before going to a URL? val spannable = resources.getSpannable(
R.string.qr_scanner_confirmation_dialog_message,
listOf(
getString(R.string.app_name) to listOf(StyleSpan(BOLD)),
result to listOf(StyleSpan(ITALIC))
)
)
setMessage(spannable)
setNegativeButton("DENY") { dialog: DialogInterface, _ ->
dialog.cancel()
}
setPositiveButton("ALLOW") { dialog: DialogInterface, _ ->
(activity as HomeActivity)
.openToBrowserAndLoad(result, from = BrowserDirection.FromSearch)
dialog.dismiss()
// TODO add metrics
}
create()
}.show()
}
}), }),
owner = this, owner = this,
view = view view = view

View File

@ -421,4 +421,8 @@
<!-- Content description (not visible, for screen readers etc.): button to close the collection creator --> <!-- Content description (not visible, for screen readers etc.): button to close the collection creator -->
<string name="create_collection_close">Close</string> <string name="create_collection_close">Close</string>
<!-- QR code scanner prompt which appears after scanning a code, but before navigating to it
First parameter is the name of the app, second parameter is the URL or text scanned-->
<string name="qr_scanner_confirmation_dialog_message">Allow %1$s to open %2$s</string>
</resources> </resources>