For #17917: Use View binding in settings screens.

This commit is contained in:
mcarare 2021-07-05 18:18:20 +03:00 committed by mergify[bot]
parent 84d9f272af
commit cca7892e91
34 changed files with 545 additions and 421 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -7,10 +7,10 @@ package org.mozilla.fenix.settings.advanced
import android.view.View
import androidx.annotation.VisibleForTesting
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 org.mozilla.fenix.R
import org.mozilla.fenix.utils.view.ViewHolder
import org.mozilla.fenix.databinding.LocaleSettingsItemBinding
import java.util.Locale
class LocaleViewHolder(
@ -19,6 +19,8 @@ class LocaleViewHolder(
private val interactor: LocaleSettingsViewInteractor
) : BaseLocaleViewHolder(view, selectedLocale) {
private val binding = LocaleSettingsItemBinding.bind(view)
override fun bind(locale: Locale) {
if (locale.toString().equals("vec", ignoreCase = true)) {
locale.toString()
@ -27,11 +29,11 @@ class LocaleViewHolder(
bindChineseLocale(locale)
} else {
// 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.
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 {
interactor.onLocaleSelected(locale)
@ -40,14 +42,14 @@ class LocaleViewHolder(
private fun bindChineseLocale(locale: Locale) {
if (locale.country == "CN") {
locale_title_text.text =
binding.localeTitleText.text =
Locale.forLanguageTag("zh-Hans").getDisplayName(locale).capitalize(locale)
locale_subtitle_text.text =
binding.localeSubtitleText.text =
Locale.forLanguageTag("zh-Hans").displayName.capitalize(Locale.getDefault())
} else if (locale.country == "TW") {
locale_title_text.text =
binding.localeTitleText.text =
Locale.forLanguageTag("zh-Hant").getDisplayName(locale).capitalize(locale)
locale_subtitle_text.text =
binding.localeSubtitleText.text =
Locale.forLanguageTag("zh-Hant").displayName.capitalize(Locale.getDefault())
}
}
@ -271,19 +273,21 @@ class SystemLocaleViewHolder(
private val interactor: LocaleSettingsViewInteractor
) : BaseLocaleViewHolder(view, selectedLocale) {
private val binding = LocaleSettingsItemBinding.bind(view)
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") {
locale_subtitle_text.text =
binding.localeSubtitleText.text =
Locale.forLanguageTag("zh-Hant").displayName.capitalize(Locale.getDefault())
} else if (locale.script == "Hans") {
locale_subtitle_text.text =
binding.localeSubtitleText.text =
Locale.forLanguageTag("zh-Hans").displayName.capitalize(Locale.getDefault())
} else {
// 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 {
interactor.onDefaultLocaleSelected()
}
@ -293,7 +297,7 @@ class SystemLocaleViewHolder(
abstract class BaseLocaleViewHolder(
view: View,
private val selectedLocale: Locale
) : ViewHolder(view) {
) : RecyclerView.ViewHolder(view) {
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
internal fun isCurrentLocaleSelected(locale: Locale, isDefault: Boolean): Boolean {

View File

@ -14,6 +14,7 @@ import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import org.mozilla.fenix.R
import org.mozilla.fenix.SecureFragment
import org.mozilla.fenix.databinding.FragmentCreditCardEditorBinding
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.redirectToReAuth
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 =
args.creditCard?.toCreditCardEditorState(storage) ?: getInitialCreditCardEditorState()
creditCardEditorView = CreditCardEditorView(view, interactor)
creditCardEditorView = CreditCardEditorView(binding, interactor)
creditCardEditorView.bind(creditCardEditorState)
}

View File

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

View File

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

View File

@ -5,12 +5,10 @@
package org.mozilla.fenix.settings.creditcards.view
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.core.view.isVisible
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.databinding.ComponentCreditCardsBinding
import org.mozilla.fenix.settings.creditcards.CreditCardsListState
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.
*/
class CreditCardsManagementView(
override val containerView: ViewGroup,
val binding: ComponentCreditCardsBinding,
val interactor: CreditCardsManagementInteractor
) : LayoutContainer {
) {
private val creditCardsAdapter = CreditCardsAdapter(interactor)
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
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].
*/
fun update(state: CreditCardsListState) {
progress_bar.isVisible = state.isLoading
credit_cards_list.isVisible = state.creditCards.isNotEmpty()
binding.progressBar.isVisible = state.isLoading
binding.creditCardsList.isVisible = state.creditCards.isNotEmpty()
creditCardsAdapter.submitList(state.creditCards)
}

View File

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

View File

@ -11,10 +11,8 @@ import android.view.View
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
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.databinding.DeleteBrowsingDataItemBinding
class DeleteBrowsingDataItem @JvmOverloads constructor(
context: Context,
@ -27,26 +25,33 @@ class DeleteBrowsingDataItem @JvmOverloads constructor(
private const val DISABLED_ALPHA = 0.6f
}
private var binding: DeleteBrowsingDataItemBinding
val titleView: TextView
get() = title
get() = binding.title
val subtitleView: TextView
get() = subtitle
get() = binding.subtitle
var isChecked: Boolean
get() = checkbox.isChecked
set(value) { checkbox.isChecked = value }
get() = binding.checkbox.isChecked
set(value) {
binding.checkbox.isChecked = value
}
var onCheckListener: ((Boolean) -> Unit)? = null
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 {
checkbox.isChecked = !checkbox.isChecked
binding.checkbox.isChecked = !binding.checkbox.isChecked
}
checkbox.setOnCheckedChangeListener { _, isChecked ->
binding.checkbox.setOnCheckedChangeListener { _, isChecked ->
onCheckListener?.invoke(isChecked)
}
@ -60,10 +65,10 @@ class DeleteBrowsingDataItem @JvmOverloads constructor(
R.string.empty_string
)
title.text = resources.getString(titleId)
binding.title.text = resources.getString(titleId)
val subtitleText = resources.getString(subtitleId)
subtitle.text = subtitleText
if (subtitleText.isBlank()) subtitle.visibility = View.GONE
binding.subtitle.text = subtitleText
if (subtitleText.isBlank()) binding.subtitle.visibility = View.GONE
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -5,14 +5,12 @@
package org.mozilla.fenix.settings.quicksettings
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat.getColor
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 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.
@ -23,10 +21,12 @@ import org.mozilla.fenix.R
*/
class WebsiteInfoView(
container: ViewGroup
) : LayoutContainer {
override val containerView: View = LayoutInflater.from(container.context)
.inflate(R.layout.quicksettings_website_info, container, true)
) {
val binding = QuicksettingsWebsiteInfoBinding.inflate(
LayoutInflater.from(container.context),
container,
false
)
/**
* Allows changing what this View displays.
@ -41,24 +41,25 @@ class WebsiteInfoView(
}
private fun bindUrl(websiteUrl: String) {
url.text = websiteUrl
binding.url.text = websiteUrl
}
private fun bindTitle(websiteTitle: String) {
title.text = websiteTitle
binding.title.text = websiteTitle
}
private fun bindCertificateName(cert: String) {
val certificateLabel = containerView.context.getString(R.string.certificate_info_verified_by, cert)
certificateInfo.text = certificateLabel
certificateInfo.isVisible = cert.isNotEmpty()
val certificateLabel =
binding.root.context.getString(R.string.certificate_info_verified_by, cert)
binding.certificateInfo.text = certificateLabel
binding.certificateInfo.isVisible = cert.isNotEmpty()
}
private fun bindSecurityInfo(uiValues: WebsiteSecurityUiValues) {
val tint = getColor(containerView.context, uiValues.iconTintRes)
securityInfo.setText(uiValues.securityInfoRes)
securityInfoIcon.setImageDrawable(
containerView.context.getDrawableWithTint(uiValues.iconRes, tint)
val tint = getColor(binding.root.context, uiValues.iconTintRes)
binding.securityInfo.setText(uiValues.securityInfoRes)
binding.securityInfoIcon.setImageDrawable(
binding.root.context.getDrawableWithTint(uiValues.iconRes, tint)
)
}
}

View File

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

View File

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

View File

@ -13,11 +13,6 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
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.Main
import kotlinx.coroutines.launch
@ -28,6 +23,8 @@ import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
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.showToolbar
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 lateinit var searchEngine: SearchEngine
private var _binding: FragmentAddSearchEngineBinding? = null
private val binding get() = _binding!!
private lateinit var customSearchEngine: CustomSearchEngineBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
@ -56,10 +57,13 @@ class EditCustomSearchEngineFragment : Fragment(R.layout.fragment_add_search_eng
val url = searchEngine.resultUrls[0]
edit_engine_name.setText(searchEngine.name)
edit_search_string.setText(url.toEditableUrl())
_binding = FragmentAddSearchEngineBinding.bind(view)
customSearchEngine = binding.customSearchEngine
custom_search_engines_learn_more.setOnClickListener {
customSearchEngine.editEngineName.setText(searchEngine.name)
customSearchEngine.editSearchString.setText(url.toEditableUrl())
customSearchEngine.customSearchEnginesLearnMore.setOnClickListener {
(activity as HomeActivity).openToBrowserAndLoad(
searchTermOrURL = SupportUtils.getSumoURLForTopic(
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))
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.edit_custom_searchengine_menu, menu)
}
@ -92,11 +101,11 @@ class EditCustomSearchEngineFragment : Fragment(R.layout.fragment_add_search_eng
@Suppress("LongMethod")
private fun saveCustomEngine() {
custom_search_engine_name_field.error = ""
custom_search_engine_search_string_field.error = ""
customSearchEngine.customSearchEngineNameField.error = ""
customSearchEngine.customSearchEngineSearchStringField.error = ""
val name = edit_engine_name.text?.toString()?.trim() ?: ""
val searchString = edit_search_string.text?.toString() ?: ""
val name = customSearchEngine.editEngineName.text?.toString()?.trim() ?: ""
val searchString = customSearchEngine.editSearchString.text?.toString() ?: ""
if (checkForErrors(name, searchString)) {
return
@ -112,7 +121,7 @@ class EditCustomSearchEngineFragment : Fragment(R.layout.fragment_add_search_eng
when (result) {
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)
}
@ -120,7 +129,8 @@ class EditCustomSearchEngineFragment : Fragment(R.layout.fragment_add_search_eng
val update = searchEngine.copy(
name = name,
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)
@ -147,17 +157,17 @@ class EditCustomSearchEngineFragment : Fragment(R.layout.fragment_add_search_eng
private fun checkForErrors(name: String, searchString: String): Boolean {
return when {
name.isEmpty() -> {
custom_search_engine_name_field.error = resources
customSearchEngine.customSearchEngineNameField.error = resources
.getString(R.string.search_add_custom_engine_error_empty_name)
true
}
searchString.isEmpty() -> {
custom_search_engine_search_string_field.error =
customSearchEngine.customSearchEngineSearchStringField.error =
resources.getString(R.string.search_add_custom_engine_error_empty_search_string)
true
}
!searchString.contains("%s") -> {
custom_search_engine_search_string_field.error =
customSearchEngine.customSearchEngineSearchStringField.error =
resources.getString(R.string.search_add_custom_engine_error_missing_template)
true
}

View File

@ -18,10 +18,6 @@ import androidx.core.view.isVisible
import androidx.navigation.Navigation
import androidx.preference.Preference
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.MainScope
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.kotlinx.coroutines.flow.ifChanged
import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.SearchEngineRadioButtonBinding
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.getRootView
import org.mozilla.fenix.utils.allowUndo
@ -102,13 +99,16 @@ class RadioSearchEngineListPreference @JvmOverloads constructor(
val isCustomSearchEngine = engine.type == SearchEngine.Type.CUSTOM
val wrapper = layoutInflater.inflate(itemResId, null) as LinearLayout
wrapper.setOnClickListener { wrapper.radio_button.isChecked = true }
wrapper.radio_button.tag = engine.id
wrapper.radio_button.isChecked = isSelected
wrapper.radio_button.setOnCheckedChangeListener(this)
wrapper.engine_text.text = engine.name
wrapper.overflow_menu.isVisible = allowDeletion || isCustomSearchEngine
wrapper.overflow_menu.setOnClickListener {
val binding = SearchEngineRadioButtonBinding.bind(wrapper)
wrapper.setOnClickListener { binding.radioButton.isChecked = true }
binding.radioButton.tag = engine.id
binding.radioButton.isChecked = isSelected
binding.radioButton.setOnCheckedChangeListener(this)
binding.engineText.text = engine.name
binding.overflowMenu.isVisible = allowDeletion || isCustomSearchEngine
binding.overflowMenu.setOnClickListener {
SearchEngineMenu(
context = context,
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 engineIcon = BitmapDrawable(res, engine.icon)
engineIcon.setBounds(0, 0, iconSize, iconSize)
wrapper.engine_icon.setImageDrawable(engineIcon)
binding.engineIcon.setImageDrawable(engineIcon)
return wrapper
}

View File

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

View File

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

View File

@ -3,6 +3,7 @@
- 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/. -->
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/add_credit_card_layout"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"

View File

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

View File

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

View File

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

View File

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

View File

@ -8,7 +8,6 @@ import android.view.LayoutInflater
import android.view.View
import io.mockk.mockk
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.CreditCardNumber
import mozilla.components.support.test.robolectric.testContext
@ -17,6 +16,7 @@ import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.databinding.CreditCardListItemBinding
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.settings.creditcards.interactor.CreditCardsManagementInteractor
import org.mozilla.fenix.settings.creditcards.view.CreditCardItemViewHolder
@ -26,6 +26,7 @@ class CreditCardItemViewHolderTest {
private lateinit var view: View
private lateinit var interactor: CreditCardsManagementInteractor
private lateinit var binding: CreditCardListItemBinding
private val creditCard = CreditCard(
guid = "id",
@ -44,6 +45,7 @@ class CreditCardItemViewHolderTest {
@Before
fun setup() {
view = LayoutInflater.from(testContext).inflate(CreditCardItemViewHolder.LAYOUT_ID, null)
binding = CreditCardListItemBinding.bind(view)
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`() {
CreditCardItemViewHolder(view, interactor).bind(creditCard)
assertEquals(creditCard.obfuscatedCardNumber, view.credit_card_number.text)
assertEquals("0${creditCard.expiryMonth}/${creditCard.expiryYear}", view.expiry_date.text)
assertEquals(creditCard.obfuscatedCardNumber, binding.creditCardNumber.text)
assertEquals("0${creditCard.expiryMonth}/${creditCard.expiryYear}", binding.expiryDate.text)
}
@Test

View File

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

View File

@ -6,9 +6,6 @@ package org.mozilla.fenix.settings.quicksettings
import android.widget.FrameLayout
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 org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
@ -16,16 +13,19 @@ import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.databinding.QuicksettingsWebsiteInfoBinding
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
@RunWith(FenixRobolectricTestRunner::class)
class WebsiteInfoViewTest {
private lateinit var view: WebsiteInfoView
private lateinit var binding: QuicksettingsWebsiteInfoBinding
@Before
fun setup() {
view = WebsiteInfoView(FrameLayout(testContext))
binding = view.binding
}
@Test
@ -37,10 +37,10 @@ class WebsiteInfoViewTest {
certificateName = ""
))
assertEquals("https://mozilla.org", view.url.text)
assertEquals("Mozilla", view.title.text)
assertEquals("Secure Connection", view.securityInfo.text)
assertFalse(view.certificateInfo.isVisible)
assertEquals("https://mozilla.org", binding.url.text)
assertEquals("Mozilla", binding.title.text)
assertEquals("Secure Connection", binding.securityInfo.text)
assertFalse(binding.certificateInfo.isVisible)
}
@Test
@ -52,8 +52,8 @@ class WebsiteInfoViewTest {
certificateName = "Certificate"
))
assertEquals("Insecure Connection", view.securityInfo.text)
assertEquals("Verified By: Certificate", view.certificateInfo.text)
assertTrue(view.certificateInfo.isVisible)
assertEquals("Insecure Connection", binding.securityInfo.text)
assertEquals("Verified By: Certificate", binding.certificateInfo.text)
assertTrue(binding.certificateInfo.isVisible)
}
}