Improve alert profile and rule editors
This commit is contained in:
@@ -8,6 +8,9 @@ import android.widget.CheckBox
|
|||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.Spinner
|
import android.widget.Spinner
|
||||||
|
import android.widget.ScrollView
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.core.widget.doAfterTextChanged
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.google.android.material.switchmaterial.SwitchMaterial
|
import com.google.android.material.switchmaterial.SwitchMaterial
|
||||||
import se.ajpanton.notificationsmaster.alerts.AlertConfiguration
|
import se.ajpanton.notificationsmaster.alerts.AlertConfiguration
|
||||||
@@ -33,17 +36,7 @@ object AlertRuleDialog {
|
|||||||
val app = apps?.let { choices ->
|
val app = apps?.let { choices ->
|
||||||
Button(context).apply {
|
Button(context).apply {
|
||||||
text = "Choose apps"
|
text = "Choose apps"
|
||||||
setOnClickListener {
|
setOnClickListener { showAppChooser(context, choices, selectedApps, this) }
|
||||||
MaterialAlertDialogBuilder(context).setTitle("Apps this rule applies to")
|
|
||||||
.setMultiChoiceItems(choices.map { choice -> choice.toString() }.toTypedArray(), choices.map { choice -> choice.packageName in selectedApps }.toBooleanArray()) { _, which, checked ->
|
|
||||||
if (checked) selectedApps += choices[which].packageName else selectedApps -= choices[which].packageName
|
|
||||||
text = when (selectedApps.size) {
|
|
||||||
0 -> "Choose apps"
|
|
||||||
1 -> choices.first { choice -> choice.packageName in selectedApps }.name
|
|
||||||
else -> choices.first { choice -> choice.packageName in selectedApps }.name + "…"
|
|
||||||
}
|
|
||||||
}.setPositiveButton("Done", null).show()
|
|
||||||
}
|
|
||||||
}.also(form::addView)
|
}.also(form::addView)
|
||||||
}
|
}
|
||||||
val profiles = configuration.profiles
|
val profiles = configuration.profiles
|
||||||
@@ -134,4 +127,56 @@ object AlertRuleDialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun dp(context: Context, value: Int) = (value * context.resources.displayMetrics.density).toInt()
|
private fun dp(context: Context, value: Int) = (value * context.resources.displayMetrics.density).toInt()
|
||||||
|
|
||||||
|
private fun showAppChooser(
|
||||||
|
context: Context,
|
||||||
|
apps: List<InstalledApp>,
|
||||||
|
selected: MutableSet<String>,
|
||||||
|
button: Button,
|
||||||
|
) {
|
||||||
|
val list = LinearLayout(context).apply { orientation = LinearLayout.VERTICAL }
|
||||||
|
val search = EditText(context).apply { hint = "Filter apps" }
|
||||||
|
fun updateButton() {
|
||||||
|
button.text = when (selected.size) {
|
||||||
|
0 -> "Choose apps"
|
||||||
|
1 -> apps.first { it.packageName in selected }.name
|
||||||
|
else -> apps.first { it.packageName in selected }.name + "…"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun showMatches(query: String) {
|
||||||
|
val terms = query.lowercase().split(Regex("\\s+")).filter(String::isNotBlank)
|
||||||
|
list.removeAllViews()
|
||||||
|
apps.filter { app ->
|
||||||
|
val searchable = "${app.name} ${app.packageName}".lowercase()
|
||||||
|
terms.all(searchable::contains)
|
||||||
|
}.forEach { app ->
|
||||||
|
val checkBox = CheckBox(context).apply { isChecked = app.packageName in selected }
|
||||||
|
list.addView(LinearLayout(context).apply {
|
||||||
|
orientation = LinearLayout.HORIZONTAL
|
||||||
|
isClickable = true
|
||||||
|
setPadding(0, dp(context, 4), 0, dp(context, 4))
|
||||||
|
addView(checkBox)
|
||||||
|
addView(LinearLayout(context).apply {
|
||||||
|
orientation = LinearLayout.VERTICAL
|
||||||
|
addView(TextView(context).apply { text = app.name; textSize = 16f; setTypeface(typeface, android.graphics.Typeface.BOLD) })
|
||||||
|
addView(TextView(context).apply { text = app.packageName; textSize = 12f; alpha = 0.72f })
|
||||||
|
})
|
||||||
|
setOnClickListener { checkBox.isChecked = !checkBox.isChecked }
|
||||||
|
})
|
||||||
|
checkBox.setOnCheckedChangeListener { _, checked ->
|
||||||
|
if (checked) selected += app.packageName else selected -= app.packageName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
search.doAfterTextChanged { showMatches(it.toString()) }
|
||||||
|
showMatches("")
|
||||||
|
val content = LinearLayout(context).apply {
|
||||||
|
orientation = LinearLayout.VERTICAL
|
||||||
|
setPadding(dp(context, 24), 0, dp(context, 24), 0)
|
||||||
|
addView(search)
|
||||||
|
addView(ScrollView(context).apply { addView(list) }, LinearLayout.LayoutParams(-1, dp(context, 420)))
|
||||||
|
}
|
||||||
|
MaterialAlertDialogBuilder(context).setTitle("Apps this rule applies to")
|
||||||
|
.setView(content).setPositiveButton("Done") { _, _ -> updateButton() }.show()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ import android.content.Intent
|
|||||||
import android.media.RingtoneManager
|
import android.media.RingtoneManager
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.os.VibrationAttributes
|
||||||
|
import android.os.VibrationEffect
|
||||||
|
import android.os.VibratorManager
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
@@ -22,9 +25,10 @@ class ProfilesFragment : Fragment(R.layout.fragment_profiles) {
|
|||||||
private lateinit var store: AlertConfigurationStore
|
private lateinit var store: AlertConfigurationStore
|
||||||
private var selectedSoundUri: String? = null
|
private var selectedSoundUri: String? = null
|
||||||
private var soundButton: Button? = null
|
private var soundButton: Button? = null
|
||||||
|
private var soundName: android.widget.TextView? = null
|
||||||
private val chooseSound = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
private val chooseSound = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||||
selectedSoundUri = result.data?.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI, Uri::class.java)?.toString()
|
selectedSoundUri = result.data?.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI, Uri::class.java)?.toString()
|
||||||
soundButton?.text = if (selectedSoundUri == null) "Choose sound" else "Change sound"
|
updateSoundSelection()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
@@ -37,6 +41,7 @@ class ProfilesFragment : Fragment(R.layout.fragment_profiles) {
|
|||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
binding = null
|
binding = null
|
||||||
soundButton = null
|
soundButton = null
|
||||||
|
soundName = null
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,9 +84,24 @@ class ProfilesFragment : Fragment(R.layout.fragment_profiles) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
soundButton = pickerButton
|
soundButton = pickerButton
|
||||||
|
val selectedSound = android.widget.TextView(context).apply { text = soundLabel() }
|
||||||
|
soundName = selectedSound
|
||||||
|
val testVibration = Button(context).apply {
|
||||||
|
text = "Test vibration"
|
||||||
|
setOnClickListener {
|
||||||
|
parsePattern(vibration.text.toString())?.takeIf { it.any { duration -> duration > 0 } }?.let { pattern ->
|
||||||
|
context.getSystemService(VibratorManager::class.java).defaultVibrator.vibrate(
|
||||||
|
VibrationEffect.createWaveform(pattern.toLongArray(), -1),
|
||||||
|
VibrationAttributes.Builder().setUsage(VibrationAttributes.USAGE_NOTIFICATION).build(),
|
||||||
|
)
|
||||||
|
} ?: run { vibration.error = "Enter a vibration pattern first" }
|
||||||
|
}
|
||||||
|
}
|
||||||
form.addView(name)
|
form.addView(name)
|
||||||
form.addView(pickerButton)
|
form.addView(pickerButton)
|
||||||
|
form.addView(selectedSound)
|
||||||
form.addView(vibration)
|
form.addView(vibration)
|
||||||
|
form.addView(testVibration)
|
||||||
val dialog = MaterialAlertDialogBuilder(context)
|
val dialog = MaterialAlertDialogBuilder(context)
|
||||||
.setTitle(if (existing == null) "Add profile" else "Edit profile")
|
.setTitle(if (existing == null) "Add profile" else "Edit profile")
|
||||||
.setView(form)
|
.setView(form)
|
||||||
@@ -134,5 +154,14 @@ class ProfilesFragment : Fragment(R.layout.fragment_profiles) {
|
|||||||
if (profile.vibrationPattern.any { it > 0 }) add("Vibration")
|
if (profile.vibrationPattern.any { it > 0 }) add("Vibration")
|
||||||
}.joinToString(" + ")
|
}.joinToString(" + ")
|
||||||
|
|
||||||
|
private fun updateSoundSelection() {
|
||||||
|
soundButton?.text = if (selectedSoundUri == null) "Choose sound" else "Change sound"
|
||||||
|
soundName?.text = soundLabel()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun soundLabel(): String = selectedSoundUri?.let { uri ->
|
||||||
|
RingtoneManager.getRingtone(requireContext(), Uri.parse(uri))?.getTitle(requireContext()) ?: uri
|
||||||
|
} ?: "No sound selected"
|
||||||
|
|
||||||
private fun dp(value: Int) = (value * resources.displayMetrics.density).toInt()
|
private fun dp(value: Int) = (value * resources.displayMetrics.density).toInt()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,12 +6,9 @@
|
|||||||
android:title="@string/page_view_logs" />
|
android:title="@string/page_view_logs" />
|
||||||
</group>
|
</group>
|
||||||
<group android:checkableBehavior="single">
|
<group android:checkableBehavior="single">
|
||||||
<item
|
|
||||||
android:id="@+id/nav_settings"
|
|
||||||
android:title="@string/navigation_settings_header" />
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/nav_alerts"
|
android:id="@+id/nav_alerts"
|
||||||
android:title="@string/navigation_indented_alerts" />
|
android:title="@string/navigation_alerts_header" />
|
||||||
<item
|
<item
|
||||||
android:id="@+id/nav_alert_status"
|
android:id="@+id/nav_alert_status"
|
||||||
android:title="@string/navigation_indented_alert_status" />
|
android:title="@string/navigation_indented_alert_status" />
|
||||||
@@ -24,6 +21,11 @@
|
|||||||
<item
|
<item
|
||||||
android:id="@+id/nav_apps"
|
android:id="@+id/nav_apps"
|
||||||
android:title="@string/navigation_indented_apps" />
|
android:title="@string/navigation_indented_apps" />
|
||||||
|
</group>
|
||||||
|
<group android:checkableBehavior="single">
|
||||||
|
<item
|
||||||
|
android:id="@+id/nav_settings"
|
||||||
|
android:title="@string/navigation_logging_header" />
|
||||||
<item
|
<item
|
||||||
android:id="@+id/nav_log_display"
|
android:id="@+id/nav_log_display"
|
||||||
android:title="@string/navigation_indented_log_display" />
|
android:title="@string/navigation_indented_log_display" />
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
<string name="notification_listener_label">Notifications Master listener</string>
|
<string name="notification_listener_label">Notifications Master listener</string>
|
||||||
<string name="navigation_open">Open navigation</string>
|
<string name="navigation_open">Open navigation</string>
|
||||||
<string name="navigation_close">Close navigation</string>
|
<string name="navigation_close">Close navigation</string>
|
||||||
<string name="navigation_settings_header">Settings</string>
|
<string name="navigation_alerts_header">Alert control</string>
|
||||||
|
<string name="navigation_logging_header">Logging</string>
|
||||||
<string name="page_view_logs">View logs</string>
|
<string name="page_view_logs">View logs</string>
|
||||||
<string name="page_settings">Settings</string>
|
<string name="page_settings">Settings</string>
|
||||||
<string name="page_alerts">Alerts</string>
|
<string name="page_alerts">Alerts</string>
|
||||||
@@ -16,7 +17,6 @@
|
|||||||
<string name="page_log_display">Log display</string>
|
<string name="page_log_display">Log display</string>
|
||||||
<string name="page_filter_logging">Filter logging</string>
|
<string name="page_filter_logging">Filter logging</string>
|
||||||
<string name="navigation_indented_log_display">    Log display</string>
|
<string name="navigation_indented_log_display">    Log display</string>
|
||||||
<string name="navigation_indented_alerts">    Alerts</string>
|
|
||||||
<string name="navigation_indented_alert_status">    Alert status</string>
|
<string name="navigation_indented_alert_status">    Alert status</string>
|
||||||
<string name="navigation_indented_profiles">    Profiles</string>
|
<string name="navigation_indented_profiles">    Profiles</string>
|
||||||
<string name="navigation_indented_rules">    Rules</string>
|
<string name="navigation_indented_rules">    Rules</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user