For #4125: Migrate Sign in to Sync to Libstate
parent
65de521ccf
commit
faf0ecbcc0
|
@ -2,26 +2,23 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
package org.mozilla.fenix.settings
|
package org.mozilla.fenix.settings.account
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.InputFilter
|
import android.text.InputFilter
|
||||||
import android.text.format.DateUtils
|
import android.text.format.DateUtils
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import androidx.preference.CheckBoxPreference
|
|
||||||
import androidx.preference.EditTextPreference
|
import androidx.preference.EditTextPreference
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import androidx.preference.PreferenceCategory
|
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
import androidx.preference.forEach
|
|
||||||
import kotlinx.coroutines.Dispatchers.IO
|
import kotlinx.coroutines.Dispatchers.IO
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import mozilla.components.concept.sync.AccountObserver
|
import mozilla.components.concept.sync.AccountObserver
|
||||||
import mozilla.components.concept.sync.ConstellationState
|
import mozilla.components.concept.sync.ConstellationState
|
||||||
import mozilla.components.concept.sync.DeviceConstellationObserver
|
import mozilla.components.concept.sync.DeviceConstellationObserver
|
||||||
|
import mozilla.components.lib.state.ext.observe
|
||||||
import mozilla.components.service.fxa.FxaException
|
import mozilla.components.service.fxa.FxaException
|
||||||
import mozilla.components.service.fxa.FxaPanicException
|
import mozilla.components.service.fxa.FxaPanicException
|
||||||
import mozilla.components.service.fxa.manager.FxaAccountManager
|
import mozilla.components.service.fxa.manager.FxaAccountManager
|
||||||
|
@ -30,13 +27,16 @@ import mozilla.components.service.fxa.sync.getLastSynced
|
||||||
import mozilla.components.support.base.log.logger.Logger
|
import mozilla.components.support.base.log.logger.Logger
|
||||||
import org.mozilla.fenix.R
|
import org.mozilla.fenix.R
|
||||||
import org.mozilla.fenix.components.FenixSnackbar
|
import org.mozilla.fenix.components.FenixSnackbar
|
||||||
|
import org.mozilla.fenix.components.StoreProvider
|
||||||
import org.mozilla.fenix.components.metrics.Event
|
import org.mozilla.fenix.components.metrics.Event
|
||||||
import org.mozilla.fenix.ext.getPreferenceKey
|
import org.mozilla.fenix.ext.getPreferenceKey
|
||||||
import org.mozilla.fenix.ext.nav
|
|
||||||
import org.mozilla.fenix.ext.requireComponents
|
import org.mozilla.fenix.ext.requireComponents
|
||||||
|
|
||||||
|
@SuppressWarnings("TooManyFunctions")
|
||||||
class AccountSettingsFragment : PreferenceFragmentCompat() {
|
class AccountSettingsFragment : PreferenceFragmentCompat() {
|
||||||
private lateinit var accountManager: FxaAccountManager
|
private lateinit var accountManager: FxaAccountManager
|
||||||
|
private lateinit var accountSettingsStore: AccountSettingsStore
|
||||||
|
private lateinit var accountSettingsInteractor: AccountSettingsInteractor
|
||||||
|
|
||||||
// Navigate away from this fragment when we encounter auth problems or logout events.
|
// Navigate away from this fragment when we encounter auth problems or logout events.
|
||||||
private val accountStateObserver = object : AccountObserver {
|
private val accountStateObserver = object : AccountObserver {
|
||||||
|
@ -78,9 +78,37 @@ class AccountSettingsFragment : PreferenceFragmentCompat() {
|
||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
setPreferencesFromResource(R.xml.account_settings_preferences, rootKey)
|
setPreferencesFromResource(R.xml.account_settings_preferences, rootKey)
|
||||||
|
|
||||||
|
accountSettingsStore = StoreProvider.get(this) {
|
||||||
|
AccountSettingsStore(
|
||||||
|
AccountSettingsState(
|
||||||
|
lastSyncedDate =
|
||||||
|
if (getLastSynced(requireContext()) == 0L)
|
||||||
|
LastSyncTime.Never
|
||||||
|
else
|
||||||
|
LastSyncTime.Success(getLastSynced(requireContext())),
|
||||||
|
deviceName = ""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
accountSettingsStore.observe(this) {
|
||||||
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
|
updateLastSyncTimePref(it)
|
||||||
|
updateDeviceName(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
accountManager = requireComponents.backgroundServices.accountManager
|
accountManager = requireComponents.backgroundServices.accountManager
|
||||||
accountManager.register(accountStateObserver, this, true)
|
accountManager.register(accountStateObserver, this, true)
|
||||||
|
|
||||||
|
accountSettingsInteractor = AccountSettingsInteractor(
|
||||||
|
findNavController(),
|
||||||
|
::onSyncNow,
|
||||||
|
::makeSnackbar,
|
||||||
|
::syncDeviceName,
|
||||||
|
accountSettingsStore
|
||||||
|
)
|
||||||
|
|
||||||
// Sign out
|
// Sign out
|
||||||
val signOut = context!!.getPreferenceKey(R.string.pref_key_sign_out)
|
val signOut = context!!.getPreferenceKey(R.string.pref_key_sign_out)
|
||||||
val preferenceSignOut = findPreference<Preference>(signOut)
|
val preferenceSignOut = findPreference<Preference>(signOut)
|
||||||
|
@ -91,7 +119,6 @@ class AccountSettingsFragment : PreferenceFragmentCompat() {
|
||||||
val preferenceSyncNow = findPreference<Preference>(syncNow)
|
val preferenceSyncNow = findPreference<Preference>(syncNow)
|
||||||
preferenceSyncNow?.let {
|
preferenceSyncNow?.let {
|
||||||
it.onPreferenceClickListener = getClickListenerForSyncNow()
|
it.onPreferenceClickListener = getClickListenerForSyncNow()
|
||||||
updateLastSyncedTimePref(context!!, it)
|
|
||||||
|
|
||||||
// Current sync state
|
// Current sync state
|
||||||
if (requireComponents.backgroundServices.accountManager.isSyncActive()) {
|
if (requireComponents.backgroundServices.accountManager.isSyncActive()) {
|
||||||
|
@ -110,6 +137,7 @@ class AccountSettingsFragment : PreferenceFragmentCompat() {
|
||||||
deviceConstellation?.state()?.currentDevice?.let { device ->
|
deviceConstellation?.state()?.currentDevice?.let { device ->
|
||||||
summary = device.displayName
|
summary = device.displayName
|
||||||
text = device.displayName
|
text = device.displayName
|
||||||
|
accountSettingsStore.dispatch(AccountSettingsAction.UpdateDeviceName(device.displayName))
|
||||||
}
|
}
|
||||||
setOnBindEditTextListener { editText ->
|
setOnBindEditTextListener { editText ->
|
||||||
editText.filters = arrayOf(InputFilter.LengthFilter(DEVICE_NAME_MAX_LENGTH))
|
editText.filters = arrayOf(InputFilter.LengthFilter(DEVICE_NAME_MAX_LENGTH))
|
||||||
|
@ -125,60 +153,63 @@ class AccountSettingsFragment : PreferenceFragmentCompat() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun onSyncNow() {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
requireComponents.analytics.metrics.track(Event.SyncAccountSyncNow)
|
||||||
|
// Trigger a sync.
|
||||||
|
requireComponents.backgroundServices.accountManager.syncNowAsync().await()
|
||||||
|
// Poll for device events.
|
||||||
|
accountManager.authenticatedAccount()
|
||||||
|
?.deviceConstellation()
|
||||||
|
?.refreshDeviceStateAsync()
|
||||||
|
?.await()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun makeSnackbar(newValue: String): Boolean {
|
||||||
|
// The network request requires a nonempty string, so don't persist any changes if the user inputs one.
|
||||||
|
if (newValue.trim().isEmpty()) {
|
||||||
|
FenixSnackbar.make(view!!, FenixSnackbar.LENGTH_LONG)
|
||||||
|
.setText(getString(R.string.empty_device_name_error))
|
||||||
|
.show()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun syncDeviceName(newValue: String) {
|
||||||
|
// This may fail, and we'll have a disparity in the UI until `updateDeviceName` is called.
|
||||||
|
lifecycleScope.launch(IO) {
|
||||||
|
try {
|
||||||
|
accountManager.authenticatedAccount()
|
||||||
|
?.deviceConstellation()
|
||||||
|
?.setDeviceNameAsync(newValue)
|
||||||
|
?.await()
|
||||||
|
} catch (e: FxaPanicException) {
|
||||||
|
throw e
|
||||||
|
} catch (e: FxaException) {
|
||||||
|
Logger.error("Setting device name failed.", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun getClickListenerForSignOut(): Preference.OnPreferenceClickListener {
|
private fun getClickListenerForSignOut(): Preference.OnPreferenceClickListener {
|
||||||
return Preference.OnPreferenceClickListener {
|
return Preference.OnPreferenceClickListener {
|
||||||
nav(
|
accountSettingsInteractor.onSignOut()
|
||||||
R.id.accountSettingsFragment,
|
|
||||||
AccountSettingsFragmentDirections.actionAccountSettingsFragmentToSignOutFragment()
|
|
||||||
)
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getClickListenerForSyncNow(): Preference.OnPreferenceClickListener {
|
private fun getClickListenerForSyncNow(): Preference.OnPreferenceClickListener {
|
||||||
return Preference.OnPreferenceClickListener {
|
return Preference.OnPreferenceClickListener {
|
||||||
lifecycleScope.launch {
|
accountSettingsInteractor.onSyncNow()
|
||||||
requireComponents.analytics.metrics.track(Event.SyncAccountSyncNow)
|
|
||||||
// Trigger a sync.
|
|
||||||
requireComponents.backgroundServices.accountManager.syncNowAsync().await()
|
|
||||||
// Poll for device events.
|
|
||||||
accountManager.authenticatedAccount()
|
|
||||||
?.deviceConstellation()
|
|
||||||
?.refreshDeviceStateAsync()
|
|
||||||
?.await()
|
|
||||||
}
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getChangeListenerForDeviceName(): Preference.OnPreferenceChangeListener {
|
private fun getChangeListenerForDeviceName(): Preference.OnPreferenceChangeListener {
|
||||||
return Preference.OnPreferenceChangeListener { _, newValue ->
|
return Preference.OnPreferenceChangeListener { _, newValue ->
|
||||||
// The network request requires a nonempty string, so don't persist any changes if the user inputs one.
|
accountSettingsInteractor.onChangeDeviceName(newValue as String)
|
||||||
if (newValue.toString().trim().isEmpty()) {
|
|
||||||
FenixSnackbar.make(view!!, FenixSnackbar.LENGTH_LONG)
|
|
||||||
.setText(getString(R.string.empty_device_name_error))
|
|
||||||
.show()
|
|
||||||
return@OnPreferenceChangeListener false
|
|
||||||
}
|
|
||||||
// Optimistically set the device name to what user requested.
|
|
||||||
val deviceNameKey = context!!.getPreferenceKey(R.string.pref_key_sync_device_name)
|
|
||||||
val preferenceDeviceName = findPreference<Preference>(deviceNameKey)
|
|
||||||
preferenceDeviceName?.summary = newValue as String
|
|
||||||
|
|
||||||
// This may fail, and we'll have a disparity in the UI until `updateDeviceName` is called.
|
|
||||||
lifecycleScope.launch(IO) {
|
|
||||||
try {
|
|
||||||
accountManager.authenticatedAccount()
|
|
||||||
?.deviceConstellation()
|
|
||||||
?.setDeviceNameAsync(newValue)
|
|
||||||
?.await()
|
|
||||||
} catch (e: FxaPanicException) {
|
|
||||||
throw e
|
|
||||||
} catch (e: FxaException) {
|
|
||||||
Logger.error("Setting device name failed.", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,8 +220,6 @@ class AccountSettingsFragment : PreferenceFragmentCompat() {
|
||||||
view?.announceForAccessibility(getString(R.string.sync_syncing_in_progress))
|
view?.announceForAccessibility(getString(R.string.sync_syncing_in_progress))
|
||||||
pref?.title = getString(R.string.sync_syncing_in_progress)
|
pref?.title = getString(R.string.sync_syncing_in_progress)
|
||||||
pref?.isEnabled = false
|
pref?.isEnabled = false
|
||||||
|
|
||||||
updateSyncingItemsPreference()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,7 +230,9 @@ class AccountSettingsFragment : PreferenceFragmentCompat() {
|
||||||
pref?.let {
|
pref?.let {
|
||||||
pref.title = getString(R.string.preferences_sync_now)
|
pref.title = getString(R.string.preferences_sync_now)
|
||||||
pref.isEnabled = true
|
pref.isEnabled = true
|
||||||
updateLastSyncedTimePref(context!!, pref, failed = false)
|
|
||||||
|
val time = getLastSynced(requireContext())
|
||||||
|
accountSettingsStore.dispatch(AccountSettingsAction.SyncEnded(time))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -213,7 +244,9 @@ class AccountSettingsFragment : PreferenceFragmentCompat() {
|
||||||
pref?.let {
|
pref?.let {
|
||||||
pref.title = getString(R.string.preferences_sync_now)
|
pref.title = getString(R.string.preferences_sync_now)
|
||||||
pref.isEnabled = true
|
pref.isEnabled = true
|
||||||
updateLastSyncedTimePref(context!!, pref, failed = true)
|
|
||||||
|
val failedTime = getLastSynced(requireContext())
|
||||||
|
accountSettingsStore.dispatch(AccountSettingsAction.SyncFailed(failedTime))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -221,48 +254,39 @@ class AccountSettingsFragment : PreferenceFragmentCompat() {
|
||||||
|
|
||||||
private val deviceConstellationObserver = object : DeviceConstellationObserver {
|
private val deviceConstellationObserver = object : DeviceConstellationObserver {
|
||||||
override fun onDevicesUpdate(constellation: ConstellationState) {
|
override fun onDevicesUpdate(constellation: ConstellationState) {
|
||||||
val deviceNameKey = context!!.getPreferenceKey(R.string.pref_key_sync_device_name)
|
constellation.currentDevice?.displayName?.also {
|
||||||
val preferenceDeviceName = findPreference<Preference>(deviceNameKey)
|
accountSettingsStore.dispatch(AccountSettingsAction.UpdateDeviceName(it))
|
||||||
preferenceDeviceName?.summary = constellation.currentDevice?.displayName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateSyncingItemsPreference() {
|
|
||||||
val syncCategory = context!!.getPreferenceKey(R.string.preferences_sync_category)
|
|
||||||
val preferencesSyncCategory = findPreference<Preference>(syncCategory) as PreferenceCategory
|
|
||||||
val stringSet = mutableSetOf<String>()
|
|
||||||
|
|
||||||
preferencesSyncCategory.forEach {
|
|
||||||
(it as? CheckBoxPreference)?.let { checkboxPreference ->
|
|
||||||
if (checkboxPreference.isChecked) {
|
|
||||||
stringSet.add(checkboxPreference.key)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateLastSyncedTimePref(context: Context, pref: Preference, failed: Boolean = false) {
|
private fun updateDeviceName(state: AccountSettingsState) {
|
||||||
val lastSyncTime = getLastSynced(context)
|
val deviceNameKey = context!!.getPreferenceKey(R.string.pref_key_sync_device_name)
|
||||||
|
val preferenceDeviceName = findPreference<Preference>(deviceNameKey)
|
||||||
|
preferenceDeviceName?.summary = state.deviceName
|
||||||
|
}
|
||||||
|
|
||||||
pref.summary = if (!failed && lastSyncTime == 0L) {
|
private fun updateLastSyncTimePref(state: AccountSettingsState) {
|
||||||
// Never tried to sync.
|
val value = when (state.lastSyncedDate) {
|
||||||
getString(R.string.sync_never_synced_summary)
|
LastSyncTime.Never -> getString(R.string.sync_never_synced_summary)
|
||||||
} else if (failed && lastSyncTime == 0L) {
|
is LastSyncTime.Failed -> {
|
||||||
// Failed to sync, never succeeded before.
|
if (state.lastSyncedDate.lastSync == 0L) {
|
||||||
getString(R.string.sync_failed_never_synced_summary)
|
getString(R.string.sync_failed_never_synced_summary)
|
||||||
} else if (!failed && lastSyncTime != 0L) {
|
} else {
|
||||||
// Successfully synced.
|
getString(
|
||||||
getString(
|
R.string.sync_failed_summary,
|
||||||
|
DateUtils.getRelativeTimeSpanString(state.lastSyncedDate.lastSync)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is LastSyncTime.Success -> getString(
|
||||||
R.string.sync_last_synced_summary,
|
R.string.sync_last_synced_summary,
|
||||||
DateUtils.getRelativeTimeSpanString(lastSyncTime)
|
DateUtils.getRelativeTimeSpanString(state.lastSyncedDate.lastSync)
|
||||||
)
|
|
||||||
} else {
|
|
||||||
// Failed to sync, succeeded before.
|
|
||||||
getString(
|
|
||||||
R.string.sync_failed_summary,
|
|
||||||
DateUtils.getRelativeTimeSpanString(lastSyncTime)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val syncNow = context!!.getPreferenceKey(R.string.pref_key_sync_now)
|
||||||
|
findPreference<Preference>(syncNow)?.summary = value
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
|
@ -0,0 +1,62 @@
|
||||||
|
/* 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.account
|
||||||
|
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
import org.mozilla.fenix.ext.nav
|
||||||
|
|
||||||
|
interface AccountSettingsUserActions {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called whenever the "Sync now" button is tapped
|
||||||
|
*/
|
||||||
|
fun onSyncNow()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called whenever user sets a new device name
|
||||||
|
* @param newDeviceName the device name to change to
|
||||||
|
* @return Boolean indicating whether the new device name has been accepted or not
|
||||||
|
*/
|
||||||
|
fun onChangeDeviceName(newDeviceName: String): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called whenever the "Sign out" button is tapped
|
||||||
|
*/
|
||||||
|
fun onSignOut()
|
||||||
|
}
|
||||||
|
|
||||||
|
class AccountSettingsInteractor(
|
||||||
|
private val navController: NavController,
|
||||||
|
private val syncNow: () -> Unit,
|
||||||
|
private val checkValidName: (String) -> Boolean,
|
||||||
|
private val setDeviceName: (String) -> Unit,
|
||||||
|
private val store: AccountSettingsStore
|
||||||
|
) : AccountSettingsUserActions {
|
||||||
|
|
||||||
|
override fun onSyncNow() {
|
||||||
|
syncNow.invoke()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onChangeDeviceName(newDeviceName: String): Boolean {
|
||||||
|
val isValidName = checkValidName.invoke(newDeviceName)
|
||||||
|
if (!isValidName) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// Optimistically set the device name to what user requested.
|
||||||
|
store.dispatch(AccountSettingsAction.UpdateDeviceName(newDeviceName))
|
||||||
|
|
||||||
|
setDeviceName.invoke(newDeviceName)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSignOut() {
|
||||||
|
val directions = AccountSettingsFragmentDirections.actionAccountSettingsFragmentToSignOutFragment()
|
||||||
|
navController.nav(
|
||||||
|
R.id.accountSettingsFragment,
|
||||||
|
directions
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
/* 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.account
|
||||||
|
|
||||||
|
import mozilla.components.lib.state.Action
|
||||||
|
import mozilla.components.lib.state.State
|
||||||
|
import mozilla.components.lib.state.Store
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The [Store] for holding the [AccountSettingsState] and applying [AccountAction]s.
|
||||||
|
*/
|
||||||
|
class AccountSettingsStore(
|
||||||
|
initialState: AccountSettingsState
|
||||||
|
) : Store<AccountSettingsState, AccountSettingsAction>(
|
||||||
|
initialState,
|
||||||
|
::accountStateReducer
|
||||||
|
)
|
||||||
|
|
||||||
|
sealed class LastSyncTime {
|
||||||
|
object Never : LastSyncTime()
|
||||||
|
data class Failed(val lastSync: Long) : LastSyncTime()
|
||||||
|
data class Success(val lastSync: Long) : LastSyncTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The state for the Account Settings Screen
|
||||||
|
*/
|
||||||
|
data class AccountSettingsState(
|
||||||
|
val lastSyncedDate: LastSyncTime,
|
||||||
|
val deviceName: String
|
||||||
|
) : State
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Actions to dispatch through the `SearchStore` to modify `SearchState` through the reducer.
|
||||||
|
*/
|
||||||
|
sealed class AccountSettingsAction : Action {
|
||||||
|
data class SyncFailed(val time: Long) : AccountSettingsAction()
|
||||||
|
data class SyncEnded(val time: Long) : AccountSettingsAction()
|
||||||
|
data class UpdateDeviceName(val name: String) : AccountSettingsAction()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The SearchState Reducer.
|
||||||
|
*/
|
||||||
|
fun accountStateReducer(state: AccountSettingsState, action: AccountSettingsAction): AccountSettingsState {
|
||||||
|
return when (action) {
|
||||||
|
is AccountSettingsAction.SyncFailed -> state.copy(lastSyncedDate = LastSyncTime.Failed(action.time))
|
||||||
|
is AccountSettingsAction.SyncEnded -> state.copy(lastSyncedDate = LastSyncTime.Success(action.time))
|
||||||
|
is AccountSettingsAction.UpdateDeviceName -> state.copy(deviceName = action.name)
|
||||||
|
}
|
||||||
|
}
|
|
@ -329,7 +329,7 @@
|
||||||
android:label="@string/preferences_accessibility" />
|
android:label="@string/preferences_accessibility" />
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/accountSettingsFragment"
|
android:id="@+id/accountSettingsFragment"
|
||||||
android:name="org.mozilla.fenix.settings.AccountSettingsFragment"
|
android:name="org.mozilla.fenix.settings.account.AccountSettingsFragment"
|
||||||
android:label="@string/preferences_account_settings">
|
android:label="@string/preferences_account_settings">
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_accountSettingsFragment_to_signOutFragment"
|
android:id="@+id/action_accountSettingsFragment_to_signOutFragment"
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
/* 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
|
||||||
|
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import androidx.navigation.NavDestination
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.verify
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Test
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
import org.mozilla.fenix.settings.account.AccountSettingsAction
|
||||||
|
import org.mozilla.fenix.settings.account.AccountSettingsFragmentDirections
|
||||||
|
import org.mozilla.fenix.settings.account.AccountSettingsInteractor
|
||||||
|
import org.mozilla.fenix.settings.account.AccountSettingsStore
|
||||||
|
|
||||||
|
class AccountSettingsInteractorTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun onSyncNow() {
|
||||||
|
var ranSyncNow = false
|
||||||
|
|
||||||
|
val interactor = AccountSettingsInteractor(
|
||||||
|
mockk(),
|
||||||
|
{ ranSyncNow = true },
|
||||||
|
mockk(),
|
||||||
|
mockk(),
|
||||||
|
mockk()
|
||||||
|
)
|
||||||
|
|
||||||
|
interactor.onSyncNow()
|
||||||
|
|
||||||
|
assertEquals(ranSyncNow, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun onChangeDeviceName() {
|
||||||
|
val store: AccountSettingsStore = mockk(relaxed = true)
|
||||||
|
|
||||||
|
val interactor = AccountSettingsInteractor(
|
||||||
|
mockk(),
|
||||||
|
mockk(),
|
||||||
|
{ true },
|
||||||
|
{},
|
||||||
|
store
|
||||||
|
)
|
||||||
|
|
||||||
|
interactor.onChangeDeviceName("New Name")
|
||||||
|
|
||||||
|
verify { store.dispatch(AccountSettingsAction.UpdateDeviceName("New Name")) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun onSignOut() {
|
||||||
|
val navController: NavController = mockk(relaxed = true)
|
||||||
|
every { navController.currentDestination } returns NavDestination("").apply { id = R.id.accountSettingsFragment }
|
||||||
|
|
||||||
|
val interactor = AccountSettingsInteractor(
|
||||||
|
navController,
|
||||||
|
mockk(),
|
||||||
|
mockk(),
|
||||||
|
mockk(),
|
||||||
|
mockk()
|
||||||
|
)
|
||||||
|
|
||||||
|
interactor.onSignOut()
|
||||||
|
|
||||||
|
verify {
|
||||||
|
navController.navigate(AccountSettingsFragmentDirections.actionAccountSettingsFragmentToSignOutFragment())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue