1
0
Fork 0
fenix/app/src/main/java/org/mozilla/fenix/settings/account/AccountSettingsFragment.kt

360 lines
15 KiB
Kotlin
Raw Normal View History

/* 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 android.os.Bundle
import android.text.InputFilter
import android.text.format.DateUtils
2019-08-09 02:14:03 +02:00
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import androidx.preference.CheckBoxPreference
import androidx.preference.EditTextPreference
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
2019-08-09 01:39:23 +02:00
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
import mozilla.components.concept.sync.AccountObserver
import mozilla.components.concept.sync.ConstellationState
import mozilla.components.concept.sync.DeviceConstellationObserver
2019-08-09 01:39:23 +02:00
import mozilla.components.lib.state.ext.consumeFrom
import mozilla.components.service.fxa.SyncEngine
import mozilla.components.service.fxa.manager.FxaAccountManager
import mozilla.components.service.fxa.manager.SyncEnginesStorage
import mozilla.components.service.fxa.sync.SyncReason
2019-07-11 11:33:37 +02:00
import mozilla.components.service.fxa.sync.SyncStatusObserver
import mozilla.components.service.fxa.sync.getLastSynced
2019-10-24 18:29:41 +02:00
import org.mozilla.fenix.FeatureFlags
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.ext.components
import org.mozilla.fenix.ext.getPreferenceKey
import org.mozilla.fenix.ext.requireComponents
2019-10-24 18:29:41 +02:00
@SuppressWarnings("TooManyFunctions", "LargeClass")
class AccountSettingsFragment : PreferenceFragmentCompat() {
private lateinit var accountManager: FxaAccountManager
private lateinit var accountSettingsStore: AccountSettingsFragmentStore
private lateinit var accountSettingsInteractor: AccountSettingsInteractor
// Navigate away from this fragment when we encounter auth problems or logout events.
private val accountStateObserver = object : AccountObserver {
override fun onAuthenticationProblems() {
lifecycleScope.launch {
findNavController().popBackStack()
}
}
override fun onLoggedOut() {
lifecycleScope.launch {
findNavController().popBackStack()
// Remove the device name when we log out.
context?.let {
val deviceNameKey = it.getPreferenceKey(R.string.pref_key_sync_device_name)
preferenceManager.sharedPreferences.edit().remove(deviceNameKey).apply()
}
}
}
}
override fun onResume() {
super.onResume()
(activity as AppCompatActivity).title = getString(R.string.preferences_account_settings)
(activity as AppCompatActivity).supportActionBar?.show()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requireComponents.analytics.metrics.track(Event.SyncAccountOpened)
}
override fun onDestroy() {
super.onDestroy()
requireComponents.analytics.metrics.track(Event.SyncAccountClosed)
}
@ExperimentalCoroutinesApi
2019-08-09 02:14:03 +02:00
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
consumeFrom(accountSettingsStore) {
updateLastSyncTimePref(it)
updateDeviceName(it)
}
accountSettingsInteractor = AccountSettingsInteractor(
findNavController(),
::syncNow,
::syncDeviceName,
accountSettingsStore
)
}
2019-10-24 18:29:41 +02:00
@Suppress("ComplexMethod", "LongMethod")
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.account_settings_preferences, rootKey)
accountSettingsStore = StoreProvider.get(this) {
AccountSettingsFragmentStore(
AccountSettingsFragmentState(
lastSyncedDate =
2019-08-09 02:14:03 +02:00
if (getLastSynced(requireContext()) == 0L)
LastSyncTime.Never
else
LastSyncTime.Success(getLastSynced(requireContext())),
2019-10-24 18:29:41 +02:00
deviceName = requireComponents.backgroundServices.defaultDeviceName(
requireContext()
)
)
)
}
accountManager = requireComponents.backgroundServices.accountManager
accountManager.register(accountStateObserver, this, true)
// Sign out
val signOut = getPreferenceKey(R.string.pref_key_sign_out)
val preferenceSignOut = findPreference<Preference>(signOut)
2019-03-14 00:55:38 +01:00
preferenceSignOut?.onPreferenceClickListener = getClickListenerForSignOut()
// Sync now
val syncNow = getPreferenceKey(R.string.pref_key_sync_now)
val preferenceSyncNow = findPreference<Preference>(syncNow)
2019-03-14 00:55:38 +01:00
preferenceSyncNow?.let {
2019-07-11 11:33:37 +02:00
it.onPreferenceClickListener = getClickListenerForSyncNow()
// Current sync state
if (requireComponents.backgroundServices.accountManager.isSyncActive()) {
it.title = getString(R.string.sync_syncing_in_progress)
it.isEnabled = false
} else {
it.isEnabled = true
}
}
// Device Name
val deviceConstellation = accountManager.authenticatedAccount()?.deviceConstellation()
val deviceNameKey = getPreferenceKey(R.string.pref_key_sync_device_name)
findPreference<EditTextPreference>(deviceNameKey)?.apply {
onPreferenceChangeListener = getChangeListenerForDeviceName()
deviceConstellation?.state()?.currentDevice?.let { device ->
summary = device.displayName
text = device.displayName
accountSettingsStore.dispatch(AccountSettingsFragmentAction.UpdateDeviceName(device.displayName))
}
setOnBindEditTextListener { editText ->
editText.filters = arrayOf(InputFilter.LengthFilter(DEVICE_NAME_MAX_LENGTH))
}
}
// Make sure out sync engine checkboxes are up-to-date.
updateSyncEngineStates()
val historyNameKey = getPreferenceKey(R.string.pref_key_sync_history)
findPreference<CheckBoxPreference>(historyNameKey)?.apply {
setOnPreferenceChangeListener { _, newValue ->
SyncEnginesStorage(context).setStatus(SyncEngine.History, newValue as Boolean)
2019-10-24 18:29:41 +02:00
@Suppress("DeferredResultUnused")
context.components.backgroundServices.accountManager.syncNowAsync(SyncReason.EngineChange)
true
}
}
val bookmarksNameKey = getPreferenceKey(R.string.pref_key_sync_bookmarks)
findPreference<CheckBoxPreference>(bookmarksNameKey)?.apply {
setOnPreferenceChangeListener { _, newValue ->
SyncEnginesStorage(context).setStatus(SyncEngine.Bookmarks, newValue as Boolean)
2019-10-24 18:29:41 +02:00
@Suppress("DeferredResultUnused")
context.components.backgroundServices.accountManager.syncNowAsync(SyncReason.EngineChange)
true
}
}
val loginsNameKey = getPreferenceKey(R.string.pref_key_sync_logins)
findPreference<CheckBoxPreference>(loginsNameKey)?.apply {
setOnPreferenceChangeListener { _, newValue ->
SyncEnginesStorage(context).setStatus(SyncEngine.Passwords, newValue as Boolean)
@Suppress("DeferredResultUnused")
context.components.backgroundServices.accountManager.syncNowAsync(SyncReason.EngineChange)
true
}
}
deviceConstellation?.registerDeviceObserver(deviceConstellationObserver, owner = this, autoPause = true)
// NB: ObserverRegistry will take care of cleaning up internal references to 'observer' and
// 'owner' when appropriate.
2019-07-11 11:33:37 +02:00
requireComponents.backgroundServices.accountManager.registerForSyncEvents(
syncStatusObserver, owner = this, autoPause = true
)
}
private fun updateSyncEngineStates() {
val syncEnginesStatus = SyncEnginesStorage(context!!).getStatus()
val bookmarksNameKey = getPreferenceKey(R.string.pref_key_sync_bookmarks)
findPreference<CheckBoxPreference>(bookmarksNameKey)?.apply {
isEnabled = syncEnginesStatus.containsKey(SyncEngine.Bookmarks)
isChecked = syncEnginesStatus.getOrElse(SyncEngine.Bookmarks) { true }
}
val historyNameKey = getPreferenceKey(R.string.pref_key_sync_history)
findPreference<CheckBoxPreference>(historyNameKey)?.apply {
isEnabled = syncEnginesStatus.containsKey(SyncEngine.History)
isChecked = syncEnginesStatus.getOrElse(SyncEngine.History) { true }
}
2019-10-24 18:29:41 +02:00
val loginsNameKey = getPreferenceKey(R.string.pref_key_sync_logins)
findPreference<CheckBoxPreference>(loginsNameKey)?.apply {
isVisible = FeatureFlags.logins
isEnabled = syncEnginesStatus.containsKey(SyncEngine.Passwords)
isChecked = syncEnginesStatus.getOrElse(SyncEngine.Passwords) { false }
}
}
2019-08-09 01:39:23 +02:00
private fun syncNow() {
lifecycleScope.launch {
requireComponents.analytics.metrics.track(Event.SyncAccountSyncNow)
// Trigger a sync.
2019-10-24 18:29:41 +02:00
requireComponents.backgroundServices.accountManager.syncNowAsync(SyncReason.User)
.await()
2019-08-21 15:38:37 +02:00
// Poll for device events & update devices.
accountManager.authenticatedAccount()
2019-08-21 15:38:37 +02:00
?.deviceConstellation()?.run {
refreshDevicesAsync().await()
pollForEventsAsync().await()
}
}
}
2019-08-09 02:14:03 +02:00
private fun syncDeviceName(newValue: String): Boolean {
if (newValue.trim().isEmpty()) {
return false
}
// This may fail, and we'll have a disparity in the UI until `updateDeviceName` is called.
2019-08-09 01:39:23 +02:00
lifecycleScope.launch(Main) {
context?.let {
accountManager.authenticatedAccount()
?.deviceConstellation()
?.setDeviceNameAsync(newValue, it)
?.await()
}
}
2019-08-09 02:14:03 +02:00
return true
}
private fun getClickListenerForSignOut(): Preference.OnPreferenceClickListener {
return Preference.OnPreferenceClickListener {
accountSettingsInteractor.onSignOut()
true
}
}
private fun getClickListenerForSyncNow(): Preference.OnPreferenceClickListener {
return Preference.OnPreferenceClickListener {
accountSettingsInteractor.onSyncNow()
true
}
}
private fun getChangeListenerForDeviceName(): Preference.OnPreferenceChangeListener {
return Preference.OnPreferenceChangeListener { _, newValue ->
2019-08-09 02:14:03 +02:00
accountSettingsInteractor.onChangeDeviceName(newValue as String) {
FenixSnackbar.make(view!!, FenixSnackbar.LENGTH_LONG)
2019-10-24 18:29:41 +02:00
.setText(getString(R.string.empty_device_name_error))
.show()
2019-08-09 02:14:03 +02:00
}
}
}
private val syncStatusObserver = object : SyncStatusObserver {
override fun onStarted() {
lifecycleScope.launch {
val pref = findPreference<Preference>(getPreferenceKey(R.string.pref_key_sync_now))
view?.announceForAccessibility(getString(R.string.sync_syncing_in_progress))
pref?.title = getString(R.string.sync_syncing_in_progress)
2019-03-14 00:55:38 +01:00
pref?.isEnabled = false
}
}
// Sync stopped successfully.
override fun onIdle() {
lifecycleScope.launch {
val pref = findPreference<Preference>(getPreferenceKey(R.string.pref_key_sync_now))
pref?.let {
2019-03-14 00:55:38 +01:00
pref.title = getString(R.string.preferences_sync_now)
pref.isEnabled = true
val time = getLastSynced(requireContext())
accountSettingsStore.dispatch(AccountSettingsFragmentAction.SyncEnded(time))
2019-03-14 00:55:38 +01:00
}
// Make sure out sync engine checkboxes are up-to-date.
updateSyncEngineStates()
}
}
// Sync stopped after encountering a problem.
override fun onError(error: Exception?) {
lifecycleScope.launch {
val pref = findPreference<Preference>(getPreferenceKey(R.string.pref_key_sync_now))
pref?.let {
2019-03-14 00:55:38 +01:00
pref.title = getString(R.string.preferences_sync_now)
pref.isEnabled = true
val failedTime = getLastSynced(requireContext())
2019-10-24 18:29:41 +02:00
accountSettingsStore.dispatch(
AccountSettingsFragmentAction.SyncFailed(
failedTime
)
)
2019-03-14 00:55:38 +01:00
}
}
}
}
private val deviceConstellationObserver = object : DeviceConstellationObserver {
override fun onDevicesUpdate(constellation: ConstellationState) {
constellation.currentDevice?.displayName?.also {
accountSettingsStore.dispatch(AccountSettingsFragmentAction.UpdateDeviceName(it))
}
}
}
private fun updateDeviceName(state: AccountSettingsFragmentState) {
val deviceNameKey = getPreferenceKey(R.string.pref_key_sync_device_name)
val preferenceDeviceName = findPreference<Preference>(deviceNameKey)
preferenceDeviceName?.summary = state.deviceName
}
private fun updateLastSyncTimePref(state: AccountSettingsFragmentState) {
val value = when (state.lastSyncedDate) {
LastSyncTime.Never -> getString(R.string.sync_never_synced_summary)
is LastSyncTime.Failed -> {
if (state.lastSyncedDate.lastSync == 0L) {
getString(R.string.sync_failed_never_synced_summary)
} else {
getString(
R.string.sync_failed_summary,
DateUtils.getRelativeTimeSpanString(state.lastSyncedDate.lastSync)
)
}
}
is LastSyncTime.Success -> getString(
R.string.sync_last_synced_summary,
DateUtils.getRelativeTimeSpanString(state.lastSyncedDate.lastSync)
)
}
val syncNow = getPreferenceKey(R.string.pref_key_sync_now)
findPreference<Preference>(syncNow)?.summary = value
}
companion object {
private const val DEVICE_NAME_MAX_LENGTH = 128
}
}