Improve prompt password dialog

Reveal password, inline error when empty
This commit is contained in:
Benoit Marty 2020-02-03 14:48:15 +01:00
parent ce13e824b6
commit 783a514496
5 changed files with 143 additions and 77 deletions

View file

@ -29,7 +29,7 @@ import im.vector.riotx.core.platform.SimpleTextWatcher
class ExportKeysDialog { class ExportKeysDialog {
var passwordVisible = false private var passwordVisible = false
fun show(activity: Activity, exportKeyDialogListener: ExportKeyDialogListener) { fun show(activity: Activity, exportKeyDialogListener: ExportKeyDialogListener) {
val dialogLayout = activity.layoutInflater.inflate(R.layout.dialog_export_e2e_keys, null) val dialogLayout = activity.layoutInflater.inflate(R.layout.dialog_export_e2e_keys, null)

View file

@ -0,0 +1,87 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotx.core.dialogs
import android.app.Activity
import android.content.DialogInterface
import android.text.Editable
import android.view.KeyEvent
import android.widget.ImageView
import androidx.appcompat.app.AlertDialog
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import im.vector.riotx.R
import im.vector.riotx.core.extensions.hideKeyboard
import im.vector.riotx.core.extensions.showPassword
import im.vector.riotx.core.platform.SimpleTextWatcher
class PromptPasswordDialog {
private var passwordVisible = false
fun show(activity: Activity, listener: (String) -> Unit) {
val dialogLayout = activity.layoutInflater.inflate(R.layout.dialog_prompt_password, null)
val passwordTil = dialogLayout.findViewById<TextInputLayout>(R.id.promptPasswordTil)
val passwordEditText = dialogLayout.findViewById<TextInputEditText>(R.id.promptPassword)
val textWatcher = object : SimpleTextWatcher() {
override fun afterTextChanged(s: Editable) {
passwordTil.error = null
}
}
passwordEditText.addTextChangedListener(textWatcher)
val showPassword = dialogLayout.findViewById<ImageView>(R.id.promptPasswordPasswordReveal)
showPassword.setOnClickListener {
passwordVisible = !passwordVisible
passwordEditText.showPassword(passwordVisible)
showPassword.setImageResource(if (passwordVisible) R.drawable.ic_eye_closed_black else R.drawable.ic_eye_black)
}
AlertDialog.Builder(activity)
.setIcon(android.R.drawable.ic_dialog_alert)
.setTitle(R.string.devices_delete_dialog_title)
.setView(dialogLayout)
.setPositiveButton(R.string.auth_submit, null)
.setNegativeButton(R.string.cancel, null)
.setOnKeyListener(DialogInterface.OnKeyListener { dialog, keyCode, event ->
if (event.action == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
dialog.cancel()
return@OnKeyListener true
}
false
})
.setOnDismissListener {
dialogLayout.hideKeyboard()
}
.create()
.apply {
setOnShowListener {
getButton(AlertDialog.BUTTON_POSITIVE)
.setOnClickListener {
if (passwordEditText.text.toString().isEmpty()) {
passwordTil.error = activity.getString(R.string.error_empty_field_your_password)
} else {
listener.invoke(passwordEditText.text.toString())
dismiss()
}
}
}
}
.show()
}
}

View file

@ -15,11 +15,8 @@
*/ */
package im.vector.riotx.features.settings.crosssigning package im.vector.riotx.features.settings.crosssigning
import android.content.DialogInterface
import android.os.Bundle import android.os.Bundle
import android.view.KeyEvent
import android.view.View import android.view.View
import android.widget.EditText
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Success import com.airbnb.mvrx.Success
@ -27,12 +24,12 @@ import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.dialogs.PromptPasswordDialog
import im.vector.riotx.core.extensions.cleanup import im.vector.riotx.core.extensions.cleanup
import im.vector.riotx.core.extensions.configureWith import im.vector.riotx.core.extensions.configureWith
import im.vector.riotx.core.extensions.observeEvent import im.vector.riotx.core.extensions.observeEvent
import im.vector.riotx.core.platform.VectorBaseActivity import im.vector.riotx.core.platform.VectorBaseActivity
import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.core.platform.VectorBaseFragment
import im.vector.riotx.core.utils.toast
import kotlinx.android.synthetic.main.fragment_generic_recycler.* import kotlinx.android.synthetic.main.fragment_generic_recycler.*
import javax.inject.Inject import javax.inject.Inject
@ -93,36 +90,13 @@ class CrossSigningSettingsFragment @Inject constructor(
} }
private fun requestPassword(sessionId: String) { private fun requestPassword(sessionId: String) {
val inflater = requireActivity().layoutInflater PromptPasswordDialog().show(requireActivity()) { password ->
val layout = inflater.inflate(R.layout.dialog_prompt_password, null) // TODO sessionId should never get out the ViewModel
val passwordEditText = layout.findViewById<EditText>(R.id.prompt_password) viewModel.handle(CrossSigningAction.InitializeCrossSigning(UserPasswordAuth(
session = sessionId,
AlertDialog.Builder(requireActivity()) password = password
.setIcon(android.R.drawable.ic_dialog_alert) )))
.setTitle(R.string.devices_delete_dialog_title) }
.setView(layout)
.setPositiveButton(R.string.auth_submit, DialogInterface.OnClickListener { _, _ ->
if (passwordEditText.toString().isEmpty()) {
requireActivity().toast(R.string.error_empty_field_your_password)
return@OnClickListener
}
val pass = passwordEditText.text.toString()
// TODO sessionId should never get out the ViewModel
viewModel.handle(CrossSigningAction.InitializeCrossSigning(UserPasswordAuth(
session = sessionId,
password = pass
)))
})
.setNegativeButton(R.string.cancel, null)
.setOnKeyListener(DialogInterface.OnKeyListener { dialog, keyCode, event ->
if (event.action == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
dialog.cancel()
return@OnKeyListener true
}
false
})
.show()
} }
override fun onInitializeCrossSigningKeys() { override fun onInitializeCrossSigningKeys() {

View file

@ -16,9 +16,7 @@
package im.vector.riotx.features.settings.devices package im.vector.riotx.features.settings.devices
import android.content.DialogInterface
import android.os.Bundle import android.os.Bundle
import android.view.KeyEvent
import android.view.View import android.view.View
import android.widget.EditText import android.widget.EditText
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
@ -30,13 +28,13 @@ import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.dialogs.PromptPasswordDialog
import im.vector.riotx.core.extensions.cleanup import im.vector.riotx.core.extensions.cleanup
import im.vector.riotx.core.extensions.configureWith import im.vector.riotx.core.extensions.configureWith
import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.extensions.exhaustive
import im.vector.riotx.core.extensions.observeEvent import im.vector.riotx.core.extensions.observeEvent
import im.vector.riotx.core.platform.VectorBaseActivity import im.vector.riotx.core.platform.VectorBaseActivity
import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.core.platform.VectorBaseFragment
import im.vector.riotx.core.utils.toast
import im.vector.riotx.features.crypto.verification.VerificationBottomSheet import im.vector.riotx.features.crypto.verification.VerificationBottomSheet
import kotlinx.android.synthetic.main.fragment_generic_recycler.* import kotlinx.android.synthetic.main.fragment_generic_recycler.*
import kotlinx.android.synthetic.main.merge_overlay_waiting_view.* import kotlinx.android.synthetic.main.merge_overlay_waiting_view.*
@ -167,31 +165,10 @@ class VectorSettingsDevicesFragment @Inject constructor(
if (mAccountPassword.isNotEmpty()) { if (mAccountPassword.isNotEmpty()) {
viewModel.handle(DevicesAction.Password(mAccountPassword)) viewModel.handle(DevicesAction.Password(mAccountPassword))
} else { } else {
val inflater = requireActivity().layoutInflater PromptPasswordDialog().show(requireActivity()) { password ->
val layout = inflater.inflate(R.layout.dialog_prompt_password, null) mAccountPassword = password
val passwordEditText = layout.findViewById<EditText>(R.id.prompt_password) viewModel.handle(DevicesAction.Password(mAccountPassword))
}
AlertDialog.Builder(requireActivity())
.setIcon(android.R.drawable.ic_dialog_alert)
.setTitle(R.string.devices_delete_dialog_title)
.setView(layout)
.setPositiveButton(R.string.devices_delete_submit_button_label, DialogInterface.OnClickListener { _, _ ->
if (passwordEditText.toString().isEmpty()) {
requireActivity().toast(R.string.error_empty_field_your_password)
return@OnClickListener
}
mAccountPassword = passwordEditText.text.toString()
viewModel.handle(DevicesAction.Password(mAccountPassword))
})
.setNegativeButton(R.string.cancel, null)
.setOnKeyListener(DialogInterface.OnKeyListener { dialog, keyCode, event ->
if (event.action == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
dialog.cancel()
return@OnKeyListener true
}
false
})
.show()
} }
} }

View file

@ -1,5 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" <ScrollView 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_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
@ -14,28 +16,54 @@
android:paddingRight="?dialogPreferredPadding"> android:paddingRight="?dialogPreferredPadding">
<TextView <TextView
android:id="@+id/delete_dialog_info_label"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="6dp" android:layout_marginTop="6dp"
android:text="@string/devices_delete_dialog_text" android:text="@string/devices_delete_dialog_text"
android:textSize="12sp" /> android:textSize="12sp" />
<TextView <FrameLayout
android:id="@+id/password_label"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="6dp" android:layout_marginTop="8dp">
android:text="@string/devices_delete_pswd"
android:textSize="12sp" <com.google.android.material.textfield.TextInputLayout
android:textStyle="bold" /> android:id="@+id/promptPasswordTil"
style="@style/VectorTextInputLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/soft_logout_signin_password_hint"
app:errorEnabled="true"
app:errorIconDrawable="@null">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/promptPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="textPassword"
android:maxLines="1"
android:paddingEnd="48dp"
android:paddingRight="48dp"
tools:ignore="RtlSymmetry"
tools:text="abc" />
</com.google.android.material.textfield.TextInputLayout>
<ImageView
android:id="@+id/promptPasswordPasswordReveal"
android:layout_width="@dimen/layout_touch_size"
android:layout_height="@dimen/layout_touch_size"
android:layout_gravity="end"
android:layout_marginTop="8dp"
android:background="?attr/selectableItemBackground"
android:scaleType="center"
android:src="@drawable/ic_eye_black"
android:tint="?attr/colorAccent"
tools:contentDescription="@string/a11y_show_password" />
</FrameLayout>
<EditText
android:id="@+id/prompt_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="textPassword"
android:maxLines="1" />
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>