package io.heckel.ntfy.ui import android.app.AlertDialog import android.app.Dialog import android.content.Context import android.os.Build import android.os.Bundle import android.text.Editable import android.text.TextWatcher import android.view.View import android.view.WindowManager import android.widget.Button import android.widget.TextView import androidx.core.content.ContextCompat import androidx.fragment.app.DialogFragment import com.google.android.material.textfield.TextInputEditText import io.heckel.ntfy.R import io.heckel.ntfy.db.User import io.heckel.ntfy.util.validUrl class UserFragment : DialogFragment() { private var user: User? = null private lateinit var baseUrlsInUse: ArrayList private lateinit var listener: UserDialogListener private lateinit var baseUrlView: TextInputEditText private lateinit var usernameView: TextInputEditText private lateinit var passwordView: TextInputEditText private lateinit var positiveButton: Button interface UserDialogListener { fun onAddUser(dialog: DialogFragment, user: User) fun onUpdateUser(dialog: DialogFragment, user: User) fun onDeleteUser(dialog: DialogFragment, baseUrl: String) } override fun onAttach(context: Context) { super.onAttach(context) listener = activity as UserDialogListener } override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { // Reconstruct user (if it is present in the bundle) val baseUrl = arguments?.getString(BUNDLE_BASE_URL) val username = arguments?.getString(BUNDLE_USERNAME) val password = arguments?.getString(BUNDLE_PASSWORD) if (baseUrl != null && username != null && password != null) { user = User(baseUrl, username, password) } // Required for validation baseUrlsInUse = arguments?.getStringArrayList(BUNDLE_BASE_URLS_IN_USE) ?: arrayListOf() // Build root view val view = requireActivity().layoutInflater.inflate(R.layout.fragment_user_dialog, null) val positiveButtonTextResId = if (user == null) 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 descriptionView = view.findViewById(R.id.user_dialog_description) as TextView 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 { 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 val builder = AlertDialog.Builder(activity) .setView(view) .setPositiveButton(positiveButtonTextResId) { _, _ -> saveClicked() } .setNegativeButton(R.string.user_dialog_button_cancel) { _, _ -> // Do nothing } if (user != null) { builder.setNeutralButton(R.string.user_dialog_button_delete) { _, _ -> if (this::listener.isInitialized) { listener.onDeleteUser(this, user!!.baseUrl) } } } val dialog = builder.create() dialog.setOnShowListener { positiveButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE) // Delete button should be red if (user != null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { dialog .getButton(AlertDialog.BUTTON_NEUTRAL) .setTextAppearance(R.style.DangerText) } else { dialog .getButton(AlertDialog.BUTTON_NEUTRAL) .setTextColor(ContextCompat.getColor(requireContext(), Colors.dangerText(requireContext()))) } } // 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) // Focus if (user != null) { usernameView.requestFocus() if (usernameView.text != null) { usernameView.setSelection(usernameView.text!!.length) } } else { baseUrlView.requestFocus() } // Validate now! validateInput() } // Show keyboard when the dialog is shown (see https://stackoverflow.com/a/19573049/1440785) dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); return dialog } private fun saveClicked() { if (!this::listener.isInitialized) return val baseUrl = baseUrlView.text?.toString() ?: "" val username = usernameView.text?.toString() ?: "" val password = passwordView.text?.toString() ?: "" if (user == null) { user = User(baseUrl, username, password) listener.onAddUser(this, user!!) } else { user = if (password.isNotEmpty()) { user!!.copy(username = username, password = password) } else { user!!.copy(username = username) } listener.onUpdateUser(this, user!!) } } private fun validateInput() { val baseUrl = baseUrlView.text?.toString() ?: "" val username = usernameView.text?.toString() ?: "" val password = passwordView.text?.toString() ?: "" if (user == null) { positiveButton.isEnabled = validUrl(baseUrl) && !baseUrlsInUse.contains(baseUrl) && username.isNotEmpty() && password.isNotEmpty() } else { positiveButton.isEnabled = username.isNotEmpty() // Unchanged if left blank } } companion object { const val TAG = "NtfyUserFragment" private const val BUNDLE_BASE_URL = "baseUrl" private const val BUNDLE_USERNAME = "username" private const val BUNDLE_PASSWORD = "password" private const val BUNDLE_BASE_URLS_IN_USE = "baseUrlsInUse" fun newInstance(user: User?, baseUrlsInUse: ArrayList): UserFragment { val fragment = UserFragment() val args = Bundle() args.putStringArrayList(BUNDLE_BASE_URLS_IN_USE, baseUrlsInUse) if (user != null) { args.putString(BUNDLE_BASE_URL, user.baseUrl) args.putString(BUNDLE_USERNAME, user.username) args.putString(BUNDLE_PASSWORD, user.password) } fragment.arguments = args return fragment } } }