For #17917: Use View binding in settings screens.

upstream-sync
mcarare 3 years ago committed by mergify[bot]
parent 84d9f272af
commit cca7892e91

@ -26,7 +26,6 @@ import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreference import androidx.preference.SwitchPreference
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.amo_collection_override_dialog.view.*
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -41,6 +40,7 @@ import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.HomeActivity
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.databinding.AmoCollectionOverrideDialogBinding
import org.mozilla.fenix.experiments.ExperimentBranch import org.mozilla.fenix.experiments.ExperimentBranch
import org.mozilla.fenix.experiments.FeatureId import org.mozilla.fenix.experiments.FeatureId
import org.mozilla.fenix.ext.application import org.mozilla.fenix.ext.application
@ -352,6 +352,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
val context = requireContext() val context = requireContext()
val dialogView = LayoutInflater.from(context).inflate(R.layout.amo_collection_override_dialog, null) val dialogView = LayoutInflater.from(context).inflate(R.layout.amo_collection_override_dialog, null)
val binding = AmoCollectionOverrideDialogBinding.bind(dialogView)
AlertDialog.Builder(context).apply { AlertDialog.Builder(context).apply {
setTitle(context.getString(R.string.preferences_customize_amo_collection)) setTitle(context.getString(R.string.preferences_customize_amo_collection))
setView(dialogView) setView(dialogView)
@ -360,8 +361,8 @@ class SettingsFragment : PreferenceFragmentCompat() {
} }
setPositiveButton(R.string.customize_addon_collection_ok) { _, _ -> setPositiveButton(R.string.customize_addon_collection_ok) { _, _ ->
context.settings().overrideAmoUser = dialogView.custom_amo_user.text.toString() context.settings().overrideAmoUser = binding.customAmoUser.text.toString()
context.settings().overrideAmoCollection = dialogView.custom_amo_collection.text.toString() context.settings().overrideAmoCollection = binding.customAmoCollection.text.toString()
Toast.makeText( Toast.makeText(
context, context,
@ -374,10 +375,10 @@ class SettingsFragment : PreferenceFragmentCompat() {
}, AMO_COLLECTION_OVERRIDE_EXIT_DELAY) }, AMO_COLLECTION_OVERRIDE_EXIT_DELAY)
} }
dialogView.custom_amo_collection.setText(context.settings().overrideAmoCollection) binding.customAmoCollection.setText(context.settings().overrideAmoCollection)
dialogView.custom_amo_user.setText(context.settings().overrideAmoUser) binding.customAmoUser.setText(context.settings().overrideAmoUser)
dialogView.custom_amo_user.requestFocus() binding.customAmoUser.requestFocus()
dialogView.custom_amo_user.showKeyboard() binding.customAmoUser.showKeyboard()
create() create()
}.show() }.show()

@ -14,7 +14,6 @@ import androidx.core.content.pm.PackageInfoCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.DividerItemDecoration
import kotlinx.android.synthetic.main.fragment_about.*
import org.mozilla.fenix.BrowserDirection import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.BuildConfig import org.mozilla.fenix.BuildConfig
import org.mozilla.fenix.Config import org.mozilla.fenix.Config
@ -22,6 +21,7 @@ import org.mozilla.fenix.HomeActivity
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.crashes.CrashListActivity import org.mozilla.fenix.crashes.CrashListActivity
import org.mozilla.fenix.databinding.FragmentAboutBinding
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
@ -43,19 +43,22 @@ class AboutFragment : Fragment(), AboutPageListener {
private lateinit var headerAppName: String private lateinit var headerAppName: String
private lateinit var appName: String private lateinit var appName: String
private var aboutPageAdapter: AboutPageAdapter? = AboutPageAdapter(this) private var aboutPageAdapter: AboutPageAdapter? = AboutPageAdapter(this)
private var _binding: FragmentAboutBinding? = null
private val binding get() = _binding!!
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View {
val rootView = inflater.inflate(R.layout.fragment_about, container, false) _binding = FragmentAboutBinding.inflate(inflater, container, false)
appName = getString(R.string.app_name) appName = getString(R.string.app_name)
headerAppName = headerAppName =
if (Config.channel.isRelease) getString(R.string.daylight_app_name) else appName if (Config.channel.isRelease) getString(R.string.daylight_app_name) else appName
showToolbar(getString(R.string.preferences_about, appName)) showToolbar(getString(R.string.preferences_about, appName))
return rootView return binding.root
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -63,7 +66,7 @@ class AboutFragment : Fragment(), AboutPageListener {
aboutPageAdapter = AboutPageAdapter(this) aboutPageAdapter = AboutPageAdapter(this)
} }
about_list.run { binding.aboutList.run {
adapter = aboutPageAdapter adapter = aboutPageAdapter
addItemDecoration( addItemDecoration(
DividerItemDecoration( DividerItemDecoration(
@ -75,7 +78,7 @@ class AboutFragment : Fragment(), AboutPageListener {
lifecycle.addObserver( lifecycle.addObserver(
SecretDebugMenuTrigger( SecretDebugMenuTrigger(
logoView = wordmark, logoView = binding.wordmark,
settings = view.context.settings() settings = view.context.settings()
) )
) )
@ -87,6 +90,7 @@ class AboutFragment : Fragment(), AboutPageListener {
override fun onDestroyView() { override fun onDestroyView() {
super.onDestroyView() super.onDestroyView()
aboutPageAdapter = null aboutPageAdapter = null
_binding = null
} }
private fun populateAboutHeader() { private fun populateAboutHeader() {
@ -121,9 +125,9 @@ class AboutFragment : Fragment(), AboutPageListener {
val content = getString(R.string.about_content, headerAppName) val content = getString(R.string.about_content, headerAppName)
val buildDate = BuildConfig.BUILD_DATE val buildDate = BuildConfig.BUILD_DATE
about_text.text = aboutText binding.aboutText.text = aboutText
about_content.text = content binding.aboutContent.text = content
build_date.text = buildDate binding.buildDate.text = buildDate
} }
private fun populateAboutList(): List<AboutPageItem> { private fun populateAboutList(): List<AboutPageItem> {

@ -13,8 +13,8 @@ import android.widget.ListView
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import kotlinx.android.synthetic.main.fragment_about_libraries.view.*
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.FragmentAboutLibrariesBinding
import org.mozilla.fenix.ext.showToolbar import org.mozilla.fenix.ext.showToolbar
import java.nio.charset.Charset import java.nio.charset.Charset
import java.util.Locale import java.util.Locale
@ -34,11 +34,16 @@ import java.util.Locale
* to show the extracted licenses to the end-user. * to show the extracted licenses to the end-user.
*/ */
class AboutLibrariesFragment : Fragment(R.layout.fragment_about_libraries) { class AboutLibrariesFragment : Fragment(R.layout.fragment_about_libraries) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val appName = getString(R.string.app_name) val appName = getString(R.string.app_name)
val binding = FragmentAboutLibrariesBinding.bind(view)
showToolbar(getString(R.string.open_source_licenses_title, appName)) showToolbar(getString(R.string.open_source_licenses_title, appName))
setupLibrariesListView(binding.aboutLibrariesListview)
}
setupLibrariesListView(view.about_libraries_listview) override fun onDestroyView() {
super.onDestroyView()
} }
private fun setupLibrariesListView(listView: ListView) { private fun setupLibrariesListView(listView: ListView) {

@ -6,8 +6,8 @@ package org.mozilla.fenix.settings.about.viewholders
import android.view.View import android.view.View
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.about_list_item.view.*
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.AboutListItemBinding
import org.mozilla.fenix.settings.about.AboutPageItem import org.mozilla.fenix.settings.about.AboutPageItem
import org.mozilla.fenix.settings.about.AboutPageListener import org.mozilla.fenix.settings.about.AboutPageListener
@ -16,8 +16,8 @@ class AboutItemViewHolder(
listener: AboutPageListener listener: AboutPageListener
) : RecyclerView.ViewHolder(view) { ) : RecyclerView.ViewHolder(view) {
private val title = view.about_item_title
private lateinit var item: AboutPageItem private lateinit var item: AboutPageItem
val binding = AboutListItemBinding.bind(view)
init { init {
itemView.setOnClickListener { itemView.setOnClickListener {
@ -27,7 +27,7 @@ class AboutItemViewHolder(
fun bind(item: AboutPageItem) { fun bind(item: AboutPageItem) {
this.item = item this.item = item
title.text = item.title binding.aboutItemTitle.text = item.title
} }
companion object { companion object {

@ -15,16 +15,19 @@ import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
import androidx.appcompat.app.AppCompatDialogFragment import androidx.appcompat.app.AppCompatDialogFragment
import kotlinx.android.synthetic.main.fragment_sign_out.view.*
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import mozilla.components.service.fxa.manager.FxaAccountManager import mozilla.components.service.fxa.manager.FxaAccountManager
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.FragmentSignOutBinding
import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.runIfFragmentIsAttached import org.mozilla.fenix.ext.runIfFragmentIsAttached
class SignOutFragment : AppCompatDialogFragment() { class SignOutFragment : AppCompatDialogFragment() {
private lateinit var accountManager: FxaAccountManager private lateinit var accountManager: FxaAccountManager
private var _binding: FragmentSignOutBinding? = null
private val binding get() = _binding!!
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, R.style.BottomSheet) setStyle(STYLE_NO_TITLE, R.style.BottomSheet)
@ -46,12 +49,13 @@ class SignOutFragment : AppCompatDialogFragment() {
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
accountManager = requireComponents.backgroundServices.accountManager accountManager = requireComponents.backgroundServices.accountManager
val view = inflater.inflate(R.layout.fragment_sign_out, container, false) _binding = FragmentSignOutBinding.inflate(inflater, container, false)
view.sign_out_message.text = String.format(
view.context.getString( binding.signOutMessage.text = String.format(
binding.root.context.getString(
R.string.sign_out_confirmation_message_2 R.string.sign_out_confirmation_message_2
), ),
view.context.getString(R.string.app_name) binding.root.context.getString(R.string.app_name)
) )
return view return view
} }
@ -59,7 +63,7 @@ class SignOutFragment : AppCompatDialogFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
view.signOutDisconnect.setOnClickListener { binding.signOutDisconnect.setOnClickListener {
lifecycleScope.launch { lifecycleScope.launch {
requireComponents requireComponents
.backgroundServices.accountAbnormalities.userRequestedLogout() .backgroundServices.accountAbnormalities.userRequestedLogout()
@ -74,8 +78,13 @@ class SignOutFragment : AppCompatDialogFragment() {
} }
} }
view.signOutCancel.setOnClickListener { binding.signOutCancel.setOnClickListener {
dismiss() dismiss()
} }
} }
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
} }

@ -14,7 +14,6 @@ import androidx.fragment.app.Fragment
import androidx.navigation.findNavController import androidx.navigation.findNavController
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import kotlinx.android.synthetic.main.fragment_turn_on_sync.view.*
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
@ -26,6 +25,7 @@ import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.components.FenixSnackbar import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.databinding.FragmentTurnOnSyncBinding
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
@ -57,6 +57,9 @@ class TurnOnSyncFragment : Fragment(), AccountObserver {
requireContext().settings().setCameraPermissionNeededState = false requireContext().settings().setCameraPermissionNeededState = false
} }
private var _binding: FragmentTurnOnSyncBinding? = null
private val binding get() = _binding!!
private fun navigateToPairFragment() { private fun navigateToPairFragment() {
val directions = TurnOnSyncFragmentDirections.actionTurnOnSyncFragmentToPairFragment() val directions = TurnOnSyncFragmentDirections.actionTurnOnSyncFragmentToPairFragment()
requireView().findNavController().navigate(directions) requireView().findNavController().navigate(directions)
@ -84,6 +87,7 @@ class TurnOnSyncFragment : Fragment(), AccountObserver {
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
requireComponents.analytics.metrics.track(Event.SyncAuthClosed) requireComponents.analytics.metrics.track(Event.SyncAuthClosed)
_binding = null
} }
override fun onResume() { override fun onResume() {
@ -110,11 +114,11 @@ class TurnOnSyncFragment : Fragment(), AccountObserver {
// Headless fragment. Don't need UI if we're taking the user to another screen. // Headless fragment. Don't need UI if we're taking the user to another screen.
return null return null
} }
_binding = FragmentTurnOnSyncBinding.inflate(inflater, container, false)
val view = inflater.inflate(R.layout.fragment_turn_on_sync, container, false) binding.signInScanButton.setOnClickListener(paringClickListener)
view.signInScanButton.setOnClickListener(paringClickListener) binding.signInEmailButton.setOnClickListener(signInClickListener)
view.signInEmailButton.setOnClickListener(signInClickListener) binding.signInInstructions.text = HtmlCompat.fromHtml(
view.signInInstructions.text = HtmlCompat.fromHtml(
if (requireContext().settings().allowDomesticChinaFxaServer && Config.channel.isMozillaOnline) if (requireContext().settings().allowDomesticChinaFxaServer && Config.channel.isMozillaOnline)
getString(R.string.sign_in_instructions_cn) getString(R.string.sign_in_instructions_cn)
else getString(R.string.sign_in_instructions), else getString(R.string.sign_in_instructions),
@ -125,14 +129,14 @@ class TurnOnSyncFragment : Fragment(), AccountObserver {
DefaultSyncController(activity = activity as HomeActivity) DefaultSyncController(activity = activity as HomeActivity)
) )
view.createAccount.apply { binding.createAccount.apply {
text = HtmlCompat.fromHtml( text = HtmlCompat.fromHtml(
getString(R.string.sign_in_create_account_text), getString(R.string.sign_in_create_account_text),
HtmlCompat.FROM_HTML_MODE_LEGACY HtmlCompat.FROM_HTML_MODE_LEGACY
) )
setOnClickListener(createAccountClickListener) setOnClickListener(createAccountClickListener)
} }
return view return binding.root
} }
override fun onAuthenticated(account: OAuthAccount, authType: AuthType) { override fun onAuthenticated(account: OAuthAccount, authType: AuthType) {

@ -13,14 +13,16 @@ import android.view.ViewGroup
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import kotlinx.android.synthetic.main.fragment_locale_settings.view.*
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import mozilla.components.lib.state.ext.consumeFrom import mozilla.components.lib.state.ext.consumeFrom
import mozilla.components.support.ktx.android.view.hideKeyboard import mozilla.components.support.ktx.android.view.hideKeyboard
import mozilla.components.support.locale.LocaleUseCases import mozilla.components.support.locale.LocaleUseCases
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.components.StoreProvider import org.mozilla.fenix.components.StoreProvider
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.databinding.FragmentLocaleSettingsBinding
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.showToolbar import org.mozilla.fenix.ext.showToolbar
class LocaleSettingsFragment : Fragment() { class LocaleSettingsFragment : Fragment() {
@ -29,6 +31,9 @@ class LocaleSettingsFragment : Fragment() {
private lateinit var interactor: LocaleSettingsInteractor private lateinit var interactor: LocaleSettingsInteractor
private lateinit var localeView: LocaleSettingsView private lateinit var localeView: LocaleSettingsView
private var _binding: FragmentLocaleSettingsBinding? = null
private val binding get() = _binding!!
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setHasOptionsMenu(true) setHasOptionsMenu(true)
@ -38,8 +43,9 @@ class LocaleSettingsFragment : Fragment() {
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View {
val view = inflater.inflate(R.layout.fragment_locale_settings, container, false) _binding = FragmentLocaleSettingsBinding.inflate(inflater, container, false)
val view = binding.root
val browserStore = requireContext().components.core.store val browserStore = requireContext().components.core.store
val localeUseCase = LocaleUseCases(browserStore) val localeUseCase = LocaleUseCases(browserStore)
@ -56,7 +62,7 @@ class LocaleSettingsFragment : Fragment() {
localeUseCase = localeUseCase localeUseCase = localeUseCase
) )
) )
localeView = LocaleSettingsView(view.locale_container, interactor) localeView = LocaleSettingsView(binding.root, interactor)
return view return view
} }
@ -97,4 +103,10 @@ class LocaleSettingsFragment : Fragment() {
localeView.update(it) localeView.update(it)
} }
} }
override fun onDestroy() {
super.onDestroy()
requireComponents.analytics.metrics.track(Event.SyncAuthClosed)
_binding = null
}
} }

@ -8,8 +8,8 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.android.synthetic.main.component_locale_settings.view.*
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.ComponentLocaleSettingsBinding
import java.util.Locale import java.util.Locale
interface LocaleSettingsViewInteractor { interface LocaleSettingsViewInteractor {
@ -29,10 +29,12 @@ class LocaleSettingsView(
val view: View = LayoutInflater.from(container.context) val view: View = LayoutInflater.from(container.context)
.inflate(R.layout.component_locale_settings, container, true) .inflate(R.layout.component_locale_settings, container, true)
val binding = ComponentLocaleSettingsBinding.bind(view)
private val localeAdapter: LocaleAdapter private val localeAdapter: LocaleAdapter
init { init {
view.locale_list.apply { binding.localeList.apply {
localeAdapter = LocaleAdapter(interactor) localeAdapter = LocaleAdapter(interactor)
adapter = localeAdapter adapter = localeAdapter
layoutManager = LinearLayoutManager(context) layoutManager = LinearLayoutManager(context)

@ -7,10 +7,10 @@ package org.mozilla.fenix.settings.advanced
import android.view.View import android.view.View
import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting
import androidx.core.view.isVisible import androidx.core.view.isVisible
import kotlinx.android.synthetic.main.locale_settings_item.* import androidx.recyclerview.widget.RecyclerView
import mozilla.components.support.locale.LocaleManager import mozilla.components.support.locale.LocaleManager
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.utils.view.ViewHolder import org.mozilla.fenix.databinding.LocaleSettingsItemBinding
import java.util.Locale import java.util.Locale
class LocaleViewHolder( class LocaleViewHolder(
@ -19,6 +19,8 @@ class LocaleViewHolder(
private val interactor: LocaleSettingsViewInteractor private val interactor: LocaleSettingsViewInteractor
) : BaseLocaleViewHolder(view, selectedLocale) { ) : BaseLocaleViewHolder(view, selectedLocale) {
private val binding = LocaleSettingsItemBinding.bind(view)
override fun bind(locale: Locale) { override fun bind(locale: Locale) {
if (locale.toString().equals("vec", ignoreCase = true)) { if (locale.toString().equals("vec", ignoreCase = true)) {
locale.toString() locale.toString()
@ -27,11 +29,11 @@ class LocaleViewHolder(
bindChineseLocale(locale) bindChineseLocale(locale)
} else { } else {
// Capitalisation is done using the rules of the appropriate locale (endonym and exonym). // Capitalisation is done using the rules of the appropriate locale (endonym and exonym).
locale_title_text.text = getDisplayName(locale) binding.localeTitleText.text = getDisplayName(locale)
// Show the given locale using the device locale for the subtitle. // Show the given locale using the device locale for the subtitle.
locale_subtitle_text.text = locale.getProperDisplayName() binding.localeSubtitleText.text = locale.getProperDisplayName()
} }
locale_selected_icon.isVisible = isCurrentLocaleSelected(locale, isDefault = false) binding.localeSelectedIcon.isVisible = isCurrentLocaleSelected(locale, isDefault = false)
itemView.setOnClickListener { itemView.setOnClickListener {
interactor.onLocaleSelected(locale) interactor.onLocaleSelected(locale)
@ -40,14 +42,14 @@ class LocaleViewHolder(
private fun bindChineseLocale(locale: Locale) { private fun bindChineseLocale(locale: Locale) {
if (locale.country == "CN") { if (locale.country == "CN") {
locale_title_text.text = binding.localeTitleText.text =
Locale.forLanguageTag("zh-Hans").getDisplayName(locale).capitalize(locale) Locale.forLanguageTag("zh-Hans").getDisplayName(locale).capitalize(locale)
locale_subtitle_text.text = binding.localeSubtitleText.text =
Locale.forLanguageTag("zh-Hans").displayName.capitalize(Locale.getDefault()) Locale.forLanguageTag("zh-Hans").displayName.capitalize(Locale.getDefault())
} else if (locale.country == "TW") { } else if (locale.country == "TW") {
locale_title_text.text = binding.localeTitleText.text =
Locale.forLanguageTag("zh-Hant").getDisplayName(locale).capitalize(locale) Locale.forLanguageTag("zh-Hant").getDisplayName(locale).capitalize(locale)
locale_subtitle_text.text = binding.localeSubtitleText.text =
Locale.forLanguageTag("zh-Hant").displayName.capitalize(Locale.getDefault()) Locale.forLanguageTag("zh-Hant").displayName.capitalize(Locale.getDefault())
} }
} }
@ -271,19 +273,21 @@ class SystemLocaleViewHolder(
private val interactor: LocaleSettingsViewInteractor private val interactor: LocaleSettingsViewInteractor
) : BaseLocaleViewHolder(view, selectedLocale) { ) : BaseLocaleViewHolder(view, selectedLocale) {
private val binding = LocaleSettingsItemBinding.bind(view)
override fun bind(locale: Locale) { override fun bind(locale: Locale) {
locale_title_text.text = itemView.context.getString(R.string.default_locale_text) binding.localeTitleText.text = itemView.context.getString(R.string.default_locale_text)
if (locale.script == "Hant") { if (locale.script == "Hant") {
locale_subtitle_text.text = binding.localeSubtitleText.text =
Locale.forLanguageTag("zh-Hant").displayName.capitalize(Locale.getDefault()) Locale.forLanguageTag("zh-Hant").displayName.capitalize(Locale.getDefault())
} else if (locale.script == "Hans") { } else if (locale.script == "Hans") {
locale_subtitle_text.text = binding.localeSubtitleText.text =
Locale.forLanguageTag("zh-Hans").displayName.capitalize(Locale.getDefault()) Locale.forLanguageTag("zh-Hans").displayName.capitalize(Locale.getDefault())
} else { } else {
// Use the device locale for the system locale subtitle. // Use the device locale for the system locale subtitle.
locale_subtitle_text.text = locale.getDisplayName(locale).capitalize(locale) binding.localeSubtitleText.text = locale.getDisplayName(locale).capitalize(locale)
} }
locale_selected_icon.isVisible = isCurrentLocaleSelected(locale, isDefault = true) binding.localeSelectedIcon.isVisible = isCurrentLocaleSelected(locale, isDefault = true)
itemView.setOnClickListener { itemView.setOnClickListener {
interactor.onDefaultLocaleSelected() interactor.onDefaultLocaleSelected()
} }
@ -293,7 +297,7 @@ class SystemLocaleViewHolder(
abstract class BaseLocaleViewHolder( abstract class BaseLocaleViewHolder(
view: View, view: View,
private val selectedLocale: Locale private val selectedLocale: Locale
) : ViewHolder(view) { ) : RecyclerView.ViewHolder(view) {
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
internal fun isCurrentLocaleSelected(locale: Locale, isDefault: Boolean): Boolean { internal fun isCurrentLocaleSelected(locale: Locale, isDefault: Boolean): Boolean {

@ -14,6 +14,7 @@ import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.SecureFragment import org.mozilla.fenix.SecureFragment
import org.mozilla.fenix.databinding.FragmentCreditCardEditorBinding
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.redirectToReAuth import org.mozilla.fenix.ext.redirectToReAuth
import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.settings
@ -63,9 +64,11 @@ class CreditCardEditorFragment : SecureFragment(R.layout.fragment_credit_card_ed
) )
) )
val binding = FragmentCreditCardEditorBinding.bind(view)
creditCardEditorState = creditCardEditorState =
args.creditCard?.toCreditCardEditorState(storage) ?: getInitialCreditCardEditorState() args.creditCard?.toCreditCardEditorState(storage) ?: getInitialCreditCardEditorState()
creditCardEditorView = CreditCardEditorView(view, interactor) creditCardEditorView = CreditCardEditorView(binding, interactor)
creditCardEditorView.bind(creditCardEditorState) creditCardEditorView.bind(creditCardEditorState)
} }

@ -10,7 +10,6 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import kotlinx.android.synthetic.main.fragment_saved_cards.view.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -18,6 +17,7 @@ import mozilla.components.lib.state.ext.consumeFrom
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.SecureFragment import org.mozilla.fenix.SecureFragment
import org.mozilla.fenix.components.StoreProvider import org.mozilla.fenix.components.StoreProvider
import org.mozilla.fenix.databinding.ComponentCreditCardsBinding
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.redirectToReAuth import org.mozilla.fenix.ext.redirectToReAuth
import org.mozilla.fenix.ext.showToolbar import org.mozilla.fenix.ext.showToolbar
@ -51,8 +51,9 @@ class CreditCardsManagementFragment : SecureFragment() {
navController = findNavController() navController = findNavController()
) )
) )
val binding = ComponentCreditCardsBinding.bind(view)
creditCardsView = CreditCardsManagementView(view.saved_cards_layout, interactor) creditCardsView = CreditCardsManagementView(binding, interactor)
loadCreditCards() loadCreditCards()

@ -7,8 +7,6 @@ package org.mozilla.fenix.settings.creditcards.view
import android.view.View import android.view.View
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.fragment_credit_card_editor.*
import mozilla.components.concept.storage.CreditCardNumber import mozilla.components.concept.storage.CreditCardNumber
import mozilla.components.concept.storage.NewCreditCardFields import mozilla.components.concept.storage.NewCreditCardFields
import mozilla.components.concept.storage.UpdatableCreditCardFields import mozilla.components.concept.storage.UpdatableCreditCardFields
@ -16,6 +14,7 @@ import mozilla.components.support.ktx.android.content.getColorFromAttr
import mozilla.components.support.ktx.android.view.hideKeyboard import mozilla.components.support.ktx.android.view.hideKeyboard
import mozilla.components.support.utils.creditCardIIN import mozilla.components.support.utils.creditCardIIN
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.FragmentCreditCardEditorBinding
import org.mozilla.fenix.ext.toEditable import org.mozilla.fenix.ext.toEditable
import org.mozilla.fenix.settings.creditcards.CreditCardEditorFragment import org.mozilla.fenix.settings.creditcards.CreditCardEditorFragment
import org.mozilla.fenix.settings.creditcards.CreditCardEditorState import org.mozilla.fenix.settings.creditcards.CreditCardEditorState
@ -31,16 +30,16 @@ import java.util.Locale
* Shows a credit card editor for adding or updating a credit card. * Shows a credit card editor for adding or updating a credit card.
*/ */
class CreditCardEditorView( class CreditCardEditorView(
override val containerView: View, private val binding: FragmentCreditCardEditorBinding,
private val interactor: CreditCardEditorInteractor private val interactor: CreditCardEditorInteractor
) : LayoutContainer { ) {
/** /**
* Binds the given [CreditCardEditorState] in the [CreditCardEditorFragment]. * Binds the given [CreditCardEditorState] in the [CreditCardEditorFragment].
*/ */
fun bind(state: CreditCardEditorState) { fun bind(state: CreditCardEditorState) {
if (state.isEditing) { if (state.isEditing) {
delete_button.apply { binding.deleteButton.apply {
visibility = View.VISIBLE visibility = View.VISIBLE
setOnClickListener { setOnClickListener {
@ -49,16 +48,16 @@ class CreditCardEditorView(
} }
} }
cancel_button.setOnClickListener { binding.cancelButton.setOnClickListener {
interactor.onCancelButtonClicked() interactor.onCancelButtonClicked()
} }
save_button.setOnClickListener { binding.saveButton.setOnClickListener {
saveCreditCard(state) saveCreditCard(state)
} }
card_number_input.text = state.cardNumber.toEditable() binding.cardNumberInput.text = state.cardNumber.toEditable()
name_on_card_input.text = state.billingName.toEditable() binding.nameOnCardInput.text = state.billingName.toEditable()
bindExpiryMonthDropDown(state.expiryMonth) bindExpiryMonthDropDown(state.expiryMonth)
bindExpiryYearDropDown(state.expiryYears) bindExpiryYearDropDown(state.expiryYears)
@ -71,28 +70,28 @@ class CreditCardEditorView(
* information. * information.
*/ */
internal fun saveCreditCard(state: CreditCardEditorState) { internal fun saveCreditCard(state: CreditCardEditorState) {
containerView.hideKeyboard() binding.root.hideKeyboard()
if (validateForm()) { if (validateForm()) {
val cardNumber = card_number_input.text.toString().toCreditCardNumber() val cardNumber = binding.cardNumberInput.text.toString().toCreditCardNumber()
if (state.isEditing) { if (state.isEditing) {
val fields = UpdatableCreditCardFields( val fields = UpdatableCreditCardFields(
billingName = name_on_card_input.text.toString(), billingName = binding.nameOnCardInput.text.toString(),
cardNumber = CreditCardNumber.Plaintext(cardNumber), cardNumber = CreditCardNumber.Plaintext(cardNumber),
cardNumberLast4 = cardNumber.last4Digits(), cardNumberLast4 = cardNumber.last4Digits(),
expiryMonth = (expiry_month_drop_down.selectedItemPosition + 1).toLong(), expiryMonth = (binding.expiryMonthDropDown.selectedItemPosition + 1).toLong(),
expiryYear = expiry_year_drop_down.selectedItem.toString().toLong(), expiryYear = binding.expiryYearDropDown.selectedItem.toString().toLong(),
cardType = cardNumber.creditCardIIN()?.creditCardIssuerNetwork?.name ?: "" cardType = cardNumber.creditCardIIN()?.creditCardIssuerNetwork?.name ?: ""
) )
interactor.onUpdateCreditCard(state.guid, fields) interactor.onUpdateCreditCard(state.guid, fields)
} else { } else {
val fields = NewCreditCardFields( val fields = NewCreditCardFields(
billingName = name_on_card_input.text.toString(), billingName = binding.nameOnCardInput.text.toString(),
plaintextCardNumber = CreditCardNumber.Plaintext(cardNumber), plaintextCardNumber = CreditCardNumber.Plaintext(cardNumber),
cardNumberLast4 = cardNumber.last4Digits(), cardNumberLast4 = cardNumber.last4Digits(),
expiryMonth = (expiry_month_drop_down.selectedItemPosition + 1).toLong(), expiryMonth = (binding.expiryMonthDropDown.selectedItemPosition + 1).toLong(),
expiryYear = expiry_year_drop_down.selectedItem.toString().toLong(), expiryYear = binding.expiryYearDropDown.selectedItem.toString().toLong(),
cardType = cardNumber.creditCardIIN()?.creditCardIssuerNetwork?.name ?: "" cardType = cardNumber.creditCardIIN()?.creditCardIssuerNetwork?.name ?: ""
) )
interactor.onSaveCreditCard(fields) interactor.onSaveCreditCard(fields)
@ -109,26 +108,26 @@ class CreditCardEditorView(
internal fun validateForm(): Boolean { internal fun validateForm(): Boolean {
var isValid = true var isValid = true
if (card_number_input.text.toString().validateCreditCardNumber()) { if (binding.cardNumberInput.text.toString().validateCreditCardNumber()) {
card_number_layout.error = null binding.cardNumberLayout.error = null
card_number_title.setTextColor(containerView.context.getColorFromAttr(R.attr.primaryText)) binding.cardNumberTitle.setTextColor(binding.root.context.getColorFromAttr(R.attr.primaryText))
} else { } else {
isValid = false isValid = false
card_number_layout.error = binding.cardNumberLayout.error =
containerView.context.getString(R.string.credit_cards_number_validation_error_message) binding.root.context.getString(R.string.credit_cards_number_validation_error_message)
card_number_title.setTextColor(containerView.context.getColorFromAttr(R.attr.destructive)) binding.cardNumberTitle.setTextColor(binding.root.context.getColorFromAttr(R.attr.destructive))
} }
if (name_on_card_input.text.toString().isNotBlank()) { if (binding.nameOnCardInput.text.toString().isNotBlank()) {
name_on_card_layout.error = null binding.nameOnCardInput.error = null
name_on_card_title.setTextColor(containerView.context.getColorFromAttr(R.attr.primaryText)) binding.nameOnCardTitle.setTextColor(binding.root.context.getColorFromAttr(R.attr.primaryText))
} else { } else {
isValid = false isValid = false
name_on_card_layout.error = binding.nameOnCardLayout.error =
containerView.context.getString(R.string.credit_cards_name_on_card_validation_error_message) binding.root.context.getString(R.string.credit_cards_name_on_card_validation_error_message)
name_on_card_title.setTextColor(containerView.context.getColorFromAttr(R.attr.destructive)) binding.nameOnCardTitle.setTextColor(binding.root.context.getColorFromAttr(R.attr.destructive))
} }
return isValid return isValid
@ -143,7 +142,7 @@ class CreditCardEditorView(
private fun bindExpiryMonthDropDown(expiryMonth: Int) { private fun bindExpiryMonthDropDown(expiryMonth: Int) {
val adapter = val adapter =
ArrayAdapter<String>( ArrayAdapter<String>(
containerView.context, binding.root.context,
android.R.layout.simple_spinner_dropdown_item android.R.layout.simple_spinner_dropdown_item
) )
val dateFormat = SimpleDateFormat("MMMM (MM)", Locale.getDefault()) val dateFormat = SimpleDateFormat("MMMM (MM)", Locale.getDefault())
@ -156,8 +155,8 @@ class CreditCardEditorView(
adapter.add(dateFormat.format(calendar.time)) adapter.add(dateFormat.format(calendar.time))
} }
expiry_month_drop_down.adapter = adapter binding.expiryMonthDropDown.adapter = adapter
expiry_month_drop_down.setSelection(expiryMonth - 1) binding.expiryMonthDropDown.setSelection(expiryMonth - 1)
} }
/** /**
@ -169,7 +168,7 @@ class CreditCardEditorView(
private fun bindExpiryYearDropDown(expiryYears: Pair<Int, Int>) { private fun bindExpiryYearDropDown(expiryYears: Pair<Int, Int>) {
val adapter = val adapter =
ArrayAdapter<String>( ArrayAdapter<String>(
containerView.context, binding.root.context,
android.R.layout.simple_spinner_dropdown_item android.R.layout.simple_spinner_dropdown_item
) )
val (startYear, endYear) = expiryYears val (startYear, endYear) = expiryYears
@ -178,7 +177,7 @@ class CreditCardEditorView(
adapter.add(year.toString()) adapter.add(year.toString())
} }
expiry_year_drop_down.adapter = adapter binding.expiryYearDropDown.adapter = adapter
} }
companion object { companion object {

@ -5,12 +5,10 @@
package org.mozilla.fenix.settings.creditcards.view package org.mozilla.fenix.settings.creditcards.view
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.component_credit_cards.*
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.ComponentCreditCardsBinding
import org.mozilla.fenix.settings.creditcards.CreditCardsListState import org.mozilla.fenix.settings.creditcards.CreditCardsListState
import org.mozilla.fenix.settings.creditcards.interactor.CreditCardsManagementInteractor import org.mozilla.fenix.settings.creditcards.interactor.CreditCardsManagementInteractor
@ -18,29 +16,29 @@ import org.mozilla.fenix.settings.creditcards.interactor.CreditCardsManagementIn
* Shows a list of credit cards. * Shows a list of credit cards.
*/ */
class CreditCardsManagementView( class CreditCardsManagementView(
override val containerView: ViewGroup, val binding: ComponentCreditCardsBinding,
val interactor: CreditCardsManagementInteractor val interactor: CreditCardsManagementInteractor
) : LayoutContainer { ) {
private val creditCardsAdapter = CreditCardsAdapter(interactor) private val creditCardsAdapter = CreditCardsAdapter(interactor)
init { init {
LayoutInflater.from(containerView.context).inflate(LAYOUT_ID, containerView, true) LayoutInflater.from(binding.root.context).inflate(LAYOUT_ID, binding.root, true)
credit_cards_list.apply { binding.creditCardsList.apply {
adapter = creditCardsAdapter adapter = creditCardsAdapter
layoutManager = LinearLayoutManager(containerView.context) layoutManager = LinearLayoutManager(binding.root.context)
} }
add_credit_card_button.setOnClickListener { interactor.onAddCreditCardClick() } binding.addCreditCardButton.addCreditCardLayout.setOnClickListener { interactor.onAddCreditCardClick() }
} }
/** /**
* Updates the display of the credit cards based on the given [CreditCardsListState]. * Updates the display of the credit cards based on the given [CreditCardsListState].
*/ */
fun update(state: CreditCardsListState) { fun update(state: CreditCardsListState) {
progress_bar.isVisible = state.isLoading binding.progressBar.isVisible = state.isLoading
credit_cards_list.isVisible = state.creditCards.isNotEmpty() binding.creditCardsList.isVisible = state.creditCards.isNotEmpty()
creditCardsAdapter.submitList(state.creditCards) creditCardsAdapter.submitList(state.creditCards)
} }

@ -11,8 +11,6 @@ import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import kotlinx.android.synthetic.main.fragment_delete_browsing_data.*
import kotlinx.android.synthetic.main.fragment_delete_browsing_data.view.*
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
@ -28,6 +26,7 @@ import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.components.FenixSnackbar import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.databinding.FragmentDeleteBrowsingDataBinding
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
@ -40,10 +39,15 @@ class DeleteBrowsingDataFragment : Fragment(R.layout.fragment_delete_browsing_da
private var scope: CoroutineScope? = null private var scope: CoroutineScope? = null
private lateinit var settings: Settings private lateinit var settings: Settings
private var _binding: FragmentDeleteBrowsingDataBinding? = null
private val binding get() = _binding!!
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
val tabsUseCases = requireComponents.useCases.tabsUseCases val tabsUseCases = requireComponents.useCases.tabsUseCases
val downloadUseCases = requireComponents.useCases.downloadUseCases val downloadUseCases = requireComponents.useCases.downloadUseCases
_binding = FragmentDeleteBrowsingDataBinding.bind(view)
controller = DefaultDeleteBrowsingDataController( controller = DefaultDeleteBrowsingDataController(
tabsUseCases.removeAllTabs, tabsUseCases.removeAllTabs,
downloadUseCases.removeAllDownloads, downloadUseCases.removeAllDownloads,
@ -74,7 +78,7 @@ class DeleteBrowsingDataFragment : Fragment(R.layout.fragment_delete_browsing_da
} }
} }
view.delete_data?.setOnClickListener { binding.deleteData.setOnClickListener {
askToDelete() askToDelete()
} }
updateDeleteButton() updateDeleteButton()
@ -117,8 +121,8 @@ class DeleteBrowsingDataFragment : Fragment(R.layout.fragment_delete_browsing_da
private fun updateDeleteButton() { private fun updateDeleteButton() {
val enabled = getCheckboxes().any { it.isChecked } val enabled = getCheckboxes().any { it.isChecked }
view?.delete_data?.isEnabled = enabled binding.deleteData.isEnabled = enabled
view?.delete_data?.alpha = if (enabled) ENABLED_ALPHA else DISABLED_ALPHA binding.deleteData.alpha = if (enabled) ENABLED_ALPHA else DISABLED_ALPHA
} }
private fun askToDelete() { private fun askToDelete() {
@ -168,18 +172,18 @@ class DeleteBrowsingDataFragment : Fragment(R.layout.fragment_delete_browsing_da
} }
private fun startDeletion() { private fun startDeletion() {
progress_bar.visibility = View.VISIBLE binding.progressBar.visibility = View.VISIBLE
delete_browsing_data_wrapper.isEnabled = false binding.deleteBrowsingDataWrapper.isEnabled = false
delete_browsing_data_wrapper.isClickable = false binding.deleteBrowsingDataWrapper.isClickable = false
delete_browsing_data_wrapper.alpha = DISABLED_ALPHA binding.deleteBrowsingDataWrapper.alpha = DISABLED_ALPHA
} }
private fun finishDeletion() { private fun finishDeletion() {
val popAfter = open_tabs_item.isChecked val popAfter = binding.openTabsItem.isChecked
progress_bar.visibility = View.GONE binding.progressBar.visibility = View.GONE
delete_browsing_data_wrapper.isEnabled = true binding.deleteBrowsingDataWrapper.isEnabled = true
delete_browsing_data_wrapper.isClickable = true binding.deleteBrowsingDataWrapper.isClickable = true
delete_browsing_data_wrapper.alpha = ENABLED_ALPHA binding.deleteBrowsingDataWrapper.alpha = ENABLED_ALPHA
updateItemCounts() updateItemCounts()
@ -206,7 +210,7 @@ class DeleteBrowsingDataFragment : Fragment(R.layout.fragment_delete_browsing_da
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
progress_bar.visibility = View.GONE binding.progressBar.visibility = View.GONE
} }
override fun onStop() { override fun onStop() {
@ -214,6 +218,11 @@ class DeleteBrowsingDataFragment : Fragment(R.layout.fragment_delete_browsing_da
scope?.cancel() scope?.cancel()
} }
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
private fun updateItemCounts() { private fun updateItemCounts() {
updateTabCount() updateTabCount()
updateHistoryCount() updateHistoryCount()
@ -223,7 +232,7 @@ class DeleteBrowsingDataFragment : Fragment(R.layout.fragment_delete_browsing_da
} }
private fun updateTabCount(openTabs: Int = requireComponents.core.store.state.tabs.size) { private fun updateTabCount(openTabs: Int = requireComponents.core.store.state.tabs.size) {
view?.open_tabs_item?.apply { binding.openTabsItem.apply {
subtitleView.text = resources.getString( subtitleView.text = resources.getString(
R.string.preferences_delete_browsing_data_tabs_subtitle, R.string.preferences_delete_browsing_data_tabs_subtitle,
openTabs openTabs
@ -232,12 +241,12 @@ class DeleteBrowsingDataFragment : Fragment(R.layout.fragment_delete_browsing_da
} }
private fun updateHistoryCount() { private fun updateHistoryCount() {
view?.browsing_data_item?.subtitleView?.text = "" binding.browsingDataItem.subtitleView.text = ""
viewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO) { viewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO) {
val historyCount = requireComponents.core.historyStorage.getVisited().size val historyCount = requireComponents.core.historyStorage.getVisited().size
launch(Dispatchers.Main) { launch(Dispatchers.Main) {
view?.browsing_data_item?.apply { binding.browsingDataItem.apply {
subtitleView.text = subtitleView.text =
resources.getString( resources.getString(
R.string.preferences_delete_browsing_data_browsing_data_subtitle, R.string.preferences_delete_browsing_data_browsing_data_subtitle,
@ -261,14 +270,13 @@ class DeleteBrowsingDataFragment : Fragment(R.layout.fragment_delete_browsing_da
} }
private fun getCheckboxes(): List<DeleteBrowsingDataItem> { private fun getCheckboxes(): List<DeleteBrowsingDataItem> {
val fragmentView = requireView()
return listOf( return listOf(
fragmentView.open_tabs_item, binding.openTabsItem,
fragmentView.browsing_data_item, binding.browsingDataItem,
fragmentView.cookies_item, binding.cookiesItem,
fragmentView.cached_files_item, binding.cachedFilesItem,
fragmentView.site_permissions_item, binding.sitePermissionsItem,
fragmentView.downloads_item binding.downloadsItem
) )
} }

@ -11,10 +11,8 @@ import android.view.View
import android.widget.TextView import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.withStyledAttributes import androidx.core.content.withStyledAttributes
import kotlinx.android.synthetic.main.delete_browsing_data_item.view.checkbox
import kotlinx.android.synthetic.main.delete_browsing_data_item.view.subtitle
import kotlinx.android.synthetic.main.delete_browsing_data_item.view.title
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.DeleteBrowsingDataItemBinding
class DeleteBrowsingDataItem @JvmOverloads constructor( class DeleteBrowsingDataItem @JvmOverloads constructor(
context: Context, context: Context,
@ -27,26 +25,33 @@ class DeleteBrowsingDataItem @JvmOverloads constructor(
private const val DISABLED_ALPHA = 0.6f private const val DISABLED_ALPHA = 0.6f
} }
private var binding: DeleteBrowsingDataItemBinding
val titleView: TextView val titleView: TextView
get() = title get() = binding.title
val subtitleView: TextView val subtitleView: TextView
get() = subtitle get() = binding.subtitle
var isChecked: Boolean var isChecked: Boolean
get() = checkbox.isChecked get() = binding.checkbox.isChecked
set(value) { checkbox.isChecked = value } set(value) {
binding.checkbox.isChecked = value
}
var onCheckListener: ((Boolean) -> Unit)? = null var onCheckListener: ((Boolean) -> Unit)? = null
init { init {
LayoutInflater.from(context).inflate(R.layout.delete_browsing_data_item, this, true) val view =
LayoutInflater.from(context).inflate(R.layout.delete_browsing_data_item, this, true)
binding = DeleteBrowsingDataItemBinding.bind(view)
setOnClickListener { setOnClickListener {
checkbox.isChecked = !checkbox.isChecked binding.checkbox.isChecked = !binding.checkbox.isChecked
} }
checkbox.setOnCheckedChangeListener { _, isChecked -> binding.checkbox.setOnCheckedChangeListener { _, isChecked ->
onCheckListener?.invoke(isChecked) onCheckListener?.invoke(isChecked)
} }
@ -60,10 +65,10 @@ class DeleteBrowsingDataItem @JvmOverloads constructor(
R.string.empty_string R.string.empty_string
) )
title.text = resources.getString(titleId) binding.title.text = resources.getString(titleId)
val subtitleText = resources.getString(subtitleId) val subtitleText = resources.getString(subtitleId)
subtitle.text = subtitleText binding.subtitle.text = subtitleText
if (subtitleText.isBlank()) subtitle.visibility = View.GONE if (subtitleText.isBlank()) binding.subtitle.visibility = View.GONE
} }
} }

@ -19,13 +19,13 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import kotlinx.android.synthetic.main.fragment_edit_login.*
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import mozilla.components.lib.state.ext.consumeFrom import mozilla.components.lib.state.ext.consumeFrom
import mozilla.components.support.ktx.android.view.hideKeyboard import mozilla.components.support.ktx.android.view.hideKeyboard
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.components.StoreProvider import org.mozilla.fenix.components.StoreProvider
import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.databinding.FragmentEditLoginBinding
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.redirectToReAuth import org.mozilla.fenix.ext.redirectToReAuth
import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.requireComponents
@ -59,9 +59,15 @@ class EditLoginFragment : Fragment(R.layout.fragment_edit_login) {
private var validPassword = true private var validPassword = true
private var validUsername = true private var validUsername = true
private var _binding: FragmentEditLoginBinding? = null
private val binding get() = _binding!!
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
setHasOptionsMenu(true) setHasOptionsMenu(true)
_binding = FragmentEditLoginBinding.bind(view)
oldLogin = args.savedLoginItem oldLogin = args.savedLoginItem
loginsFragmentStore = StoreProvider.get(this) { loginsFragmentStore = StoreProvider.get(this) {
@ -83,16 +89,16 @@ class EditLoginFragment : Fragment(R.layout.fragment_edit_login) {
interactor.findPotentialDuplicates(args.savedLoginItem.guid) interactor.findPotentialDuplicates(args.savedLoginItem.guid)
// initialize editable values // initialize editable values
hostnameText.text = args.savedLoginItem.origin.toEditable() binding.hostnameText.text = args.savedLoginItem.origin.toEditable()
usernameText.text = args.savedLoginItem.username.toEditable() binding.usernameText.text = args.savedLoginItem.username.toEditable()
passwordText.text = args.savedLoginItem.password.toEditable() binding.passwordText.text = args.savedLoginItem.password.toEditable()
clearUsernameTextButton.isEnabled = oldLogin.username.isNotEmpty() binding.clearUsernameTextButton.isEnabled = oldLogin.username.isNotEmpty()
formatEditableValues() formatEditableValues()
setUpClickListeners() setUpClickListeners()
setUpTextListeners() setUpTextListeners()
togglePasswordReveal(passwordText, revealPasswordButton) togglePasswordReveal(binding.passwordText, binding.revealPasswordButton)
consumeFrom(loginsFragmentStore) { consumeFrom(loginsFragmentStore) {
listOfPossibleDupes = loginsFragmentStore.state.duplicateLogins listOfPossibleDupes = loginsFragmentStore.state.duplicateLogins
@ -100,32 +106,32 @@ class EditLoginFragment : Fragment(R.layout.fragment_edit_login) {
} }
private fun formatEditableValues() { private fun formatEditableValues() {
hostnameText.isClickable = false binding.hostnameText.isClickable = false
hostnameText.isFocusable = false binding.hostnameText.isFocusable = false
usernameText.inputType = InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS binding.usernameText.inputType = InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
// TODO: extend PasswordTransformationMethod() to change bullets to asterisks // TODO: extend PasswordTransformationMethod() to change bullets to asterisks
passwordText.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD binding.passwordText.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
passwordText.compoundDrawablePadding = binding.passwordText.compoundDrawablePadding =
requireContext().resources requireContext().resources
.getDimensionPixelOffset(R.dimen.saved_logins_end_icon_drawable_padding) .getDimensionPixelOffset(R.dimen.saved_logins_end_icon_drawable_padding)
} }
private fun setUpClickListeners() { private fun setUpClickListeners() {
clearUsernameTextButton.setOnClickListener { binding.clearUsernameTextButton.setOnClickListener {
usernameText.text?.clear() binding.usernameText.text?.clear()
usernameText.isCursorVisible = true binding.usernameText.isCursorVisible = true
usernameText.hasFocus() binding.usernameText.hasFocus()
inputLayoutUsername.hasFocus() binding.inputLayoutUsername.hasFocus()
it.isEnabled = false it.isEnabled = false
} }
clearPasswordTextButton.setOnClickListener { binding.clearPasswordTextButton.setOnClickListener {
passwordText.text?.clear() binding.passwordText.text?.clear()
passwordText.isCursorVisible = true binding.passwordText.isCursorVisible = true
passwordText.hasFocus() binding.passwordText.hasFocus()
inputLayoutPassword.hasFocus() binding.inputLayoutPassword.hasFocus()
} }
revealPasswordButton.setOnClickListener { binding.revealPasswordButton.setOnClickListener {
togglePasswordReveal(passwordText, revealPasswordButton) togglePasswordReveal(binding.passwordText, binding.revealPasswordButton)
} }
} }
@ -136,28 +142,28 @@ class EditLoginFragment : Fragment(R.layout.fragment_edit_login) {
view?.hideKeyboard() view?.hideKeyboard()
} }
} }
editLoginLayout.onFocusChangeListener = View.OnFocusChangeListener { _, hasFocus -> binding.editLoginLayout.onFocusChangeListener = View.OnFocusChangeListener { _, hasFocus ->
if (!hasFocus) { if (!hasFocus) {
view?.hideKeyboard() view?.hideKeyboard()
} }
} }
usernameText.addTextChangedListener(object : TextWatcher { binding.usernameText.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(u: Editable?) { override fun afterTextChanged(u: Editable?) {
when (oldLogin.username) { when (oldLogin.username) {
u.toString() -> { u.toString() -> {
usernameChanged = false usernameChanged = false
validUsername = true validUsername = true
inputLayoutUsername.error = null binding.inputLayoutUsername.error = null
inputLayoutUsername.errorIconDrawable = null binding.inputLayoutUsername.errorIconDrawable = null
clearUsernameTextButton.isVisible = true binding.clearUsernameTextButton.isVisible = true
} }
else -> { else -> {
usernameChanged = true usernameChanged = true
setDupeError() setDupeError()
} }
} }
clearUsernameTextButton.isEnabled = u.toString().isNotEmpty() binding.clearUsernameTextButton.isEnabled = u.toString().isNotEmpty()
setSaveButtonState() setSaveButtonState()
} }
@ -170,30 +176,30 @@ class EditLoginFragment : Fragment(R.layout.fragment_edit_login) {
} }
}) })
passwordText.addTextChangedListener(object : TextWatcher { binding.passwordText.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(p: Editable?) { override fun afterTextChanged(p: Editable?) {
when { when {
p.toString().isEmpty() -> { p.toString().isEmpty() -> {
passwordChanged = true passwordChanged = true
revealPasswordButton.isVisible = false binding.revealPasswordButton.isVisible = false
clearPasswordTextButton.isVisible = false binding.clearPasswordTextButton.isVisible = false
setPasswordError() setPasswordError()
} }
p.toString() == oldLogin.password -> { p.toString() == oldLogin.password -> {
passwordChanged = false passwordChanged = false
validPassword = true validPassword = true
inputLayoutPassword.error = null binding.inputLayoutPassword.error = null
inputLayoutPassword.errorIconDrawable = null binding.inputLayoutPassword.errorIconDrawable = null
revealPasswordButton.isVisible = true binding.revealPasswordButton.isVisible = true
clearPasswordTextButton.isVisible = true binding.clearPasswordTextButton.isVisible = true
} }
else -> { else -> {
passwordChanged = true passwordChanged = true
validPassword = true validPassword = true
inputLayoutPassword.error = null binding.inputLayoutPassword.error = null
inputLayoutPassword.errorIconDrawable = null binding.inputLayoutPassword.errorIconDrawable = null
revealPasswordButton.isVisible = true binding.revealPasswordButton.isVisible = true
clearPasswordTextButton.isVisible = true binding.clearPasswordTextButton.isVisible = true
} }
} }
setSaveButtonState() setSaveButtonState()
@ -213,8 +219,8 @@ class EditLoginFragment : Fragment(R.layout.fragment_edit_login) {
loginsFragmentStore.state.duplicateLogins.filter { it.username == username }.any() loginsFragmentStore.state.duplicateLogins.filter { it.username == username }.any()
private fun setDupeError() { private fun setDupeError() {
if (isDupe(usernameText.text.toString())) { if (isDupe(binding.usernameText.text.toString())) {
inputLayoutUsername?.let { binding.inputLayoutUsername.let {
usernameChanged = true usernameChanged = true
validUsername = false validUsername = false
it.error = context?.getString(R.string.saved_login_duplicate) it.error = context?.getString(R.string.saved_login_duplicate)
@ -224,19 +230,19 @@ class EditLoginFragment : Fragment(R.layout.fragment_edit_login) {
ContextCompat.getColor(requireContext(), R.color.design_error) ContextCompat.getColor(requireContext(), R.color.design_error)
) )
) )
clearUsernameTextButton.isVisible = false binding.clearUsernameTextButton.isVisible = false
} }
} else { } else {
usernameChanged = true usernameChanged = true
validUsername = true validUsername = true
inputLayoutUsername.error = null binding.inputLayoutUsername.error = null
inputLayoutUsername.errorIconDrawable = null binding.inputLayoutUsername.errorIconDrawable = null
clearUsernameTextButton.isVisible = true binding.clearUsernameTextButton.isVisible = true
} }
} }
private fun setPasswordError() { private fun setPasswordError() {
inputLayoutPassword?.let { layout -> binding.inputLayoutPassword.let { layout ->
validPassword = false validPassword = false
layout.error = context?.getString(R.string.saved_login_password_required) layout.error = context?.getString(R.string.saved_login_password_required)
layout.setErrorIconDrawable(R.drawable.mozac_ic_warning_with_bottom_padding) layout.setErrorIconDrawable(R.drawable.mozac_ic_warning_with_bottom_padding)
@ -277,12 +283,17 @@ class EditLoginFragment : Fragment(R.layout.fragment_edit_login) {
view?.hideKeyboard() view?.hideKeyboard()
interactor.onSaveLogin( interactor.onSaveLogin(
args.savedLoginItem.guid, args.savedLoginItem.guid,
usernameText.text.toString(), binding.usernameText.text.toString(),
passwordText.text.toString() binding.passwordText.text.toString()
) )
requireComponents.analytics.metrics.track(Event.EditLoginSave) requireComponents.analytics.metrics.track(Event.EditLoginSave)
true true
} }
else -> false else -> false
} }
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
} }

@ -21,7 +21,6 @@ import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.fragment_login_detail.*
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.ObsoleteCoroutinesApi import kotlinx.coroutines.ObsoleteCoroutinesApi
import mozilla.components.lib.state.ext.consumeFrom import mozilla.components.lib.state.ext.consumeFrom
@ -31,6 +30,7 @@ import org.mozilla.fenix.R
import org.mozilla.fenix.components.FenixSnackbar import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.components.StoreProvider import org.mozilla.fenix.components.StoreProvider
import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.databinding.FragmentLoginDetailBinding
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.redirectToReAuth import org.mozilla.fenix.ext.redirectToReAuth
@ -61,12 +61,16 @@ class LoginDetailFragment : Fragment(R.layout.fragment_login_detail) {
private lateinit var menu: Menu private lateinit var menu: Menu
private var deleteDialog: AlertDialog? = null private var deleteDialog: AlertDialog? = null
private var _binding: FragmentLoginDetailBinding? = null
private val binding get() = _binding!!
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
val view = inflater.inflate(R.layout.fragment_login_detail, container, false) val view = inflater.inflate(R.layout.fragment_login_detail, container, false)
_binding = FragmentLoginDetailBinding.bind(view)
savedLoginsStore = StoreProvider.get(this) { savedLoginsStore = StoreProvider.get(this) {
LoginsFragmentStore( LoginsFragmentStore(
createInitialLoginsListState(requireContext().settings()) createInitialLoginsListState(requireContext().settings())
@ -104,7 +108,7 @@ class LoginDetailFragment : Fragment(R.layout.fragment_login_detail) {
) )
setUpPasswordReveal() setUpPasswordReveal()
} }
togglePasswordReveal(passwordText, revealPasswordButton) togglePasswordReveal(binding.passwordText, binding.revealPasswordButton)
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -137,34 +141,34 @@ class LoginDetailFragment : Fragment(R.layout.fragment_login_detail) {
} }
private fun setUpPasswordReveal() { private fun setUpPasswordReveal() {
passwordText.inputType = binding.passwordText.inputType =
InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
revealPasswordButton.increaseTapArea(BUTTON_INCREASE_DPS) binding.revealPasswordButton.increaseTapArea(BUTTON_INCREASE_DPS)
revealPasswordButton.setOnClickListener { binding.revealPasswordButton.setOnClickListener {
togglePasswordReveal(passwordText, revealPasswordButton) togglePasswordReveal(binding.passwordText, binding.revealPasswordButton)
} }
passwordText.setOnClickListener { binding.passwordText.setOnClickListener {
togglePasswordReveal(passwordText, revealPasswordButton) togglePasswordReveal(binding.passwordText, binding.revealPasswordButton)
} }
} }
private fun setUpCopyButtons() { private fun setUpCopyButtons() {
webAddressText.text = login?.origin binding.webAddressText.text = login?.origin
openWebAddress.increaseTapArea(BUTTON_INCREASE_DPS) binding.openWebAddress.increaseTapArea(BUTTON_INCREASE_DPS)
copyUsername.increaseTapArea(BUTTON_INCREASE_DPS) binding.copyUsername.increaseTapArea(BUTTON_INCREASE_DPS)
copyPassword.increaseTapArea(BUTTON_INCREASE_DPS) binding.copyPassword.increaseTapArea(BUTTON_INCREASE_DPS)
openWebAddress.setOnClickListener { binding.openWebAddress.setOnClickListener {
navigateToBrowser(requireNotNull(login?.origin)) navigateToBrowser(requireNotNull(login?.origin))
} }
usernameText.text = login?.username binding.usernameText.text = login?.username
copyUsername.setOnClickListener( binding.copyUsername.setOnClickListener(
CopyButtonListener(login?.username, R.string.logins_username_copied) CopyButtonListener(login?.username, R.string.logins_username_copied)
) )
passwordText.text = login?.password binding.passwordText.text = login?.password
copyPassword.setOnClickListener( binding.copyPassword.setOnClickListener(
CopyButtonListener(login?.password, R.string.logins_password_copied) CopyButtonListener(login?.password, R.string.logins_password_copied)
) )
} }
@ -247,6 +251,11 @@ class LoginDetailFragment : Fragment(R.layout.fragment_login_detail) {
} }
} }
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
private companion object { private companion object {
private const val BUTTON_INCREASE_DPS = 24 private const val BUTTON_INCREASE_DPS = 24
} }

@ -20,7 +20,6 @@ import androidx.constraintlayout.widget.ConstraintLayout
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import kotlinx.android.synthetic.main.fragment_saved_logins.view.*
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import mozilla.components.concept.menu.MenuController import mozilla.components.concept.menu.MenuController
import mozilla.components.concept.menu.Orientation import mozilla.components.concept.menu.Orientation
@ -29,6 +28,7 @@ import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.components.StoreProvider import org.mozilla.fenix.components.StoreProvider
import org.mozilla.fenix.databinding.FragmentSavedLoginsBinding
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.redirectToReAuth import org.mozilla.fenix.ext.redirectToReAuth
import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.settings
@ -70,6 +70,8 @@ class SavedLoginsFragment : Fragment() {
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
val view = inflater.inflate(R.layout.fragment_saved_logins, container, false) val view = inflater.inflate(R.layout.fragment_saved_logins, container, false)
val binding = FragmentSavedLoginsBinding.bind(view)
savedLoginsStore = StoreProvider.get(this) { savedLoginsStore = StoreProvider.get(this) {
LoginsFragmentStore( LoginsFragmentStore(
createInitialLoginsListState(requireContext().settings()) createInitialLoginsListState(requireContext().settings())
@ -99,7 +101,7 @@ class SavedLoginsFragment : Fragment() {
) )
savedLoginsListView = SavedLoginsListView( savedLoginsListView = SavedLoginsListView(
view.savedLoginsLayout, binding.savedLoginsLayout,
savedLoginsInteractor savedLoginsInteractor
) )
savedLoginsInteractor.loadAndMapLogins() savedLoginsInteractor.loadAndMapLogins()

@ -25,8 +25,6 @@ import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
import kotlinx.android.synthetic.main.fragment_quick_settings_dialog_sheet.*
import kotlinx.android.synthetic.main.fragment_quick_settings_dialog_sheet.view.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.plus import kotlinx.coroutines.plus
@ -35,6 +33,7 @@ import org.mozilla.fenix.BuildConfig
import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.IntentReceiverActivity import org.mozilla.fenix.IntentReceiverActivity
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.FragmentQuickSettingsDialogSheetBinding
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.settings.PhoneFeature import org.mozilla.fenix.settings.PhoneFeature
import com.google.android.material.R as MaterialR import com.google.android.material.R as MaterialR
@ -55,6 +54,9 @@ class QuickSettingsSheetDialogFragment : AppCompatDialogFragment() {
private var tryToRequestPermissions: Boolean = false private var tryToRequestPermissions: Boolean = false
private val args by navArgs<QuickSettingsSheetDialogFragmentArgs>() private val args by navArgs<QuickSettingsSheetDialogFragmentArgs>()
private var _binding: FragmentQuickSettingsDialogSheetBinding? = null
private val binding get() = _binding!!
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
// https://github.com/mozilla-mobile/fenix/issues/19920 // https://github.com/mozilla-mobile/fenix/issues/19920
override fun onCreateView( override fun onCreateView(
@ -64,7 +66,10 @@ class QuickSettingsSheetDialogFragment : AppCompatDialogFragment() {
): View { ): View {
val context = requireContext() val context = requireContext()
val components = context.components val components = context.components
val rootView = inflateRootView(container) val rootView = inflateRootView(container)
_binding = FragmentQuickSettingsDialogSheetBinding.bind(rootView)
quickSettingsStore = QuickSettingsFragmentStore.createStore( quickSettingsStore = QuickSettingsFragmentStore.createStore(
context = context, context = context,
websiteUrl = args.url, websiteUrl = args.url,
@ -98,9 +103,9 @@ class QuickSettingsSheetDialogFragment : AppCompatDialogFragment() {
interactor = QuickSettingsInteractor(quickSettingsController) interactor = QuickSettingsInteractor(quickSettingsController)
websiteInfoView = WebsiteInfoView(rootView.websiteInfoLayout) websiteInfoView = WebsiteInfoView(binding.websiteInfoLayout)
websitePermissionsView = websitePermissionsView =
WebsitePermissionsView(rootView.websitePermissionsLayout, interactor) WebsitePermissionsView(binding.websitePermissionsLayout, interactor)
return rootView return rootView
} }
@ -152,7 +157,8 @@ class QuickSettingsSheetDialogFragment : AppCompatDialogFragment() {
quickSettingsController.handleAndroidPermissionGranted(it) quickSettingsController.handleAndroidPermissionGranted(it)
} }
} else { } else {
val shouldShowRequestPermissionRationale = permissions.all { shouldShowRequestPermissionRationale(it) } val shouldShowRequestPermissionRationale =
permissions.all { shouldShowRequestPermissionRationale(it) }
if (!shouldShowRequestPermissionRationale && tryToRequestPermissions) { if (!shouldShowRequestPermissionRationale && tryToRequestPermissions) {
// The user has permanently blocked these permissions and he/she is trying to enabling them. // The user has permanently blocked these permissions and he/she is trying to enabling them.
@ -187,7 +193,7 @@ class QuickSettingsSheetDialogFragment : AppCompatDialogFragment() {
requestCode == REQUEST_CODE_QUICK_SETTINGS_PERMISSIONS && grantResults.all { it == PERMISSION_GRANTED } requestCode == REQUEST_CODE_QUICK_SETTINGS_PERMISSIONS && grantResults.all { it == PERMISSION_GRANTED }
private fun showPermissionsView() { private fun showPermissionsView() {
websitePermissionsGroup.isVisible = true binding.websitePermissionsGroup.isVisible = true
} }
private fun launchIntentReceiver() { private fun launchIntentReceiver() {

@ -5,14 +5,12 @@
package org.mozilla.fenix.settings.quicksettings package org.mozilla.fenix.settings.quicksettings
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.content.ContextCompat.getColor import androidx.core.content.ContextCompat.getColor
import androidx.core.view.isVisible import androidx.core.view.isVisible
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.quicksettings_website_info.*
import mozilla.components.support.ktx.android.content.getDrawableWithTint import mozilla.components.support.ktx.android.content.getDrawableWithTint
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.QuicksettingsWebsiteInfoBinding
/** /**
* MVI View that knows to display a whether the current website uses a secure connection or not. * MVI View that knows to display a whether the current website uses a secure connection or not.
@ -23,10 +21,12 @@ import org.mozilla.fenix.R
*/ */
class WebsiteInfoView( class WebsiteInfoView(
container: ViewGroup container: ViewGroup
) : LayoutContainer { ) {
val binding = QuicksettingsWebsiteInfoBinding.inflate(
override val containerView: View = LayoutInflater.from(container.context) LayoutInflater.from(container.context),
.inflate(R.layout.quicksettings_website_info, container, true) container,
false
)
/** /**
* Allows changing what this View displays. * Allows changing what this View displays.
@ -41,24 +41,25 @@ class WebsiteInfoView(
} }
private fun bindUrl(websiteUrl: String) { private fun bindUrl(websiteUrl: String) {
url.text = websiteUrl binding.url.text = websiteUrl
} }
private fun bindTitle(websiteTitle: String) { private fun bindTitle(websiteTitle: String) {
title.text = websiteTitle binding.title.text = websiteTitle
} }
private fun bindCertificateName(cert: String) { private fun bindCertificateName(cert: String) {
val certificateLabel = containerView.context.getString(R.string.certificate_info_verified_by, cert) val certificateLabel =
certificateInfo.text = certificateLabel binding.root.context.getString(R.string.certificate_info_verified_by, cert)
certificateInfo.isVisible = cert.isNotEmpty() binding.certificateInfo.text = certificateLabel
binding.certificateInfo.isVisible = cert.isNotEmpty()
} }
private fun bindSecurityInfo(uiValues: WebsiteSecurityUiValues) { private fun bindSecurityInfo(uiValues: WebsiteSecurityUiValues) {
val tint = getColor(containerView.context, uiValues.iconTintRes) val tint = getColor(binding.root.context, uiValues.iconTintRes)
securityInfo.setText(uiValues.securityInfoRes) binding.securityInfo.setText(uiValues.securityInfoRes)
securityInfoIcon.setImageDrawable( binding.securityInfoIcon.setImageDrawable(
containerView.context.getDrawableWithTint(uiValues.iconRes, tint) binding.root.context.getDrawableWithTint(uiValues.iconRes, tint)
) )
} }
} }

@ -14,9 +14,8 @@ import androidx.annotation.VisibleForTesting
import androidx.appcompat.widget.AppCompatSpinner import androidx.appcompat.widget.AppCompatSpinner
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.quicksettings_permissions.view.*
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.QuicksettingsPermissionsBinding
import org.mozilla.fenix.settings.PhoneFeature import org.mozilla.fenix.settings.PhoneFeature
import org.mozilla.fenix.settings.PhoneFeature.AUTOPLAY import org.mozilla.fenix.settings.PhoneFeature.AUTOPLAY
import org.mozilla.fenix.settings.PhoneFeature.CAMERA import org.mozilla.fenix.settings.PhoneFeature.CAMERA
@ -65,38 +64,38 @@ interface WebsitePermissionInteractor {
* @param interactor [WebsitePermissionInteractor] which will have delegated to all user interactions. * @param interactor [WebsitePermissionInteractor] which will have delegated to all user interactions.
*/ */
class WebsitePermissionsView( class WebsitePermissionsView(
override val containerView: ViewGroup, containerView: ViewGroup,
val interactor: WebsitePermissionInteractor val interactor: WebsitePermissionInteractor
) : LayoutContainer { ) {
private val context = containerView.context private val context = containerView.context
val view: View = LayoutInflater.from(context) val binding =
.inflate(R.layout.quicksettings_permissions, containerView, true) QuicksettingsPermissionsBinding.inflate(LayoutInflater.from(context), containerView, false)
@VisibleForTesting @VisibleForTesting
internal var permissionViews: Map<PhoneFeature, PermissionViewHolder> = EnumMap( internal var permissionViews: Map<PhoneFeature, PermissionViewHolder> = EnumMap(
mapOf( mapOf(
CAMERA to ToggleablePermission(view.cameraLabel, view.cameraStatus), CAMERA to ToggleablePermission(binding.cameraLabel, binding.cameraStatus),
LOCATION to ToggleablePermission(view.locationLabel, view.locationStatus), LOCATION to ToggleablePermission(binding.locationLabel, binding.locationStatus),
MICROPHONE to ToggleablePermission( MICROPHONE to ToggleablePermission(
view.microphoneLabel, binding.microphoneLabel,
view.microphoneStatus binding.microphoneStatus
), ),
NOTIFICATION to ToggleablePermission( NOTIFICATION to ToggleablePermission(
view.notificationLabel, binding.notificationLabel,
view.notificationStatus binding.notificationStatus
), ),
PERSISTENT_STORAGE to ToggleablePermission( PERSISTENT_STORAGE to ToggleablePermission(
view.persistentStorageLabel, binding.persistentStorageLabel,
view.persistentStorageStatus binding.persistentStorageStatus
), ),
MEDIA_KEY_SYSTEM_ACCESS to ToggleablePermission( MEDIA_KEY_SYSTEM_ACCESS to ToggleablePermission(
view.mediaKeySystemAccessLabel, binding.mediaKeySystemAccessLabel,
view.mediaKeySystemAccessStatus binding.mediaKeySystemAccessStatus
), ),
AUTOPLAY to SpinnerPermission( AUTOPLAY to SpinnerPermission(
view.autoplayLabel, binding.autoplayLabel,
view.autoplayStatus binding.autoplayStatus
) )
) )
) )

@ -15,21 +15,11 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.CompoundButton import android.widget.CompoundButton
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.RadioButton
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import kotlinx.android.synthetic.main.custom_search_engine.custom_search_engine_form
import kotlinx.android.synthetic.main.custom_search_engine.custom_search_engine_name_field
import kotlinx.android.synthetic.main.custom_search_engine.custom_search_engine_search_string_field
import kotlinx.android.synthetic.main.custom_search_engine.custom_search_engines_learn_more
import kotlinx.android.synthetic.main.custom_search_engine.edit_engine_name
import kotlinx.android.synthetic.main.custom_search_engine.edit_search_string
import kotlinx.android.synthetic.main.fragment_add_search_engine.search_engine_group
import kotlinx.android.synthetic.main.search_engine_radio_button.view.engine_icon
import kotlinx.android.synthetic.main.search_engine_radio_button.view.engine_text
import kotlinx.android.synthetic.main.search_engine_radio_button.view.overflow_menu
import kotlinx.android.synthetic.main.search_engine_radio_button.view.radio_button
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -43,6 +33,10 @@ import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.components.FenixSnackbar import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.databinding.CustomSearchEngineBinding
import org.mozilla.fenix.databinding.CustomSearchEngineRadioButtonBinding
import org.mozilla.fenix.databinding.FragmentAddSearchEngineBinding
import org.mozilla.fenix.databinding.SearchEngineRadioButtonBinding
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.showToolbar import org.mozilla.fenix.ext.showToolbar
@ -55,6 +49,10 @@ class AddSearchEngineFragment : Fragment(R.layout.fragment_add_search_engine),
private var selectedIndex: Int = -1 private var selectedIndex: Int = -1
private val engineViews = mutableListOf<View>() private val engineViews = mutableListOf<View>()
private var _binding: FragmentAddSearchEngineBinding? = null
private val binding get() = _binding!!
private lateinit var customSearchEngine: CustomSearchEngineBinding
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setHasOptionsMenu(true) setHasOptionsMenu(true)
@ -77,6 +75,8 @@ class AddSearchEngineFragment : Fragment(R.layout.fragment_add_search_engine),
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT ViewGroup.LayoutParams.WRAP_CONTENT
) )
_binding = FragmentAddSearchEngineBinding.bind(view)
customSearchEngine = binding.customSearchEngine
val setupSearchEngineItem: (Int, SearchEngine) -> Unit = { index, engine -> val setupSearchEngineItem: (Int, SearchEngine) -> Unit = { index, engine ->
val engineId = engine.id val engineId = engine.id
@ -85,24 +85,24 @@ class AddSearchEngineFragment : Fragment(R.layout.fragment_add_search_engine),
layoutInflater = layoutInflater, layoutInflater = layoutInflater,
res = requireContext().resources res = requireContext().resources
) )
engineItem.id = index engineItem.root.id = index
engineItem.tag = engineId engineItem.root.tag = engineId
engineItem.radio_button.isChecked = selectedIndex == index engineItem.radioButton.isChecked = selectedIndex == index
engineViews.add(engineItem) engineViews.add(engineItem.root)
search_engine_group.addView(engineItem, layoutParams) binding.searchEngineGroup.addView(engineItem.root, layoutParams)
} }
availableEngines.forEachIndexed(setupSearchEngineItem) availableEngines.forEachIndexed(setupSearchEngineItem)
val engineItem = makeCustomButton(layoutInflater) val engineItem = makeCustomButton(layoutInflater)
engineItem.id = CUSTOM_INDEX engineItem.root.id = CUSTOM_INDEX
engineItem.radio_button.isChecked = selectedIndex == CUSTOM_INDEX engineItem.radioButton.isChecked = selectedIndex == CUSTOM_INDEX
engineViews.add(engineItem) engineViews.add(engineItem.root)
search_engine_group.addView(engineItem, layoutParams) binding.searchEngineGroup.addView(engineItem.root, layoutParams)
toggleCustomForm(selectedIndex == CUSTOM_INDEX) toggleCustomForm(selectedIndex == CUSTOM_INDEX)
custom_search_engines_learn_more.setOnClickListener { customSearchEngine.customSearchEnginesLearnMore.setOnClickListener {
(activity as HomeActivity).openToBrowserAndLoad( (activity as HomeActivity).openToBrowserAndLoad(
searchTermOrURL = SupportUtils.getSumoURLForTopic( searchTermOrURL = SupportUtils.getSumoURLForTopic(
requireContext(), requireContext(),
@ -119,6 +119,11 @@ class AddSearchEngineFragment : Fragment(R.layout.fragment_add_search_engine),
showToolbar(getString(R.string.search_engine_add_custom_search_engine_title)) showToolbar(getString(R.string.search_engine_add_custom_search_engine_title))
} }
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.add_custom_searchengine_menu, menu) inflater.inflate(R.menu.add_custom_searchengine_menu, menu)
} }
@ -143,11 +148,11 @@ class AddSearchEngineFragment : Fragment(R.layout.fragment_add_search_engine),
@Suppress("ComplexMethod") @Suppress("ComplexMethod")
private fun createCustomEngine() { private fun createCustomEngine() {
custom_search_engine_name_field.error = "" customSearchEngine.customSearchEngineNameField.error = ""
custom_search_engine_search_string_field.error = "" customSearchEngine.customSearchEngineSearchStringField.error = ""
val name = edit_engine_name.text?.toString()?.trim() ?: "" val name = customSearchEngine.editEngineName.text?.toString()?.trim() ?: ""
val searchString = edit_search_string.text?.toString() ?: "" val searchString = customSearchEngine.editSearchString.text?.toString() ?: ""
if (checkForErrors(name, searchString)) { if (checkForErrors(name, searchString)) {
return return
@ -163,7 +168,7 @@ class AddSearchEngineFragment : Fragment(R.layout.fragment_add_search_engine),
when (result) { when (result) {
SearchStringValidator.Result.CannotReach -> { SearchStringValidator.Result.CannotReach -> {
custom_search_engine_search_string_field.error = resources customSearchEngine.customSearchEngineSearchStringField.error = resources
.getString(R.string.search_add_custom_engine_error_cannot_reach, name) .getString(R.string.search_add_custom_engine_error_cannot_reach, name)
} }
SearchStringValidator.Result.Success -> { SearchStringValidator.Result.Success -> {
@ -198,17 +203,17 @@ class AddSearchEngineFragment : Fragment(R.layout.fragment_add_search_engine),
fun checkForErrors(name: String, searchString: String): Boolean { fun checkForErrors(name: String, searchString: String): Boolean {
return when { return when {
name.isEmpty() -> { name.isEmpty() -> {
custom_search_engine_name_field.error = resources customSearchEngine.customSearchEngineNameField.error = resources
.getString(R.string.search_add_custom_engine_error_empty_name) .getString(R.string.search_add_custom_engine_error_empty_name)
true true
} }
searchString.isEmpty() -> { searchString.isEmpty() -> {
custom_search_engine_search_string_field.error = customSearchEngine.customSearchEngineSearchStringField.error =
resources.getString(R.string.search_add_custom_engine_error_empty_search_string) resources.getString(R.string.search_add_custom_engine_error_empty_search_string)
true true
} }
!searchString.contains("%s") -> { !searchString.contains("%s") -> {
custom_search_engine_search_string_field.error = customSearchEngine.customSearchEngineSearchStringField.error =
resources.getString(R.string.search_add_custom_engine_error_missing_template) resources.getString(R.string.search_add_custom_engine_error_missing_template)
true true
} }
@ -218,14 +223,16 @@ class AddSearchEngineFragment : Fragment(R.layout.fragment_add_search_engine),
override fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) { override fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) {
engineViews.forEach { engineViews.forEach {
when (it.radio_button == buttonView) { when (it.findViewById<RadioButton>(R.id.radio_button) == buttonView) {
true -> { true -> {
selectedIndex = it.id selectedIndex = it.id
} }
false -> { false -> {
it.radio_button.setOnCheckedChangeListener(null) it.findViewById<RadioButton>(R.id.radio_button).also { radioButton ->
it.radio_button.isChecked = false radioButton.setOnCheckedChangeListener(null)
it.radio_button.setOnCheckedChangeListener(this) radioButton.isChecked = false
radioButton.setOnCheckedChangeListener(this)
}
} }
} }
} }
@ -233,37 +240,40 @@ class AddSearchEngineFragment : Fragment(R.layout.fragment_add_search_engine),
toggleCustomForm(selectedIndex == -1) toggleCustomForm(selectedIndex == -1)
} }
private fun makeCustomButton(layoutInflater: LayoutInflater): View { private fun makeCustomButton(layoutInflater: LayoutInflater): CustomSearchEngineRadioButtonBinding {
val wrapper = layoutInflater val wrapper = layoutInflater
.inflate(R.layout.custom_search_engine_radio_button, null) as ConstraintLayout .inflate(R.layout.custom_search_engine_radio_button, null) as ConstraintLayout
wrapper.setOnClickListener { wrapper.radio_button.isChecked = true } val customSearchEngineRadioButtonBinding = CustomSearchEngineRadioButtonBinding.bind(wrapper)
wrapper.radio_button.setOnCheckedChangeListener(this) wrapper.setOnClickListener { customSearchEngineRadioButtonBinding.radioButton.isChecked = true }
return wrapper customSearchEngineRadioButtonBinding.radioButton.setOnCheckedChangeListener(this)
return customSearchEngineRadioButtonBinding
} }
private fun toggleCustomForm(isEnabled: Boolean) { private fun toggleCustomForm(isEnabled: Boolean) {
custom_search_engine_form.alpha = if (isEnabled) ENABLED_ALPHA else DISABLED_ALPHA customSearchEngine.customSearchEngineForm.alpha = if (isEnabled) ENABLED_ALPHA else DISABLED_ALPHA
edit_search_string.isEnabled = isEnabled customSearchEngine.editSearchString.isEnabled = isEnabled
edit_engine_name.isEnabled = isEnabled customSearchEngine.editEngineName.isEnabled = isEnabled
custom_search_engines_learn_more.isEnabled = isEnabled customSearchEngine.customSearchEnginesLearnMore.isEnabled = isEnabled
} }
private fun makeButtonFromSearchEngine( private fun makeButtonFromSearchEngine(
engine: SearchEngine, engine: SearchEngine,
layoutInflater: LayoutInflater, layoutInflater: LayoutInflater,
res: Resources res: Resources
): View { ): SearchEngineRadioButtonBinding {
val wrapper = layoutInflater val wrapper = layoutInflater
.inflate(R.layout.search_engine_radio_button, null) as LinearLayout .inflate(R.layout.search_engine_radio_button, null) as LinearLayout
wrapper.setOnClickListener { wrapper.radio_button.isChecked = true } val searchEngineRadioButtonBinding = SearchEngineRadioButtonBinding.bind(wrapper)
wrapper.radio_button.setOnCheckedChangeListener(this)
wrapper.engine_text.text = engine.name wrapper.setOnClickListener { searchEngineRadioButtonBinding.radioButton.isChecked = true }
searchEngineRadioButtonBinding.radioButton.setOnCheckedChangeListener(this)
searchEngineRadioButtonBinding.engineText.text = engine.name
val iconSize = res.getDimension(R.dimen.preference_icon_drawable_size).toInt() val iconSize = res.getDimension(R.dimen.preference_icon_drawable_size).toInt()
val engineIcon = BitmapDrawable(res, engine.icon) val engineIcon = BitmapDrawable(res, engine.icon)
engineIcon.setBounds(0, 0, iconSize, iconSize) engineIcon.setBounds(0, 0, iconSize, iconSize)
wrapper.engine_icon.setImageDrawable(engineIcon) searchEngineRadioButtonBinding.engineIcon.setImageDrawable(engineIcon)
wrapper.overflow_menu.visibility = View.GONE searchEngineRadioButtonBinding.overflowMenu.visibility = View.GONE
return wrapper return searchEngineRadioButtonBinding
} }
companion object { companion object {

@ -13,11 +13,6 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import kotlinx.android.synthetic.main.custom_search_engine.custom_search_engine_name_field
import kotlinx.android.synthetic.main.custom_search_engine.custom_search_engine_search_string_field
import kotlinx.android.synthetic.main.custom_search_engine.custom_search_engines_learn_more
import kotlinx.android.synthetic.main.custom_search_engine.edit_engine_name
import kotlinx.android.synthetic.main.custom_search_engine.edit_search_string
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -28,6 +23,8 @@ import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.components.FenixSnackbar import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.databinding.CustomSearchEngineBinding
import org.mozilla.fenix.databinding.FragmentAddSearchEngineBinding
import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.showToolbar import org.mozilla.fenix.ext.showToolbar
import org.mozilla.fenix.settings.SupportUtils import org.mozilla.fenix.settings.SupportUtils
@ -40,6 +37,10 @@ class EditCustomSearchEngineFragment : Fragment(R.layout.fragment_add_search_eng
private val args by navArgs<EditCustomSearchEngineFragmentArgs>() private val args by navArgs<EditCustomSearchEngineFragmentArgs>()
private lateinit var searchEngine: SearchEngine private lateinit var searchEngine: SearchEngine
private var _binding: FragmentAddSearchEngineBinding? = null
private val binding get() = _binding!!
private lateinit var customSearchEngine: CustomSearchEngineBinding
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setHasOptionsMenu(true) setHasOptionsMenu(true)
@ -56,10 +57,13 @@ class EditCustomSearchEngineFragment : Fragment(R.layout.fragment_add_search_eng
val url = searchEngine.resultUrls[0] val url = searchEngine.resultUrls[0]
edit_engine_name.setText(searchEngine.name) _binding = FragmentAddSearchEngineBinding.bind(view)
edit_search_string.setText(url.toEditableUrl()) customSearchEngine = binding.customSearchEngine
customSearchEngine.editEngineName.setText(searchEngine.name)
customSearchEngine.editSearchString.setText(url.toEditableUrl())
custom_search_engines_learn_more.setOnClickListener { customSearchEngine.customSearchEnginesLearnMore.setOnClickListener {
(activity as HomeActivity).openToBrowserAndLoad( (activity as HomeActivity).openToBrowserAndLoad(
searchTermOrURL = SupportUtils.getSumoURLForTopic( searchTermOrURL = SupportUtils.getSumoURLForTopic(
requireContext(), requireContext(),
@ -76,6 +80,11 @@ class EditCustomSearchEngineFragment : Fragment(R.layout.fragment_add_search_eng
showToolbar(getString(R.string.search_engine_edit_custom_search_engine_title)) showToolbar(getString(R.string.search_engine_edit_custom_search_engine_title))
} }
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.edit_custom_searchengine_menu, menu) inflater.inflate(R.menu.edit_custom_searchengine_menu, menu)
} }
@ -92,11 +101,11 @@ class EditCustomSearchEngineFragment : Fragment(R.layout.fragment_add_search_eng
@Suppress("LongMethod") @Suppress("LongMethod")
private fun saveCustomEngine() { private fun saveCustomEngine() {
custom_search_engine_name_field.error = "" customSearchEngine.customSearchEngineNameField.error = ""
custom_search_engine_search_string_field.error = "" customSearchEngine.customSearchEngineSearchStringField.error = ""
val name = edit_engine_name.text?.toString()?.trim() ?: "" val name = customSearchEngine.editEngineName.text?.toString()?.trim() ?: ""
val searchString = edit_search_string.text?.toString() ?: "" val searchString = customSearchEngine.editSearchString.text?.toString() ?: ""
if (checkForErrors(name, searchString)) { if (checkForErrors(name, searchString)) {
return return
@ -112,7 +121,7 @@ class EditCustomSearchEngineFragment : Fragment(R.layout.fragment_add_search_eng
when (result) { when (result) {
SearchStringValidator.Result.CannotReach -> { SearchStringValidator.Result.CannotReach -> {
custom_search_engine_search_string_field.error = resources customSearchEngine.customSearchEngineSearchStringField.error = resources
.getString(R.string.search_add_custom_engine_error_cannot_reach, name) .getString(R.string.search_add_custom_engine_error_cannot_reach, name)
} }
@ -120,7 +129,8 @@ class EditCustomSearchEngineFragment : Fragment(R.layout.fragment_add_search_eng
val update = searchEngine.copy( val update = searchEngine.copy(
name = name, name = name,
resultUrls = listOf(searchString.toSearchUrl()), resultUrls = listOf(searchString.toSearchUrl()),
icon = requireComponents.core.icons.loadIcon(IconRequest(searchString)).await().bitmap icon = requireComponents.core.icons.loadIcon(IconRequest(searchString))
.await().bitmap
) )
requireComponents.useCases.searchUseCases.addSearchEngine(update) requireComponents.useCases.searchUseCases.addSearchEngine(update)
@ -147,17 +157,17 @@ class EditCustomSearchEngineFragment : Fragment(R.layout.fragment_add_search_eng
private fun checkForErrors(name: String, searchString: String): Boolean { private fun checkForErrors(name: String, searchString: String): Boolean {
return when { return when {
name.isEmpty() -> { name.isEmpty() -> {
custom_search_engine_name_field.error = resources customSearchEngine.customSearchEngineNameField.error = resources
.getString(R.string.search_add_custom_engine_error_empty_name) .getString(R.string.search_add_custom_engine_error_empty_name)
true true
} }
searchString.isEmpty() -> { searchString.isEmpty() -> {
custom_search_engine_search_string_field.error = customSearchEngine.customSearchEngineSearchStringField.error =
resources.getString(R.string.search_add_custom_engine_error_empty_search_string) resources.getString(R.string.search_add_custom_engine_error_empty_search_string)
true true
} }
!searchString.contains("%s") -> { !searchString.contains("%s") -> {
custom_search_engine_search_string_field.error = customSearchEngine.customSearchEngineSearchStringField.error =
resources.getString(R.string.search_add_custom_engine_error_missing_template) resources.getString(R.string.search_add_custom_engine_error_missing_template)
true true
} }

@ -18,10 +18,6 @@ import androidx.core.view.isVisible
import androidx.navigation.Navigation import androidx.navigation.Navigation
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceViewHolder import androidx.preference.PreferenceViewHolder
import kotlinx.android.synthetic.main.search_engine_radio_button.view.engine_icon
import kotlinx.android.synthetic.main.search_engine_radio_button.view.engine_text
import kotlinx.android.synthetic.main.search_engine_radio_button.view.overflow_menu
import kotlinx.android.synthetic.main.search_engine_radio_button.view.radio_button
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.MainScope import kotlinx.coroutines.MainScope
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
@ -36,6 +32,7 @@ import mozilla.components.lib.state.ext.flow
import mozilla.components.support.ktx.android.view.toScope import mozilla.components.support.ktx.android.view.toScope
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.SearchEngineRadioButtonBinding
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.getRootView import org.mozilla.fenix.ext.getRootView
import org.mozilla.fenix.utils.allowUndo import org.mozilla.fenix.utils.allowUndo
@ -102,13 +99,16 @@ class RadioSearchEngineListPreference @JvmOverloads constructor(
val isCustomSearchEngine = engine.type == SearchEngine.Type.CUSTOM val isCustomSearchEngine = engine.type == SearchEngine.Type.CUSTOM
val wrapper = layoutInflater.inflate(itemResId, null) as LinearLayout val wrapper = layoutInflater.inflate(itemResId, null) as LinearLayout
wrapper.setOnClickListener { wrapper.radio_button.isChecked = true }
wrapper.radio_button.tag = engine.id val binding = SearchEngineRadioButtonBinding.bind(wrapper)
wrapper.radio_button.isChecked = isSelected
wrapper.radio_button.setOnCheckedChangeListener(this) wrapper.setOnClickListener { binding.radioButton.isChecked = true }
wrapper.engine_text.text = engine.name binding.radioButton.tag = engine.id
wrapper.overflow_menu.isVisible = allowDeletion || isCustomSearchEngine binding.radioButton.isChecked = isSelected
wrapper.overflow_menu.setOnClickListener { binding.radioButton.setOnCheckedChangeListener(this)
binding.engineText.text = engine.name
binding.overflowMenu.isVisible = allowDeletion || isCustomSearchEngine
binding.overflowMenu.setOnClickListener {
SearchEngineMenu( SearchEngineMenu(
context = context, context = context,
allowDeletion = allowDeletion, allowDeletion = allowDeletion,
@ -122,12 +122,12 @@ class RadioSearchEngineListPreference @JvmOverloads constructor(
) )
} }
} }
).menuBuilder.build(context).show(wrapper.overflow_menu) ).menuBuilder.build(context).show(binding.overflowMenu)
} }
val iconSize = res.getDimension(R.dimen.preference_icon_drawable_size).toInt() val iconSize = res.getDimension(R.dimen.preference_icon_drawable_size).toInt()
val engineIcon = BitmapDrawable(res, engine.icon) val engineIcon = BitmapDrawable(res, engine.icon)
engineIcon.setBounds(0, 0, iconSize, iconSize) engineIcon.setBounds(0, 0, iconSize, iconSize)
wrapper.engine_icon.setImageDrawable(engineIcon) binding.engineIcon.setImageDrawable(engineIcon)
return wrapper return wrapper
} }

@ -21,20 +21,19 @@ import android.widget.Button
import android.widget.RadioButton import android.widget.RadioButton
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import kotlinx.android.synthetic.main.fragment_manage_site_permissions_feature_phone.view.*
import mozilla.components.feature.sitepermissions.SitePermissionsRules import mozilla.components.feature.sitepermissions.SitePermissionsRules
import mozilla.components.feature.sitepermissions.SitePermissionsRules.Action.ALLOWED import mozilla.components.feature.sitepermissions.SitePermissionsRules.Action.ALLOWED
import mozilla.components.feature.sitepermissions.SitePermissionsRules.Action.ASK_TO_ALLOW
import mozilla.components.feature.sitepermissions.SitePermissionsRules.Action.BLOCKED import mozilla.components.feature.sitepermissions.SitePermissionsRules.Action.BLOCKED
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.databinding.FragmentManageSitePermissionsFeaturePhoneBinding
import org.mozilla.fenix.ext.components 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.settings.PhoneFeature
import org.mozilla.fenix.settings.PhoneFeature.AUTOPLAY_AUDIBLE import org.mozilla.fenix.settings.PhoneFeature.AUTOPLAY_AUDIBLE
import org.mozilla.fenix.settings.PhoneFeature.AUTOPLAY_INAUDIBLE import org.mozilla.fenix.settings.PhoneFeature.AUTOPLAY_INAUDIBLE
import org.mozilla.fenix.settings.PhoneFeature.MEDIA_KEY_SYSTEM_ACCESS
import org.mozilla.fenix.settings.setStartCheckedIndicator import org.mozilla.fenix.settings.setStartCheckedIndicator
import org.mozilla.fenix.utils.Settings import org.mozilla.fenix.utils.Settings
@ -49,25 +48,23 @@ class SitePermissionsManagePhoneFeatureFragment : Fragment() {
private val args by navArgs<SitePermissionsManagePhoneFeatureFragmentArgs>() private val args by navArgs<SitePermissionsManagePhoneFeatureFragmentArgs>()
private val settings by lazy { requireContext().settings() } private val settings by lazy { requireContext().settings() }
private lateinit var blockedByAndroidView: View private lateinit var blockedByAndroidView: View
private var _binding: FragmentManageSitePermissionsFeaturePhoneBinding? = null
private val binding get() = _binding!!
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View {
val rootView = inflater.inflate( _binding = FragmentManageSitePermissionsFeaturePhoneBinding.inflate(inflater, container, false)
R.layout.fragment_manage_site_permissions_feature_phone,
container,
false
)
initFirstRadio(rootView) initFirstRadio()
initSecondRadio(rootView) initSecondRadio()
initThirdRadio(rootView) initThirdRadio()
initFourthRadio(rootView) initFourthRadio()
bindBlockedByAndroidContainer(rootView) bindBlockedByAndroidContainer()
return rootView return binding.root
} }
override fun onResume() { override fun onResume() {
@ -76,8 +73,13 @@ class SitePermissionsManagePhoneFeatureFragment : Fragment() {
initBlockedByAndroidView(args.phoneFeature, blockedByAndroidView) initBlockedByAndroidView(args.phoneFeature, blockedByAndroidView)
} }
private fun initFirstRadio(rootView: View) { override fun onDestroyView() {
with(rootView.ask_to_allow_radio) { super.onDestroyView()
_binding = null
}
private fun initFirstRadio() {
with(binding.askToAllowRadio) {
if (args.phoneFeature == AUTOPLAY_AUDIBLE) { if (args.phoneFeature == AUTOPLAY_AUDIBLE) {
text = getString(R.string.preference_option_autoplay_allowed2) text = getString(R.string.preference_option_autoplay_allowed2)
setOnClickListener { setOnClickListener {
@ -91,16 +93,16 @@ class SitePermissionsManagePhoneFeatureFragment : Fragment() {
getString(R.string.phone_feature_recommended) getString(R.string.phone_feature_recommended)
) )
setOnClickListener { setOnClickListener {
saveActionInSettings(ASK_TO_ALLOW) saveActionInSettings(SitePermissionsRules.Action.ASK_TO_ALLOW)
} }
restoreState(ASK_TO_ALLOW) restoreState(SitePermissionsRules.Action.ASK_TO_ALLOW)
visibility = View.VISIBLE visibility = View.VISIBLE
} }
} }
} }
private fun initSecondRadio(rootView: View) { private fun initSecondRadio() {
with(rootView.block_radio) { with(binding.blockRadio) {
if (args.phoneFeature == AUTOPLAY_AUDIBLE) { if (args.phoneFeature == AUTOPLAY_AUDIBLE) {
text = getCombinedLabel( text = getCombinedLabel(
getString(R.string.preference_option_autoplay_allowed_wifi_only2), getString(R.string.preference_option_autoplay_allowed_wifi_only2),
@ -120,8 +122,8 @@ class SitePermissionsManagePhoneFeatureFragment : Fragment() {
} }
} }
private fun initThirdRadio(rootView: View) { private fun initThirdRadio() {
with(rootView.third_radio) { with(binding.thirdRadio) {
if (args.phoneFeature == AUTOPLAY_AUDIBLE) { if (args.phoneFeature == AUTOPLAY_AUDIBLE) {
visibility = View.VISIBLE visibility = View.VISIBLE
text = getCombinedLabel( text = getCombinedLabel(
@ -132,7 +134,7 @@ class SitePermissionsManagePhoneFeatureFragment : Fragment() {
saveActionInSettings(AUTOPLAY_BLOCK_AUDIBLE) saveActionInSettings(AUTOPLAY_BLOCK_AUDIBLE)
} }
restoreState(AUTOPLAY_BLOCK_AUDIBLE) restoreState(AUTOPLAY_BLOCK_AUDIBLE)
} else if (args.phoneFeature == MEDIA_KEY_SYSTEM_ACCESS) { } else if (args.phoneFeature == PhoneFeature.MEDIA_KEY_SYSTEM_ACCESS) {
visibility = View.VISIBLE visibility = View.VISIBLE
text = getString(R.string.preference_option_phone_feature_allowed) text = getString(R.string.preference_option_phone_feature_allowed)
setOnClickListener { setOnClickListener {
@ -145,8 +147,8 @@ class SitePermissionsManagePhoneFeatureFragment : Fragment() {
} }
} }
private fun initFourthRadio(rootView: View) { private fun initFourthRadio() {
with(rootView.fourth_radio) { with(binding.fourthRadio) {
if (args.phoneFeature == AUTOPLAY_AUDIBLE) { if (args.phoneFeature == AUTOPLAY_AUDIBLE) {
visibility = View.VISIBLE visibility = View.VISIBLE
text = getString(R.string.preference_option_autoplay_blocked3) text = getString(R.string.preference_option_autoplay_blocked3)
@ -215,8 +217,8 @@ class SitePermissionsManagePhoneFeatureFragment : Fragment() {
context?.components?.useCases?.sessionUseCases?.reload?.invoke() context?.components?.useCases?.sessionUseCases?.reload?.invoke()
} }
private fun bindBlockedByAndroidContainer(rootView: View) { private fun bindBlockedByAndroidContainer() {
blockedByAndroidView = rootView.findViewById(R.id.permissions_blocked_container) blockedByAndroidView = binding.root.findViewById(R.id.permissions_blocked_container)
initSettingsButton(blockedByAndroidView) initSettingsButton(blockedByAndroidView)
} }

@ -19,6 +19,8 @@
android:id="@+id/search_engine_group" android:id="@+id/search_engine_group"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
<include layout="@layout/custom_search_engine" /> <include
android:id="@+id/custom_search_engine"
layout="@layout/custom_search_engine" />
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>

@ -3,6 +3,7 @@
- License, v. 2.0. If a copy of the MPL was not distributed with this - 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/. --> - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/add_credit_card_layout"
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"

@ -5,18 +5,18 @@
package org.mozilla.fenix.settings.about package org.mozilla.fenix.settings.about
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.TextView
import io.mockk.Runs import io.mockk.Runs
import io.mockk.every import io.mockk.every
import io.mockk.just import io.mockk.just
import io.mockk.mockk import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.spyk import io.mockk.spyk
import io.mockk.verify import io.mockk.verify
import kotlinx.android.synthetic.main.about_list_item.view.*
import mozilla.components.support.test.robolectric.testContext import mozilla.components.support.test.robolectric.testContext
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mozilla.fenix.databinding.AboutListItemBinding
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.settings.about.viewholders.AboutItemViewHolder import org.mozilla.fenix.settings.about.viewholders.AboutItemViewHolder
@ -66,14 +66,22 @@ class AboutPageAdapterTest {
fun `the adapter binds the right item to a ViewHolder`() { fun `the adapter binds the right item to a ViewHolder`() {
val adapter = AboutPageAdapter(listener) val adapter = AboutPageAdapter(listener)
val parentView: ViewGroup = mockk(relaxed = true) val parentView: ViewGroup = mockk(relaxed = true)
every { parentView.about_item_title } returns TextView(testContext)
mockkStatic(AboutListItemBinding::class)
val binding: AboutListItemBinding = mockk()
every { AboutListItemBinding.bind(parentView) } returns binding
every { binding.root } returns mockk()
val viewHolder = spyk(AboutItemViewHolder(parentView, mockk())) val viewHolder = spyk(AboutItemViewHolder(parentView, mockk()))
every { every {
adapter.onCreateViewHolder( adapter.onCreateViewHolder(
parentView, parentView,
AboutItemViewHolder.LAYOUT_ID AboutItemViewHolder.LAYOUT_ID
) )
} returns viewHolder } returns viewHolder
every { viewHolder.bind(any()) } just Runs every { viewHolder.bind(any()) } just Runs
adapter.submitList(aboutList) adapter.submitList(aboutList)

@ -8,7 +8,6 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import io.mockk.mockk import io.mockk.mockk
import io.mockk.verify import io.mockk.verify
import kotlinx.android.synthetic.main.about_list_item.view.*
import mozilla.components.support.test.robolectric.testContext import mozilla.components.support.test.robolectric.testContext
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Before import org.junit.Before
@ -37,7 +36,7 @@ class AboutItemViewHolderTest {
val holder = AboutItemViewHolder(view, listener) val holder = AboutItemViewHolder(view, listener)
holder.bind(item) holder.bind(item)
assertEquals("Libraries", view.about_item_title.text) assertEquals("Libraries", holder.binding.aboutItemTitle.text)
} }
@Test @Test

@ -13,7 +13,6 @@ import io.mockk.just
import io.mockk.mockk import io.mockk.mockk
import io.mockk.mockkObject import io.mockk.mockkObject
import io.mockk.verify import io.mockk.verify
import kotlinx.android.synthetic.main.locale_settings_item.view.*
import mozilla.components.support.locale.LocaleManager import mozilla.components.support.locale.LocaleManager
import mozilla.components.support.test.robolectric.testContext import mozilla.components.support.test.robolectric.testContext
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
@ -23,6 +22,7 @@ import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.LocaleSettingsItemBinding
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import java.util.Locale import java.util.Locale
@ -34,6 +34,7 @@ class LocaleViewHoldersTest {
private lateinit var interactor: LocaleSettingsViewInteractor private lateinit var interactor: LocaleSettingsViewInteractor
private lateinit var localeViewHolder: LocaleViewHolder private lateinit var localeViewHolder: LocaleViewHolder
private lateinit var systemLocaleViewHolder: SystemLocaleViewHolder private lateinit var systemLocaleViewHolder: SystemLocaleViewHolder
private lateinit var localeSettingsItemBinding: LocaleSettingsItemBinding
@Before @Before
fun setup() { fun setup() {
@ -42,6 +43,8 @@ class LocaleViewHoldersTest {
view = LayoutInflater.from(testContext) view = LayoutInflater.from(testContext)
.inflate(R.layout.locale_settings_item, null) .inflate(R.layout.locale_settings_item, null)
localeSettingsItemBinding = LocaleSettingsItemBinding.bind(view)
interactor = mockk() interactor = mockk()
localeViewHolder = LocaleViewHolder(view, selectedLocale, interactor) localeViewHolder = LocaleViewHolder(view, selectedLocale, interactor)
@ -52,9 +55,9 @@ class LocaleViewHoldersTest {
fun `bind LocaleViewHolder`() { fun `bind LocaleViewHolder`() {
localeViewHolder.bind(selectedLocale) localeViewHolder.bind(selectedLocale)
assertEquals("English (United States)", view.locale_title_text.text) assertEquals("English (United States)", localeSettingsItemBinding.localeTitleText.text)
assertEquals("English (United States)", view.locale_subtitle_text.text) assertEquals("English (United States)", localeSettingsItemBinding.localeSubtitleText.text)
assertFalse(view.locale_selected_icon.isVisible) assertFalse(localeSettingsItemBinding.localeSelectedIcon.isVisible)
} }
@Test @Test
@ -74,8 +77,8 @@ class LocaleViewHoldersTest {
localeViewHolder.bind(otherLocale) localeViewHolder.bind(otherLocale)
assertEquals("Vèneto", view.locale_title_text.text) assertEquals("Vèneto", localeSettingsItemBinding.localeTitleText.text)
assertEquals("Venetian", view.locale_subtitle_text.text) assertEquals("Venetian", localeSettingsItemBinding.localeSubtitleText.text)
} }
@Test @Test
@ -85,17 +88,17 @@ class LocaleViewHoldersTest {
localeViewHolder.bind(otherLocale) localeViewHolder.bind(otherLocale)
assertEquals("Yyy", view.locale_title_text.text) assertEquals("Yyy", localeSettingsItemBinding.localeTitleText.text)
assertEquals("Yyy", view.locale_subtitle_text.text) assertEquals("Yyy", localeSettingsItemBinding.localeSubtitleText.text)
} }
@Test @Test
fun `bind SystemLocaleViewHolder`() { fun `bind SystemLocaleViewHolder`() {
systemLocaleViewHolder.bind(selectedLocale) systemLocaleViewHolder.bind(selectedLocale)
assertEquals("Follow device language", view.locale_title_text.text) assertEquals("Follow device language", localeSettingsItemBinding.localeTitleText.text)
assertEquals("English (United States)", view.locale_subtitle_text.text) assertEquals("English (United States)", localeSettingsItemBinding.localeSubtitleText.text)
assertTrue(view.locale_selected_icon.isVisible) assertTrue(localeSettingsItemBinding.localeSelectedIcon.isVisible)
} }
@Test @Test

@ -10,7 +10,6 @@ import io.mockk.every
import io.mockk.mockk import io.mockk.mockk
import io.mockk.spyk import io.mockk.spyk
import io.mockk.verify import io.mockk.verify
import kotlinx.android.synthetic.main.fragment_credit_card_editor.view.*
import mozilla.components.concept.storage.CreditCard import mozilla.components.concept.storage.CreditCard
import mozilla.components.concept.storage.CreditCardNumber import mozilla.components.concept.storage.CreditCardNumber
import mozilla.components.concept.storage.NewCreditCardFields import mozilla.components.concept.storage.NewCreditCardFields
@ -26,6 +25,7 @@ import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.FragmentCreditCardEditorBinding
import org.mozilla.fenix.ext.toEditable import org.mozilla.fenix.ext.toEditable
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.settings.creditcards.CreditCardEditorFragment.Companion.NUMBER_OF_YEARS_TO_SHOW import org.mozilla.fenix.settings.creditcards.CreditCardEditorFragment.Companion.NUMBER_OF_YEARS_TO_SHOW
@ -41,6 +41,7 @@ class CreditCardEditorViewTest {
private lateinit var creditCardEditorView: CreditCardEditorView private lateinit var creditCardEditorView: CreditCardEditorView
private lateinit var storage: AutofillCreditCardsAddressesStorage private lateinit var storage: AutofillCreditCardsAddressesStorage
private lateinit var crypto: AutofillCrypto private lateinit var crypto: AutofillCrypto
private lateinit var fragmentCreditCardEditorBinding: FragmentCreditCardEditorBinding
private val cardNumber = "4111111111111111" private val cardNumber = "4111111111111111"
private val creditCard = CreditCard( private val creditCard = CreditCard(
@ -60,6 +61,7 @@ class CreditCardEditorViewTest {
@Before @Before
fun setup() { fun setup() {
view = LayoutInflater.from(testContext).inflate(R.layout.fragment_credit_card_editor, null) view = LayoutInflater.from(testContext).inflate(R.layout.fragment_credit_card_editor, null)
fragmentCreditCardEditorBinding = FragmentCreditCardEditorBinding.bind(view)
interactor = mockk(relaxed = true) interactor = mockk(relaxed = true)
storage = mockk(relaxed = true) storage = mockk(relaxed = true)
crypto = mockk(relaxed = true) crypto = mockk(relaxed = true)
@ -67,7 +69,7 @@ class CreditCardEditorViewTest {
every { storage.getCreditCardCrypto() } returns crypto every { storage.getCreditCardCrypto() } returns crypto
every { crypto.decrypt(any(), any()) } returns CreditCardNumber.Plaintext(cardNumber) every { crypto.decrypt(any(), any()) } returns CreditCardNumber.Plaintext(cardNumber)
creditCardEditorView = spyk(CreditCardEditorView(view, interactor)) creditCardEditorView = spyk(CreditCardEditorView(fragmentCreditCardEditorBinding, interactor))
} }
@Test @Test
@ -78,37 +80,37 @@ class CreditCardEditorViewTest {
val startYear = calendar.get(Calendar.YEAR) val startYear = calendar.get(Calendar.YEAR)
val endYear = startYear + NUMBER_OF_YEARS_TO_SHOW - 1 val endYear = startYear + NUMBER_OF_YEARS_TO_SHOW - 1
assertEquals("", view.card_number_input.text.toString()) assertEquals("", fragmentCreditCardEditorBinding.cardNumberInput.text.toString())
assertEquals("", view.name_on_card_input.text.toString()) assertEquals("", fragmentCreditCardEditorBinding.nameOnCardInput.text.toString())
with(view.expiry_month_drop_down) { with(fragmentCreditCardEditorBinding.expiryMonthDropDown) {
assertEquals(12, count) assertEquals(12, count)
assertEquals("January (01)", selectedItem.toString()) assertEquals("January (01)", selectedItem.toString())
assertEquals("December (12)", getItemAtPosition(count - 1).toString()) assertEquals("December (12)", getItemAtPosition(count - 1).toString())
} }
with(view.expiry_year_drop_down) { with(fragmentCreditCardEditorBinding.expiryYearDropDown) {
assertEquals(10, count) assertEquals(10, count)
assertEquals(startYear.toString(), selectedItem.toString()) assertEquals(startYear.toString(), selectedItem.toString())
assertEquals(endYear.toString(), getItemAtPosition(count - 1).toString()) assertEquals(endYear.toString(), getItemAtPosition(count - 1).toString())
} }
assertEquals(View.GONE, view.delete_button.visibility) assertEquals(View.GONE, fragmentCreditCardEditorBinding.deleteButton.visibility)
} }
@Test @Test
fun `GIVEN a credit card THEN credit card form inputs are displaying the provided credit card information`() { fun `GIVEN a credit card THEN credit card form inputs are displaying the provided credit card information`() {
creditCardEditorView.bind(creditCard.toCreditCardEditorState(storage)) creditCardEditorView.bind(creditCard.toCreditCardEditorState(storage))
assertEquals(cardNumber, view.card_number_input.text.toString()) assertEquals(cardNumber, fragmentCreditCardEditorBinding.cardNumberInput.text.toString())
assertEquals(creditCard.billingName, view.name_on_card_input.text.toString()) assertEquals(creditCard.billingName, fragmentCreditCardEditorBinding.nameOnCardInput.text.toString())
with(view.expiry_month_drop_down) { with(fragmentCreditCardEditorBinding.expiryMonthDropDown) {
assertEquals(12, count) assertEquals(12, count)
assertEquals("May (05)", selectedItem.toString()) assertEquals("May (05)", selectedItem.toString())
} }
with(view.expiry_year_drop_down) { with(fragmentCreditCardEditorBinding.expiryYearDropDown) {
val endYear = creditCard.expiryYear + NUMBER_OF_YEARS_TO_SHOW - 1 val endYear = creditCard.expiryYear + NUMBER_OF_YEARS_TO_SHOW - 1
assertEquals(10, count) assertEquals(10, count)
@ -121,9 +123,9 @@ class CreditCardEditorViewTest {
fun `GIVEN a credit card WHEN the delete card button is clicked THEN interactor is called`() { fun `GIVEN a credit card WHEN the delete card button is clicked THEN interactor is called`() {
creditCardEditorView.bind(creditCard.toCreditCardEditorState(storage)) creditCardEditorView.bind(creditCard.toCreditCardEditorState(storage))
assertEquals(View.VISIBLE, view.delete_button.visibility) assertEquals(View.VISIBLE, fragmentCreditCardEditorBinding.deleteButton.visibility)
view.delete_button.performClick() fragmentCreditCardEditorBinding.deleteButton.performClick()
verify { interactor.onDeleteCardButtonClicked(creditCard.guid) } verify { interactor.onDeleteCardButtonClicked(creditCard.guid) }
} }
@ -132,7 +134,7 @@ class CreditCardEditorViewTest {
fun `WHEN the cancel button is clicked THEN interactor is called`() { fun `WHEN the cancel button is clicked THEN interactor is called`() {
creditCardEditorView.bind(getInitialCreditCardEditorState()) creditCardEditorView.bind(getInitialCreditCardEditorState())
view.cancel_button.performClick() fragmentCreditCardEditorBinding.cancelButton.performClick()
verify { interactor.onCancelButtonClicked() } verify { interactor.onCancelButtonClicked() }
} }
@ -148,11 +150,11 @@ class CreditCardEditorViewTest {
val expiryMonth = 5 val expiryMonth = 5
val expiryYear = calendar.get(Calendar.YEAR) val expiryYear = calendar.get(Calendar.YEAR)
view.card_number_input.text = cardNumber.toEditable() fragmentCreditCardEditorBinding.cardNumberInput.text = cardNumber.toEditable()
view.name_on_card_input.text = billingName.toEditable() fragmentCreditCardEditorBinding.nameOnCardInput.text = billingName.toEditable()
view.expiry_month_drop_down.setSelection(expiryMonth - 1) fragmentCreditCardEditorBinding.expiryMonthDropDown.setSelection(expiryMonth - 1)
view.save_button.performClick() fragmentCreditCardEditorBinding.saveButton.performClick()
verify { verify {
creditCardEditorView.validateForm() creditCardEditorView.validateForm()
@ -174,9 +176,9 @@ class CreditCardEditorViewTest {
} }
billingName = "" billingName = ""
view.name_on_card_input.text = billingName.toEditable() fragmentCreditCardEditorBinding.nameOnCardInput.text = billingName.toEditable()
view.save_button.performClick() fragmentCreditCardEditorBinding.saveButton.performClick()
assertFalse(creditCardEditorView.validateForm()) assertFalse(creditCardEditorView.validateForm())
@ -205,11 +207,11 @@ class CreditCardEditorViewTest {
val expiryMonth = 5 val expiryMonth = 5
val expiryYear = calendar.get(Calendar.YEAR) val expiryYear = calendar.get(Calendar.YEAR)
view.card_number_input.text = cardNumber.toEditable() fragmentCreditCardEditorBinding.cardNumberInput.text = cardNumber.toEditable()
view.name_on_card_input.text = billingName.toEditable() fragmentCreditCardEditorBinding.nameOnCardInput.text = billingName.toEditable()
view.expiry_month_drop_down.setSelection(expiryMonth - 1) fragmentCreditCardEditorBinding.expiryMonthDropDown.setSelection(expiryMonth - 1)
view.save_button.performClick() fragmentCreditCardEditorBinding.saveButton.performClick()
verify { verify {
creditCardEditorView.validateForm() creditCardEditorView.validateForm()
@ -242,11 +244,11 @@ class CreditCardEditorViewTest {
val expiryMonth = 5 val expiryMonth = 5
val expiryYear = calendar.get(Calendar.YEAR) val expiryYear = calendar.get(Calendar.YEAR)
view.card_number_input.text = cardNumber.toEditable() fragmentCreditCardEditorBinding.cardNumberInput.text = cardNumber.toEditable()
view.name_on_card_input.text = billingName.toEditable() fragmentCreditCardEditorBinding.nameOnCardInput.text = billingName.toEditable()
view.expiry_month_drop_down.setSelection(expiryMonth - 1) fragmentCreditCardEditorBinding.expiryMonthDropDown.setSelection(expiryMonth - 1)
view.save_button.performClick() fragmentCreditCardEditorBinding.saveButton.performClick()
verify { verify {
creditCardEditorView.validateForm() creditCardEditorView.validateForm()
@ -272,7 +274,7 @@ class CreditCardEditorViewTest {
fun `GIVEN a valid credit card WHEN the save button is clicked THEN interactor is called`() { fun `GIVEN a valid credit card WHEN the save button is clicked THEN interactor is called`() {
creditCardEditorView.bind(creditCard.toCreditCardEditorState(storage)) creditCardEditorView.bind(creditCard.toCreditCardEditorState(storage))
view.save_button.performClick() fragmentCreditCardEditorBinding.saveButton.performClick()
verify { verify {
interactor.onUpdateCreditCard( interactor.onUpdateCreditCard(

@ -8,7 +8,6 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import io.mockk.mockk import io.mockk.mockk
import io.mockk.verify import io.mockk.verify
import kotlinx.android.synthetic.main.credit_card_list_item.view.*
import mozilla.components.concept.storage.CreditCard import mozilla.components.concept.storage.CreditCard
import mozilla.components.concept.storage.CreditCardNumber import mozilla.components.concept.storage.CreditCardNumber
import mozilla.components.support.test.robolectric.testContext import mozilla.components.support.test.robolectric.testContext
@ -17,6 +16,7 @@ import org.junit.Assert.assertEquals
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mozilla.fenix.databinding.CreditCardListItemBinding
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.settings.creditcards.interactor.CreditCardsManagementInteractor import org.mozilla.fenix.settings.creditcards.interactor.CreditCardsManagementInteractor
import org.mozilla.fenix.settings.creditcards.view.CreditCardItemViewHolder import org.mozilla.fenix.settings.creditcards.view.CreditCardItemViewHolder
@ -26,6 +26,7 @@ class CreditCardItemViewHolderTest {
private lateinit var view: View private lateinit var view: View
private lateinit var interactor: CreditCardsManagementInteractor private lateinit var interactor: CreditCardsManagementInteractor
private lateinit var binding: CreditCardListItemBinding
private val creditCard = CreditCard( private val creditCard = CreditCard(
guid = "id", guid = "id",
@ -44,6 +45,7 @@ class CreditCardItemViewHolderTest {
@Before @Before
fun setup() { fun setup() {
view = LayoutInflater.from(testContext).inflate(CreditCardItemViewHolder.LAYOUT_ID, null) view = LayoutInflater.from(testContext).inflate(CreditCardItemViewHolder.LAYOUT_ID, null)
binding = CreditCardListItemBinding.bind(view)
interactor = mockk(relaxed = true) interactor = mockk(relaxed = true)
} }
@ -51,8 +53,8 @@ class CreditCardItemViewHolderTest {
fun `GIVEN a new credit card item on bind THEN set the card number and expiry date text`() { fun `GIVEN a new credit card item on bind THEN set the card number and expiry date text`() {
CreditCardItemViewHolder(view, interactor).bind(creditCard) CreditCardItemViewHolder(view, interactor).bind(creditCard)
assertEquals(creditCard.obfuscatedCardNumber, view.credit_card_number.text) assertEquals(creditCard.obfuscatedCardNumber, binding.creditCardNumber.text)
assertEquals("0${creditCard.expiryMonth}/${creditCard.expiryYear}", view.expiry_date.text) assertEquals("0${creditCard.expiryMonth}/${creditCard.expiryYear}", binding.expiryDate.text)
} }
@Test @Test

@ -8,7 +8,6 @@ import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.isVisible import androidx.core.view.isVisible
import io.mockk.mockk import io.mockk.mockk
import kotlinx.android.synthetic.main.component_credit_cards.view.*
import mozilla.components.concept.storage.CreditCard import mozilla.components.concept.storage.CreditCard
import mozilla.components.support.test.robolectric.testContext import mozilla.components.support.test.robolectric.testContext
import org.junit.Assert.assertFalse import org.junit.Assert.assertFalse
@ -17,6 +16,7 @@ import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.ComponentCreditCardsBinding
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.settings.creditcards.interactor.CreditCardsManagementInteractor import org.mozilla.fenix.settings.creditcards.interactor.CreditCardsManagementInteractor
import org.mozilla.fenix.settings.creditcards.view.CreditCardsManagementView import org.mozilla.fenix.settings.creditcards.view.CreditCardsManagementView
@ -27,22 +27,24 @@ class CreditCardsManagementViewTest {
private lateinit var view: ViewGroup private lateinit var view: ViewGroup
private lateinit var interactor: CreditCardsManagementInteractor private lateinit var interactor: CreditCardsManagementInteractor
private lateinit var creditCardsView: CreditCardsManagementView private lateinit var creditCardsView: CreditCardsManagementView
private lateinit var componentCreditCardsBinding: ComponentCreditCardsBinding
@Before @Before
fun setup() { fun setup() {
view = LayoutInflater.from(testContext).inflate(CreditCardsManagementView.LAYOUT_ID, null) view = LayoutInflater.from(testContext).inflate(CreditCardsManagementView.LAYOUT_ID, null)
.findViewById(R.id.credit_cards_wrapper) .findViewById(R.id.credit_cards_wrapper)
componentCreditCardsBinding = ComponentCreditCardsBinding.bind(view)
interactor = mockk(relaxed = true) interactor = mockk(relaxed = true)
creditCardsView = CreditCardsManagementView(view, interactor) creditCardsView = CreditCardsManagementView(componentCreditCardsBinding, interactor)
} }
@Test @Test
fun testUpdate() { fun testUpdate() {
creditCardsView.update(CreditCardsListState(creditCards = emptyList())) creditCardsView.update(CreditCardsListState(creditCards = emptyList()))
assertTrue(view.progress_bar.isVisible) assertTrue(componentCreditCardsBinding.progressBar.isVisible)
assertFalse(view.credit_cards_list.isVisible) assertFalse(componentCreditCardsBinding.creditCardsList.isVisible)
val creditCards: List<CreditCard> = listOf(mockk(), mockk()) val creditCards: List<CreditCard> = listOf(mockk(), mockk())
creditCardsView.update(CreditCardsListState( creditCardsView.update(CreditCardsListState(
@ -50,7 +52,7 @@ class CreditCardsManagementViewTest {
isLoading = false isLoading = false
)) ))
assertFalse(view.progress_bar.isVisible) assertFalse(componentCreditCardsBinding.progressBar.isVisible)
assertTrue(view.credit_cards_list.isVisible) assertTrue(componentCreditCardsBinding.creditCardsList.isVisible)
} }
} }

@ -6,9 +6,6 @@ package org.mozilla.fenix.settings.quicksettings
import android.widget.FrameLayout import android.widget.FrameLayout
import androidx.core.view.isVisible import androidx.core.view.isVisible
import kotlinx.android.synthetic.main.library_site_item.title
import kotlinx.android.synthetic.main.library_site_item.url
import kotlinx.android.synthetic.main.quicksettings_website_info.*
import mozilla.components.support.test.robolectric.testContext import mozilla.components.support.test.robolectric.testContext
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse import org.junit.Assert.assertFalse
@ -16,16 +13,19 @@ import org.junit.Assert.assertTrue
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mozilla.fenix.databinding.QuicksettingsWebsiteInfoBinding
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
@RunWith(FenixRobolectricTestRunner::class) @RunWith(FenixRobolectricTestRunner::class)
class WebsiteInfoViewTest { class WebsiteInfoViewTest {
private lateinit var view: WebsiteInfoView private lateinit var view: WebsiteInfoView
private lateinit var binding: QuicksettingsWebsiteInfoBinding
@Before @Before
fun setup() { fun setup() {
view = WebsiteInfoView(FrameLayout(testContext)) view = WebsiteInfoView(FrameLayout(testContext))
binding = view.binding
} }
@Test @Test
@ -37,10 +37,10 @@ class WebsiteInfoViewTest {
certificateName = "" certificateName = ""
)) ))
assertEquals("https://mozilla.org", view.url.text) assertEquals("https://mozilla.org", binding.url.text)
assertEquals("Mozilla", view.title.text) assertEquals("Mozilla", binding.title.text)
assertEquals("Secure Connection", view.securityInfo.text) assertEquals("Secure Connection", binding.securityInfo.text)
assertFalse(view.certificateInfo.isVisible) assertFalse(binding.certificateInfo.isVisible)
} }
@Test @Test
@ -52,8 +52,8 @@ class WebsiteInfoViewTest {
certificateName = "Certificate" certificateName = "Certificate"
)) ))
assertEquals("Insecure Connection", view.securityInfo.text) assertEquals("Insecure Connection", binding.securityInfo.text)
assertEquals("Verified By: Certificate", view.certificateInfo.text) assertEquals("Verified By: Certificate", binding.certificateInfo.text)
assertTrue(view.certificateInfo.isVisible) assertTrue(binding.certificateInfo.isVisible)
} }
} }

Loading…
Cancel
Save