For #5544: Bare Minimum Logins UI
parent
6f5fba7718
commit
f49331de55
@ -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)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
<?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/. -->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="14dp"
|
||||||
|
android:height="17dp"
|
||||||
|
android:viewportWidth="14"
|
||||||
|
android:viewportHeight="17">
|
||||||
|
<group>
|
||||||
|
<clip-path android:pathData="M13.707,9.1431L10.707,6.1431C10.5195,5.9556 10.2652,5.8502 10,5.8501L9,5.8501L9,4.8501C8.9999,4.5849 8.8945,4.3306 8.707,4.1431L5.707,1.1431C5.5195,0.9556 5.2652,0.8501 5,0.8501L2,0.8501C0.8954,0.8501 0,1.7455 0,2.8501L0,9.8501C0,10.9547 0.8954,11.8501 2,11.8501L5,11.8501L5,14.8501C5,15.9547 5.8954,16.8501 7,16.8501L12,16.8501C13.1046,16.8501 14,15.9547 14,14.8501L14,9.8501C14,9.5849 13.8945,9.3306 13.707,9.1431ZM11.586,9.8501L10,9.8501L10,8.2641L11.586,9.8501ZM6.586,4.8501L5,4.8501L5,3.2641L6.586,4.8501ZM5,7.8501L5,9.8501L2,9.8501L2,2.8501L4,2.8501L4,5.3501C4,5.6262 4.2239,5.8501 4.5,5.8501L7,5.8501C5.8954,5.8501 5,6.7455 5,7.8501ZM7,14.8501L7,7.8501L9,7.8501L9,10.3501C9,10.6262 9.2239,10.8501 9.5,10.8501L12,10.8501L12,14.8501L7,14.8501Z M 0,0" />
|
||||||
|
<path
|
||||||
|
android:fillColor="?primaryText"
|
||||||
|
android:pathData="M-1,0.8501h16v16h-16z" />
|
||||||
|
</group>
|
||||||
|
</vector>
|
@ -0,0 +1,15 @@
|
|||||||
|
<?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/. -->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="16dp"
|
||||||
|
android:height="15dp"
|
||||||
|
android:viewportWidth="16"
|
||||||
|
android:viewportHeight="15">
|
||||||
|
<group>
|
||||||
|
<clip-path android:pathData="M13.293,1.1431C13.6835,0.7527 14.3165,0.7527 14.707,1.1431C15.0974,1.5336 15.0974,2.1666 14.707,2.5571L14.707,2.5571L2.707,14.5571C2.4559,14.8171 2.0841,14.9213 1.7345,14.8298C1.3849,14.7383 1.1118,14.4652 1.0203,14.1156C0.9288,13.766 1.033,13.3942 1.293,13.1431L1.293,13.1431L2.577,11.8561C1.3904,10.891 0.5105,9.6011 0.045,8.1441C-0.0152,7.9507 -0.0152,7.7435 0.045,7.5501C0.814,5.2568 2.5392,3.4093 4.7746,2.4852C7.01,1.5612 9.5361,1.6512 11.7,2.7321L11.7,2.7321ZM14.235,4.6121C15.0172,5.4532 15.6045,6.4563 15.955,7.5501C16.0164,7.7454 16.0164,7.9548 15.955,8.1501C14.8179,11.5726 11.6064,13.8738 8,13.8501C7.1203,13.849 6.2462,13.711 5.409,13.4411L5.409,13.4411L7.07,11.7801C7.3779,11.8256 7.6887,11.849 8,11.8501C10.6174,11.8735 12.9794,10.2838 13.943,7.8501C13.6875,7.1764 13.3083,6.5564 12.825,6.0221L12.825,6.0221ZM12,6.8501C12,9.0592 10.2091,10.8501 8,10.8501L8,10.8501ZM4.557,4.8501C3.4155,5.5469 2.5365,6.6017 2.057,7.8501C2.449,8.878 3.1194,9.7765 3.993,10.4451L3.993,10.4451L4.979,9.4591C3.8536,8.1769 3.6831,6.3154 4.557,4.8501ZM8.5,4.8501C7.6716,4.8501 7,5.5217 7,6.3501C7.0026,6.6447 7.0943,6.9316 7.263,7.1731L7.263,7.1731L9.323,5.1131C9.0815,4.9444 8.7946,4.8527 8.5,4.8501Z M 0,0" />
|
||||||
|
<path
|
||||||
|
android:fillColor="?primaryText"
|
||||||
|
android:pathData="M0,-0.1499h16v16h-16z" />
|
||||||
|
</group>
|
||||||
|
</vector>
|
@ -0,0 +1,15 @@
|
|||||||
|
<?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/. -->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="16dp"
|
||||||
|
android:height="13dp"
|
||||||
|
android:viewportWidth="16"
|
||||||
|
android:viewportHeight="13">
|
||||||
|
<group>
|
||||||
|
<clip-path android:pathData="M15.955,6.5501C14.8179,3.1276 11.6064,0.8264 8,0.8501C4.3936,0.8264 1.1821,3.1276 0.045,6.5501C-0.0152,6.7435 -0.0152,6.9507 0.045,7.1441C1.18,10.569 4.392,12.8729 8,12.8501C11.6064,12.8738 14.8179,10.5726 15.955,7.1501C16.0164,6.9548 16.0164,6.7454 15.955,6.5501ZM8.5,3.8501C9.3284,3.8501 10,4.5217 10,5.3501C10,6.1785 9.3284,6.8501 8.5,6.8501C7.6716,6.8501 7,6.1785 7,5.3501C7,4.5217 7.6716,3.8501 8.5,3.8501ZM8,10.8501C5.3826,10.8735 3.0206,9.2838 2.057,6.8501C2.5365,5.6017 3.4155,4.5469 4.557,3.8501C4.1965,4.4553 4.0043,5.1457 4,5.8501C4,8.0592 5.7909,9.8501 8,9.8501C10.2091,9.8501 12,8.0592 12,5.8501C11.9964,5.1459 11.8048,4.4555 11.445,3.8501C12.5865,4.5469 13.4655,5.6017 13.945,6.8501C12.9811,9.2845 10.6181,10.8744 8,10.8501Z M 0,0" />
|
||||||
|
<path
|
||||||
|
android:fillColor="?primaryText"
|
||||||
|
android:pathData="M0,-1.1499h16v16h-16z" />
|
||||||
|
</group>
|
||||||
|
</vector>
|
@ -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>
|
@ -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>
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue