Loading app/src/main/java/io/heckel/ntfy/db/Database.kt +5 −2 Original line number Diff line number Diff line Loading @@ -265,6 +265,9 @@ interface SubscriptionDao { @Query("DELETE FROM subscription WHERE id = :subscriptionId") fun remove(subscriptionId: Long) @Query("UPDATE subscription SET authUserId = null WHERE authUserId = :authUserId") fun removeAuthUserFromSubscriptions(authUserId: Long) } @Dao Loading Loading @@ -311,8 +314,8 @@ interface UserDao { @Update suspend fun update(user: User) @Delete suspend fun delete(user: User) @Query("DELETE FROM user WHERE id = :id") suspend fun delete(id: Long) } @Dao Loading app/src/main/java/io/heckel/ntfy/db/Repository.kt +13 −1 Original line number Diff line number Diff line Loading @@ -84,6 +84,10 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas subscriptionDao.remove(subscriptionId) } suspend fun removeAuthUserFromSubscriptions(authUserId: Long) { subscriptionDao.removeAuthUserFromSubscriptions(authUserId) } fun getNotificationsLiveData(subscriptionId: Long): LiveData<List<Notification>> { return notificationDao.listFlow(subscriptionId).asLiveData() } Loading Loading @@ -137,13 +141,21 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas } suspend fun addUser(user: User) { return userDao.insert(user) userDao.insert(user) } suspend fun updateUser(user: User) { userDao.update(user) } suspend fun getUser(userId: Long): User { return userDao.get(userId) } suspend fun deleteUser(userId: Long) { userDao.delete(userId) } fun getPollWorkerVersion(): Int { return sharedPrefs.getInt(SHARED_PREFS_POLL_WORKER_VERSION, 0) } Loading app/src/main/java/io/heckel/ntfy/service/SubscriberServiceManager.kt +6 −0 Original line number Diff line number Diff line Loading @@ -24,6 +24,12 @@ class SubscriberServiceManager(private val context: Context) { workManager.enqueue(startServiceRequest) } fun stop() { Intent(context, SubscriberService::class.java).also { intent -> context.stopService(intent) // Service will auto-restart } } /** * Starts or stops the foreground service by figuring out how many instant delivery subscriptions * exist. If there's > 0, then we need a foreground service. Loading app/src/main/java/io/heckel/ntfy/ui/SettingsActivity.kt +74 −23 Original line number Diff line number Diff line Loading @@ -15,6 +15,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatDelegate import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.fragment.app.DialogFragment import androidx.lifecycle.lifecycleScope import androidx.preference.* import androidx.preference.Preference.OnPreferenceClickListener Loading @@ -25,6 +26,7 @@ import io.heckel.ntfy.db.Repository import io.heckel.ntfy.db.User import io.heckel.ntfy.log.Log import io.heckel.ntfy.service.SubscriberService import io.heckel.ntfy.service.SubscriberServiceManager import io.heckel.ntfy.util.formatBytes import io.heckel.ntfy.util.formatDateShort import io.heckel.ntfy.util.shortUrl Loading @@ -43,8 +45,13 @@ import java.util.concurrent.TimeUnit * The "nested screen" navigation stuff (for user management) has been taken from * https://github.com/googlearchive/android-preferences/blob/master/app/src/main/java/com/example/androidx/preference/sample/MainActivity.kt */ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPreferenceStartFragmentCallback { private lateinit var fragment: SettingsFragment class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPreferenceStartFragmentCallback, UserFragment.UserDialogListener { private lateinit var settingsFragment: SettingsFragment private lateinit var userSettingsFragment: UserSettingsFragment private lateinit var repository: Repository private lateinit var serviceManager: SubscriberServiceManager override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) Loading @@ -52,11 +59,14 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere Log.d(TAG, "Create $this") repository = Repository.getInstance(this) serviceManager = SubscriberServiceManager(this) if (savedInstanceState == null) { fragment = SettingsFragment() // Empty constructor! settingsFragment = SettingsFragment() // Empty constructor! supportFragmentManager .beginTransaction() .replace(R.id.settings_layout, fragment) .replace(R.id.settings_layout, settingsFragment) .commit() } else { title = savedInstanceState.getCharSequence(TITLE_TAG) Loading Loading @@ -84,7 +94,6 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere return super.onSupportNavigateUp() } override fun onPreferenceStartFragment( caller: PreferenceFragmentCompat, pref: Preference Loading @@ -98,17 +107,25 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere arguments = args setTargetFragment(caller, 0) } // Replace the existing Fragment with the new Fragment supportFragmentManager.beginTransaction() .replace(R.id.settings_layout, fragment) .addToBackStack(null) .commit() title = pref.title // Save user settings fragment for later if (fragment is UserSettingsFragment) { userSettingsFragment = fragment } return true } class SettingsFragment : PreferenceFragmentCompat() { private lateinit var repository: Repository private lateinit var serviceManager: SubscriberServiceManager private var autoDownloadSelection = AUTO_DOWNLOAD_SELECTION_NOT_SET override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { Loading @@ -116,6 +133,7 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere // Dependencies (Fragments need a default constructor) repository = Repository.getInstance(requireActivity()) serviceManager = SubscriberServiceManager(requireActivity()) autoDownloadSelection = repository.getAutoDownloadMaxSize() // Only used for <= Android P, due to permissions request // Important note: We do not use the default shared prefs to store settings. Every Loading Loading @@ -421,10 +439,7 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere } private fun restartService() { val context = this@SettingsFragment.context Intent(context, SubscriberService::class.java).also { intent -> context?.stopService(intent) // Service will auto-restart } serviceManager.stop() // Service will auto-restart } private fun copyLogsToClipboard() { Loading Loading @@ -516,10 +531,17 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { setPreferencesFromResource(R.xml.user_preferences, rootKey) // Dependencies (Fragments need a default constructor) repository = Repository.getInstance(requireActivity()) reload() } data class UserWithMetadata( val user: User, val topics: List<String> ) fun reload() { preferenceScreen.removeAll() lifecycleScope.launch(Dispatchers.IO) { val userIdsWithTopics = repository.getSubscriptions() .groupBy { it.authUserId } Loading @@ -530,18 +552,12 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere UserWithMetadata(user, topics) } .groupBy { it.user.baseUrl } activity?.runOnUiThread { addUserPreferences(usersByBaseUrl) } } } data class UserWithMetadata( val user: User, val topics: List<String> ) private fun addUserPreferences(usersByBaseUrl: Map<String, List<UserWithMetadata>>) { usersByBaseUrl.forEach { entry -> val baseUrl = entry.key Loading Loading @@ -574,9 +590,14 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere } // Add user val preference = Preference(preferenceScreen.context) preference.title = getString(R.string.settings_users_prefs_user_add) preference.onPreferenceClickListener = OnPreferenceClickListener { _ -> val userAddCategory = PreferenceCategory(preferenceScreen.context) userAddCategory.title = getString(R.string.settings_users_prefs_user_add) preferenceScreen.addPreference(userAddCategory) val userAddPref = Preference(preferenceScreen.context) userAddPref.title = getString(R.string.settings_users_prefs_user_add_title) userAddPref.summary = getString(R.string.settings_users_prefs_user_add_summary) userAddPref.onPreferenceClickListener = OnPreferenceClickListener { _ -> activity?.let { UserFragment .newInstance(user = null) Loading @@ -584,7 +605,7 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere } true } preferenceScreen.addPreference(preference) userAddCategory.addPreference(userAddPref) } } Loading @@ -597,9 +618,39 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere } } override fun onAddUser(dialog: DialogFragment, user: User) { lifecycleScope.launch(Dispatchers.IO) { repository.addUser(user) // New users are not used, so no service refresh required runOnUiThread { userSettingsFragment.reload() } } } override fun onUpdateUser(dialog: DialogFragment, user: User) { lifecycleScope.launch(Dispatchers.IO) { repository.updateUser(user) serviceManager.stop() // Editing does not change the user ID runOnUiThread { userSettingsFragment.reload() } } } override fun onDeleteUser(dialog: DialogFragment, authUserId: Long) { lifecycleScope.launch(Dispatchers.IO) { repository.removeAuthUserFromSubscriptions(authUserId) repository.deleteUser(authUserId) serviceManager.refresh() // authUserId changed, so refresh is enough runOnUiThread { userSettingsFragment.reload() } } } private fun setAutoDownload() { if (!this::fragment.isInitialized) return fragment.setAutoDownload() if (!this::settingsFragment.isInitialized) return settingsFragment.setAutoDownload() } companion object { Loading app/src/main/java/io/heckel/ntfy/ui/UserFragment.kt +37 −3 Original line number Diff line number Diff line Loading @@ -2,6 +2,7 @@ package io.heckel.ntfy.ui import android.app.AlertDialog import android.app.Dialog import android.content.Context import android.os.Bundle import android.text.Editable import android.text.TextWatcher Loading @@ -14,15 +15,28 @@ import androidx.fragment.app.DialogFragment import com.google.android.material.textfield.TextInputEditText import io.heckel.ntfy.R import io.heckel.ntfy.db.User import kotlin.random.Random class UserFragment : DialogFragment() { private var user: User? = null 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, authUserId: Long) } 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 userId = arguments?.getLong(BUNDLE_USER_ID) Loading Loading @@ -62,14 +76,16 @@ class UserFragment : DialogFragment() { val builder = AlertDialog.Builder(activity) .setView(view) .setPositiveButton(positiveButtonTextResId) { _, _ -> // This will be overridden below to avoid closing the dialog immediately saveClicked() } .setNegativeButton(R.string.user_dialog_button_cancel) { _, _ -> // This will be overridden below // Do nothing } if (user != null) { builder.setNeutralButton(R.string.user_dialog_button_delete) { _, _ -> // This will be overridden below if (this::listener.isInitialized && userId != null) { listener.onDeleteUser(this, userId) } } } val dialog = builder.create() Loading Loading @@ -109,6 +125,24 @@ class UserFragment : DialogFragment() { 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(Random.nextLong(), 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() ?: "" Loading Loading
app/src/main/java/io/heckel/ntfy/db/Database.kt +5 −2 Original line number Diff line number Diff line Loading @@ -265,6 +265,9 @@ interface SubscriptionDao { @Query("DELETE FROM subscription WHERE id = :subscriptionId") fun remove(subscriptionId: Long) @Query("UPDATE subscription SET authUserId = null WHERE authUserId = :authUserId") fun removeAuthUserFromSubscriptions(authUserId: Long) } @Dao Loading Loading @@ -311,8 +314,8 @@ interface UserDao { @Update suspend fun update(user: User) @Delete suspend fun delete(user: User) @Query("DELETE FROM user WHERE id = :id") suspend fun delete(id: Long) } @Dao Loading
app/src/main/java/io/heckel/ntfy/db/Repository.kt +13 −1 Original line number Diff line number Diff line Loading @@ -84,6 +84,10 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas subscriptionDao.remove(subscriptionId) } suspend fun removeAuthUserFromSubscriptions(authUserId: Long) { subscriptionDao.removeAuthUserFromSubscriptions(authUserId) } fun getNotificationsLiveData(subscriptionId: Long): LiveData<List<Notification>> { return notificationDao.listFlow(subscriptionId).asLiveData() } Loading Loading @@ -137,13 +141,21 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas } suspend fun addUser(user: User) { return userDao.insert(user) userDao.insert(user) } suspend fun updateUser(user: User) { userDao.update(user) } suspend fun getUser(userId: Long): User { return userDao.get(userId) } suspend fun deleteUser(userId: Long) { userDao.delete(userId) } fun getPollWorkerVersion(): Int { return sharedPrefs.getInt(SHARED_PREFS_POLL_WORKER_VERSION, 0) } Loading
app/src/main/java/io/heckel/ntfy/service/SubscriberServiceManager.kt +6 −0 Original line number Diff line number Diff line Loading @@ -24,6 +24,12 @@ class SubscriberServiceManager(private val context: Context) { workManager.enqueue(startServiceRequest) } fun stop() { Intent(context, SubscriberService::class.java).also { intent -> context.stopService(intent) // Service will auto-restart } } /** * Starts or stops the foreground service by figuring out how many instant delivery subscriptions * exist. If there's > 0, then we need a foreground service. Loading
app/src/main/java/io/heckel/ntfy/ui/SettingsActivity.kt +74 −23 Original line number Diff line number Diff line Loading @@ -15,6 +15,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatDelegate import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.fragment.app.DialogFragment import androidx.lifecycle.lifecycleScope import androidx.preference.* import androidx.preference.Preference.OnPreferenceClickListener Loading @@ -25,6 +26,7 @@ import io.heckel.ntfy.db.Repository import io.heckel.ntfy.db.User import io.heckel.ntfy.log.Log import io.heckel.ntfy.service.SubscriberService import io.heckel.ntfy.service.SubscriberServiceManager import io.heckel.ntfy.util.formatBytes import io.heckel.ntfy.util.formatDateShort import io.heckel.ntfy.util.shortUrl Loading @@ -43,8 +45,13 @@ import java.util.concurrent.TimeUnit * The "nested screen" navigation stuff (for user management) has been taken from * https://github.com/googlearchive/android-preferences/blob/master/app/src/main/java/com/example/androidx/preference/sample/MainActivity.kt */ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPreferenceStartFragmentCallback { private lateinit var fragment: SettingsFragment class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPreferenceStartFragmentCallback, UserFragment.UserDialogListener { private lateinit var settingsFragment: SettingsFragment private lateinit var userSettingsFragment: UserSettingsFragment private lateinit var repository: Repository private lateinit var serviceManager: SubscriberServiceManager override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) Loading @@ -52,11 +59,14 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere Log.d(TAG, "Create $this") repository = Repository.getInstance(this) serviceManager = SubscriberServiceManager(this) if (savedInstanceState == null) { fragment = SettingsFragment() // Empty constructor! settingsFragment = SettingsFragment() // Empty constructor! supportFragmentManager .beginTransaction() .replace(R.id.settings_layout, fragment) .replace(R.id.settings_layout, settingsFragment) .commit() } else { title = savedInstanceState.getCharSequence(TITLE_TAG) Loading Loading @@ -84,7 +94,6 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere return super.onSupportNavigateUp() } override fun onPreferenceStartFragment( caller: PreferenceFragmentCompat, pref: Preference Loading @@ -98,17 +107,25 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere arguments = args setTargetFragment(caller, 0) } // Replace the existing Fragment with the new Fragment supportFragmentManager.beginTransaction() .replace(R.id.settings_layout, fragment) .addToBackStack(null) .commit() title = pref.title // Save user settings fragment for later if (fragment is UserSettingsFragment) { userSettingsFragment = fragment } return true } class SettingsFragment : PreferenceFragmentCompat() { private lateinit var repository: Repository private lateinit var serviceManager: SubscriberServiceManager private var autoDownloadSelection = AUTO_DOWNLOAD_SELECTION_NOT_SET override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { Loading @@ -116,6 +133,7 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere // Dependencies (Fragments need a default constructor) repository = Repository.getInstance(requireActivity()) serviceManager = SubscriberServiceManager(requireActivity()) autoDownloadSelection = repository.getAutoDownloadMaxSize() // Only used for <= Android P, due to permissions request // Important note: We do not use the default shared prefs to store settings. Every Loading Loading @@ -421,10 +439,7 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere } private fun restartService() { val context = this@SettingsFragment.context Intent(context, SubscriberService::class.java).also { intent -> context?.stopService(intent) // Service will auto-restart } serviceManager.stop() // Service will auto-restart } private fun copyLogsToClipboard() { Loading Loading @@ -516,10 +531,17 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { setPreferencesFromResource(R.xml.user_preferences, rootKey) // Dependencies (Fragments need a default constructor) repository = Repository.getInstance(requireActivity()) reload() } data class UserWithMetadata( val user: User, val topics: List<String> ) fun reload() { preferenceScreen.removeAll() lifecycleScope.launch(Dispatchers.IO) { val userIdsWithTopics = repository.getSubscriptions() .groupBy { it.authUserId } Loading @@ -530,18 +552,12 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere UserWithMetadata(user, topics) } .groupBy { it.user.baseUrl } activity?.runOnUiThread { addUserPreferences(usersByBaseUrl) } } } data class UserWithMetadata( val user: User, val topics: List<String> ) private fun addUserPreferences(usersByBaseUrl: Map<String, List<UserWithMetadata>>) { usersByBaseUrl.forEach { entry -> val baseUrl = entry.key Loading Loading @@ -574,9 +590,14 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere } // Add user val preference = Preference(preferenceScreen.context) preference.title = getString(R.string.settings_users_prefs_user_add) preference.onPreferenceClickListener = OnPreferenceClickListener { _ -> val userAddCategory = PreferenceCategory(preferenceScreen.context) userAddCategory.title = getString(R.string.settings_users_prefs_user_add) preferenceScreen.addPreference(userAddCategory) val userAddPref = Preference(preferenceScreen.context) userAddPref.title = getString(R.string.settings_users_prefs_user_add_title) userAddPref.summary = getString(R.string.settings_users_prefs_user_add_summary) userAddPref.onPreferenceClickListener = OnPreferenceClickListener { _ -> activity?.let { UserFragment .newInstance(user = null) Loading @@ -584,7 +605,7 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere } true } preferenceScreen.addPreference(preference) userAddCategory.addPreference(userAddPref) } } Loading @@ -597,9 +618,39 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere } } override fun onAddUser(dialog: DialogFragment, user: User) { lifecycleScope.launch(Dispatchers.IO) { repository.addUser(user) // New users are not used, so no service refresh required runOnUiThread { userSettingsFragment.reload() } } } override fun onUpdateUser(dialog: DialogFragment, user: User) { lifecycleScope.launch(Dispatchers.IO) { repository.updateUser(user) serviceManager.stop() // Editing does not change the user ID runOnUiThread { userSettingsFragment.reload() } } } override fun onDeleteUser(dialog: DialogFragment, authUserId: Long) { lifecycleScope.launch(Dispatchers.IO) { repository.removeAuthUserFromSubscriptions(authUserId) repository.deleteUser(authUserId) serviceManager.refresh() // authUserId changed, so refresh is enough runOnUiThread { userSettingsFragment.reload() } } } private fun setAutoDownload() { if (!this::fragment.isInitialized) return fragment.setAutoDownload() if (!this::settingsFragment.isInitialized) return settingsFragment.setAutoDownload() } companion object { Loading
app/src/main/java/io/heckel/ntfy/ui/UserFragment.kt +37 −3 Original line number Diff line number Diff line Loading @@ -2,6 +2,7 @@ package io.heckel.ntfy.ui import android.app.AlertDialog import android.app.Dialog import android.content.Context import android.os.Bundle import android.text.Editable import android.text.TextWatcher Loading @@ -14,15 +15,28 @@ import androidx.fragment.app.DialogFragment import com.google.android.material.textfield.TextInputEditText import io.heckel.ntfy.R import io.heckel.ntfy.db.User import kotlin.random.Random class UserFragment : DialogFragment() { private var user: User? = null 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, authUserId: Long) } 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 userId = arguments?.getLong(BUNDLE_USER_ID) Loading Loading @@ -62,14 +76,16 @@ class UserFragment : DialogFragment() { val builder = AlertDialog.Builder(activity) .setView(view) .setPositiveButton(positiveButtonTextResId) { _, _ -> // This will be overridden below to avoid closing the dialog immediately saveClicked() } .setNegativeButton(R.string.user_dialog_button_cancel) { _, _ -> // This will be overridden below // Do nothing } if (user != null) { builder.setNeutralButton(R.string.user_dialog_button_delete) { _, _ -> // This will be overridden below if (this::listener.isInitialized && userId != null) { listener.onDeleteUser(this, userId) } } } val dialog = builder.create() Loading Loading @@ -109,6 +125,24 @@ class UserFragment : DialogFragment() { 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(Random.nextLong(), 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() ?: "" Loading