1
0
Fork 0

Copione merged onto master
continuous-integration/drone/push Build is passing Details

master
blallo 2020-07-08 00:00:42 +02:00
commit e13bbe159b
30 changed files with 302 additions and 246 deletions

View File

@ -216,7 +216,7 @@ class SettingsPrivacyTest {
}.enterURLAndEnterToBrowser(saveLoginTest.url) { }.enterURLAndEnterToBrowser(saveLoginTest.url) {
verifySaveLoginPromptIsShown() verifySaveLoginPromptIsShown()
// Don't save the login // Don't save the login
saveLoginFromPrompt("Dont save") saveLoginFromPrompt("Never save")
}.openTabDrawer { }.openTabDrawer {
}.openHomeScreen { }.openHomeScreen {
}.openThreeDotMenu { }.openThreeDotMenu {

View File

@ -7,6 +7,8 @@ package org.mozilla.fenix.ext
import android.app.Activity import android.app.Activity
import android.view.View import android.view.View
import android.view.WindowManager import android.view.WindowManager
import mozilla.components.support.base.log.Log
import org.mozilla.fenix.perf.Performance
/** /**
* Attempts to call immersive mode using the View to hide the status bar and navigation buttons. * Attempts to call immersive mode using the View to hide the status bar and navigation buttons.
@ -22,3 +24,17 @@ fun Activity.enterToImmersiveMode() {
or View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_FULLSCREEN
or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY) or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY)
} }
/**
* Calls [Activity.reportFullyDrawn] while also preventing crashes under some circumstances.
*/
fun Activity.reportFullyDrawnSafe() {
try {
reportFullyDrawn()
} catch (e: SecurityException) {
// This exception is throw on some Samsung devices. We were unable to identify the root
// cause but suspect it's related to Samsung security features. See
// https://github.com/mozilla-mobile/fenix/issues/12345#issuecomment-655058864 for details.
Log.log(Log.Priority.ERROR, Performance.TAG, e, "Unable to call reportFullyDrawn")
}
}

View File

@ -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)
}
}
}
}
}

View File

