For #5544: Bare Minimum Logins UI
parent
6f5fba7718
commit
f49331de55
|
@ -384,6 +384,7 @@ dependencies {
|
||||||
implementation Deps.mozilla_feature_sendtab
|
implementation Deps.mozilla_feature_sendtab
|
||||||
implementation Deps.mozilla_feature_webcompat
|
implementation Deps.mozilla_feature_webcompat
|
||||||
|
|
||||||
|
implementation Deps.mozilla_service_sync_logins
|
||||||
implementation Deps.mozilla_service_firefox_accounts
|
implementation Deps.mozilla_service_firefox_accounts
|
||||||
implementation Deps.mozilla_service_fretboard
|
implementation Deps.mozilla_service_fretboard
|
||||||
implementation Deps.mozilla_service_glean
|
implementation Deps.mozilla_service_glean
|
||||||
|
|
|
@ -56,4 +56,9 @@ object FeatureFlags {
|
||||||
val progressiveWebApps = nightly or debug
|
val progressiveWebApps = nightly or debug
|
||||||
|
|
||||||
val forceZoomPreference = nightly or debug
|
val forceZoomPreference = nightly or debug
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gives option in Settings to see logins and sync logins
|
||||||
|
*/
|
||||||
|
const val logins = false
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,6 +85,7 @@ class BackgroundServices(
|
||||||
val syncConfig = if (context.isInExperiment(Experiments.asFeatureSyncDisabled)) {
|
val syncConfig = if (context.isInExperiment(Experiments.asFeatureSyncDisabled)) {
|
||||||
null
|
null
|
||||||
} else {
|
} 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
|
SyncConfig(setOf(SyncEngine.History, SyncEngine.Bookmarks), syncPeriodInMinutes = 240L) // four hours
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,9 +94,11 @@ class BackgroundServices(
|
||||||
val push by lazy { makePushConfig()?.let { makePush(it) } }
|
val push by lazy { makePushConfig()?.let { makePush(it) } }
|
||||||
|
|
||||||
init {
|
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.History to historyStorage)
|
||||||
GlobalSyncableStoreProvider.configureStore(SyncEngine.Bookmarks to bookmarkStorage)
|
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 {
|
private val deviceEventObserver = object : DeviceEventsObserver {
|
||||||
|
|
|
@ -7,6 +7,7 @@ package org.mozilla.fenix.components
|
||||||
import GeckoProvider
|
import GeckoProvider
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
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.media.state.MediaStateMachine
|
||||||
import mozilla.components.feature.session.HistoryDelegate
|
import mozilla.components.feature.session.HistoryDelegate
|
||||||
import mozilla.components.feature.webcompat.WebCompatFeature
|
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.AppRequestInterceptor
|
||||||
import org.mozilla.fenix.FeatureFlags
|
import org.mozilla.fenix.FeatureFlags
|
||||||
import org.mozilla.fenix.ext.components
|
import org.mozilla.fenix.ext.components
|
||||||
import org.mozilla.fenix.ext.settings
|
import org.mozilla.fenix.ext.settings
|
||||||
import org.mozilla.fenix.test.Mockable
|
import org.mozilla.fenix.test.Mockable
|
||||||
|
import java.io.File
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -152,6 +156,19 @@ class Core(private val context: Context) {
|
||||||
|
|
||||||
val permissionStorage by lazy { PermissionStorage(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.
|
* Constructs a [TrackingProtectionPolicy] based on current preferences.
|
||||||
*
|
*
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,6 +25,7 @@ import mozilla.components.concept.sync.OAuthAccount
|
||||||
import mozilla.components.concept.sync.Profile
|
import mozilla.components.concept.sync.Profile
|
||||||
import org.mozilla.fenix.BrowserDirection
|
import org.mozilla.fenix.BrowserDirection
|
||||||
import org.mozilla.fenix.Config
|
import org.mozilla.fenix.Config
|
||||||
|
import org.mozilla.fenix.FeatureFlags
|
||||||
import org.mozilla.fenix.FenixApplication
|
import org.mozilla.fenix.FenixApplication
|
||||||
import org.mozilla.fenix.HomeActivity
|
import org.mozilla.fenix.HomeActivity
|
||||||
import org.mozilla.fenix.R
|
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_language
|
||||||
import org.mozilla.fenix.R.string.pref_key_leakcanary
|
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_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_privacy_link
|
||||||
import org.mozilla.fenix.R.string.pref_key_rate
|
import org.mozilla.fenix.R.string.pref_key_rate
|
||||||
import org.mozilla.fenix.R.string.pref_key_remote_debugging
|
import org.mozilla.fenix.R.string.pref_key_remote_debugging
|
||||||
|
@ -105,7 +107,7 @@ class SettingsFragment : PreferenceFragmentCompat(), AccountObserver {
|
||||||
(activity as AppCompatActivity).supportActionBar?.show()
|
(activity as AppCompatActivity).supportActionBar?.show()
|
||||||
|
|
||||||
val trackingProtectionPreference =
|
val trackingProtectionPreference =
|
||||||
findPreference<Preference>(getPreferenceKey(R.string.pref_key_tracking_protection_settings))
|
findPreference<Preference>(getPreferenceKey(pref_key_tracking_protection_settings))
|
||||||
trackingProtectionPreference?.summary = context?.let {
|
trackingProtectionPreference?.summary = context?.let {
|
||||||
if (it.settings().shouldUseTrackingProtection) {
|
if (it.settings().shouldUseTrackingProtection) {
|
||||||
getString(R.string.tracking_protection_on)
|
getString(R.string.tracking_protection_on)
|
||||||
|
@ -115,7 +117,7 @@ class SettingsFragment : PreferenceFragmentCompat(), AccountObserver {
|
||||||
}
|
}
|
||||||
|
|
||||||
val themesPreference =
|
val themesPreference =
|
||||||
findPreference<Preference>(getPreferenceKey(R.string.pref_key_theme))
|
findPreference<Preference>(getPreferenceKey(pref_key_theme))
|
||||||
themesPreference?.summary = context?.settings()?.themeSettingString
|
themesPreference?.summary = context?.settings()?.themeSettingString
|
||||||
|
|
||||||
val aboutPreference = findPreference<Preference>(getPreferenceKey(R.string.pref_key_about))
|
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)
|
aboutPreference?.title = getString(R.string.preferences_about, appName)
|
||||||
|
|
||||||
val deleteBrowsingDataPreference =
|
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 {
|
deleteBrowsingDataPreference?.summary = context?.let {
|
||||||
if (it.settings().shouldDeleteBrowsingDataOnQuit) {
|
if (it.settings().shouldDeleteBrowsingDataOnQuit) {
|
||||||
getString(R.string.delete_browsing_data_quit_on)
|
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)
|
isVisible = !PrivateShortcutCreateManager.doesPrivateBrowsingPinnedShortcutExist(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
setupPreferences()
|
setupPreferences()
|
||||||
|
|
||||||
updateAccountUIState(context!!, requireComponents.backgroundServices.accountManager.accountProfile())
|
updateAccountUIState(context!!, requireComponents.backgroundServices.accountManager.accountProfile())
|
||||||
|
|
||||||
|
updatePreferenceVisibilityForFeatureFlags()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updatePreferenceVisibilityForFeatureFlags() {
|
||||||
|
findPreference<Preference>(getPreferenceKey(pref_key_passwords))?.apply {
|
||||||
|
isVisible = FeatureFlags.logins
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("ComplexMethod", "LongMethod")
|
@Suppress("ComplexMethod", "LongMethod")
|
||||||
|
@ -191,6 +205,9 @@ class SettingsFragment : PreferenceFragmentCompat(), AccountObserver {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
resources.getString(pref_key_passwords) -> {
|
||||||
|
navigateToLoginsSettingsFragment()
|
||||||
|
}
|
||||||
resources.getString(pref_key_about) -> {
|
resources.getString(pref_key_about) -> {
|
||||||
navigateToAbout()
|
navigateToAbout()
|
||||||
}
|
}
|
||||||
|
@ -262,6 +279,11 @@ class SettingsFragment : PreferenceFragmentCompat(), AccountObserver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun navigateToLoginsSettingsFragment() {
|
||||||
|
val directions = SettingsFragmentDirections.actionSettingsFragmentToLoginsFragment()
|
||||||
|
Navigation.findNavController(view!!).navigate(directions)
|
||||||
|
}
|
||||||
|
|
||||||
private fun navigateToSearchEngineSettings() {
|
private fun navigateToSearchEngineSettings() {
|
||||||
val directions = SettingsFragmentDirections.actionSettingsFragmentToSearchEngineFragment()
|
val directions = SettingsFragmentDirections.actionSettingsFragmentToSearchEngineFragment()
|
||||||
Navigation.findNavController(view!!).navigate(directions)
|
Navigation.findNavController(view!!).navigate(directions)
|
||||||
|
|
|
@ -28,6 +28,7 @@ import mozilla.components.service.fxa.manager.SyncEnginesStorage
|
||||||
import mozilla.components.service.fxa.sync.SyncReason
|
import mozilla.components.service.fxa.sync.SyncReason
|
||||||
import mozilla.components.service.fxa.sync.SyncStatusObserver
|
import mozilla.components.service.fxa.sync.SyncStatusObserver
|
||||||
import mozilla.components.service.fxa.sync.getLastSynced
|
import mozilla.components.service.fxa.sync.getLastSynced
|
||||||
|
import org.mozilla.fenix.FeatureFlags
|
||||||
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.StoreProvider
|
||||||
|
@ -36,7 +37,7 @@ import org.mozilla.fenix.ext.components
|
||||||
import org.mozilla.fenix.ext.getPreferenceKey
|
import org.mozilla.fenix.ext.getPreferenceKey
|
||||||
import org.mozilla.fenix.ext.requireComponents
|
import org.mozilla.fenix.ext.requireComponents
|
||||||
|
|
||||||
@SuppressWarnings("TooManyFunctions")
|
@SuppressWarnings("TooManyFunctions", "LargeClass")
|
||||||
class AccountSettingsFragment : PreferenceFragmentCompat() {
|
class AccountSettingsFragment : PreferenceFragmentCompat() {
|
||||||
private lateinit var accountManager: FxaAccountManager
|
private lateinit var accountManager: FxaAccountManager
|
||||||
private lateinit var accountSettingsStore: AccountSettingsFragmentStore
|
private lateinit var accountSettingsStore: AccountSettingsFragmentStore
|
||||||
|
@ -96,7 +97,7 @@ class AccountSettingsFragment : PreferenceFragmentCompat() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("ComplexMethod")
|
@Suppress("ComplexMethod", "LongMethod")
|
||||||
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)
|
||||||
|
|
||||||
|
@ -108,7 +109,9 @@ class AccountSettingsFragment : PreferenceFragmentCompat() {
|
||||||
LastSyncTime.Never
|
LastSyncTime.Never
|
||||||
else
|
else
|
||||||
LastSyncTime.Success(getLastSynced(requireContext())),
|
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 {
|
findPreference<CheckBoxPreference>(historyNameKey)?.apply {
|
||||||
setOnPreferenceChangeListener { _, newValue ->
|
setOnPreferenceChangeListener { _, newValue ->
|
||||||
SyncEnginesStorage(context).setStatus(SyncEngine.History, newValue as Boolean)
|
SyncEnginesStorage(context).setStatus(SyncEngine.History, newValue as Boolean)
|
||||||
|
@Suppress("DeferredResultUnused")
|
||||||
context.components.backgroundServices.accountManager.syncNowAsync(SyncReason.EngineChange)
|
context.components.backgroundServices.accountManager.syncNowAsync(SyncReason.EngineChange)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
@ -167,6 +171,17 @@ class AccountSettingsFragment : PreferenceFragmentCompat() {
|
||||||
findPreference<CheckBoxPreference>(bookmarksNameKey)?.apply {
|
findPreference<CheckBoxPreference>(bookmarksNameKey)?.apply {
|
||||||
setOnPreferenceChangeListener { _, newValue ->
|
setOnPreferenceChangeListener { _, newValue ->
|
||||||
SyncEnginesStorage(context).setStatus(SyncEngine.Bookmarks, newValue as Boolean)
|
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)
|
context.components.backgroundServices.accountManager.syncNowAsync(SyncReason.EngineChange)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
@ -193,13 +208,20 @@ class AccountSettingsFragment : PreferenceFragmentCompat() {
|
||||||
isEnabled = syncEnginesStatus.containsKey(SyncEngine.History)
|
isEnabled = syncEnginesStatus.containsKey(SyncEngine.History)
|
||||||
isChecked = syncEnginesStatus.getOrElse(SyncEngine.History) { true }
|
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() {
|
private fun syncNow() {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
requireComponents.analytics.metrics.track(Event.SyncAccountSyncNow)
|
requireComponents.analytics.metrics.track(Event.SyncAccountSyncNow)
|
||||||
// Trigger a sync.
|
// Trigger a sync.
|
||||||
requireComponents.backgroundServices.accountManager.syncNowAsync(SyncReason.User).await()
|
requireComponents.backgroundServices.accountManager.syncNowAsync(SyncReason.User)
|
||||||
|
.await()
|
||||||
// Poll for device events & update devices.
|
// Poll for device events & update devices.
|
||||||
accountManager.authenticatedAccount()
|
accountManager.authenticatedAccount()
|
||||||
?.deviceConstellation()?.run {
|
?.deviceConstellation()?.run {
|
||||||
|
@ -284,7 +306,11 @@ class AccountSettingsFragment : PreferenceFragmentCompat() {
|
||||||
pref.isEnabled = true
|
pref.isEnabled = true
|
||||||
|
|
||||||
val failedTime = getLastSynced(requireContext())
|
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
|
@ -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>
|
|
@ -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>
|
|
@ -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" />
|
|
@ -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>
|
|
@ -198,8 +198,14 @@
|
||||||
android:id="@+id/externalAppBrowserFragment"
|
android:id="@+id/externalAppBrowserFragment"
|
||||||
android:name="org.mozilla.fenix.customtabs.ExternalAppBrowserFragment"
|
android:name="org.mozilla.fenix.customtabs.ExternalAppBrowserFragment"
|
||||||
tools:layout="@layout/fragment_browser">
|
tools:layout="@layout/fragment_browser">
|
||||||
<argument android:name="activeSessionId" app:argType="string" app:nullable="true" />
|
<argument
|
||||||
<argument android:name="webAppManifest" app:argType="string" app:nullable="true"/>
|
android:name="activeSessionId"
|
||||||
|
app:argType="string"
|
||||||
|
app:nullable="true" />
|
||||||
|
<argument
|
||||||
|
android:name="webAppManifest"
|
||||||
|
app:argType="string"
|
||||||
|
app:nullable="true" />
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_externalAppBrowserFragment_to_shareFragment"
|
android:id="@+id/action_externalAppBrowserFragment_to_shareFragment"
|
||||||
app:destination="@id/shareFragment" />
|
app:destination="@id/shareFragment" />
|
||||||
|
@ -314,6 +320,24 @@
|
||||||
app:destination="@id/bookmarkSelectFolderFragment" />
|
app:destination="@id/bookmarkSelectFolderFragment" />
|
||||||
</fragment>
|
</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
|
<fragment
|
||||||
android:id="@+id/settingsFragment"
|
android:id="@+id/settingsFragment"
|
||||||
android:name="org.mozilla.fenix.settings.SettingsFragment"
|
android:name="org.mozilla.fenix.settings.SettingsFragment"
|
||||||
|
@ -329,6 +353,9 @@
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_settingsFragment_to_sitePermissionsFragment"
|
android:id="@+id/action_settingsFragment_to_sitePermissionsFragment"
|
||||||
app:destination="@id/sitePermissionsFragment" />
|
app:destination="@id/sitePermissionsFragment" />
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_settingsFragment_to_loginsFragment"
|
||||||
|
app:destination="@id/loginsFragment" />
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_settingsFragment_to_accessibilityFragment"
|
android:id="@+id/action_settingsFragment_to_accessibilityFragment"
|
||||||
app:destination="@id/accessibilityFragment" />
|
app:destination="@id/accessibilityFragment" />
|
||||||
|
@ -618,4 +645,22 @@
|
||||||
android:id="@+id/action_defaultBrowserSettingsFragment_to_browserFragment"
|
android:id="@+id/action_defaultBrowserSettingsFragment_to_browserFragment"
|
||||||
app:destination="@id/browserFragment" />
|
app:destination="@id/browserFragment" />
|
||||||
</fragment>
|
</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>
|
</navigation>
|
||||||
|
|
|
@ -55,6 +55,7 @@
|
||||||
<string name="pref_key_sync_now" translatable="false">pref_key_sync_now</string>
|
<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_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_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_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_cached_account" translatable="false">pref_key_cached_account</string>
|
||||||
<string name="pref_key_sync_pair" translatable="false">pref_key_sync_pair</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_strict" translatable="false">pref_key_tracking_protection_strict</string>
|
||||||
<string name="pref_key_tracking_protection_onboarding" translatable="false">pref_key_tracking_protection_onboarding</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 -->
|
<!-- Privacy Settings -->
|
||||||
<string name="pref_key_open_links_in_a_private_tab" translatable="false">pref_key_open_links_in_a_private_tab</string>
|
<string name="pref_key_open_links_in_a_private_tab" translatable="false">pref_key_open_links_in_a_private_tab</string>
|
||||||
|
|
||||||
|
|
|
@ -196,6 +196,8 @@
|
||||||
<string name="preferences_sync_history">History</string>
|
<string name="preferences_sync_history">History</string>
|
||||||
<!-- Preference for syncing bookmarks -->
|
<!-- Preference for syncing bookmarks -->
|
||||||
<string name="preferences_sync_bookmarks">Bookmarks</string>
|
<string name="preferences_sync_bookmarks">Bookmarks</string>
|
||||||
|
<!-- Preference for syncing logins -->
|
||||||
|
<string name="preferences_sync_logins">Logins</string>
|
||||||
<!-- Preference for signing out -->
|
<!-- Preference for signing out -->
|
||||||
<string name="preferences_sign_out">Sign out</string>
|
<string name="preferences_sign_out">Sign out</string>
|
||||||
<!-- Preference displays and allows changing current FxA device name -->
|
<!-- Preference displays and allows changing current FxA device name -->
|
||||||
|
@ -984,5 +986,20 @@
|
||||||
<string name="logins_doorhanger_save_confirmation">Save</string>
|
<string name="logins_doorhanger_save_confirmation">Save</string>
|
||||||
<!-- Negative confirmation that Fenix should not save the new or updated login -->
|
<!-- Negative confirmation that Fenix should not save the new or updated login -->
|
||||||
<string name="logins_doorhanger_save_dont_save">Don\'t save</string>
|
<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>
|
</resources>
|
||||||
|
|
|
@ -2,33 +2,39 @@
|
||||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
<!-- 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
|
- 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/. -->
|
||||||
|
<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
|
<androidx.preference.Preference
|
||||||
android:key="@string/pref_key_sync_now"
|
android:key="@string/pref_key_sync_now"
|
||||||
android:title="@string/preferences_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
|
<androidx.preference.PreferenceCategory
|
||||||
android:key="@string/preferences_sync_category"
|
android:key="@string/preferences_sync_category"
|
||||||
android:title="@string/preferences_sync_category">
|
android:title="@string/preferences_sync_category">
|
||||||
|
|
||||||
<androidx.preference.CheckBoxPreference
|
<androidx.preference.CheckBoxPreference
|
||||||
android:key="@string/pref_key_sync_bookmarks"
|
|
||||||
android:defaultValue="true"
|
android:defaultValue="true"
|
||||||
|
android:key="@string/pref_key_sync_bookmarks"
|
||||||
android:title="@string/preferences_sync_bookmarks" />
|
android:title="@string/preferences_sync_bookmarks" />
|
||||||
|
|
||||||
<androidx.preference.CheckBoxPreference
|
<androidx.preference.CheckBoxPreference
|
||||||
android:key="@string/pref_key_sync_history"
|
|
||||||
android:defaultValue="true"
|
android:defaultValue="true"
|
||||||
|
android:key="@string/pref_key_sync_history"
|
||||||
android:title="@string/preferences_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
|
<androidx.preference.EditTextPreference
|
||||||
android:key="@string/pref_key_sync_device_name"
|
android:key="@string/pref_key_sync_device_name"
|
||||||
android:title="@string/preferences_sync_device_name" />
|
android:title="@string/preferences_sync_device_name" />
|
||||||
|
|
||||||
</androidx.preference.PreferenceCategory>
|
</androidx.preference.PreferenceCategory>
|
||||||
|
|
||||||
<androidx.preference.Preference
|
|
||||||
android:key="@string/pref_key_sign_out"
|
|
||||||
android:title="@string/preferences_sign_out" />
|
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
|
@ -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>
|
|
@ -59,6 +59,11 @@
|
||||||
android:icon="@drawable/ic_tracking_protection_enabled"
|
android:icon="@drawable/ic_tracking_protection_enabled"
|
||||||
android:key="@string/pref_key_tracking_protection_settings"
|
android:key="@string/pref_key_tracking_protection_settings"
|
||||||
android:title="@string/preference_enhanced_tracking_protection" />
|
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
|
<androidx.preference.Preference
|
||||||
android:icon="@drawable/ic_private_browsing"
|
android:icon="@drawable/ic_private_browsing"
|
||||||
android:key="@string/pref_key_add_private_browsing_shortcut"
|
android:key="@string/pref_key_add_private_browsing_shortcut"
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -136,6 +136,8 @@ object Deps {
|
||||||
const val mozilla_feature_sendtab = "org.mozilla.components:feature-sendtab:${Versions.mozilla_android_components}"
|
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_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_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_fretboard = "org.mozilla.components:service-fretboard:${Versions.mozilla_android_components}"
|
||||||
const val mozilla_service_glean = "org.mozilla.components:service-glean:${Versions.mozilla_android_components}"
|
const val mozilla_service_glean = "org.mozilla.components:service-glean:${Versions.mozilla_android_components}"
|
||||||
|
|
Loading…
Reference in New Issue