From 5f15be4bcae27c3add5c8122539952524d7631a2 Mon Sep 17 00:00:00 2001 From: Philipp Heckel Date: Thu, 25 Nov 2021 15:45:12 -0500 Subject: [PATCH] Auto complete home servers --- .../java/io/heckel/ntfy/ui/AddFragment.kt | 83 +++++++++++++++++-- .../main/res/drawable/ic_cancel_gray_24dp.xml | 28 +++++++ .../res/drawable/ic_drop_down_gray_24dp.xml | 27 ++++++ .../res/drawable/ic_drop_up_gray_24dp.xml | 28 +++++++ .../main/res/layout/fragment_add_dialog.xml | 38 +++++++-- .../fragment_add_dialog_dropdown_item.xml | 14 ++++ app/src/main/res/values/strings.xml | 4 +- 7 files changed, 206 insertions(+), 16 deletions(-) create mode 100644 app/src/main/res/drawable/ic_cancel_gray_24dp.xml create mode 100644 app/src/main/res/drawable/ic_drop_down_gray_24dp.xml create mode 100644 app/src/main/res/drawable/ic_drop_up_gray_24dp.xml create mode 100644 app/src/main/res/layout/fragment_add_dialog_dropdown_item.xml diff --git a/app/src/main/java/io/heckel/ntfy/ui/AddFragment.kt b/app/src/main/java/io/heckel/ntfy/ui/AddFragment.kt index 3c923e8..baf4114 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/AddFragment.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/AddFragment.kt @@ -6,25 +6,31 @@ import android.content.Context import android.os.Bundle import android.text.Editable import android.text.TextWatcher +import android.util.Log import android.view.View +import android.widget.ArrayAdapter +import android.widget.AutoCompleteTextView import android.widget.Button import android.widget.CheckBox import androidx.fragment.app.DialogFragment import androidx.lifecycle.lifecycleScope import com.google.android.material.textfield.TextInputEditText +import com.google.android.material.textfield.TextInputLayout +import io.heckel.ntfy.BuildConfig import io.heckel.ntfy.R import io.heckel.ntfy.data.Database import io.heckel.ntfy.data.Repository -import io.heckel.ntfy.BuildConfig import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch + class AddFragment : DialogFragment() { private lateinit var repository: Repository private lateinit var subscribeListener: SubscribeListener private lateinit var topicNameText: TextInputEditText - private lateinit var baseUrlText: TextInputEditText + private lateinit var baseUrlLayout: TextInputLayout + private lateinit var baseUrlText: AutoCompleteTextView private lateinit var useAnotherServerCheckbox: CheckBox private lateinit var useAnotherServerDescription: View private lateinit var instantDeliveryBox: View @@ -32,6 +38,8 @@ class AddFragment : DialogFragment() { private lateinit var instantDeliveryDescription: View private lateinit var subscribeButton: Button + private lateinit var baseUrls: List // List of base URLs already used, excluding app_base_url + interface SubscribeListener { fun onSubscribe(topic: String, baseUrl: String, instant: Boolean) } @@ -53,14 +61,73 @@ class AddFragment : DialogFragment() { // Build root view val view = requireActivity().layoutInflater.inflate(R.layout.fragment_add_dialog, null) - topicNameText = view.findViewById(R.id.add_dialog_topic_text) as TextInputEditText - baseUrlText = view.findViewById(R.id.add_dialog_base_url_text) as TextInputEditText + topicNameText = view.findViewById(R.id.add_dialog_topic_text) + baseUrlLayout = view.findViewById(R.id.add_dialog_base_url_layout) + baseUrlText = view.findViewById(R.id.add_dialog_base_url_text) instantDeliveryBox = view.findViewById(R.id.add_dialog_instant_delivery_box) - instantDeliveryCheckbox = view.findViewById(R.id.add_dialog_instant_delivery_checkbox) as CheckBox + instantDeliveryCheckbox = view.findViewById(R.id.add_dialog_instant_delivery_checkbox) instantDeliveryDescription = view.findViewById(R.id.add_dialog_instant_delivery_description) - useAnotherServerCheckbox = view.findViewById(R.id.add_dialog_use_another_server_checkbox) as CheckBox + useAnotherServerCheckbox = view.findViewById(R.id.add_dialog_use_another_server_checkbox) useAnotherServerDescription = view.findViewById(R.id.add_dialog_use_another_server_description) + // Base URL dropdown behavior; Oh my, why is this so complicated?! + val toggleEndIcon = { + if (baseUrlText.text.isNotEmpty()) { + baseUrlLayout.setEndIconDrawable(R.drawable.ic_cancel_gray_24dp) + } else if (baseUrls.isEmpty()) { + baseUrlLayout.setEndIconDrawable(0) + } else { + baseUrlLayout.setEndIconDrawable(R.drawable.ic_drop_down_gray_24dp) + } + } + baseUrlLayout.setEndIconOnClickListener { + if (baseUrlText.text.isNotEmpty()) { + baseUrlText.text.clear() + if (baseUrls.isEmpty()) { + baseUrlLayout.setEndIconDrawable(0) + } else { + baseUrlLayout.setEndIconDrawable(R.drawable.ic_drop_down_gray_24dp) + } + } else if (baseUrlText.text.isEmpty() && baseUrls.isNotEmpty()) { + baseUrlLayout.setEndIconDrawable(R.drawable.ic_drop_up_gray_24dp) + baseUrlText.showDropDown() + } + } + baseUrlText.setOnDismissListener { toggleEndIcon() } + baseUrlText.addTextChangedListener(object : TextWatcher { + override fun afterTextChanged(s: Editable?) { + toggleEndIcon() + } + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { + // Nothing + } + override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { + // Nothing + } + }) + + // Fill autocomplete for base URL + lifecycleScope.launch(Dispatchers.IO) { + // Sort existing base URLs by most frequently used + val appBaseUrl = getString(R.string.app_base_url) + baseUrls = repository.getSubscriptions() + .groupBy { it.baseUrl } + .mapValues { it.value.size } + .toList() + .sortedBy { (_, size) -> size } + .reversed() + .map { (baseUrl, _) -> baseUrl } + .filterNot { it == appBaseUrl } + val adapter = ArrayAdapter(requireActivity(), R.layout.fragment_add_dialog_dropdown_item, baseUrls) + requireActivity().runOnUiThread { + baseUrlText.threshold = 1 + baseUrlText.setAdapter(adapter) + if (baseUrls.isNotEmpty()) { + baseUrlText.setText(baseUrls.first()) + } + } + } + // Show/hide based on flavor instantDeliveryBox.visibility = if (BuildConfig.FIREBASE_AVAILABLE) View.VISIBLE else View.GONE @@ -109,12 +176,12 @@ class AddFragment : DialogFragment() { useAnotherServerCheckbox.setOnCheckedChangeListener { _, isChecked -> if (isChecked) { useAnotherServerDescription.visibility = View.VISIBLE - baseUrlText.visibility = View.VISIBLE + baseUrlLayout.visibility = View.VISIBLE instantDeliveryBox.visibility = View.GONE instantDeliveryDescription.visibility = View.GONE } else { useAnotherServerDescription.visibility = View.GONE - baseUrlText.visibility = View.GONE + baseUrlLayout.visibility = View.GONE instantDeliveryBox.visibility = if (BuildConfig.FIREBASE_AVAILABLE) View.VISIBLE else View.GONE if (instantDeliveryCheckbox.isChecked) instantDeliveryDescription.visibility = View.VISIBLE else instantDeliveryDescription.visibility = View.GONE diff --git a/app/src/main/res/drawable/ic_cancel_gray_24dp.xml b/app/src/main/res/drawable/ic_cancel_gray_24dp.xml new file mode 100644 index 0000000..b159894 --- /dev/null +++ b/app/src/main/res/drawable/ic_cancel_gray_24dp.xml @@ -0,0 +1,28 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_drop_down_gray_24dp.xml b/app/src/main/res/drawable/ic_drop_down_gray_24dp.xml new file mode 100644 index 0000000..d4fabbd --- /dev/null +++ b/app/src/main/res/drawable/ic_drop_down_gray_24dp.xml @@ -0,0 +1,27 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_drop_up_gray_24dp.xml b/app/src/main/res/drawable/ic_drop_up_gray_24dp.xml new file mode 100644 index 0000000..6964929 --- /dev/null +++ b/app/src/main/res/drawable/ic_drop_up_gray_24dp.xml @@ -0,0 +1,28 @@ + + + + + + diff --git a/app/src/main/res/layout/fragment_add_dialog.xml b/app/src/main/res/layout/fragment_add_dialog.xml index dfde2be..dcaa7a9 100644 --- a/app/src/main/res/layout/fragment_add_dialog.xml +++ b/app/src/main/res/layout/fragment_add_dialog.xml @@ -1,6 +1,7 @@ - + android:layout_height="wrap_content" + android:layout_margin="0dp" + android:background="@android:color/transparent" + android:padding="0dp" + android:visibility="gone" + app:endIconMode="custom" + app:hintEnabled="false" + app:boxBackgroundColor="@android:color/transparent"> + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c28582e..c42eefa 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -60,8 +60,8 @@ Topic name, e.g. phils_alerts Use another server - You can subscribe to topics from your own server. Due to platform limitations, this option requires a foreground - service and consumes more power, but also delivers notifications faster (even in doze mode). + You can subscribe to topics from your own server. This option requires a foreground service and + consumes more power, but also delivers notifications faster (even in doze mode). Instant delivery in doze mode