Skip to content
This repository has been archived by the owner on Feb 20, 2023. It is now read-only.

Commit

Permalink
For #18268 - [Saved cards] Add a Saved cards screen for credit cards
Browse files Browse the repository at this point in the history
  • Loading branch information
gabrielluong committed Apr 6, 2021
1 parent 01568d5 commit 3010c59
Show file tree
Hide file tree
Showing 23 changed files with 818 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import android.widget.ArrayAdapter
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_credit_card_editor.*
import mozilla.components.concept.storage.UpdatableCreditCardFields
import mozilla.components.support.ktx.android.view.hideKeyboard
Expand All @@ -31,11 +32,16 @@ import java.util.Locale
class CreditCardEditorFragment : Fragment(R.layout.fragment_credit_card_editor) {

private lateinit var controller: CreditCardEditorController
private val args by navArgs<CreditCardEditorFragmentArgs>()

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

showToolbar(getString(R.string.credit_cards_add_card))
if (args.creditCard == null) {
showToolbar(getString(R.string.credit_cards_add_card))
} else {
showToolbar(getString(R.string.credit_cards_edit_card))
}

setHasOptionsMenu(true)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.fenix.settings.creditcards

import mozilla.components.concept.storage.CreditCard
import mozilla.components.lib.state.Action
import mozilla.components.lib.state.State
import mozilla.components.lib.state.Store

/**
* The [Store] for holding the [CreditCardsListState] and applying [CreditCardsAction]s.
*/
class CreditCardsFragmentStore(initialState: CreditCardsListState) :
Store<CreditCardsListState, CreditCardsAction>(
initialState, ::creditCardsFragmentStateReducer
)

/**
* The state for [CreditCardsManagementFragment].
*
* @property creditCards The list of [CreditCard]s to display in the credit card list.
* @property isLoading True if the credit cards are still being loaded from storage,
* otherwise false.
*/
data class CreditCardsListState(
val creditCards: List<CreditCard>,
val isLoading: Boolean = true
) : State

/**
* Actions to dispatch through the [CreditCardsFragmentStore] to modify the [CreditCardsListState]
* through the [creditCardsFragmentStateReducer].
*/
sealed class CreditCardsAction : Action {
/**
* Updates the list of credit cards with the provided [creditCards].
*
* @param creditCards The list of [CreditCard]s to display in the credit card list.
*/
data class UpdateCreditCards(val creditCards: List<CreditCard>) : CreditCardsAction()
}

/**
* Reduces the credit cards state from the current state with the provided [action] to be performed.
*
* @param state The current credit cards state.
* @param action The action to be performed on the state.
* @return the new [CreditCardsListState] with the [action] executed.
*/
private fun creditCardsFragmentStateReducer(
state: CreditCardsListState,
action: CreditCardsAction
): CreditCardsListState {
return when (action) {
is CreditCardsAction.UpdateCreditCards -> {
state.copy(
creditCards = action.creditCards,
isLoading = false
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.fenix.settings.creditcards

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
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
import mozilla.components.lib.state.ext.consumeFrom
import org.mozilla.fenix.R
import org.mozilla.fenix.components.StoreProvider
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.showToolbar
import org.mozilla.fenix.settings.creditcards.controller.DefaultCreditCardsManagementController
import org.mozilla.fenix.settings.creditcards.interactor.CreditCardsManagementInteractor
import org.mozilla.fenix.settings.creditcards.interactor.DefaultCreditCardsManagementInteractor
import org.mozilla.fenix.settings.creditcards.view.CreditCardsManagementView

/**
* Displays a list of saved credit cards.
*/
class CreditCardsManagementFragment : Fragment() {

private lateinit var creditCardsStore: CreditCardsFragmentStore
private lateinit var interactor: CreditCardsManagementInteractor
private lateinit var creditCardsView: CreditCardsManagementView

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_saved_cards, container, false)

creditCardsStore = StoreProvider.get(this) {
CreditCardsFragmentStore(CreditCardsListState(creditCards = emptyList()))
}

interactor = DefaultCreditCardsManagementInteractor(
controller = DefaultCreditCardsManagementController(
navController = findNavController()
)
)

creditCardsView = CreditCardsManagementView(view.saved_cards_layout, interactor)

loadCreditCards()

return view
}

@ExperimentalCoroutinesApi
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
consumeFrom(creditCardsStore) { state ->
creditCardsView.update(state)
}
}

override fun onResume() {
super.onResume()
showToolbar(getString(R.string.credit_cards_saved_cards))
}

/**
* Fetches all the credit cards from the autofill storage and updates the
* [CreditCardsFragmentStore] with the list of credit cards.
*/
private fun loadCreditCards() {
lifecycleScope.launch(Dispatchers.IO) {
val creditCards = requireContext().components.core.autofillStorage.getAllCreditCards()

lifecycleScope.launch(Dispatchers.Main) {
creditCardsStore.dispatch(CreditCardsAction.UpdateCreditCards(creditCards))
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,19 @@ class CreditCardsSettingFragment : PreferenceFragmentCompat() {
)
}

@Suppress("MaxLineLength")
override fun onPreferenceTreeClick(preference: Preference): Boolean {
when (preference.key) {
getPreferenceKey(R.string.pref_key_credit_cards_add_credit_card) -> {
val directions =
CreditCardsSettingFragmentDirections.actionCreditCardsSettingFragmentToCreditCardEditorFragment()
findNavController().navigate(directions)
}
getPreferenceKey(R.string.pref_key_credit_cards_manage_saved_cards) -> {
val directions =
CreditCardsSettingFragmentDirections.actionCreditCardsSettingFragmentToCreditCardsManagementFragment()
findNavController().navigate(directions)
}
}

return super.onPreferenceTreeClick(preference)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.fenix.settings.creditcards.controller

import androidx.navigation.NavController
import mozilla.components.concept.storage.CreditCard
import org.mozilla.fenix.settings.creditcards.CreditCardsManagementFragmentDirections

/**
* [CreditCardsManagementFragment] controller. An interface that handles the view manipulation of
* the credit cards manager triggered by the Interactor.
*/
interface CreditCardsManagementController {

/**
* @see [CreditCardsManagementInteractor.onSelectCreditCard]
*/
fun handleCreditCardClicked(creditCard: CreditCard)
}

/**
* The default implementation of [CreditCardsManagementController].
*/
class DefaultCreditCardsManagementController(
private val navController: NavController
) : CreditCardsManagementController {

override fun handleCreditCardClicked(creditCard: CreditCard) {
navController.navigate(
CreditCardsManagementFragmentDirections.actionCreditCardsManagementFragmentToCreditCardEditorFragment(
creditCard = creditCard
)
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.fenix.settings.creditcards.interactor

import mozilla.components.concept.storage.CreditCard
import org.mozilla.fenix.settings.creditcards.controller.CreditCardsManagementController

/**
* Interface for the credit cards management Interactor.
*/
interface CreditCardsManagementInteractor {

/**
* Navigates to the credit card editor to edit the selected credit card. Called when a user
* taps on a credit card item.
*/
fun onSelectCreditCard(creditCard: CreditCard)
}

/**
* The default implementation of [CreditCardEditorInteractor]
*/
class DefaultCreditCardsManagementInteractor(
private val controller: CreditCardsManagementController
) : CreditCardsManagementInteractor {

override fun onSelectCreditCard(creditCard: CreditCard) {
controller.handleCreditCardClicked(creditCard)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.fenix.settings.creditcards.view

import android.view.View
import kotlinx.android.synthetic.main.credit_card_list_item.*
import mozilla.components.concept.storage.CreditCard
import org.mozilla.fenix.R
import org.mozilla.fenix.settings.creditcards.interactor.CreditCardsManagementInteractor
import org.mozilla.fenix.utils.view.ViewHolder
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale

/**
* View holder for a credit card list item.
*/
class CreditCardItemViewHolder(
view: View,
private val interactor: CreditCardsManagementInteractor
) : ViewHolder(view) {

fun bind(creditCard: CreditCard) {
credit_card_number.text = creditCard.cardNumber

bindCreditCardExpiryDate(creditCard)

itemView.setOnClickListener {
interactor.onSelectCreditCard(creditCard)
}
}

/**
* Set the credit card expiry date formatted according to the locale.
*/
private fun bindCreditCardExpiryDate(creditCard: CreditCard) {
val dateFormat = SimpleDateFormat(DATE_PATTERN, Locale.getDefault())

val calendar = Calendar.getInstance()
calendar.set(Calendar.DAY_OF_MONTH, 1)
// Calendar.Month is based on a 0-indexed.
calendar.set(Calendar.MONTH, creditCard.expiryMonth.toInt() - 1)
calendar.set(Calendar.YEAR, creditCard.expiryYear.toInt())

expiry_date.text = dateFormat.format(calendar.time)
}

companion object {
const val LAYOUT_ID = R.layout.credit_card_list_item

// Date format pattern for the credit card expiry date.
private const val DATE_PATTERN = "MM/yyyy"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.fenix.settings.creditcards.view

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import mozilla.components.concept.storage.CreditCard
import org.mozilla.fenix.settings.creditcards.interactor.CreditCardsManagementInteractor

/**
* Adapter for a list of credit cards to be displayed.
*/
class CreditCardsAdapter(
private val interactor: CreditCardsManagementInteractor
) : ListAdapter<CreditCard, CreditCardItemViewHolder>(DiffCallback) {

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CreditCardItemViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(CreditCardItemViewHolder.LAYOUT_ID, parent, false)
return CreditCardItemViewHolder(view, interactor)
}

override fun onBindViewHolder(holder: CreditCardItemViewHolder, position: Int) {
holder.bind(getItem(position))
}

internal object DiffCallback : DiffUtil.ItemCallback<CreditCard>() {
override fun areItemsTheSame(oldItem: CreditCard, newItem: CreditCard) =
oldItem.guid == newItem.guid

override fun areContentsTheSame(oldItem: CreditCard, newItem: CreditCard) =
oldItem == newItem
}
}
Loading

0 comments on commit 3010c59

Please sign in to comment.