@ -62,6 +62,7 @@ import mozilla.components.browser.session.Session
import mozilla.components.browser.session.SessionManager import mozilla.components.browser.session.SessionManager
import mozilla.components.browser.state.selector.normalTabs import mozilla.components.browser.state.selector.normalTabs
import mozilla.components.browser.state.selector.privateTabs import mozilla.components.browser.state.selector.privateTabs
import mozilla.components.browser.state.state.BrowserState
import mozilla.components.concept.sync.AccountObserver import mozilla.components.concept.sync.AccountObserver
import mozilla.components.concept.sync.AuthType import mozilla.components.concept.sync.AuthType
import mozilla.components.concept.sync.OAuthAccount import mozilla.components.concept.sync.OAuthAccount
@ -390,15 +391,9 @@ class HomeFragment : Fragment() {
} }
consumeFrom(requireComponents.core.store) { consumeFrom(requireComponents.core.store) {
val tabCount = if (browsingModeManager.mode.isPrivate) { updateTabCounter(it)
it.privateTabs.size
} else {
it.normalTabs.size
}
view.tab_button?.setCountWithAnimation(tabCount)
view.add_tabs_to_collections_button?.isVisible = tabCount > 0
} }
updateTabCounter(requireComponents.core.store.state)
} }
override fun onDestroyView() { override fun onDestroyView() {
@ -927,6 +922,17 @@ class HomeFragment : Fragment() {
TabTrayDialogFragment.show(parentFragmentManager) TabTrayDialogFragment.show(parentFragmentManager)
} }
private fun updateTabCounter(browserState: BrowserState) {
val tabCount = if (browsingModeManager.mode.isPrivate) {
browserState.privateTabs.size
} else {
browserState.normalTabs.size
}
view?.tab_button?.setCountWithAnimation(tabCount)
view?.add_tabs_to_collections_button?.isVisible = tabCount > 0
}
companion object { companion object {
private const val ANIMATION_DELAY = 100L private const val ANIMATION_DELAY = 100L

View File

@ -14,7 +14,7 @@ import mozilla.components.browser.menu.item.SimpleBrowserMenuItem
import mozilla.components.browser.state.selector.normalTabs import mozilla.components.browser.state.selector.normalTabs
import mozilla.components.feature.tab.collections.TabCollection import mozilla.components.feature.tab.collections.TabCollection
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.ext.ViewHolder import org.mozilla.fenix.utils.view.ViewHolder
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.getIconColor import org.mozilla.fenix.ext.getIconColor
import org.mozilla.fenix.ext.increaseTapArea import org.mozilla.fenix.ext.increaseTapArea

View File

@ -6,9 +6,9 @@ package org.mozilla.fenix.home.sessioncontrol.viewholders
import android.view.View import android.view.View
import androidx.core.view.isVisible import androidx.core.view.isVisible
import kotlinx.android.synthetic.main.no_collections_message.view.* import kotlinx.android.synthetic.main.no_collections_message.*
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.ext.ViewHolder import org.mozilla.fenix.utils.view.ViewHolder
import org.mozilla.fenix.home.sessioncontrol.CollectionInteractor import org.mozilla.fenix.home.sessioncontrol.CollectionInteractor
open class NoCollectionsMessageViewHolder( open class NoCollectionsMessageViewHolder(
@ -18,11 +18,12 @@ open class NoCollectionsMessageViewHolder(
) : ViewHolder(view) { ) : ViewHolder(view) {
init { init {
view.add_tabs_to_collections_button.setOnClickListener { add_tabs_to_collections_button.setOnClickListener {
interactor.onAddTabsToCollectionTapped() interactor.onAddTabsToCollectionTapped()
} }
view.add_tabs_to_collections_button.isVisible = hasNormalTabsOpened add_tabs_to_collections_button.isVisible = hasNormalTabsOpened
} }
companion object { companion object {
const val LAYOUT_ID = R.layout.no_collections_message const val LAYOUT_ID = R.layout.no_collections_message
} }

View File

@ -13,7 +13,7 @@ import mozilla.components.feature.tab.collections.TabCollection
import mozilla.components.support.ktx.android.content.getColorFromAttr import mozilla.components.support.ktx.android.content.getColorFromAttr
import mozilla.components.support.ktx.android.util.dpToFloat import mozilla.components.support.ktx.android.util.dpToFloat
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.ext.ViewHolder import org.mozilla.fenix.utils.view.ViewHolder
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.increaseTapArea import org.mozilla.fenix.ext.increaseTapArea
import org.mozilla.fenix.ext.loadIntoView import org.mozilla.fenix.ext.loadIntoView

View File

@ -9,18 +9,14 @@ import android.os.Build.VERSION.SDK_INT
import android.view.View import android.view.View
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.onboarding_theme_picker.view.clickable_region_automatic import kotlinx.android.synthetic.main.onboarding_theme_picker.view.*
import kotlinx.android.synthetic.main.onboarding_theme_picker.view.theme_automatic_radio_button
import kotlinx.android.synthetic.main.onboarding_theme_picker.view.theme_dark_image
import kotlinx.android.synthetic.main.onboarding_theme_picker.view.theme_dark_radio_button
import kotlinx.android.synthetic.main.onboarding_theme_picker.view.theme_light_image
import kotlinx.android.synthetic.main.onboarding_theme_picker.view.theme_light_radio_button
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.metrics.Event.OnboardingThemePicker.Theme import org.mozilla.fenix.components.metrics.Event.OnboardingThemePicker.Theme
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.onboarding.OnboardingRadioButton import org.mozilla.fenix.onboarding.OnboardingRadioButton
import org.mozilla.fenix.utils.view.addToRadioGroup
class OnboardingThemePickerViewHolder(view: View) : RecyclerView.ViewHolder(view) { class OnboardingThemePickerViewHolder(view: View) : RecyclerView.ViewHolder(view) {
@ -35,17 +31,14 @@ class OnboardingThemePickerViewHolder(view: View) : RecyclerView.ViewHolder(view
R.string.pref_key_auto_battery_theme R.string.pref_key_auto_battery_theme
} }
radioLightTheme.addToRadioGroup(radioDarkTheme) addToRadioGroup(
radioLightTheme.addToRadioGroup(radioFollowDeviceTheme) radioLightTheme,
radioDarkTheme,
radioFollowDeviceTheme
)
radioLightTheme.addIllustration(view.theme_light_image) radioLightTheme.addIllustration(view.theme_light_image)
radioDarkTheme.addToRadioGroup(radioLightTheme)
radioDarkTheme.addToRadioGroup(radioFollowDeviceTheme)
radioDarkTheme.addIllustration(view.theme_dark_image) radioDarkTheme.addIllustration(view.theme_dark_image)
radioFollowDeviceTheme.addToRadioGroup(radioDarkTheme)
radioFollowDeviceTheme.addToRadioGroup(radioLightTheme)
view.theme_dark_image.setOnClickListener { view.theme_dark_image.setOnClickListener {
it.context.components.analytics.metrics.track(Event.OnboardingThemePicker(Theme.DARK)) it.context.components.analytics.metrics.track(Event.OnboardingThemePicker(Theme.DARK))
radioDarkTheme.performClick() radioDarkTheme.performClick()

View File

@ -6,10 +6,7 @@ package org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding
import android.view.View import android.view.View
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.onboarding_toolbar_position_picker.view.toolbar_bottom_image import kotlinx.android.synthetic.main.onboarding_toolbar_position_picker.view.*
import kotlinx.android.synthetic.main.onboarding_toolbar_position_picker.view.toolbar_bottom_radio_button
import kotlinx.android.synthetic.main.onboarding_toolbar_position_picker.view.toolbar_top_image
import kotlinx.android.synthetic.main.onboarding_toolbar_position_picker.view.toolbar_top_radio_button
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.metrics.Event.OnboardingToolbarPosition.Position import org.mozilla.fenix.components.metrics.Event.OnboardingToolbarPosition.Position
@ -17,6 +14,7 @@ import org.mozilla.fenix.ext.asActivity
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.onboarding.OnboardingRadioButton import org.mozilla.fenix.onboarding.OnboardingRadioButton
import org.mozilla.fenix.utils.view.addToRadioGroup
class OnboardingToolbarPositionPickerViewHolder(view: View) : RecyclerView.ViewHolder(view) { class OnboardingToolbarPositionPickerViewHolder(view: View) : RecyclerView.ViewHolder(view) {
@ -25,10 +23,8 @@ class OnboardingToolbarPositionPickerViewHolder(view: View) : RecyclerView.ViewH
val radioBottomToolbar = view.toolbar_bottom_radio_button val radioBottomToolbar = view.toolbar_bottom_radio_button
val radio: OnboardingRadioButton val radio: OnboardingRadioButton
radioTopToolbar.addToRadioGroup(radioBottomToolbar) addToRadioGroup(radioTopToolbar, radioBottomToolbar)
radioTopToolbar.addIllustration(view.toolbar_top_image) radioTopToolbar.addIllustration(view.toolbar_top_image)
radioBottomToolbar.addToRadioGroup(radioTopToolbar)
radioBottomToolbar.addIllustration(view.toolbar_bottom_image) radioBottomToolbar.addIllustration(view.toolbar_bottom_image)
radio = if (view.context.settings().shouldUseBottomToolbar) { radio = if (view.context.settings().shouldUseBottomToolbar) {

View File

@ -14,6 +14,7 @@ import org.mozilla.fenix.components.metrics.Event.OnboardingTrackingProtection.S
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.onboarding.OnboardingRadioButton import org.mozilla.fenix.onboarding.OnboardingRadioButton
import org.mozilla.fenix.utils.view.addToRadioGroup
class OnboardingTrackingProtectionViewHolder(view: View) : RecyclerView.ViewHolder(view) { class OnboardingTrackingProtectionViewHolder(view: View) : RecyclerView.ViewHolder(view) {
@ -48,8 +49,7 @@ class OnboardingTrackingProtectionViewHolder(view: View) : RecyclerView.ViewHold
updateRadioGroupState(view, isChecked) updateRadioGroupState(view, isChecked)
standardTrackingProtection.addToRadioGroup(strictTrackingProtection) addToRadioGroup(standardTrackingProtection, strictTrackingProtection)
strictTrackingProtection.addToRadioGroup(standardTrackingProtection)
strictTrackingProtection.isChecked = strictTrackingProtection.isChecked =
itemView.context.settings().useStrictTrackingProtection itemView.context.settings().useStrictTrackingProtection

View File

@ -15,7 +15,7 @@ import mozilla.components.browser.menu.BrowserMenuBuilder
import mozilla.components.browser.menu.item.SimpleBrowserMenuItem import mozilla.components.browser.menu.item.SimpleBrowserMenuItem
import mozilla.components.feature.top.sites.TopSite import mozilla.components.feature.top.sites.TopSite
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.ext.ViewHolder import org.mozilla.fenix.utils.view.ViewHolder
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.loadIntoView import org.mozilla.fenix.ext.loadIntoView
import org.mozilla.fenix.home.sessioncontrol.TopSiteInteractor import org.mozilla.fenix.home.sessioncontrol.TopSiteInteractor

View File

@ -12,9 +12,14 @@ import androidx.core.content.edit
import androidx.core.content.withStyledAttributes import androidx.core.content.withStyledAttributes
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.utils.view.GroupableRadioButton
import org.mozilla.fenix.utils.view.uncheckAll
class OnboardingRadioButton(context: Context, attrs: AttributeSet) : AppCompatRadioButton(context, attrs) { class OnboardingRadioButton(
private val radioGroups = mutableListOf<OnboardingRadioButton>() context: Context,
attrs: AttributeSet
) : AppCompatRadioButton(context, attrs), GroupableRadioButton {
private val radioGroups = mutableListOf<GroupableRadioButton>()
private var illustration: ImageView? = null private var illustration: ImageView? = null
private var clickListener: (() -> Unit)? = null private var clickListener: (() -> Unit)? = null
var key: Int = 0 var key: Int = 0
@ -29,7 +34,7 @@ class OnboardingRadioButton(context: Context, attrs: AttributeSet) : AppCompatRa
} }
} }
fun addToRadioGroup(radioButton: OnboardingRadioButton) { override fun addToRadioGroup(radioButton: GroupableRadioButton) {
radioGroups.add(radioButton) radioGroups.add(radioButton)
} }
@ -49,7 +54,7 @@ class OnboardingRadioButton(context: Context, attrs: AttributeSet) : AppCompatRa
} }
} }
fun updateRadioValue(isChecked: Boolean) { override fun updateRadioValue(isChecked: Boolean) {
this.isChecked = isChecked this.isChecked = isChecked
illustration?.let { illustration?.let {
it.isSelected = isChecked it.isSelected = isChecked
@ -61,7 +66,7 @@ class OnboardingRadioButton(context: Context, attrs: AttributeSet) : AppCompatRa
private fun toggleRadioGroups() { private fun toggleRadioGroups() {
if (isChecked) { if (isChecked) {
radioGroups.forEach { it.updateRadioValue(false) } radioGroups.uncheckAll()
} }
} }
} }

View File

@ -9,6 +9,7 @@ import android.view.View
import androidx.core.view.doOnPreDraw import androidx.core.view.doOnPreDraw
import kotlinx.android.synthetic.main.activity_home.* import kotlinx.android.synthetic.main.activity_home.*
import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.ext.reportFullyDrawnSafe
import org.mozilla.fenix.home.sessioncontrol.viewholders.topsites.TopSiteItemViewHolder import org.mozilla.fenix.home.sessioncontrol.viewholders.topsites.TopSiteItemViewHolder
import org.mozilla.fenix.perf.StartupTimelineStateMachine.StartupDestination.APP_LINK import org.mozilla.fenix.perf.StartupTimelineStateMachine.StartupDestination.APP_LINK
import org.mozilla.fenix.perf.StartupTimelineStateMachine.StartupDestination.HOMESCREEN import org.mozilla.fenix.perf.StartupTimelineStateMachine.StartupDestination.HOMESCREEN
@ -64,6 +65,6 @@ class StartupReportFullyDrawn {
// - the difference in timing is minimal (< 7ms on Pixel 2) // - the difference in timing is minimal (< 7ms on Pixel 2)
// - if we compare against another app using a preDrawListener, as we are with Fennec, it // - if we compare against another app using a preDrawListener, as we are with Fennec, it
// should be comparable // should be comparable
view.doOnPreDraw { activity.reportFullyDrawn() } view.doOnPreDraw { activity.reportFullyDrawnSafe() }
} }
} }

View File

@ -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, _ ->

View File

@ -16,6 +16,7 @@ import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.ext.showToolbar import org.mozilla.fenix.ext.showToolbar
import org.mozilla.fenix.utils.view.addToRadioGroup
/** /**
* Lets the user customize the UI. * Lets the user customize the UI.
@ -48,23 +49,15 @@ class CustomizationFragment : PreferenceFragmentCompat() {
} }
private fun setupRadioGroups() { private fun setupRadioGroups() {
radioLightTheme.addToRadioGroup(radioDarkTheme) addToRadioGroup(
radioLightTheme,
radioDarkTheme.addToRadioGroup(radioLightTheme) radioDarkTheme,
if (SDK_INT >= Build.VERSION_CODES.P) {
if (SDK_INT >= Build.VERSION_CODES.P) { radioFollowDeviceTheme
radioLightTheme.addToRadioGroup(radioFollowDeviceTheme) } else {
radioDarkTheme.addToRadioGroup(radioFollowDeviceTheme) radioAutoBatteryTheme
}
radioFollowDeviceTheme.addToRadioGroup(radioDarkTheme) )
radioFollowDeviceTheme.addToRadioGroup(radioLightTheme)
} else {
radioLightTheme.addToRadioGroup(radioAutoBatteryTheme)
radioDarkTheme.addToRadioGroup(radioAutoBatteryTheme)
radioAutoBatteryTheme.addToRadioGroup(radioLightTheme)
radioAutoBatteryTheme.addToRadioGroup(radioDarkTheme)
}
} }
private fun bindLightTheme() { private fun bindLightTheme() {
@ -132,7 +125,6 @@ class CustomizationFragment : PreferenceFragmentCompat() {
topPreference.setCheckedWithoutClickListener(!requireContext().settings().shouldUseBottomToolbar) topPreference.setCheckedWithoutClickListener(!requireContext().settings().shouldUseBottomToolbar)
bottomPreference.setCheckedWithoutClickListener(requireContext().settings().shouldUseBottomToolbar) bottomPreference.setCheckedWithoutClickListener(requireContext().settings().shouldUseBottomToolbar)
topPreference.addToRadioGroup(bottomPreference) addToRadioGroup(topPreference, bottomPreference)
bottomPreference.addToRadioGroup(topPreference)
} }
} }

View File

@ -16,12 +16,15 @@ import androidx.preference.Preference
import androidx.preference.PreferenceViewHolder import androidx.preference.PreferenceViewHolder
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.utils.view.GroupableRadioButton
import org.mozilla.fenix.utils.view.uncheckAll
@Suppress("RestrictedApi", "PrivateResource")
open class RadioButtonPreference @JvmOverloads constructor( open class RadioButtonPreference @JvmOverloads constructor(
context: Context, context: Context,
attrs: AttributeSet? = null attrs: AttributeSet? = null
) : Preference(context, attrs) { ) : Preference(context, attrs), GroupableRadioButton {
private val radioGroups = mutableListOf<RadioButtonPreference>() private val radioGroups = mutableListOf<GroupableRadioButton>()
private var summaryView: TextView? = null private var summaryView: TextView? = null
private var titleView: TextView? = null private var titleView: TextView? = null
private var radioButton: RadioButton? = null private var radioButton: RadioButton? = null
@ -55,8 +58,8 @@ open class RadioButtonPreference @JvmOverloads constructor(
} }
} }
fun addToRadioGroup(radioPreference: RadioButtonPreference) { override fun addToRadioGroup(radioButton: GroupableRadioButton) {
radioGroups.add(radioPreference) radioGroups.add(radioButton)
} }
fun onClickListener(listener: (() -> Unit)) { fun onClickListener(listener: (() -> Unit)) {
@ -97,7 +100,7 @@ open class RadioButtonPreference @JvmOverloads constructor(
toggleRadioGroups() toggleRadioGroups()
} }
private fun updateRadioValue(isChecked: Boolean) { override fun updateRadioValue(isChecked: Boolean) {
persistBoolean(isChecked) persistBoolean(isChecked)
radioButton?.isChecked = isChecked radioButton?.isChecked = isChecked
context.settings().preferences.edit().putBoolean(key, isChecked) context.settings().preferences.edit().putBoolean(key, isChecked)
@ -113,7 +116,7 @@ open class RadioButtonPreference @JvmOverloads constructor(
private fun toggleRadioGroups() { private fun toggleRadioGroups() {
if (radioButton?.isChecked == true) { if (radioButton?.isChecked == true) {
radioGroups.forEach { it.updateRadioValue(false) } radioGroups.uncheckAll()
} }
} }

View File

@ -21,6 +21,7 @@ import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.ext.showToolbar import org.mozilla.fenix.ext.showToolbar
import org.mozilla.fenix.trackingprotection.TrackingProtectionMode import org.mozilla.fenix.trackingprotection.TrackingProtectionMode
import org.mozilla.fenix.utils.view.addToRadioGroup
/** /**
* Displays the toggle for tracking protection, options for tracking protection policy and a button * Displays the toggle for tracking protection, options for tracking protection policy and a button
@ -46,7 +47,7 @@ class TrackingProtectionFragment : PreferenceFragmentCompat() {
val radioStrict = bindTrackingProtectionRadio(TrackingProtectionMode.STRICT) val radioStrict = bindTrackingProtectionRadio(TrackingProtectionMode.STRICT)
val radioStandard = bindTrackingProtectionRadio(TrackingProtectionMode.STANDARD) val radioStandard = bindTrackingProtectionRadio(TrackingProtectionMode.STANDARD)
val radioCustom = bindCustom() val radioCustom = bindCustom()
setupRadioGroups(radioStrict, radioStandard, radioCustom) addToRadioGroup(radioStrict, radioStandard, radioCustom)
updateCustomOptionsVisibility() updateCustomOptionsVisibility()
} }
@ -208,21 +209,6 @@ class TrackingProtectionFragment : PreferenceFragmentCompat() {
} }
} }
private fun setupRadioGroups(
radioStrict: RadioButtonInfoPreference,
radioStandard: RadioButtonInfoPreference,
radioCustom: RadioButtonInfoPreference
) {
radioStandard.addToRadioGroup(radioStrict)
radioStrict.addToRadioGroup(radioStandard)
radioStandard.addToRadioGroup(radioCustom)
radioCustom.addToRadioGroup(radioStandard)
radioStrict.addToRadioGroup(radioCustom)
radioCustom.addToRadioGroup(radioStrict)
}
private fun updateCustomOptionsVisibility() { private fun updateCustomOptionsVisibility() {
val isCustomSelected = requireContext().settings().useCustomTrackingProtection val isCustomSelected = requireContext().settings().useCustomTrackingProtection
customCookies.isVisible = isCustomSelected customCookies.isVisible = isCustomSelected

View File

@ -17,6 +17,7 @@ import android.util.Log
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.biometric.BiometricManager import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt import androidx.biometric.BiometricPrompt
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.preference.Preference import androidx.preference.Preference
@ -31,6 +32,7 @@ import mozilla.components.concept.sync.OAuthAccount
import mozilla.components.service.fxa.SyncEngine import mozilla.components.service.fxa.SyncEngine
import mozilla.components.service.fxa.manager.SyncEnginesStorage import mozilla.components.service.fxa.manager.SyncEnginesStorage
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.addons.runIfFragmentIsAttached
import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.requireComponents
@ -39,7 +41,7 @@ import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.ext.showToolbar import org.mozilla.fenix.ext.showToolbar
import org.mozilla.fenix.settings.SharedPreferenceUpdater import org.mozilla.fenix.settings.SharedPreferenceUpdater
import org.mozilla.fenix.settings.requirePreference import org.mozilla.fenix.settings.requirePreference
import java.util.concurrent.Executors import java.util.concurrent.Executor
@Suppress("TooManyFunctions", "LargeClass") @Suppress("TooManyFunctions", "LargeClass")
class SavedLoginsAuthFragment : PreferenceFragmentCompat(), AccountObserver { class SavedLoginsAuthFragment : PreferenceFragmentCompat(), AccountObserver {
@ -48,7 +50,7 @@ class SavedLoginsAuthFragment : PreferenceFragmentCompat(), AccountObserver {
private lateinit var biometricPromptCallback: BiometricPrompt.AuthenticationCallback private lateinit var biometricPromptCallback: BiometricPrompt.AuthenticationCallback
@TargetApi(M) @TargetApi(M)
private val executor = Executors.newSingleThreadExecutor() private lateinit var executor: Executor
@TargetApi(M) @TargetApi(M)
private lateinit var biometricPrompt: BiometricPrompt private lateinit var biometricPrompt: BiometricPrompt
@ -60,6 +62,28 @@ class SavedLoginsAuthFragment : PreferenceFragmentCompat(), AccountObserver {
setPreferencesFromResource(R.xml.logins_preferences, rootKey) setPreferencesFromResource(R.xml.logins_preferences, rootKey)
} }
/**
* There is a bug where while the biometric prompt is showing, you were able to quickly navigate
* so we are disabling the settings that navigate while authenticating.
* https://github.com/mozilla-mobile/fenix/issues/12312
*/
private fun togglePrefsEnabledWhileAuthenticating(enabled: Boolean) {
requirePreference<Preference>(R.string.pref_key_password_sync_logins).isEnabled = enabled
requirePreference<Preference>(R.string.pref_key_save_logins_settings).isEnabled = enabled
requirePreference<Preference>(R.string.pref_key_saved_logins).isEnabled = enabled
}
private fun navigateToSavedLogins() {
runIfFragmentIsAttached {
viewLifecycleOwner.lifecycleScope.launch(Main) {
// Workaround for likely biometric library bug
// https://github.com/mozilla-mobile/fenix/issues/8438
delay(SHORT_DELAY_MS)
navigateToSavedLoginsFragment()
}
}
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -67,23 +91,22 @@ class SavedLoginsAuthFragment : PreferenceFragmentCompat(), AccountObserver {
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
Log.e(LOG_TAG, "onAuthenticationError $errString") Log.e(LOG_TAG, "onAuthenticationError $errString")
togglePrefsEnabledWhileAuthenticating(enabled = true)
} }
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
Log.d(LOG_TAG, "onAuthenticationSucceeded") Log.d(LOG_TAG, "onAuthenticationSucceeded")
viewLifecycleOwner.lifecycleScope.launch(Main) { navigateToSavedLogins()
// Workaround for likely biometric library bug
// https://github.com/mozilla-mobile/fenix/issues/8438
delay(SHORT_DELAY_MS)
navigateToSavedLoginsFragment()
}
} }
override fun onAuthenticationFailed() { override fun onAuthenticationFailed() {
Log.e(LOG_TAG, "onAuthenticationFailed") Log.e(LOG_TAG, "onAuthenticationFailed")
togglePrefsEnabledWhileAuthenticating(enabled = true)
} }
} }
executor = ContextCompat.getMainExecutor(requireContext())
biometricPrompt = BiometricPrompt(this, executor, biometricPromptCallback) biometricPrompt = BiometricPrompt(this, executor, biometricPromptCallback)
promptInfo = BiometricPrompt.PromptInfo.Builder() promptInfo = BiometricPrompt.PromptInfo.Builder()
@ -122,6 +145,7 @@ class SavedLoginsAuthFragment : PreferenceFragmentCompat(), AccountObserver {
requirePreference<Preference>(R.string.pref_key_saved_logins).setOnPreferenceClickListener { requirePreference<Preference>(R.string.pref_key_saved_logins).setOnPreferenceClickListener {
if (Build.VERSION.SDK_INT >= M && isHardwareAvailable && hasBiometricEnrolled) { if (Build.VERSION.SDK_INT >= M && isHardwareAvailable && hasBiometricEnrolled) {
togglePrefsEnabledWhileAuthenticating(enabled = false)
biometricPrompt.authenticate(promptInfo) biometricPrompt.authenticate(promptInfo)
} else { } else {
verifyPinOrShowSetupWarning() verifyPinOrShowSetupWarning()
@ -148,7 +172,7 @@ class SavedLoginsAuthFragment : PreferenceFragmentCompat(), AccountObserver {
override fun onAuthenticationProblems() = updateSyncPreferenceNeedsReauth() override fun onAuthenticationProblems() = updateSyncPreferenceNeedsReauth()
val isHardwareAvailable: Boolean by lazy { private val isHardwareAvailable: Boolean by lazy {
if (Build.VERSION.SDK_INT >= M) { if (Build.VERSION.SDK_INT >= M) {
context?.let { context?.let {
val bm = BiometricManager.from(it) val bm = BiometricManager.from(it)
@ -161,7 +185,7 @@ class SavedLoginsAuthFragment : PreferenceFragmentCompat(), AccountObserver {
} }
} }
val hasBiometricEnrolled: Boolean by lazy { private val hasBiometricEnrolled: Boolean by lazy {
if (Build.VERSION.SDK_INT >= M) { if (Build.VERSION.SDK_INT >= M) {
context?.let { context?.let {
val bm = BiometricManager.from(it) val bm = BiometricManager.from(it)
@ -252,7 +276,8 @@ class SavedLoginsAuthFragment : PreferenceFragmentCompat(), AccountObserver {
getString(R.string.logins_biometric_prompt_message_pin), getString(R.string.logins_biometric_prompt_message_pin),
getString(R.string.logins_biometric_prompt_message) getString(R.string.logins_biometric_prompt_message)
) )
startActivityForResult(intent, startActivityForResult(
intent,
PIN_REQUEST PIN_REQUEST
) )
} }
@ -267,7 +292,8 @@ class SavedLoginsAuthFragment : PreferenceFragmentCompat(), AccountObserver {
private fun navigateToSavedLoginsFragment() { private fun navigateToSavedLoginsFragment() {
context?.components?.analytics?.metrics?.track(Event.OpenLogins) context?.components?.analytics?.metrics?.track(Event.OpenLogins)
val directions = SavedLoginsAuthFragmentDirections.actionSavedLoginsAuthFragmentToLoginsListFragment() val directions =
SavedLoginsAuthFragmentDirections.actionSavedLoginsAuthFragmentToLoginsListFragment()
findNavController().navigate(directions) findNavController().navigate(directions)
} }
@ -283,7 +309,8 @@ class SavedLoginsAuthFragment : PreferenceFragmentCompat(), AccountObserver {
} }
private fun navigateToTurnOnSyncFragment() { private fun navigateToTurnOnSyncFragment() {
val directions = SavedLoginsAuthFragmentDirections.actionSavedLoginsAuthFragmentToTurnOnSyncFragment() val directions =
SavedLoginsAuthFragmentDirections.actionSavedLoginsAuthFragmentToTurnOnSyncFragment()
findNavController().navigate(directions) findNavController().navigate(directions)
} }

View File

@ -15,6 +15,7 @@ import org.mozilla.fenix.ext.showToolbar
import org.mozilla.fenix.settings.RadioButtonPreference import org.mozilla.fenix.settings.RadioButtonPreference
import org.mozilla.fenix.settings.SharedPreferenceUpdater import org.mozilla.fenix.settings.SharedPreferenceUpdater
import org.mozilla.fenix.settings.requirePreference import org.mozilla.fenix.settings.requirePreference
import org.mozilla.fenix.utils.view.addToRadioGroup
class SavedLoginsSettingFragment : PreferenceFragmentCompat() { class SavedLoginsSettingFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
@ -26,7 +27,7 @@ class SavedLoginsSettingFragment : PreferenceFragmentCompat() {
showToolbar(getString(R.string.preferences_passwords_save_logins)) showToolbar(getString(R.string.preferences_passwords_save_logins))
val save = bindSave() val save = bindSave()
val neverSave = bindNeverSave() val neverSave = bindNeverSave()
setupRadioGroups(save, neverSave) addToRadioGroup(save, neverSave)
} }
private fun bindSave(): RadioButtonPreference { private fun bindSave(): RadioButtonPreference {
@ -66,12 +67,4 @@ class SavedLoginsSettingFragment : PreferenceFragmentCompat() {
} }
return preferenceNeverSave return preferenceNeverSave
} }
private fun setupRadioGroups(
radioNeverSave: RadioButtonPreference,
radioSave: RadioButtonPreference
) {
radioNeverSave.addToRadioGroup(radioSave)
radioSave.addToRadioGroup(radioNeverSave)
}
} }

View File

@ -0,0 +1,28 @@
/* 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.utils.view
interface GroupableRadioButton {
fun updateRadioValue(isChecked: Boolean)
fun addToRadioGroup(radioButton: GroupableRadioButton)
}
/**
* Connect all the given radio buttons into a group,
* so that when one radio is checked the others are unchecked.
*/
fun addToRadioGroup(vararg radios: GroupableRadioButton) {
for (i in 0..radios.lastIndex) {
for (j in (i + 1)..radios.lastIndex) {
radios[i].addToRadioGroup(radios[j])
radios[j].addToRadioGroup(radios[i])
}
}
}
fun Iterable<GroupableRadioButton>.uncheckAll() {
forEach { it.updateRadioValue(isChecked = false) }
}

View File

@ -1,4 +1,4 @@
package org.mozilla.fenix.ext package org.mozilla.fenix.utils.view
import android.view.View import android.view.View
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView

View File

@ -110,6 +110,7 @@
android:layout_height="0dp" android:layout_height="0dp"
android:paddingBottom="140dp" android:paddingBottom="140dp"
android:clipToPadding="false" android:clipToPadding="false"
android:scrollbars="vertical"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"

View File

@ -834,11 +834,17 @@
<!-- text for firefox preview moving tip header. "Firefox Nightly" is intentionally hardcoded --> <!-- text for firefox preview moving tip header. "Firefox Nightly" is intentionally hardcoded -->
<string name="tip_firefox_preview_moved_header_preview_installed">Firefox Nightly пераехаў</string> <string name="tip_firefox_preview_moved_header_preview_installed">Firefox Nightly пераехаў</string>
<!-- text for firefox preview moving tip description -->
<string name="tip_firefox_preview_moved_description_preview_installed">Гэта праграма больш не будзе атрымліваць абнаўленні бяспекі. Перастаньце карыстацца гэтай праграмай і перайдзіце на новы Nightly.
\n\nКаб перанесці закладкі, лагіны і гісторыю ў другую праграму, стварыце ўліковы запіс Firefox.</string>
<!-- text for firefox preview moving tip button --> <!-- text for firefox preview moving tip button -->
<string name="tip_firefox_preview_moved_button_preview_installed">Пераключыцца на новы Nightly</string> <string name="tip_firefox_preview_moved_button_preview_installed">Пераключыцца на новы Nightly</string>
<!-- text for firefox preview moving tip header. "Firefox Nightly" is intentionally hardcoded --> <!-- text for firefox preview moving tip header. "Firefox Nightly" is intentionally hardcoded -->
<string name="tip_firefox_preview_moved_header_preview_not_installed">Firefox Nightly пераехаў</string> <string name="tip_firefox_preview_moved_header_preview_not_installed">Firefox Nightly пераехаў</string>
<!-- text for firefox preview moving tip description -->
<string name="tip_firefox_preview_moved_description_preview_not_installed">Гэта праграма больш не будзе атрымліваць абнаўленні бяспекі. Атрымайце новы Nightly і больш не карыстайцеся гэтай праграмай.
\n\nКаб перанесці закладкі, лагіны і гісторыю ў другую праграму, стварыце ўліковы запіс Firefox.</string>
<!-- text for firefox preview moving tip button --> <!-- text for firefox preview moving tip button -->
<string name="tip_firefox_preview_moved_button_preview_not_installed">Атрымаць новы Nightly</string> <string name="tip_firefox_preview_moved_button_preview_not_installed">Атрымаць новы Nightly</string>
@ -1024,6 +1030,8 @@
<string name="etp_fingerprinters_title">Збіральнікі лічбавых адбіткаў</string> <string name="etp_fingerprinters_title">Збіральнікі лічбавых адбіткаў</string>
<!-- Category of trackers (tracking content) that can be blocked by Enhanced Tracking Protection --> <!-- Category of trackers (tracking content) that can be blocked by Enhanced Tracking Protection -->
<string name="etp_tracking_content_title">Змест з элементамі сачэння</string> <string name="etp_tracking_content_title">Змест з элементамі сачэння</string>
<!-- Enhanced Tracking Protection message that protection is currently on for this site -->
<string name="etp_panel_on">Ахова ўключана на гэтым сайце</string>
<!-- Header for exceptions list for which sites enhanced tracking protection is always off --> <!-- Header for exceptions list for which sites enhanced tracking protection is always off -->
<string name="enhanced_tracking_protection_exceptions">Узмоцненая ахова ад сачэння выключана на гэтых сайтах</string> <string name="enhanced_tracking_protection_exceptions">Узмоцненая ахова ад сачэння выключана на гэтых сайтах</string>

View File

@ -15,8 +15,6 @@
<string name="content_description_disable_private_browsing_button">Tichup ri ichinan okem pa k\'amaya\'l</string> <string name="content_description_disable_private_browsing_button">Tichup ri ichinan okem pa k\'amaya\'l</string>
<!-- Placeholder text shown in the search bar before a user enters text --> <!-- Placeholder text shown in the search bar before a user enters text -->
<string name="search_hint">Tikanöx o ketz\'ib\'äx ri taq ochochib\'äl</string> <string name="search_hint">Tikanöx o ketz\'ib\'äx ri taq ochochib\'äl</string>
<!-- No Open Tabs Message Header -->
<string name="no_open_tabs_header_2">Majun ruwi\' ejaqon</string>
<!-- No Open Tabs Message Description --> <!-- No Open Tabs Message Description -->
<string name="no_open_tabs_description">Wawe\' xkeq\'alajin pe ri jaqäl taq ruwi\'.</string> <string name="no_open_tabs_description">Wawe\' xkeq\'alajin pe ri jaqäl taq ruwi\'.</string>
@ -301,8 +299,6 @@
<string name="preferences_sync_bookmarks">Taq yaketal</string> <string name="preferences_sync_bookmarks">Taq yaketal</string>
<!-- Preference for syncing logins --> <!-- Preference for syncing logins -->
<string name="preferences_sync_logins">Ketikirisäx molojri\'ïl</string> <string name="preferences_sync_logins">Ketikirisäx molojri\'ïl</string>
<!-- Preference for syncing tabs -->
<string name="preferences_sync_tabs">Taq ruwi\'</string>
<!-- Preference for signing out --> <!-- Preference for signing out -->
<string name="preferences_sign_out">Titz\'apïx molojri\'ïl</string> <string name="preferences_sign_out">Titz\'apïx molojri\'ïl</string>
<!-- Preference displays and allows changing current FxA device name --> <!-- Preference displays and allows changing current FxA device name -->
@ -480,7 +476,7 @@
<!-- Shortcut action to open the home screen --> <!-- Shortcut action to open the home screen -->
<string name="tab_tray_menu_home">Tib\'e pa tikirib\'äl</string> <string name="tab_tray_menu_home">Tib\'e pa tikirib\'äl</string>
<!-- Shortcut action to toggle private mode --> <!-- Shortcut action to toggle private mode -->
<string name="tab_tray_menu_toggle">Tik\'ex rik\'in ruwi\' rub\'anikil</string> <string name="tab_tray_menu_toggle">Tik\'exlöx pa ruwi\' b\'anikil</string>
<!-- Content description (not visible, for screen readers etc.): Removes tab from collection button. Removes the selected tab from collection when pressed --> <!-- Content description (not visible, for screen readers etc.): Removes tab from collection button. Removes the selected tab from collection when pressed -->
<string name="remove_tab_from_collection">Tiyuj ruwi\' pa molb\'äl</string> <string name="remove_tab_from_collection">Tiyuj ruwi\' pa molb\'äl</string>
<!-- Content description (not visible, for screen readers etc.): Close tab button. Closes the current session when pressed --> <!-- Content description (not visible, for screen readers etc.): Close tab button. Closes the current session when pressed -->
@ -493,8 +489,6 @@
<string name="tabs_menu_close_all_tabs">Titz\'apïx ronojel ri taq ruwi\'</string> <string name="tabs_menu_close_all_tabs">Titz\'apïx ronojel ri taq ruwi\'</string>
<!-- Open tabs menu item to share all tabs --> <!-- Open tabs menu item to share all tabs -->
<string name="tabs_menu_share_tabs">Kekomonïx taq ruwi\'</string> <string name="tabs_menu_share_tabs">Kekomonïx taq ruwi\'</string>
<!-- Open tabs menu item to save tabs to collection -->
<string name="tabs_menu_save_to_collection">Tiyak pa mol</string>
<!-- Content description (not visible, for screen readers etc.): Opens the tab menu when pressed --> <!-- Content description (not visible, for screen readers etc.): Opens the tab menu when pressed -->
<string name="tab_menu">Ruk\'utsamaj ruwi\'</string> <string name="tab_menu">Ruk\'utsamaj ruwi\'</string>
<!-- Tab menu item to share the tab --> <!-- Tab menu item to share the tab -->
@ -711,17 +705,11 @@
<string name="delete_browsing_data_quit_off">Tichup</string> <string name="delete_browsing_data_quit_off">Tichup</string>
<!-- Collections --> <!-- Collections -->
<!-- Label to describe what collections are to a new user without any collections -->
<string name="collections_description">Ke\'ayaka\' ri nimaläj taq awachinäq. Richin natikirisaj, ke\'ayaka\' ri jaqon taq ruwi\' pa jun k\'ak\'a\' mol.</string>
<!-- Collections header on home fragment --> <!-- Collections header on home fragment -->
<string name="collections_header">Taq mol</string> <string name="collections_header">Taq mol</string>
<!-- Content description (not visible, for screen readers etc.): Opens the collection menu when pressed --> <!-- Content description (not visible, for screen readers etc.): Opens the collection menu when pressed -->
<string name="collection_menu_button_content_description">Ruk\'utsamaj mol</string> <string name="collection_menu_button_content_description">Ruk\'utsamaj mol</string>
<!-- No Open Tabs Message Header -->
<string name="no_collections_header">Majun mol</string>
<!-- No Open Tabs Message Description -->
<string name="no_collections_description">Xkewachin wawe\' ri taq amol.</string>
<!-- Title for the "select tabs" step of the collection creator --> <!-- Title for the "select tabs" step of the collection creator -->
<string name="create_collection_select_tabs">Kecha\' taq Ruwi\'</string> <string name="create_collection_select_tabs">Kecha\' taq Ruwi\'</string>
@ -909,8 +897,6 @@
<string name="preference_summary_delete_browsing_data_on_quit">Keyuj ri taq tzij okem pa k\'amaya\'l pa ruyonil toq xtacha\' &quot;Tel&quot; pa ri nïm k\'utsamajib\'äl</string> <string name="preference_summary_delete_browsing_data_on_quit">Keyuj ri taq tzij okem pa k\'amaya\'l pa ruyonil toq xtacha\' &quot;Tel&quot; pa ri nïm k\'utsamajib\'äl</string>
<!-- Summary for the Delete browsing data on quit preference. "Quit" translation should match delete_browsing_data_on_quit_action translation. --> <!-- Summary for the Delete browsing data on quit preference. "Quit" translation should match delete_browsing_data_on_quit_action translation. -->
<string name="preference_summary_delete_browsing_data_on_quit_2">Keyuj ri taq tzij okem pa k\'amaya\'l pa ruyonil toq xtacha\' \&quot;Tel\&quot; pa ri nïm k\'utsamajib\'äl</string> <string name="preference_summary_delete_browsing_data_on_quit_2">Keyuj ri taq tzij okem pa k\'amaya\'l pa ruyonil toq xtacha\' \&quot;Tel\&quot; pa ri nïm k\'utsamajib\'äl</string>
<!-- Category for history items to delete on quit in delete browsing data on quit -->
<string name="preferences_delete_browsing_data_on_quit_browsing_history">Runatab\'al okem pa k\'amaya\'l</string>
<!-- Action item in menu for the Delete browsing data on quit feature --> <!-- Action item in menu for the Delete browsing data on quit feature -->
<string name="delete_browsing_data_on_quit_action">Tel</string> <string name="delete_browsing_data_on_quit_action">Tel</string>
@ -934,8 +920,6 @@
<!-- text for firefox preview moving tip description --> <!-- text for firefox preview moving tip description -->
<string name="tip_firefox_preview_moved_description">Firefox Nightly nuk\'ëx ri\' ronojel taq aq\'a\' chuqa\' k\'o k\'ak\'a\' tojtob\'enel taq rusamaj. <string name="tip_firefox_preview_moved_description">Firefox Nightly nuk\'ëx ri\' ronojel taq aq\'a\' chuqa\' k\'o k\'ak\'a\' tojtob\'enel taq rusamaj.
Po rik\'in jub\'a\' man kan ta jikïl. Taqasaj ri beta qokik\'amaya\'l richin jun jikïl samaj.</string> Po rik\'in jub\'a\' man kan ta jikïl. Taqasaj ri beta qokik\'amaya\'l richin jun jikïl samaj.</string>
<!-- text for firefox preview moving tip button. "Mozilla Firefox Browser" is intentionally hardcoded -->
<string name="tip_firefox_preview_moved_button">Tak\'ulu\' ri Mozilla Firefox Okik\'amaya\'l</string>
<!-- text for firefox preview moving tip header. "Firefox Nightly" is intentionally hardcoded --> <!-- text for firefox preview moving tip header. "Firefox Nightly" is intentionally hardcoded -->
<string name="tip_firefox_preview_moved_header_preview_installed">Firefox Nightly xsilöx</string> <string name="tip_firefox_preview_moved_header_preview_installed">Firefox Nightly xsilöx</string>
@ -1402,7 +1386,7 @@ Achi\'el: \nhttps://www.google.com/search?q=%s</string>
<!-- Second step for the allowing a permission --> <!-- Second step for the allowing a permission -->
<string name="phone_feature_blocked_step_permissions"><![CDATA[2. Tachapa\' <b>Taq Ya\'oj Q\'ij</b>]]></string> <string name="phone_feature_blocked_step_permissions"><![CDATA[2. Tachapa\' <b>Taq Ya\'oj Q\'ij</b>]]></string>
<!-- Third step for the allowing a permission (Fore example: Camera) --> <!-- Third step for the allowing a permission (Fore example: Camera) -->
<string name="phone_feature_blocked_step_feature"><![CDATA[3. Tajala\' <b>%1$s</b> rik\'in TZIJÏL]]></string> <string name="phone_feature_blocked_step_feature"><![CDATA[3. Tik\'exlöx <b>%1$s</b> rik\'in TZIJÏL]]></string>
<!-- Label that indicates a site is using a secure connection --> <!-- Label that indicates a site is using a secure connection -->
<string name="quick_settings_sheet_secure_connection">Ütz Okem</string> <string name="quick_settings_sheet_secure_connection">Ütz Okem</string>
@ -1464,4 +1448,4 @@ Achi\'el: \nhttps://www.google.com/search?q=%s</string>
<!-- Text displayed when user has disabled tab syncing in Firefox Sync Account --> <!-- Text displayed when user has disabled tab syncing in Firefox Sync Account -->
<string name="synced_tabs_enable_tab_syncing">Tatzija\' ri kiximik taq ruwi\'.</string> <string name="synced_tabs_enable_tab_syncing">Tatzija\' ri kiximik taq ruwi\'.</string>
</resources> </resources>

View File

@ -530,13 +530,13 @@
<!-- Text for the button to clear all history --> <!-- Text for the button to clear all history -->
<string name="history_delete_all">기록 삭제</string> <string name="history_delete_all">기록 삭제</string>
<!-- Text for the dialog to confirm clearing all history --> <!-- Text for the dialog to confirm clearing all history -->
<string name="history_delete_all_dialog">기록을 삭제하시겠습니까?</string> <string name="history_delete_all_dialog">기록을 지우시겠습니까?</string>
<!-- Text for the snackbar to confirm that multiple browsing history items has been deleted --> <!-- Text for the snackbar to confirm that multiple browsing history items has been deleted -->
<string name="history_delete_multiple_items_snackbar">기록 삭제됨</string> <string name="history_delete_multiple_items_snackbar">기록 삭제됨</string>
<!-- Text for the snackbar to confirm that a single browsing history item has been deleted. The first parameter is the shortened URL of the deleted history item. --> <!-- Text for the snackbar to confirm that a single browsing history item has been deleted. The first parameter is the shortened URL of the deleted history item. -->
<string name="history_delete_single_item_snackbar">%1$s 삭제됨</string> <string name="history_delete_single_item_snackbar">%1$s 삭제됨</string>
<!-- Text for positive action to delete history in deleting history dialog --> <!-- Text for positive action to delete history in deleting history dialog -->
<string name="history_clear_dialog">삭제</string> <string name="history_clear_dialog">지우기</string>
<!-- History overflow menu copy button --> <!-- History overflow menu copy button -->
<string name="history_menu_copy_button">복사</string> <string name="history_menu_copy_button">복사</string>
<!-- History overflow menu share button --> <!-- History overflow menu share button -->
@ -672,9 +672,9 @@
<!-- button that allows editing site permissions settings --> <!-- button that allows editing site permissions settings -->
<string name="quick_settings_sheet_manage_site_permissions">사이트 권한 관리</string> <string name="quick_settings_sheet_manage_site_permissions">사이트 권한 관리</string>
<!-- Button label for clearing all the information of site permissions--> <!-- Button label for clearing all the information of site permissions-->
<string name="clear_permissions">모든 권한 삭제</string> <string name="clear_permissions">모든 권한 지우기</string>
<!-- Button label for clearing a site permission--> <!-- Button label for clearing a site permission-->
<string name="clear_permission">권한 삭제</string> <string name="clear_permission">권한 지우기</string>
<!-- Button label for clearing all the information on all sites--> <!-- Button label for clearing all the information on all sites-->
<string name="clear_permissions_on_all_sites">모든 사이트에 대해 권한 지우기</string> <string name="clear_permissions_on_all_sites">모든 사이트에 대해 권한 지우기</string>
<!-- Preference for altering video and audio autoplay for all websites --> <!-- Preference for altering video and audio autoplay for all websites -->
@ -928,8 +928,6 @@
<string name="preference_summary_delete_browsing_data_on_quit">주 메뉴에서 &quot;종료&quot;를 선택하면 탐색 데이터를 자동으로 삭제합니다</string> <string name="preference_summary_delete_browsing_data_on_quit">주 메뉴에서 &quot;종료&quot;를 선택하면 탐색 데이터를 자동으로 삭제합니다</string>
<!-- Summary for the Delete browsing data on quit preference. "Quit" translation should match delete_browsing_data_on_quit_action translation. --> <!-- Summary for the Delete browsing data on quit preference. "Quit" translation should match delete_browsing_data_on_quit_action translation. -->
<string name="preference_summary_delete_browsing_data_on_quit_2">주 메뉴에서 \&quot;종료\&quot;를 선택하면 탐색 데이터를 자동으로 삭제합니다</string> <string name="preference_summary_delete_browsing_data_on_quit_2">주 메뉴에서 \&quot;종료\&quot;를 선택하면 탐색 데이터를 자동으로 삭제합니다</string>
<!-- Category for history items to delete on quit in delete browsing data on quit -->
<string name="preferences_delete_browsing_data_on_quit_browsing_history">방문 기록</string>
<!-- Action item in menu for the Delete browsing data on quit feature --> <!-- Action item in menu for the Delete browsing data on quit feature -->
<string name="delete_browsing_data_on_quit_action">종료</string> <string name="delete_browsing_data_on_quit_action">종료</string>

File diff suppressed because one or more lines are too long

View File

@ -1297,7 +1297,7 @@
<!-- Saved logins sorting strategy menu item -by name- (if selected, it will sort saved logins alphabetically) --> <!-- Saved logins sorting strategy menu item -by name- (if selected, it will sort saved logins alphabetically) -->
<string name="saved_logins_sort_strategy_alphabetically">Nome (A-Z)</string> <string name="saved_logins_sort_strategy_alphabetically">Nome (A-Z)</string>
<!-- Saved logins sorting strategy menu item -by last used- (if selected, it will sort saved logins by last used) --> <!-- Saved logins sorting strategy menu item -by last used- (if selected, it will sort saved logins by last used) -->
<string name="saved_logins_sort_strategy_last_used">Usados por último</string> <string name="saved_logins_sort_strategy_last_used">Data de uso</string>
<!-- Content description (not visible, for screen readers etc.): Sort saved logins dropdown menu chevron icon --> <!-- Content description (not visible, for screen readers etc.): Sort saved logins dropdown menu chevron icon -->
<string name="saved_logins_menu_dropdown_chevron_icon_content_description">Menu de ordenação de contas</string> <string name="saved_logins_menu_dropdown_chevron_icon_content_description">Menu de ordenação de contas</string>

View File

@ -0,0 +1,66 @@
/* 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.utils.view
import io.mockk.Called
import io.mockk.mockk
import io.mockk.verify
import io.mockk.verifySequence
import org.junit.Test
class GroupableRadioButtonTest {
@Test
fun `test add 1 radio to group`() {
val radio = mockk<GroupableRadioButton>(relaxed = true)
addToRadioGroup(radio)
verify { radio wasNot Called }
}
@Test
fun `test add 2 radios to group`() {
val radio1 = mockk<GroupableRadioButton>(relaxed = true)
val radio2 = mockk<GroupableRadioButton>(relaxed = true)
addToRadioGroup(radio1, radio2)
verifySequence {
radio1.addToRadioGroup(radio2)
radio2.addToRadioGroup(radio1)
}
}
@Test
fun `test add 3 radios to group`() {
val radio1 = mockk<GroupableRadioButton>(relaxed = true)
val radio2 = mockk<GroupableRadioButton>(relaxed = true)
val radio3 = mockk<GroupableRadioButton>(relaxed = true)
addToRadioGroup(radio1, radio2, radio3)
verifySequence {
radio1.addToRadioGroup(radio2)
radio2.addToRadioGroup(radio1)
radio1.addToRadioGroup(radio3)
radio3.addToRadioGroup(radio1)
radio2.addToRadioGroup(radio3)
radio3.addToRadioGroup(radio2)
}
}
@Test
fun `test uncheck all`() {
val radio1 = mockk<GroupableRadioButton>(relaxed = true)
val radio2 = mockk<GroupableRadioButton>(relaxed = true)
val radio3 = mockk<GroupableRadioButton>(relaxed = true)
listOf(radio1, radio2, radio3).uncheckAll()
verifySequence {
radio1.updateRadioValue(false)
radio2.updateRadioValue(false)
radio3.updateRadioValue(false)
}
}
}

View File

@ -3,5 +3,5 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
object AndroidComponents { object AndroidComponents {
const val VERSION = "49.0.20200706130124" const val VERSION = "49.0.20200707131055"
} }

View File

@ -17,11 +17,8 @@ primary-dependency: signing
group-by: build-type group-by: build-type
only-for-build-types: only-for-build-types:
- fennec-nightly
- fennec-beta - fennec-beta
- fennec-production - fennec-production
- nightly
- beta
- production - production
job-template: job-template:
@ -34,11 +31,8 @@ job-template:
default: true default: true
channel: channel:
by-build-type: by-build-type:
fennec-nightly: fennec-nightly
fennec-beta: fennec-beta fennec-beta: fennec-beta
fennec-production: fennec-production fennec-production: fennec-production
nightly: nightly
beta: beta
production: production production: production
dep: dep:
by-level: by-level: