Continued "user add/edit" dialog

This commit is contained in:
Philipp Heckel 2022-01-29 16:59:51 -05:00
parent a8dd3bebc0
commit 43757eb7b5
5 changed files with 142 additions and 22 deletions

View file

@ -26,7 +26,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlin.random.Random import kotlin.random.Random
class AddFragment : DialogFragment() { class AddFragment : DialogFragment() {
private val api = ApiService() private val api = ApiService()

View file

@ -563,13 +563,28 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere
} }
preference.onPreferenceClickListener = OnPreferenceClickListener { _ -> preference.onPreferenceClickListener = OnPreferenceClickListener { _ ->
activity?.let { activity?.let {
UserFragment().show(it.supportFragmentManager, UserFragment.TAG) UserFragment
.newInstance(user.user)
.show(it.supportFragmentManager, UserFragment.TAG)
} }
true true
} }
preferenceCategory.addPreference(preference) preferenceCategory.addPreference(preference)
} }
} }
// Add user
val preference = Preference(preferenceScreen.context)
preference.title = getString(R.string.settings_users_prefs_user_add)
preference.onPreferenceClickListener = OnPreferenceClickListener { _ ->
activity?.let {
UserFragment
.newInstance(user = null)
.show(it.supportFragmentManager, UserFragment.TAG)
}
true
}
preferenceScreen.addPreference(preference)
} }
} }

View file

