Continued "user add/edit" dialog
This commit is contained in:
parent
a8dd3bebc0
commit
43757eb7b5
5 changed files with 142 additions and 22 deletions
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue