Don't make AddFragment crash when screen is rotated

This commit is contained in:
Philipp Heckel 2021-11-17 20:16:58 -05:00
parent c3f9cfbb13
commit b40176c9f4
3 changed files with 92 additions and 73 deletions

View file

@ -2,9 +2,11 @@ package io.heckel.ntfy.ui
import android.app.AlertDialog import android.app.AlertDialog
import android.app.Dialog import android.app.Dialog
import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.text.Editable import android.text.Editable
import android.text.TextWatcher import android.text.TextWatcher
import android.util.Log
import android.view.View import android.view.View
import android.widget.Button import android.widget.Button
import android.widget.CheckBox import android.widget.CheckBox
@ -12,10 +14,15 @@ import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.google.android.material.textfield.TextInputEditText import com.google.android.material.textfield.TextInputEditText
import io.heckel.ntfy.R import io.heckel.ntfy.R
import io.heckel.ntfy.data.Database
import io.heckel.ntfy.data.Repository
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class AddFragment(private val viewModel: SubscriptionsViewModel, private val onSubscribe: (topic: String, baseUrl: String, instant: Boolean) -> Unit) : DialogFragment() { class AddFragment : DialogFragment() {
private lateinit var repository: Repository
private lateinit var subscribeListener: SubscribeListener
private lateinit var topicNameText: TextInputEditText private lateinit var topicNameText: TextInputEditText
private lateinit var baseUrlText: TextInputEditText private lateinit var baseUrlText: TextInputEditText
private lateinit var useAnotherServerCheckbox: CheckBox private lateinit var useAnotherServerCheckbox: CheckBox
@ -25,81 +32,96 @@ class AddFragment(private val viewModel: SubscriptionsViewModel, private val onS
private lateinit var instantDeliveryDescription: View private lateinit var instantDeliveryDescription: View
private lateinit var subscribeButton: Button private lateinit var subscribeButton: Button
interface SubscribeListener {
fun onSubscribe(topic: String, baseUrl: String, instant: Boolean)
}
override fun onAttach(context: Context) {
super.onAttach(context)
subscribeListener = activity as SubscribeListener
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let { if (activity == null) {
// Build root view throw IllegalStateException("Activity cannot be null")
val view = requireActivity().layoutInflater.inflate(R.layout.add_dialog_fragment, null) }
topicNameText = view.findViewById(R.id.add_dialog_topic_text) as TextInputEditText
baseUrlText = view.findViewById(R.id.add_dialog_base_url_text) as TextInputEditText
instantDeliveryBox = view.findViewById(R.id.add_dialog_instant_delivery_box)
instantDeliveryCheckbox = view.findViewById(R.id.add_dialog_instant_delivery_checkbox) as CheckBox
instantDeliveryDescription = view.findViewById(R.id.add_dialog_instant_delivery_description)
useAnotherServerCheckbox = view.findViewById(R.id.add_dialog_use_another_server_checkbox) as CheckBox
useAnotherServerDescription = view.findViewById(R.id.add_dialog_use_another_server_description)
// Build dialog // Dependencies
val alert = AlertDialog.Builder(it) val database = Database.getInstance(activity!!.applicationContext)
.setView(view) repository = Repository.getInstance(database.subscriptionDao(), database.notificationDao())
.setPositiveButton(R.string.add_dialog_button_subscribe) { _, _ ->
val topic = topicNameText.text.toString()
val baseUrl = getBaseUrl()
val instant = if (useAnotherServerCheckbox.isChecked) true else instantDeliveryCheckbox.isChecked
onSubscribe(topic, baseUrl, instant)
}
.setNegativeButton(R.string.add_dialog_button_cancel) { _, _ ->
dialog?.cancel()
}
.create()
// Add logic to disable "Subscribe" button on invalid input // Build root view
alert.setOnShowListener { val view = requireActivity().layoutInflater.inflate(R.layout.add_dialog_fragment, null)
val dialog = it as AlertDialog topicNameText = view.findViewById(R.id.add_dialog_topic_text) as TextInputEditText
baseUrlText = view.findViewById(R.id.add_dialog_base_url_text) as TextInputEditText
instantDeliveryBox = view.findViewById(R.id.add_dialog_instant_delivery_box)
instantDeliveryCheckbox = view.findViewById(R.id.add_dialog_instant_delivery_checkbox) as CheckBox
instantDeliveryDescription = view.findViewById(R.id.add_dialog_instant_delivery_description)
useAnotherServerCheckbox = view.findViewById(R.id.add_dialog_use_another_server_checkbox) as CheckBox
useAnotherServerDescription = view.findViewById(R.id.add_dialog_use_another_server_description)
subscribeButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE) // Build dialog
subscribeButton.isEnabled = false val alert = AlertDialog.Builder(activity)
.setView(view)
.setPositiveButton(R.string.add_dialog_button_subscribe) { _, _ ->
val topic = topicNameText.text.toString()
val baseUrl = getBaseUrl()
val instant = if (useAnotherServerCheckbox.isChecked) true else instantDeliveryCheckbox.isChecked
subscribeListener.onSubscribe(topic, baseUrl, instant)
}
.setNegativeButton(R.string.add_dialog_button_cancel) { _, _ ->
dialog?.cancel()
}
.create()
val textWatcher = object : TextWatcher { // Add logic to disable "Subscribe" button on invalid input
override fun afterTextChanged(s: Editable?) { alert.setOnShowListener {
validateInput() val dialog = it as AlertDialog
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { subscribeButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE)
// Nothing subscribeButton.isEnabled = false
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { val textWatcher = object : TextWatcher {
// Nothing override fun afterTextChanged(s: Editable?) {
}
}
topicNameText.addTextChangedListener(textWatcher)
baseUrlText.addTextChangedListener(textWatcher)
instantDeliveryCheckbox.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) instantDeliveryDescription.visibility = View.VISIBLE
else instantDeliveryDescription.visibility = View.GONE
}
useAnotherServerCheckbox.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) {
useAnotherServerDescription.visibility = View.VISIBLE
baseUrlText.visibility = View.VISIBLE
instantDeliveryBox.visibility = View.GONE
instantDeliveryDescription.visibility = View.GONE
} else {
useAnotherServerDescription.visibility = View.GONE
baseUrlText.visibility = View.GONE
instantDeliveryBox.visibility = View.VISIBLE
if (instantDeliveryCheckbox.isChecked) instantDeliveryDescription.visibility = View.VISIBLE
else instantDeliveryDescription.visibility = View.GONE
}
validateInput() 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
}
} }
topicNameText.addTextChangedListener(textWatcher)
baseUrlText.addTextChangedListener(textWatcher)
instantDeliveryCheckbox.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) instantDeliveryDescription.visibility = View.VISIBLE
else instantDeliveryDescription.visibility = View.GONE
}
useAnotherServerCheckbox.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) {
useAnotherServerDescription.visibility = View.VISIBLE
baseUrlText.visibility = View.VISIBLE
instantDeliveryBox.visibility = View.GONE
instantDeliveryDescription.visibility = View.GONE
} else {
useAnotherServerDescription.visibility = View.GONE
baseUrlText.visibility = View.GONE
instantDeliveryBox.visibility = View.VISIBLE
if (instantDeliveryCheckbox.isChecked) instantDeliveryDescription.visibility = View.VISIBLE
else instantDeliveryDescription.visibility = View.GONE
}
validateInput()
}
}
alert return alert
} ?: throw IllegalStateException("Activity cannot be null")
} }
private fun validateInput() = lifecycleScope.launch(Dispatchers.IO) { private fun validateInput() = lifecycleScope.launch(Dispatchers.IO) {
val baseUrl = getBaseUrl() val baseUrl = getBaseUrl()
val topic = topicNameText.text.toString() val topic = topicNameText.text.toString()
val subscription = viewModel.get(baseUrl, topic) val subscription = repository.getSubscription(baseUrl, topic)
activity?.let { activity?.let {
it.runOnUiThread { it.runOnUiThread {
@ -127,8 +149,6 @@ class AddFragment(private val viewModel: SubscriptionsViewModel, private val onS
} }
companion object { companion object {
fun newInstance() { const val TAG = "NtfyAffFragment"
// ... make it not crash
}
} }
} }

View file

@ -35,7 +35,7 @@ import java.util.concurrent.TimeUnit
import kotlin.random.Random import kotlin.random.Random
class MainActivity : AppCompatActivity(), ActionMode.Callback { class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.SubscribeListener {
private val viewModel by viewModels<SubscriptionsViewModel> { private val viewModel by viewModels<SubscriptionsViewModel> {
SubscriptionsViewModelFactory((application as Application).repository) SubscriptionsViewModelFactory((application as Application).repository)
} }
@ -152,12 +152,11 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback {
} }
private fun onSubscribeButtonClick() { private fun onSubscribeButtonClick() {
val newFragment = AddFragment(viewModel, ::onSubscribe) val newFragment = AddFragment()
newFragment newFragment.show(supportFragmentManager, AddFragment.TAG)
.show(supportFragmentManager, "AddFragment")
} }
private fun onSubscribe(topic: String, baseUrl: String, instant: Boolean) { override fun onSubscribe(topic: String, baseUrl: String, instant: Boolean) {
Log.d(TAG, "Adding subscription ${topicShortUrl(baseUrl, topic)}") Log.d(TAG, "Adding subscription ${topicShortUrl(baseUrl, topic)}")
// Add subscription to database // Add subscription to database

View file

@ -1,10 +1,10 @@
<menu xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:android="http://schemas.android.com/apk/res/android" > <menu xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:id="@+id/detail_menu_enable_instant" android:title="@string/detail_menu_enable_instant" <item android:id="@+id/detail_menu_enable_instant" android:title="@string/detail_menu_enable_instant"
app:showAsAction="ifRoom|withText" android:icon="@drawable/ic_bolt_outline_white_24dp"/> app:showAsAction="ifRoom" android:icon="@drawable/ic_bolt_outline_white_24dp"/>
<item android:id="@+id/detail_menu_disable_instant" android:title="@string/detail_menu_disable_instant" <item android:id="@+id/detail_menu_disable_instant" android:title="@string/detail_menu_disable_instant"
android:icon="@drawable/ic_bolt_white_24dp" app:showAsAction="ifRoom|withText"/> android:icon="@drawable/ic_bolt_white_24dp" app:showAsAction="ifRoom"/>
<item android:id="@+id/detail_menu_instant_info" android:title="@string/detail_menu_instant_info" <item android:id="@+id/detail_menu_instant_info" android:title="@string/detail_menu_instant_info"
android:icon="@drawable/ic_bolt_white_24dp" app:showAsAction="ifRoom|withText"/> android:icon="@drawable/ic_bolt_white_24dp" app:showAsAction="ifRoom"/>
<item android:id="@+id/detail_menu_test" android:title="@string/detail_menu_test"/> <item android:id="@+id/detail_menu_test" android:title="@string/detail_menu_test"/>
<item android:id="@+id/detail_menu_copy_url" android:title="@string/detail_menu_copy_url"/> <item android:id="@+id/detail_menu_copy_url" android:title="@string/detail_menu_copy_url"/>
<item android:id="@+id/detail_menu_unsubscribe" android:title="@string/detail_menu_unsubscribe"/> <item android:id="@+id/detail_menu_unsubscribe" android:title="@string/detail_menu_unsubscribe"/>