@ -3,31 +3,63 @@ package io.heckel.ntfy.ui
import android.app.AlertDialog import android.app.AlertDialog
import android.app.Dialog import android.app.Dialog
import android.os.Bundle import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.View
import android.view.WindowManager import android.view.WindowManager
import android.widget.Button
import android.widget.TextView import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import com.google.android.material.textfield.TextInputEditText
import io.heckel.ntfy.R import io.heckel.ntfy.R
import io.heckel.ntfy.db.User
class UserFragment : DialogFragment() { class UserFragment : DialogFragment() {
private var user: User? = null
private lateinit var baseUrlView: TextInputEditText
private lateinit var usernameView: TextInputEditText
private lateinit var passwordView: TextInputEditText
private lateinit var positiveButton: Button
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
if (activity == null) { // Reconstruct user (if it is present in the bundle)
throw IllegalStateException("Activity cannot be null") val userId = arguments?.getLong(BUNDLE_USER_ID)
val baseUrl = arguments?.getString(BUNDLE_BASE_URL)
val username = arguments?.getString(BUNDLE_USERNAME)
val password = arguments?.getString(BUNDLE_PASSWORD)
if (userId != null && baseUrl != null && username != null && password != null) {
user = User(userId, baseUrl, username, password)
} }
// Build root view // Build root view
val view = requireActivity().layoutInflater.inflate(R.layout.fragment_user_dialog, null) val view = requireActivity().layoutInflater.inflate(R.layout.fragment_user_dialog, null)
val addMode = false // FIXME val positiveButtonTextResId = if (user == null) R.string.user_dialog_button_add else R.string.user_dialog_button_save
val positiveButtonTextResId = if (addMode) R.string.user_dialog_button_add else R.string.user_dialog_button_save val titleView = view.findViewById(R.id.user_dialog_title) as TextView
val titleText = view.findViewById(R.id.user_dialog_title) as TextView val descriptionView = view.findViewById(R.id.user_dialog_description) as TextView
titleText.text = if (addMode) {
getString(R.string.user_dialog_title_add) baseUrlView = view.findViewById(R.id.user_dialog_base_url)
usernameView = view.findViewById(R.id.user_dialog_username)
passwordView = view.findViewById(R.id.user_dialog_password)
if (user == null) {
titleView.text = getString(R.string.user_dialog_title_add)
descriptionView.text = getString(R.string.user_dialog_description_add)
baseUrlView.visibility = View.VISIBLE
passwordView.hint = getString(R.string.user_dialog_password_hint_add)
} else { } else {
getString(R.string.user_dialog_title_edit) titleView.text = getString(R.string.user_dialog_title_edit)
descriptionView.text = getString(R.string.user_dialog_description_edit)
baseUrlView.visibility = View.GONE
usernameView.setText(user!!.username)
passwordView.hint = getString(R.string.user_dialog_password_hint_edit)
} }
// Build dialog // Build dialog
val dialog = AlertDialog.Builder(activity) val builder = AlertDialog.Builder(activity)
.setView(view) .setView(view)
.setPositiveButton(positiveButtonTextResId) { _, _ -> .setPositiveButton(positiveButtonTextResId) { _, _ ->
// This will be overridden below to avoid closing the dialog immediately // This will be overridden below to avoid closing the dialog immediately
@ -35,23 +67,78 @@ class UserFragment : DialogFragment() {
.setNegativeButton(R.string.user_dialog_button_cancel) { _, _ -> .setNegativeButton(R.string.user_dialog_button_cancel) { _, _ ->
// This will be overridden below // This will be overridden below
} }
.setNeutralButton(R.string.user_dialog_button_delete) { _, _ -> if (user != null) {
builder.setNeutralButton(R.string.user_dialog_button_delete) { _, _ ->
// This will be overridden below // This will be overridden below
} }
.create() }
val dialog = builder.create()
dialog.setOnShowListener {
positiveButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE)
// Delete button should be red
if (user != null) {
dialog
.getButton(AlertDialog.BUTTON_NEUTRAL)
.setTextColor(ContextCompat.getColor(requireContext(), R.color.primaryDangerButtonColor))
}
// Validate input when typing
val textWatcher = object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
validateInput()
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
// Nothing
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
// Nothing
}
}
baseUrlView.addTextChangedListener(textWatcher)
usernameView.addTextChangedListener(textWatcher)
passwordView.addTextChangedListener(textWatcher)
// Validate now!
validateInput()
}
// Show keyboard when the dialog is shown (see https://stackoverflow.com/a/19573049/1440785) // Show keyboard when the dialog is shown (see https://stackoverflow.com/a/19573049/1440785)
dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
// Add logic to disable "Subscribe" button on invalid input return dialog
dialog.setOnShowListener {
} }
return dialog private fun validateInput() {
val baseUrl = baseUrlView.text?.toString() ?: ""
val username = usernameView.text?.toString() ?: ""
val password = passwordView.text?.toString() ?: ""
if (user == null) {
positiveButton.isEnabled = (baseUrl.startsWith("http://") || baseUrl.startsWith("https://"))
&& username.isNotEmpty() && password.isNotEmpty()
} else {
positiveButton.isEnabled = username.isNotEmpty() // Unchanged if left blank
}
} }
companion object { companion object {
const val TAG = "NtfyUserFragment" const val TAG = "NtfyUserFragment"
private const val BUNDLE_USER_ID = "userId"
private const val BUNDLE_BASE_URL = "baseUrl"
private const val BUNDLE_USERNAME = "username"
private const val BUNDLE_PASSWORD = "password"
fun newInstance(user: User?): UserFragment {
val fragment = UserFragment()
val args = Bundle()
if (user != null) {
args.putLong(BUNDLE_USER_ID, user.id)
args.putString(BUNDLE_BASE_URL, user.baseUrl)
args.putString(BUNDLE_USERNAME, user.username)
args.putString(BUNDLE_PASSWORD, user.password)
fragment.arguments = args
}
return fragment
}
} }
} }

View file

