For #6623 - Adds ability to delete a login
parent
b231afb05f
commit
2264e6e1b1
|
@ -7,15 +7,26 @@ package org.mozilla.fenix.settings.logins
|
|||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.text.InputType
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.appcompat.content.res.AppCompatResources.getDrawable
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import kotlinx.android.synthetic.main.fragment_saved_login_site_info.*
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.Dispatchers.Main
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.components.FenixSnackbar
|
||||
import org.mozilla.fenix.components.metrics.Event
|
||||
|
@ -29,6 +40,11 @@ class SavedLoginSiteInfoFragment : Fragment(R.layout.fragment_saved_login_site_i
|
|||
|
||||
private val args by navArgs<SavedLoginSiteInfoFragmentArgs>()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setHasOptionsMenu(true)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
// If we pause this fragment, we want to pop users back to reauth
|
||||
if (findNavController().currentDestination?.id != R.id.savedLoginsFragment) {
|
||||
|
@ -62,16 +78,61 @@ class SavedLoginSiteInfoFragment : Fragment(R.layout.fragment_saved_login_site_i
|
|||
)
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.login_edit, menu)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) {
|
||||
R.id.delete_login_button -> {
|
||||
deleteLogin()
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
|
||||
private fun deleteLogin() {
|
||||
var deleteLoginJob: Deferred<Boolean>? = null
|
||||
val deleteJob = lifecycleScope.launch(IO) {
|
||||
deleteLoginJob = async {
|
||||
requireContext().components.core.syncablePasswordsStorage.withUnlocked {
|
||||
it.delete(args.savedLoginItem.id).await()
|
||||
}
|
||||
}
|
||||
deleteLoginJob?.await()
|
||||
withContext(Main) {
|
||||
findNavController().popBackStack(R.id.savedLoginsFragment, false)
|
||||
}
|
||||
}
|
||||
deleteJob.invokeOnCompletion {
|
||||
if (it is CancellationException) {
|
||||
deleteLoginJob?.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun togglePasswordReveal(context: Context) {
|
||||
if (passwordInfoText.inputType == InputType.TYPE_TEXT_VARIATION_PASSWORD or InputType.TYPE_CLASS_TEXT) {
|
||||
context.components.analytics.metrics.track(Event.ViewLoginPassword)
|
||||
passwordInfoText.inputType = InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
|
||||
revealPasswordItem.setImageDrawable(getDrawable(context, R.drawable.mozac_ic_password_hide))
|
||||
revealPasswordItem.contentDescription = context.getString(R.string.saved_login_hide_password)
|
||||
revealPasswordItem.setImageDrawable(
|
||||
getDrawable(
|
||||
context,
|
||||
R.drawable.mozac_ic_password_hide
|
||||
)
|
||||
)
|
||||
revealPasswordItem.contentDescription =
|
||||
context.getString(R.string.saved_login_hide_password)
|
||||
} else {
|
||||
passwordInfoText.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
|
||||
revealPasswordItem.setImageDrawable(getDrawable(context, R.drawable.mozac_ic_password_reveal))
|
||||
revealPasswordItem.contentDescription = context.getString(R.string.saved_login_reveal_password)
|
||||
passwordInfoText.inputType =
|
||||
InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
|
||||
revealPasswordItem.setImageDrawable(
|
||||
getDrawable(
|
||||
context,
|
||||
R.drawable.mozac_ic_password_reveal
|
||||
)
|
||||
)
|
||||
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 = args.savedLoginItem.password
|
||||
|
|
|
@ -13,12 +13,16 @@ 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.CancellationException
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.Dispatchers.Main
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.ObsoleteCoroutinesApi
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import mozilla.appservices.logins.ServerPassword
|
||||
import mozilla.components.lib.state.ext.consumeFrom
|
||||
import org.mozilla.fenix.BrowserDirection
|
||||
import org.mozilla.fenix.HomeActivity
|
||||
|
@ -58,7 +62,7 @@ class SavedLoginsFragment : Fragment() {
|
|||
}
|
||||
savedLoginsInteractor = SavedLoginsInteractor(::itemClicked, ::openLearnMore)
|
||||
savedLoginsView = SavedLoginsView(view.savedLoginsLayout, savedLoginsInteractor)
|
||||
lifecycleScope.launch(Main) { loadAndMapLogins() }
|
||||
loadAndMapLogins()
|
||||
return view
|
||||
}
|
||||
|
||||
|
@ -98,16 +102,27 @@ class SavedLoginsFragment : Fragment() {
|
|||
)
|
||||
}
|
||||
|
||||
private suspend fun loadAndMapLogins() {
|
||||
val syncedLogins = withContext(IO) {
|
||||
requireContext().components.core.syncablePasswordsStorage.withUnlocked {
|
||||
it.list().await().map { item ->
|
||||
SavedLoginsItem(item.hostname, item.username, item.password)
|
||||
private fun loadAndMapLogins() {
|
||||
var deferredLogins: Deferred<List<ServerPassword>>? = null
|
||||
val fetchLoginsJob = lifecycleScope.launch(IO) {
|
||||
deferredLogins = async {
|
||||
requireContext().components.core.syncablePasswordsStorage.withUnlocked {
|
||||
it.list().await()
|
||||
}
|
||||
}
|
||||
val logins = deferredLogins?.await()
|
||||
logins?.let {
|
||||
withContext(Main) {
|
||||
savedLoginsStore.dispatch(SavedLoginsFragmentAction.UpdateLogins(logins.map { item ->
|
||||
SavedLoginsItem(item.hostname, item.username, item.password, item.id)
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
withContext(Main) {
|
||||
savedLoginsStore.dispatch(SavedLoginsFragmentAction.UpdateLogins(syncedLogins))
|
||||
fetchLoginsJob.invokeOnCompletion {
|
||||
if (it is CancellationException) {
|
||||
deferredLogins?.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,12 @@ import mozilla.components.lib.state.Store
|
|||
* @property password Password that's saved for this site
|
||||
*/
|
||||
@Parcelize
|
||||
data class SavedLoginsItem(val url: String, val userName: String?, val password: String?) :
|
||||
data class SavedLoginsItem(
|
||||
val url: String,
|
||||
val userName: String?,
|
||||
val password: String?,
|
||||
val id: String
|
||||
) :
|
||||
Parcelable
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
<?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/. -->
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/delete_login_button"
|
||||
android:contentDescription="@string/login_menu_delete_button"
|
||||
android:icon="@drawable/ic_delete"
|
||||
android:title="@string/login_menu_delete_button"
|
||||
app:iconTint="?primaryText"
|
||||
app:showAsAction="ifRoom" />
|
||||
</menu>
|
|
@ -19,7 +19,7 @@ class SavedLoginsInteractorTest {
|
|||
learnMore
|
||||
)
|
||||
|
||||
val item = SavedLoginsItem("mozilla.org", "username", "password")
|
||||
val item = SavedLoginsItem("mozilla.org", "username", "password", "id")
|
||||
interactor.itemClicked(item)
|
||||
|
||||
verify {
|
||||
|
|
Loading…
Reference in New Issue