Improve password UI/UX
This commit is contained in:
parent
68fc48d3a9
commit
ea9a57fabe
@ -34,6 +34,7 @@ import androidx.core.view.ViewCompat;
|
|||||||
import androidx.core.view.WindowCompat;
|
import androidx.core.view.WindowCompat;
|
||||||
import androidx.core.view.WindowInsetsCompat;
|
import androidx.core.view.WindowInsetsCompat;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
import androidx.loader.app.LoaderManager;
|
import androidx.loader.app.LoaderManager;
|
||||||
import androidx.loader.content.Loader;
|
import androidx.loader.content.Loader;
|
||||||
|
|
||||||
@ -44,6 +45,7 @@ import app.grapheneos.pdfviewer.fragment.DocumentPropertiesFragment;
|
|||||||
import app.grapheneos.pdfviewer.fragment.PasswordPromptFragment;
|
import app.grapheneos.pdfviewer.fragment.PasswordPromptFragment;
|
||||||
import app.grapheneos.pdfviewer.fragment.JumpToPageFragment;
|
import app.grapheneos.pdfviewer.fragment.JumpToPageFragment;
|
||||||
import app.grapheneos.pdfviewer.loader.DocumentPropertiesLoader;
|
import app.grapheneos.pdfviewer.loader.DocumentPropertiesLoader;
|
||||||
|
import app.grapheneos.pdfviewer.viewModel.PasswordStatus;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@ -126,6 +128,7 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
|
|||||||
private Toast mToast;
|
private Toast mToast;
|
||||||
private Snackbar snackbar;
|
private Snackbar snackbar;
|
||||||
private PasswordPromptFragment mPasswordPromptFragment;
|
private PasswordPromptFragment mPasswordPromptFragment;
|
||||||
|
public PasswordStatus passwordValidationViewModel;
|
||||||
|
|
||||||
private final ActivityResultLauncher<Intent> openDocumentLauncher = registerForActivityResult(
|
private final ActivityResultLauncher<Intent> openDocumentLauncher = registerForActivityResult(
|
||||||
new ActivityResultContracts.StartActivityForResult(), result -> {
|
new ActivityResultContracts.StartActivityForResult(), result -> {
|
||||||
@ -190,20 +193,23 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
|
|||||||
|
|
||||||
@JavascriptInterface
|
@JavascriptInterface
|
||||||
public void showPasswordPrompt() {
|
public void showPasswordPrompt() {
|
||||||
if (getPasswordPromptFragment().isAdded()) {
|
if (!getPasswordPromptFragment().isAdded()){
|
||||||
getPasswordPromptFragment().dismiss();
|
getPasswordPromptFragment().show(getSupportFragmentManager(), PasswordPromptFragment.class.getName());
|
||||||
}
|
}
|
||||||
getPasswordPromptFragment().show(getSupportFragmentManager(), PasswordPromptFragment.class.getName());
|
passwordValidationViewModel.passwordMissing();
|
||||||
}
|
}
|
||||||
|
|
||||||
@JavascriptInterface
|
@JavascriptInterface
|
||||||
public void invalidPassword() {
|
public void invalidPassword() {
|
||||||
runOnUiThread(PdfViewer.this::notifyInvalidPassword);
|
runOnUiThread(() -> passwordValidationViewModel.invalid());
|
||||||
showPasswordPrompt();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@JavascriptInterface
|
@JavascriptInterface
|
||||||
public void onLoaded() {
|
public void onLoaded() {
|
||||||
|
passwordValidationViewModel.validated();
|
||||||
|
if (getPasswordPromptFragment().isAdded()) {
|
||||||
|
getPasswordPromptFragment().dismiss();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@JavascriptInterface
|
@JavascriptInterface
|
||||||
@ -212,10 +218,6 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void notifyInvalidPassword() {
|
|
||||||
snackbar.setText(R.string.password_prompt_invalid_password).show();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressLint({"SetJavaScriptEnabled", "ClickableViewAccessibility"})
|
@SuppressLint({"SetJavaScriptEnabled", "ClickableViewAccessibility"})
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
@ -223,6 +225,7 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
|
|||||||
binding = PdfviewerBinding.inflate(getLayoutInflater());
|
binding = PdfviewerBinding.inflate(getLayoutInflater());
|
||||||
setContentView(binding.getRoot());
|
setContentView(binding.getRoot());
|
||||||
setSupportActionBar(binding.toolbar);
|
setSupportActionBar(binding.toolbar);
|
||||||
|
passwordValidationViewModel = new ViewModelProvider(this, ViewModelProvider.AndroidViewModelFactory.getInstance(getApplication())).get(PasswordStatus.class);
|
||||||
|
|
||||||
WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
|
WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
|
||||||
|
|
||||||
|
@ -14,16 +14,21 @@ import androidx.fragment.app.DialogFragment
|
|||||||
import app.grapheneos.pdfviewer.PdfViewer
|
import app.grapheneos.pdfviewer.PdfViewer
|
||||||
import app.grapheneos.pdfviewer.R
|
import app.grapheneos.pdfviewer.R
|
||||||
import app.grapheneos.pdfviewer.databinding.PasswordDialogFragmentBinding
|
import app.grapheneos.pdfviewer.databinding.PasswordDialogFragmentBinding
|
||||||
|
import app.grapheneos.pdfviewer.viewModel.PasswordStatus
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.google.android.material.textfield.TextInputEditText
|
import com.google.android.material.textfield.TextInputEditText
|
||||||
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
|
|
||||||
class PasswordPromptFragment : DialogFragment() {
|
class PasswordPromptFragment : DialogFragment() {
|
||||||
|
|
||||||
|
private lateinit var passwordLayout : TextInputLayout
|
||||||
private lateinit var passwordEditText : TextInputEditText
|
private lateinit var passwordEditText : TextInputEditText
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
val passwordPrompt = AlertDialog.Builder(requireContext())
|
val passwordPrompt = MaterialAlertDialogBuilder(requireContext())
|
||||||
val passwordDialogFragmentBinding =
|
val passwordDialogFragmentBinding =
|
||||||
PasswordDialogFragmentBinding.inflate(LayoutInflater.from(requireContext()))
|
PasswordDialogFragmentBinding.inflate(LayoutInflater.from(requireContext()))
|
||||||
|
passwordLayout = passwordDialogFragmentBinding.pdfPasswordTextInputLayout
|
||||||
passwordEditText = passwordDialogFragmentBinding.pdfPasswordEditText
|
passwordEditText = passwordDialogFragmentBinding.pdfPasswordEditText
|
||||||
passwordPrompt.setView(passwordDialogFragmentBinding.root)
|
passwordPrompt.setView(passwordDialogFragmentBinding.root)
|
||||||
passwordEditText.addTextChangedListener(object : TextWatcher {
|
passwordEditText.addTextChangedListener(object : TextWatcher {
|
||||||
@ -38,15 +43,39 @@ class PasswordPromptFragment : DialogFragment() {
|
|||||||
sendPassword()
|
sendPassword()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
passwordPrompt.setPositiveButton(R.string.open) { _, _ -> sendPassword() }
|
passwordPrompt.setPositiveButton(R.string.open, null)
|
||||||
passwordPrompt.setNegativeButton(R.string.cancel, null)
|
passwordPrompt.setNegativeButton(R.string.cancel, null)
|
||||||
val dialog = passwordPrompt.create()
|
val dialog = passwordPrompt.create()
|
||||||
|
passwordPrompt.setCancelable(false)
|
||||||
|
isCancelable = false
|
||||||
dialog.setCanceledOnTouchOutside(false)
|
dialog.setCanceledOnTouchOutside(false)
|
||||||
dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
|
dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
|
||||||
|
(requireActivity() as PdfViewer).passwordValidationViewModel.status.observe(
|
||||||
|
this
|
||||||
|
) {
|
||||||
|
when (it) {
|
||||||
|
PasswordStatus.Status.MissingPassword -> {
|
||||||
|
passwordEditText.editableText.clear()
|
||||||
|
passwordDialogFragmentBinding.title.setText(R.string.password_prompt_description)
|
||||||
|
}
|
||||||
|
PasswordStatus.Status.InvalidPassword -> {
|
||||||
|
passwordEditText.editableText.clear()
|
||||||
|
passwordDialogFragmentBinding.pdfPasswordTextInputLayout.error =
|
||||||
|
"invalid password"
|
||||||
|
}
|
||||||
|
PasswordStatus.Status.Validated -> {
|
||||||
|
//Activity will dismiss the dialog
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
throw NullPointerException("status shouldn't be null")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return dialog
|
return dialog
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updatePositiveButton() {
|
private fun updatePositiveButton() {
|
||||||
|
passwordLayout.error = ""
|
||||||
val btn = (dialog as AlertDialog).getButton(DialogInterface.BUTTON_POSITIVE)
|
val btn = (dialog as AlertDialog).getButton(DialogInterface.BUTTON_POSITIVE)
|
||||||
btn.isEnabled = passwordEditText.text?.isNotEmpty() ?: false
|
btn.isEnabled = passwordEditText.text?.isNotEmpty() ?: false
|
||||||
}
|
}
|
||||||
@ -55,7 +84,6 @@ class PasswordPromptFragment : DialogFragment() {
|
|||||||
val password = passwordEditText.text.toString()
|
val password = passwordEditText.text.toString()
|
||||||
if (!TextUtils.isEmpty(password)) {
|
if (!TextUtils.isEmpty(password)) {
|
||||||
(activity as PdfViewer).loadPdfWithPassword(password)
|
(activity as PdfViewer).loadPdfWithPassword(password)
|
||||||
dialog?.dismiss()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,4 +92,11 @@ class PasswordPromptFragment : DialogFragment() {
|
|||||||
updatePositiveButton()
|
updatePositiveButton()
|
||||||
passwordEditText.requestFocus()
|
passwordEditText.requestFocus()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
(dialog as AlertDialog).getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener {
|
||||||
|
sendPassword()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
package app.grapheneos.pdfviewer.viewModel
|
||||||
|
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
|
||||||
|
class PasswordStatus : ViewModel() {
|
||||||
|
|
||||||
|
enum class Status {
|
||||||
|
MissingPassword,
|
||||||
|
InvalidPassword,
|
||||||
|
Validated
|
||||||
|
}
|
||||||
|
|
||||||
|
val status: MutableLiveData<Status> = MutableLiveData(Status.MissingPassword)
|
||||||
|
|
||||||
|
fun passwordMissing() {
|
||||||
|
status.postValue(Status.MissingPassword)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun invalid() {
|
||||||
|
status.postValue(Status.InvalidPassword)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun validated() {
|
||||||
|
status.postValue(Status.Validated)
|
||||||
|
}
|
||||||
|
}
|
@ -5,7 +5,8 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<TextView
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/title"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="24dp"
|
android:layout_marginStart="24dp"
|
||||||
@ -17,6 +18,7 @@
|
|||||||
android:textSize="20sp" />
|
android:textSize="20sp" />
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/pdf_password_text_input_layout"
|
||||||
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
|
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
Loading…
Reference in New Issue
Block a user