@ -8,6 +8,13 @@
android:paddingLeft="16dp" android:paddingLeft="16dp"
android:paddingRight="16dp" android:paddingRight="16dp"
android:visibility="visible"> android:visibility="visible">
<TextView
android:text="This topic requires you to login. Please pick an existing user or type in a username and password."
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/user_dialog_description"
android:paddingStart="4dp" android:paddingTop="3dp" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/user_dialog_title"/>
<TextView <TextView
android:id="@+id/user_dialog_title" android:id="@+id/user_dialog_title"
android:layout_width="0dp" android:layout_width="0dp"
@ -18,9 +25,16 @@
android:textAlignment="viewStart" android:textAlignment="viewStart"
android:textAppearance="@style/TextAppearance.AppCompat.Large" android:paddingStart="4dp" android:textAppearance="@style/TextAppearance.AppCompat.Large" android:paddingStart="4dp"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"/> app:layout_constraintEnd_toEndOf="parent"/>
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/user_dialog_base_url"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:hint="@string/user_dialog_base_url_hint"
android:importantForAutofill="no"
android:maxLines="1" android:inputType="text"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="6dp" app:layout_constraintTop_toBottomOf="@id/user_dialog_description"/>
<com.google.android.material.textfield.TextInputEditText <com.google.android.material.textfield.TextInputEditText
android:id="@+id/user_dialog_username" android:id="@+id/user_dialog_username"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -28,11 +42,11 @@
android:importantForAutofill="no" android:importantForAutofill="no"
android:maxLines="1" android:inputType="text" android:maxLines="1" android:inputType="text"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="6dp" app:layout_constraintTop_toBottomOf="@+id/user_dialog_title"/> android:layout_marginTop="6dp" app:layout_constraintTop_toBottomOf="@id/user_dialog_base_url"/>
<com.google.android.material.textfield.TextInputEditText <com.google.android.material.textfield.TextInputEditText
android:id="@+id/user_dialog_password" android:id="@+id/user_dialog_password"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:hint="@string/user_dialog_password_hint" android:layout_height="wrap_content" android:hint="@string/user_dialog_password_hint_add"
android:importantForAutofill="no" android:importantForAutofill="no"
android:maxLines="1" android:inputType="textPassword" android:maxLines="1" android:inputType="textPassword"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"

View file

@ -246,6 +246,7 @@
<string name="settings_users_prefs_user_not_used">Not used by any topics</string> <string name="settings_users_prefs_user_not_used">Not used by any topics</string>
<string name="settings_users_prefs_user_used_by_one">Used by topic %1$s</string> <string name="settings_users_prefs_user_used_by_one">Used by topic %1$s</string>
<string name="settings_users_prefs_user_used_by_many">Used by topics %1$s</string> <string name="settings_users_prefs_user_used_by_many">Used by topics %1$s</string>
<string name="settings_users_prefs_user_add">Add user</string>
<string name="settings_unified_push_header">UnifiedPush</string> <string name="settings_unified_push_header">UnifiedPush</string>
<string name="settings_unified_push_header_summary">Allows other apps to use ntfy as a message distributor. Find out more at unifiedpush.org.</string> <string name="settings_unified_push_header_summary">Allows other apps to use ntfy as a message distributor. Find out more at unifiedpush.org.</string>
<string name="settings_unified_push_enabled_key">UnifiedPushEnabled</string> <string name="settings_unified_push_enabled_key">UnifiedPushEnabled</string>
@ -297,8 +298,12 @@
<!-- User add/edit dialog --> <!-- User add/edit dialog -->
<string name="user_dialog_title_add">Add user</string> <string name="user_dialog_title_add">Add user</string>
<string name="user_dialog_title_edit">Edit user</string> <string name="user_dialog_title_edit">Edit user</string>
<string name="user_dialog_description_add">You can add a user here that you can later associate with a specific topic.</string>
<string name="user_dialog_description_edit">You may edit username/password for the selected user, or delete it entirely.</string>
<string name="user_dialog_base_url_hint">Server URL</string>
<string name="user_dialog_username_hint">Username</string> <string name="user_dialog_username_hint">Username</string>
<string name="user_dialog_password_hint">Password</string> <string name="user_dialog_password_hint_add">Password</string>
<string name="user_dialog_password_hint_edit">Password (unchanged if blank)</string>
<string name="user_dialog_button_add">Add user</string> <string name="user_dialog_button_add">Add user</string>
<string name="user_dialog_button_cancel">Cancel</string> <string name="user_dialog_button_cancel">Cancel</string>
<string name="user_dialog_button_delete">Delete user</string> <string name="user_dialog_button_delete">Delete user</string>