1
0
Fork 0

For #5544: Bare Minimum Logins UI

master
ekager 2019-10-24 09:29:41 -07:00 committed by Emily Kager
parent 6f5fba7718
commit f49331de55
30 changed files with 1015 additions and 46 deletions

View File

@ -384,6 +384,7 @@ dependencies {
implementation Deps.mozilla_feature_sendtab
implementation Deps.mozilla_feature_webcompat
implementation Deps.mozilla_service_sync_logins
implementation Deps.mozilla_service_firefox_accounts
implementation Deps.mozilla_service_fretboard
implementation Deps.mozilla_service_glean

View File

@ -56,4 +56,9 @@ object FeatureFlags {
val progressiveWebApps = nightly or debug
val forceZoomPreference = nightly or debug
/**
* Gives option in Settings to see logins and sync logins
*/
const val logins = false
}

View File

@ -85,6 +85,7 @@ class BackgroundServices(
val syncConfig = if (context.isInExperiment(Experiments.asFeatureSyncDisabled)) {
null
} else {
// TODO Add Passwords Here Waiting On https://github.com/mozilla-mobile/android-components/issues/4741
SyncConfig(setOf(SyncEngine.History, SyncEngine.Bookmarks), syncPeriodInMinutes = 240L) // four hours
}
@ -93,9 +94,11 @@ class BackgroundServices(
val push by lazy { makePushConfig()?.let { makePush(it) } }
init {
// Make the "history" and "bookmark" stores accessible to workers spawned by the sync manager.
// Make the "history", "bookmark", and "logins" stores accessible to workers spawned by the sync manager.
GlobalSyncableStoreProvider.configureStore(SyncEngine.History to historyStorage)
GlobalSyncableStoreProvider.configureStore(SyncEngine.Bookmarks to bookmarkStorage)
// TODO Add Passwords Here Waiting On https://github.com/mozilla-mobile/android-components/issues/4741
// GlobalSyncableStoreProvider.configureStore(SyncEngine.Passwords to loginsStorage)
}
private val deviceEventObserver = object : DeviceEventsObserver {

View File

@ -7,6 +7,7 @@ package org.mozilla.fenix.components
import GeckoProvider
import android.content.Context
import android.content.res.Configuration
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
@ -31,11 +32,14 @@ import mozilla.components.feature.media.RecordingDevicesNotificationFeature
import mozilla.components.feature.media.state.MediaStateMachine
import mozilla.components.feature.session.HistoryDelegate
import mozilla.components.feature.webcompat.WebCompatFeature
import mozilla.components.service.sync.logins.AsyncLoginsStorageAdapter
import mozilla.components.service.sync.logins.SyncableLoginsStore
import org.mozilla.fenix.AppRequestInterceptor
import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.test.Mockable
import java.io.File
import java.util.concurrent.TimeUnit
/**
@ -152,6 +156,19 @@ class Core(private val context: Context) {
val permissionStorage by lazy { PermissionStorage(context) }
val loginsStorage by lazy {
SyncableLoginsStore(
AsyncLoginsStorageAdapter.forDatabase(
File(
context.filesDir,
"logins.sqlite"
).canonicalPath
)
) {
CompletableDeferred("very-insecure-key")
}
}
/**
* Constructs a [TrackingProtectionPolicy] based on current preferences.
*

View File

@ -0,0 +1,85 @@
/* 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.logins
import android.os.Bundle
import android.text.InputType
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.fragment_saved_login_site_info.*
import org.mozilla.fenix.R
import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.ext.components
class SavedLoginSiteInfoFragment : Fragment(R.layout.fragment_saved_login_site_info) {
private val safeArguments get() = requireNotNull(arguments)
private val savedLoginItem: SavedLoginsItem by lazy {
SavedLoginSiteInfoFragmentArgs.fromBundle(
safeArguments
).savedLoginItem
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
siteInfoText.text = savedLoginItem.url
copySiteItem.setOnClickListener {
val clipboard = view.context.components.clipboardHandler
clipboard.text = savedLoginItem.url
showCopiedSnackbar(getString(R.string.logins_site_copied))
}
usernameInfoText.text = savedLoginItem.userName
copyUsernameItem.setOnClickListener {
val clipboard = view.context.components.clipboardHandler
clipboard.text = savedLoginItem.userName
showCopiedSnackbar(getString(R.string.logins_username_copied))
}
passwordInfoText.inputType =
InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
passwordInfoText.text = savedLoginItem.password
revealPasswordItem.setOnClickListener {
togglePasswordReveal()
}
copyPasswordItem.setOnClickListener {
val clipboard = view.context.components.clipboardHandler
clipboard.text = savedLoginItem.password
showCopiedSnackbar(getString(R.string.logins_password_copied))
}
}
private fun showCopiedSnackbar(copiedItem: String) {
view?.let {
FenixSnackbar.make(it, Snackbar.LENGTH_SHORT).setText(copiedItem).show()
}
}
private fun togglePasswordReveal() {
if (passwordInfoText.inputType == InputType.TYPE_TEXT_VARIATION_PASSWORD or InputType.TYPE_CLASS_TEXT) {
revealPasswordItem.setImageDrawable(context?.getDrawable(R.drawable.ic_password_hide))
passwordInfoText.inputType = InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
revealPasswordItem.contentDescription =
context?.getString(R.string.saved_login_hide_password)
} else {
revealPasswordItem.setImageDrawable(context?.getDrawable(R.drawable.ic_password_reveal))
passwordInfoText.inputType =
InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
revealPasswordItem.contentDescription =
context?.getString(R.string.saved_login_reveal_password)
}
// For the new type to take effect you need to reset the text
passwordInfoText.text = savedLoginItem.password
}
override fun onResume() {
super.onResume()
activity?.title = savedLoginItem.url
(activity as AppCompatActivity).supportActionBar?.show()
}
}

View File

@ -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.logins
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
private sealed class AdapterItem {
data class Item(val item: SavedLoginsItem) : AdapterItem()
}
private class SavedLoginsList(savedLogins: List<SavedLoginsItem>) {
val items: List<AdapterItem> = savedLogins.map { AdapterItem.Item(it) }
}
class SavedLoginsAdapter(
private val interactor: SavedLoginsInteractor
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var savedLoginsList: SavedLoginsList = SavedLoginsList(emptyList())
fun updateData(items: List<SavedLoginsItem>) {
this.savedLoginsList = SavedLoginsList(items)
notifyDataSetChanged()
}
override fun getItemCount(): Int = savedLoginsList.items.size
override fun getItemViewType(position: Int): Int {
return when (savedLoginsList.items[position]) {
is AdapterItem.Item -> SavedLoginsListItemViewHolder.LAYOUT_ID
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
return when (viewType) {
SavedLoginsListItemViewHolder.LAYOUT_ID -> SavedLoginsListItemViewHolder(
view,
interactor
)
else -> throw IllegalStateException()
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is SavedLoginsListItemViewHolder -> (savedLoginsList.items[position] as AdapterItem.Item).also {
holder.bind(it.item)
}
}
}
}

View File

@ -0,0 +1,90 @@
/* 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.logins
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
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.Dispatchers
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.ObsoleteCoroutinesApi
import kotlinx.coroutines.async
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
class SavedLoginsFragment : Fragment() {
private lateinit var savedLoginsStore: SavedLoginsFragmentStore
private lateinit var savedLoginsView: SavedLoginsView
private lateinit var savedLoginsInteractor: SavedLoginsInteractor
override fun onResume() {
super.onResume()
activity?.title = getString(R.string.preferences_passwords_saved_logins)
(activity as AppCompatActivity).supportActionBar?.show()
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_saved_logins, container, false)
savedLoginsStore = StoreProvider.get(this) {
SavedLoginsFragmentStore(
SavedLoginsFragmentState(
items = listOf()
)
)
}
savedLoginsInteractor = SavedLoginsInteractor(::itemClicked)
savedLoginsView = SavedLoginsView(view.savedLoginsLayout, savedLoginsInteractor)
loadAndMapLogins()
return view
}
@ObsoleteCoroutinesApi
@ExperimentalCoroutinesApi
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
consumeFrom(savedLoginsStore) {
savedLoginsView.update(it)
}
}
private fun itemClicked(item: SavedLoginsItem) {
val directions =
SavedLoginsFragmentDirections.actionSavedLoginsFragmentToSavedLoginSiteInfoFragment(item)
findNavController().navigate(directions)
}
private fun loadAndMapLogins() {
lifecycleScope.launch(IO) {
val syncedLogins = async {
context!!.components.core.loginsStorage.withUnlocked {
it.list().await().map { item ->
SavedLoginsItem(
item.hostname,
item.username,
item.password
)
}
}
}.await()
launch(Dispatchers.Main) {
savedLoginsStore.dispatch(SavedLoginsFragmentAction.UpdateLogins(syncedLogins))
}
}
}
}

View File

@ -0,0 +1,55 @@
/* 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.logins
import android.os.Parcelable
import kotlinx.android.parcel.Parcelize
import mozilla.components.lib.state.Action
import mozilla.components.lib.state.State
import mozilla.components.lib.state.Store
/**
* Class representing an saved logins item
* @property url Site of the saved login
* @property userName Username that's saved for this site
* @property password Password that's saved for this site
*/
@Parcelize
data class SavedLoginsItem(val url: String, val userName: String?, val password: String?) :
Parcelable
/**
* The [Store] for holding the [SavedLoginsFragmentState] and applying [SavedLoginsFragmentAction]s.
*/
class SavedLoginsFragmentStore(initialState: SavedLoginsFragmentState) :
Store<SavedLoginsFragmentState, SavedLoginsFragmentAction>(
initialState,
::savedLoginsStateReducer
)
/**
* Actions to dispatch through the `SavedLoginsStore` to modify `SavedLoginsFragmentState` through the reducer.
*/
sealed class SavedLoginsFragmentAction : Action {
data class UpdateLogins(val list: List<SavedLoginsItem>) : SavedLoginsFragmentAction()
}
/**
* The state for the Saved Logins Screen
* @property items List of logins to display
*/
data class SavedLoginsFragmentState(val items: List<SavedLoginsItem>) : State
/**
* The SavedLoginsState Reducer.
*/
private fun savedLoginsStateReducer(
state: SavedLoginsFragmentState,
action: SavedLoginsFragmentAction
): SavedLoginsFragmentState {
return when (action) {
is SavedLoginsFragmentAction.UpdateLogins -> state.copy(items = action.list)
}
}

View File

@ -0,0 +1,17 @@
/* 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.logins
/**
* Interactor for the saved logins screen
* Provides implementations for the SavedLoginsViewInteractor
*/
class SavedLoginsInteractor(
private val itemClicked: (SavedLoginsItem) -> Unit
) : SavedLoginsViewInteractor {
override fun itemClicked(item: SavedLoginsItem) {
itemClicked.invoke(item)
}
}

View File

@ -0,0 +1,42 @@
/* 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.logins
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.logins_item.view.*
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.loadIntoView
class SavedLoginsListItemViewHolder(
private val view: View,
private val interactor: SavedLoginsInteractor
) : RecyclerView.ViewHolder(view) {
private val favicon = view.favicon_image
private val url = view.domainView
private val userName = view.userView
private var item: SavedLoginsItem? = null
fun bind(item: SavedLoginsItem) {
this.item = item
url.text = item.url
userName.text = item.userName
updateFavIcon(item.url)
view.setOnClickListener {
interactor.itemClicked(item)
}
}
private fun updateFavIcon(url: String) {
favicon.context.components.core.icons.loadIntoView(favicon, url)
}
companion object {
const val LAYOUT_ID = R.layout.logins_item
}
}

View File

@ -0,0 +1,54 @@
/* 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.logins
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.component_saved_logins.view.*
import org.mozilla.fenix.R
/**
* Interface for the SavedLoginsViewInteractor. This interface is implemented by objects that want
* to respond to user interaction on the SavedLoginsView
*/
interface SavedLoginsViewInteractor {
/**
* Called whenever one item is clicked
*/
fun itemClicked(item: SavedLoginsItem)
}
/**
* View that contains and configures the Saved Logins List
*/
class SavedLoginsView(
private val container: ViewGroup,
val interactor: SavedLoginsInteractor
) : LayoutContainer {
val view: FrameLayout = LayoutInflater.from(container.context)
.inflate(R.layout.component_saved_logins, container, true)
.findViewById(R.id.saved_logins_wrapper)
override val containerView: View?
get() = container
init {
view.saved_logins_list.apply {
adapter = SavedLoginsAdapter(interactor)
layoutManager = LinearLayoutManager(container.context)
}
}
fun update(state: SavedLoginsFragmentState) {
view.saved_logins_list.isVisible = state.items.isNotEmpty()
(view.saved_logins_list.adapter as SavedLoginsAdapter).updateData(state.items)
}
}

View File

@ -0,0 +1,114 @@
/* 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 android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.fragment.findNavController
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import mozilla.components.concept.sync.AccountObserver
import mozilla.components.concept.sync.AuthType
import mozilla.components.concept.sync.OAuthAccount
import mozilla.components.service.fxa.SyncEngine
import mozilla.components.service.fxa.manager.SyncEnginesStorage
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.getPreferenceKey
import org.mozilla.fenix.ext.requireComponents
@Suppress("TooManyFunctions")
class LoginsFragment : PreferenceFragmentCompat(), AccountObserver {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.logins_preferences, rootKey)
}
override fun onResume() {
super.onResume()
activity?.title = getString(R.string.preferences_passwords_logins_and_passwords)
(activity as AppCompatActivity).supportActionBar?.show()
val savedLoginsKey = getPreferenceKey(R.string.pref_key_saved_logins)
findPreference<Preference>(savedLoginsKey)?.setOnPreferenceClickListener {
navigateToLoginsSettingsFragment()
true
}
val accountManager = requireComponents.backgroundServices.accountManager
accountManager.register(this, owner = this)
val accountExists = accountManager.authenticatedAccount() != null
val needsReauth = accountManager.accountNeedsReauth()
when {
needsReauth -> updateSyncPreferenceNeedsReauth()
accountExists -> updateSyncPreferenceStatus()
!accountExists -> updateSyncPreferenceNeedsLogin()
}
}
override fun onAuthenticated(account: OAuthAccount, authType: AuthType) =
updateSyncPreferenceStatus()
override fun onLoggedOut() = updateSyncPreferenceNeedsLogin()
override fun onAuthenticationProblems() = updateSyncPreferenceNeedsReauth()
private fun updateSyncPreferenceStatus() {
val syncLogins = getPreferenceKey(R.string.pref_key_password_sync_logins)
findPreference<Preference>(syncLogins)?.apply {
val syncEnginesStatus = SyncEnginesStorage(context!!).getStatus()
val loginsSyncStatus = syncEnginesStatus.getOrElse(SyncEngine.Passwords) { false }
summary = getString(
if (loginsSyncStatus) R.string.preferences_passwords_sync_logins_on
else R.string.preferences_passwords_sync_logins_off
)
setOnPreferenceClickListener {
navigateToAccountSettingsFragment()
true
}
}
}
private fun updateSyncPreferenceNeedsLogin() {
val syncLogins = getPreferenceKey(R.string.pref_key_password_sync_logins)
findPreference<Preference>(syncLogins)?.apply {
summary = getString(R.string.preferences_passwords_sync_logins_sign_in)
setOnPreferenceClickListener {
navigateToTurnOnSyncFragment()
true
}
}
}
private fun updateSyncPreferenceNeedsReauth() {
val syncLogins = getPreferenceKey(R.string.pref_key_password_sync_logins)
findPreference<Preference>(syncLogins)?.apply {
summary = getString(R.string.preferences_passwords_sync_logins_reconnect)
setOnPreferenceClickListener {
navigateToAccountProblemFragment()
true
}
}
}
private fun navigateToLoginsSettingsFragment() {
val directions = LoginsFragmentDirections.actionLoginsFragmentToSavedLoginsFragment()
findNavController().navigate(directions)
}
private fun navigateToAccountSettingsFragment() {
val directions = LoginsFragmentDirections.actionLoginsFragmentToAccountSettingsFragment()
findNavController().navigate(directions)
}
private fun navigateToAccountProblemFragment() {
val directions = LoginsFragmentDirections.actionLoginsFragmentToAccountProblemFragment()
findNavController().navigate(directions)
}
private fun navigateToTurnOnSyncFragment() {
val directions = LoginsFragmentDirections.actionLoginsFragmentToTurnOnSyncFragment()
findNavController().navigate(directions)
}
}

View File

@ -25,6 +25,7 @@ import mozilla.components.concept.sync.OAuthAccount
import mozilla.components.concept.sync.Profile
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.Config
import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.FenixApplication
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
@ -41,6 +42,7 @@ import org.mozilla.fenix.R.string.pref_key_help
import org.mozilla.fenix.R.string.pref_key_language
import org.mozilla.fenix.R.string.pref_key_leakcanary
import org.mozilla.fenix.R.string.pref_key_make_default_browser
import org.mozilla.fenix.R.string.pref_key_passwords
import org.mozilla.fenix.R.string.pref_key_privacy_link
import org.mozilla.fenix.R.string.pref_key_rate
import org.mozilla.fenix.R.string.pref_key_remote_debugging
@ -105,7 +107,7 @@ class SettingsFragment : PreferenceFragmentCompat(), AccountObserver {
(activity as AppCompatActivity).supportActionBar?.show()
val trackingProtectionPreference =
findPreference<Preference>(getPreferenceKey(R.string.pref_key_tracking_protection_settings))
findPreference<Preference>(getPreferenceKey(pref_key_tracking_protection_settings))
trackingProtectionPreference?.summary = context?.let {
if (it.settings().shouldUseTrackingProtection) {
getString(R.string.tracking_protection_on)
@ -115,7 +117,7 @@ class SettingsFragment : PreferenceFragmentCompat(), AccountObserver {
}
val themesPreference =
findPreference<Preference>(getPreferenceKey(R.string.pref_key_theme))
findPreference<Preference>(getPreferenceKey(pref_key_theme))
themesPreference?.summary = context?.settings()?.themeSettingString
val aboutPreference = findPreference<Preference>(getPreferenceKey(R.string.pref_key_about))
@ -123,7 +125,11 @@ class SettingsFragment : PreferenceFragmentCompat(), AccountObserver {
aboutPreference?.title = getString(R.string.preferences_about, appName)
val deleteBrowsingDataPreference =
findPreference<Preference>(getPreferenceKey(R.string.pref_key_delete_browsing_data_on_quit_preference))
findPreference<Preference>(
getPreferenceKey(
pref_key_delete_browsing_data_on_quit_preference
)
)
deleteBrowsingDataPreference?.summary = context?.let {
if (it.settings().shouldDeleteBrowsingDataOnQuit) {
getString(R.string.delete_browsing_data_quit_on)
@ -132,13 +138,21 @@ class SettingsFragment : PreferenceFragmentCompat(), AccountObserver {
}
}
findPreference<Preference>(getPreferenceKey(R.string.pref_key_add_private_browsing_shortcut))?.apply {
findPreference<Preference>(getPreferenceKey(pref_key_add_private_browsing_shortcut))?.apply {
isVisible = !PrivateShortcutCreateManager.doesPrivateBrowsingPinnedShortcutExist(context)
}
setupPreferences()
updateAccountUIState(context!!, requireComponents.backgroundServices.accountManager.accountProfile())
updatePreferenceVisibilityForFeatureFlags()
}
private fun updatePreferenceVisibilityForFeatureFlags() {
findPreference<Preference>(getPreferenceKey(pref_key_passwords))?.apply {
isVisible = FeatureFlags.logins
}
}
@Suppress("ComplexMethod", "LongMethod")
@ -191,6 +205,9 @@ class SettingsFragment : PreferenceFragmentCompat(), AccountObserver {
)
}
}
resources.getString(pref_key_passwords) -> {
navigateToLoginsSettingsFragment()
}
resources.getString(pref_key_about) -> {
navigateToAbout()
}
@ -255,13 +272,18 @@ class SettingsFragment : PreferenceFragmentCompat(), AccountObserver {
}
preferenceRemoteDebugging?.setOnPreferenceChangeListener { preference, newValue ->
preference.context.settings().preferences.edit()
preference.context.settings().preferences.edit()
.putBoolean(preference.key, newValue as Boolean).apply()
requireComponents.core.engine.settings.remoteDebuggingEnabled = newValue
true
}
}
private fun navigateToLoginsSettingsFragment() {
val directions = SettingsFragmentDirections.actionSettingsFragmentToLoginsFragment()
Navigation.findNavController(view!!).navigate(directions)
}
private fun navigateToSearchEngineSettings() {
val directions = SettingsFragmentDirections.actionSettingsFragmentToSearchEngineFragment()
Navigation.findNavController(view!!).navigate(directions)

View File

@ -28,6 +28,7 @@ import mozilla.components.service.fxa.manager.SyncEnginesStorage
import mozilla.components.service.fxa.sync.SyncReason
import mozilla.components.service.fxa.sync.SyncStatusObserver
import mozilla.components.service.fxa.sync.getLastSynced
import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.R
import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.components.StoreProvider
@ -36,7 +37,7 @@ import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.getPreferenceKey
import org.mozilla.fenix.ext.requireComponents
@SuppressWarnings("TooManyFunctions")
@SuppressWarnings("TooManyFunctions", "LargeClass")
class AccountSettingsFragment : PreferenceFragmentCompat() {
private lateinit var accountManager: FxaAccountManager
private lateinit var accountSettingsStore: AccountSettingsFragmentStore
@ -96,7 +97,7 @@ class AccountSettingsFragment : PreferenceFragmentCompat() {
)
}
@Suppress("ComplexMethod")
@Suppress("ComplexMethod", "LongMethod")
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.account_settings_preferences, rootKey)
@ -108,7 +109,9 @@ class AccountSettingsFragment : PreferenceFragmentCompat() {
LastSyncTime.Never
else
LastSyncTime.Success(getLastSynced(requireContext())),
deviceName = requireComponents.backgroundServices.defaultDeviceName(requireContext())
deviceName = requireComponents.backgroundServices.defaultDeviceName(
requireContext()
)
)
)
}
@ -158,6 +161,7 @@ class AccountSettingsFragment : PreferenceFragmentCompat() {
findPreference<CheckBoxPreference>(historyNameKey)?.apply {
setOnPreferenceChangeListener { _, newValue ->
SyncEnginesStorage(context).setStatus(SyncEngine.History, newValue as Boolean)
@Suppress("DeferredResultUnused")
context.components.backgroundServices.accountManager.syncNowAsync(SyncReason.EngineChange)
true
}
@ -167,6 +171,17 @@ class AccountSettingsFragment : PreferenceFragmentCompat() {
findPreference<CheckBoxPreference>(bookmarksNameKey)?.apply {
setOnPreferenceChangeListener { _, newValue ->
SyncEnginesStorage(context).setStatus(SyncEngine.Bookmarks, newValue as Boolean)
@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
}
@ -193,13 +208,20 @@ class AccountSettingsFragment : PreferenceFragmentCompat() {
isEnabled = syncEnginesStatus.containsKey(SyncEngine.History)
isChecked = syncEnginesStatus.getOrElse(SyncEngine.History) { true }
}
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 }
}
}
private fun syncNow() {
lifecycleScope.launch {
requireComponents.analytics.metrics.track(Event.SyncAccountSyncNow)
// Trigger a sync.
requireComponents.backgroundServices.accountManager.syncNowAsync(SyncReason.User).await()
requireComponents.backgroundServices.accountManager.syncNowAsync(SyncReason.User)
.await()
// Poll for device events & update devices.
accountManager.authenticatedAccount()
?.deviceConstellation()?.run {
@ -243,8 +265,8 @@ class AccountSettingsFragment : PreferenceFragmentCompat() {
return Preference.OnPreferenceChangeListener { _, newValue ->
accountSettingsInteractor.onChangeDeviceName(newValue as String) {
FenixSnackbar.make(view!!, FenixSnackbar.LENGTH_LONG)
.setText(getString(R.string.empty_device_name_error))
.show()
.setText(getString(R.string.empty_device_name_error))
.show()
}
}
}
@ -284,7 +306,11 @@ class AccountSettingsFragment : PreferenceFragmentCompat() {
pref.isEnabled = true
val failedTime = getLastSynced(requireContext())
accountSettingsStore.dispatch(AccountSettingsFragmentAction.SyncFailed(failedTime))
accountSettingsStore.dispatch(
AccountSettingsFragmentAction.SyncFailed(
failedTime
)
)
}
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?><!-- 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/. -->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/saved_logins_wrapper"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/saved_logins_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
tools:listitem="@layout/logins_item" />
</FrameLayout>

View File

@ -0,0 +1,130 @@
<?xml version="1.0" encoding="utf-8"?><!-- 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/. -->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/siteLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="66dp">
<TextView
android:id="@+id/siteHeaderText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/preferences_passwords_saved_logins_site"
android:textColor="?primaryText"
android:textSize="12sp"
app:layout_constraintBottom_toTopOf="@id/siteInfoText"
app:layout_constraintEnd_toStartOf="@id/copySiteItem"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" />
<TextView
android:id="@+id/siteInfoText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="@id/copySiteItem"
app:layout_constraintEnd_toEndOf="@id/siteHeaderText"
app:layout_constraintStart_toStartOf="@id/siteHeaderText"
app:layout_constraintTop_toBottomOf="@id/siteHeaderText"
app:layout_constraintVertical_chainStyle="packed"
tools:text="Info" />
<ImageButton
android:id="@+id/copySiteItem"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/saved_login_copy_site"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_copy" />
<TextView
android:id="@+id/usernameHeaderText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/preferences_passwords_saved_logins_username"
android:textColor="?primaryText"
android:textSize="12sp"
app:layout_constraintBottom_toTopOf="@id/usernameInfoText"
app:layout_constraintEnd_toStartOf="@id/copyUsernameItem"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/copySiteItem"
app:layout_constraintVertical_chainStyle="packed" />
<TextView
android:id="@+id/usernameInfoText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="@id/copyUsernameItem"
app:layout_constraintEnd_toEndOf="@id/usernameHeaderText"
app:layout_constraintStart_toStartOf="@id/usernameHeaderText"
app:layout_constraintTop_toBottomOf="@id/usernameHeaderText"
app:layout_constraintVertical_chainStyle="packed"
tools:text="Info" />
<ImageButton
android:id="@+id/copyUsernameItem"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/saved_login_copy_username"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/copySiteItem"
app:srcCompat="@drawable/ic_copy" />
<TextView
android:id="@+id/passwordHeaderText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/preferences_passwords_saved_logins_password"
android:textColor="?primaryText"
android:textSize="12sp"
app:layout_constraintBottom_toTopOf="@id/passwordInfoText"
app:layout_constraintEnd_toStartOf="@id/revealPasswordItem"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/copyUsernameItem"
app:layout_constraintVertical_chainStyle="packed" />
<TextView
android:id="@+id/passwordInfoText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:inputType="textPassword|text"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/passwordHeaderText"
app:layout_constraintStart_toStartOf="@id/passwordHeaderText"
app:layout_constraintTop_toBottomOf="@id/passwordHeaderText"
app:layout_constraintVertical_chainStyle="packed"
tools:text="Info" />
<ImageButton
android:id="@+id/revealPasswordItem"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginEnd="16dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/saved_login_reveal_password"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/copyPasswordItem"
app:layout_constraintTop_toBottomOf="@id/copyUsernameItem"
app:srcCompat="@drawable/ic_password_reveal" />
<ImageButton
android:id="@+id/copyPasswordItem"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/saved_logins_copy_password"
app:layout_constraintBottom_toBottomOf="@id/revealPasswordItem"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/revealPasswordItem"
app:srcCompat="@drawable/ic_copy" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?><!-- 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/. -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/savedLoginsLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="org.mozilla.fenix.logins.SavedLoginsFragment" />

View File

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?><!-- 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/. -->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:minHeight="?android:attr/listPreferredItemHeightSmall">
<ImageView
android:id="@+id/favicon_image"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="16dp"
android:adjustViewBounds="true"
android:importantForAccessibility="no"
android:scaleType="fitCenter"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/domainView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_marginEnd="12dp"
android:layout_weight="1"
android:ellipsize="middle"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceListItem"
android:textColor="?primaryText"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/favicon_image"
app:layout_constraintTop_toTopOf="parent"
tools:text="mozilla.org" />
<TextView
android:id="@+id/userView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:layout_marginBottom="4dp"
android:layout_weight="1"
android:ellipsize="middle"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:singleLine="true"
android:textColor="?secondaryText"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/favicon_image"
app:layout_constraintTop_toBottomOf="@id/domainView"
tools:text="mozilla.org" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -181,8 +181,8 @@
android:id="@+id/action_browserFragment_to_createCollectionFragment"
app:destination="@id/collectionCreationFragment" />
<action
android:id="@+id/action_browserFragment_to_createShortcutFragment"
app:destination="@id/createShortcutFragment" />
android:id="@+id/action_browserFragment_to_createShortcutFragment"
app:destination="@id/createShortcutFragment" />
<action
android:id="@+id/action_browserFragment_to_shareFragment"
app:destination="@id/shareFragment" />
@ -198,8 +198,14 @@
android:id="@+id/externalAppBrowserFragment"
android:name="org.mozilla.fenix.customtabs.ExternalAppBrowserFragment"
tools:layout="@layout/fragment_browser">
<argument android:name="activeSessionId" app:argType="string" app:nullable="true" />
<argument android:name="webAppManifest" app:argType="string" app:nullable="true"/>
<argument
android:name="activeSessionId"
app:argType="string"
app:nullable="true" />
<argument
android:name="webAppManifest"
app:argType="string"
app:nullable="true" />
<action
android:id="@+id/action_externalAppBrowserFragment_to_shareFragment"
app:destination="@id/shareFragment" />
@ -314,6 +320,24 @@
app:destination="@id/bookmarkSelectFolderFragment" />
</fragment>
<fragment
android:id="@+id/loginsFragment"
android:name="org.mozilla.fenix.settings.LoginsFragment"
android:label="@string/preferences_passwords_logins_and_passwords">
<action
android:id="@+id/action_loginsFragment_to_savedLoginsFragment"
app:destination="@id/savedLoginsFragment" />
<action
android:id="@+id/action_loginsFragment_to_accountSettingsFragment"
app:destination="@id/accountSettingsFragment" />
<action
android:id="@+id/action_loginsFragment_to_accountProblemFragment"
app:destination="@id/accountProblemFragment" />
<action
android:id="@+id/action_loginsFragment_to_turnOnSyncFragment"
app:destination="@id/turnOnSyncFragment" />
</fragment>
<fragment
android:id="@+id/settingsFragment"
android:name="org.mozilla.fenix.settings.SettingsFragment"
@ -329,6 +353,9 @@
<action
android:id="@+id/action_settingsFragment_to_sitePermissionsFragment"
app:destination="@id/sitePermissionsFragment" />
<action
android:id="@+id/action_settingsFragment_to_loginsFragment"
app:destination="@id/loginsFragment" />
<action
android:id="@+id/action_settingsFragment_to_accessibilityFragment"
app:destination="@id/accessibilityFragment" />
@ -494,10 +521,10 @@
app:nullable="false" />
</dialog>
<dialog
android:id="@+id/createShortcutFragment"
android:name="org.mozilla.fenix.shortcut.CreateShortcutFragment"
android:label="fragment_create_shortcut"
tools:layout="@layout/fragment_create_shortcut" />
android:id="@+id/createShortcutFragment"
android:name="org.mozilla.fenix.shortcut.CreateShortcutFragment"
android:label="fragment_create_shortcut"
tools:layout="@layout/fragment_create_shortcut" />
<dialog
android:id="@+id/shareFragment"
android:name="org.mozilla.fenix.share.ShareFragment"
@ -618,4 +645,22 @@
android:id="@+id/action_defaultBrowserSettingsFragment_to_browserFragment"
app:destination="@id/browserFragment" />
</fragment>
<fragment
android:id="@+id/savedLoginsFragment"
android:name="org.mozilla.fenix.logins.SavedLoginsFragment"
android:label="fragment_saved_logins"
tools:layout="@layout/fragment_saved_logins">
<action
android:id="@+id/action_savedLoginsFragment_to_savedLoginSiteInfoFragment"
app:destination="@id/savedLoginSiteInfoFragment" />
</fragment>
<fragment
android:id="@+id/savedLoginSiteInfoFragment"
android:name="org.mozilla.fenix.logins.SavedLoginSiteInfoFragment"
android:label="fragment_saved_login_site_info"
tools:layout="@layout/fragment_saved_login_site_info">
<argument
android:name="savedLoginItem"
app:argType="org.mozilla.fenix.logins.SavedLoginsItem" />
</fragment>
</navigation>

View File

@ -55,6 +55,7 @@
<string name="pref_key_sync_now" translatable="false">pref_key_sync_now</string>
<string name="pref_key_sync_history" translatable="false">pref_key_sync_history</string>
<string name="pref_key_sync_bookmarks" translatable="false">pref_key_sync_bookmarks</string>
<string name="pref_key_sync_logins" translatable="false">pref_key_sync_logins</string>
<string name="pref_key_sign_out" translatable="false">pref_key_sign_out</string>
<string name="pref_key_cached_account" translatable="false">pref_key_cached_account</string>
<string name="pref_key_sync_pair" translatable="false">pref_key_sync_pair</string>
@ -100,6 +101,10 @@
<string name="pref_key_tracking_protection_strict" translatable="false">pref_key_tracking_protection_strict</string>
<string name="pref_key_tracking_protection_onboarding" translatable="false">pref_key_tracking_protection_onboarding</string>
<!-- Logins Settings -->
<string name="pref_key_saved_logins" translatable="false">pref_key_saved_logins</string>
<string name="pref_key_password_sync_logins" translatable="false">pref_key_password_sync_logins</string>
<!-- Privacy Settings -->
<string name="pref_key_open_links_in_a_private_tab" translatable="false">pref_key_open_links_in_a_private_tab</string>

View File

@ -196,6 +196,8 @@
<string name="preferences_sync_history">History</string>
<!-- Preference for syncing bookmarks -->
<string name="preferences_sync_bookmarks">Bookmarks</string>
<!-- Preference for syncing logins -->
<string name="preferences_sync_logins">Logins</string>
<!-- Preference for signing out -->
<string name="preferences_sign_out">Sign out</string>
<!-- Preference displays and allows changing current FxA device name -->
@ -920,7 +922,7 @@
<string name="browser_toolbar_long_press_popup_paste_and_go">Paste &amp; Go</string>
<!-- Paste the text in the clipboard -->
<string name="browser_toolbar_long_press_popup_paste">Paste</string>
<!-- Title text for the Add To Homescreen dialog -->
<string name="add_to_homescreen_title">Add to Home screen</string>
<!-- Cancel button text for the Add to Homescreen dialog -->
@ -984,5 +986,20 @@
<string name="logins_doorhanger_save_confirmation">Save</string>
<!-- Negative confirmation that Fenix should not save the new or updated login -->
<string name="logins_doorhanger_save_dont_save">Don\'t save</string>
<!-- Shown in snackbar to tell user that the password has been copied -->
<string name="logins_password_copied">Password copied to clipboard</string>
<!-- Shown in snackbar to tell user that the username has been copied -->
<string name="logins_username_copied">Username copied to clipboard</string>
<!-- Shown in snackbar to tell user that the site has been copied -->
<string name="logins_site_copied">Site copied to clipboard</string>
<!-- Content Description (for screenreaders etc) read for the button to copy a password in logins-->
<string name="saved_logins_copy_password">Copy password</string>
<!-- Content Description (for screenreaders etc) read for the button to copy a username in logins -->
<string name="saved_login_copy_username">Copy username</string>
<!-- Content Description (for screenreaders etc) read for the button to copy a site in logins -->
<string name="saved_login_copy_site">Copy site</string>
<!-- Content Description (for screenreaders etc) read for the button to reveal a password in logins -->
<string name="saved_login_reveal_password">Show password</string>
<!-- Content Description (for screenreaders etc) read for the button to hide a password in logins -->
<string name="saved_login_hide_password">Hide password</string>
</resources>

View File

@ -2,33 +2,39 @@
<!-- 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/. -->
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.preference.Preference
android:key="@string/pref_key_sync_now"
android:title="@string/preferences_sync_now" />
android:key="@string/pref_key_sync_now"
android:title="@string/preferences_sync_now" />
<androidx.preference.Preference
android:key="@string/pref_key_sign_out"
android:title="@string/preferences_sign_out" />
<androidx.preference.PreferenceCategory
android:key="@string/preferences_sync_category"
android:title="@string/preferences_sync_category">
android:key="@string/preferences_sync_category"
android:title="@string/preferences_sync_category">
<androidx.preference.CheckBoxPreference
android:key="@string/pref_key_sync_bookmarks"
android:defaultValue="true"
android:title="@string/preferences_sync_bookmarks" />
android:defaultValue="true"
android:key="@string/pref_key_sync_bookmarks"
android:title="@string/preferences_sync_bookmarks" />
<androidx.preference.CheckBoxPreference
android:key="@string/pref_key_sync_history"
android:defaultValue="true"
android:title="@string/preferences_sync_history" />
android:defaultValue="true"
android:key="@string/pref_key_sync_history"
android:title="@string/preferences_sync_history" />
<androidx.preference.CheckBoxPreference
android:defaultValue="false"
android:key="@string/pref_key_sync_logins"
android:title="@string/preferences_sync_logins"
app:isPreferenceVisible="false" />
<androidx.preference.EditTextPreference
android:key="@string/pref_key_sync_device_name"
android:title="@string/preferences_sync_device_name" />
android:key="@string/pref_key_sync_device_name"
android:title="@string/preferences_sync_device_name" />
</androidx.preference.PreferenceCategory>
<androidx.preference.Preference
android:key="@string/pref_key_sign_out"
android:title="@string/preferences_sign_out" />
</PreferenceScreen>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?><!-- 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/. -->
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<androidx.preference.Preference
android:key="@string/pref_key_password_sync_logins"
android:summary="@string/preferences_passwords_sync_logins_off"
android:title="@string/preferences_passwords_sync_logins" />
<androidx.preference.Preference
android:key="@string/pref_key_saved_logins"
android:title="@string/preferences_passwords_saved_logins" />
</androidx.preference.PreferenceScreen>

View File

@ -59,6 +59,11 @@
android:icon="@drawable/ic_tracking_protection_enabled"
android:key="@string/pref_key_tracking_protection_settings"
android:title="@string/preference_enhanced_tracking_protection" />
<androidx.preference.Preference
app:isPreferenceVisible="false"
android:icon="@drawable/ic_login"
android:key="@string/pref_key_passwords"
android:title="@string/preferences_passwords" />
<androidx.preference.Preference
android:icon="@drawable/ic_private_browsing"
android:key="@string/pref_key_add_private_browsing_shortcut"

View File

@ -0,0 +1,27 @@
/* 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.logins
import io.mockk.mockk
import io.mockk.verify
import org.junit.Test
class SavedLoginsInteractorTest {
@Test
fun itemClicked() {
val savedLoginClicked: (SavedLoginsItem) -> Unit = mockk(relaxed = true)
val interactor = SavedLoginsInteractor(
savedLoginClicked
)
val item = SavedLoginsItem("mozilla.org", "username", "password")
interactor.itemClicked(item)
verify {
savedLoginClicked.invoke(item)
}
}
}

View File

@ -136,6 +136,8 @@ object Deps {
const val mozilla_feature_sendtab = "org.mozilla.components:feature-sendtab:${Versions.mozilla_android_components}"
const val mozilla_feature_webcompat = "org.mozilla.components:feature-webcompat:${Versions.mozilla_android_components}"
const val mozilla_service_sync_logins =
"org.mozilla.components:service-sync-logins:${Versions.mozilla_android_components}"
const val mozilla_service_firefox_accounts = "org.mozilla.components:service-firefox-accounts:${Versions.mozilla_android_components}"
const val mozilla_service_fretboard = "org.mozilla.components:service-fretboard:${Versions.mozilla_android_components}"
const val mozilla_service_glean = "org.mozilla.components:service-glean:${Versions.mozilla_android_components}"