Files
notifications-master/app/src/main/java/se/ajpanton/notificationsmaster/ProfilesFragment.kt
T
2026-08-18 01:37:28 +00:00

139 lines
6.0 KiB
Kotlin

package se.ajpanton.notificationsmaster
import android.content.Intent
import android.media.RingtoneManager
import android.net.Uri
import android.os.Bundle
import android.view.View
import android.widget.Button
import android.widget.EditText
import android.widget.LinearLayout
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.Fragment
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import se.ajpanton.notificationsmaster.alerts.AlertConfigurationStore
import se.ajpanton.notificationsmaster.alerts.AlertProfile
import se.ajpanton.notificationsmaster.databinding.FragmentProfilesBinding
import java.util.UUID
class ProfilesFragment : Fragment(R.layout.fragment_profiles) {
private var binding: FragmentProfilesBinding? = null
private lateinit var store: AlertConfigurationStore
private var selectedSoundUri: String? = null
private var soundButton: Button? = null
private val chooseSound = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
selectedSoundUri = result.data?.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI, Uri::class.java)?.toString()
soundButton?.text = if (selectedSoundUri == null) "Choose sound" else "Change sound"
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding = FragmentProfilesBinding.bind(view)
store = AlertConfigurationStore(requireContext())
binding!!.addProfile.setOnClickListener { showEditor() }
refresh()
}
override fun onDestroyView() {
binding = null
soundButton = null
super.onDestroyView()
}
private fun refresh() {
val container = binding!!.profiles
container.removeAllViews()
store.load().profiles.forEach { profile ->
container.addView(Button(requireContext()).apply {
text = profile.name + "\n" + profileSummary(profile)
isAllCaps = false
setOnClickListener { showEditor(profile) }
})
}
}
private fun showEditor(existing: AlertProfile? = null) {
selectedSoundUri = existing?.soundUri
val context = requireContext()
val form = LinearLayout(context).apply {
orientation = LinearLayout.VERTICAL
setPadding(dp(24), 0, dp(24), 0)
}
val name = EditText(context).apply {
hint = "Profile name"
setText(existing?.name)
}
val vibration = EditText(context).apply {
hint = "Vibration timing in milliseconds, for example 0, 200, 100, 200"
setText(existing?.vibrationPattern?.joinToString(", ") ?: "0, 200")
inputType = android.text.InputType.TYPE_CLASS_TEXT
}
val pickerButton = Button(context).apply {
text = if (selectedSoundUri == null) "Choose sound" else "Change sound"
setOnClickListener {
chooseSound.launch(
Intent(RingtoneManager.ACTION_RINGTONE_PICKER)
.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION)
.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, selectedSoundUri?.let(Uri::parse)),
)
}
}
soundButton = pickerButton
form.addView(name)
form.addView(pickerButton)
form.addView(vibration)
val dialog = MaterialAlertDialogBuilder(context)
.setTitle(if (existing == null) "Add profile" else "Edit profile")
.setView(form)
.setNegativeButton("Cancel", null)
.setPositiveButton("Save", null)
.apply {
if (existing != null) setNeutralButton("Delete", null)
}
.show()
dialog.getButton(android.content.DialogInterface.BUTTON_POSITIVE).setOnClickListener {
val pattern = parsePattern(vibration.text.toString()) ?: run {
vibration.error = "Use non-negative comma-separated milliseconds"
return@setOnClickListener
}
val profileName = name.text.toString().trim()
if (profileName.isEmpty() || selectedSoundUri == null && pattern.none { it > 0 }) {
name.error = if (profileName.isEmpty()) "Enter a name" else null
vibration.error = if (selectedSoundUri == null && pattern.none { it > 0 }) "Choose a sound or vibration" else null
return@setOnClickListener
}
val profile = AlertProfile(existing?.id ?: UUID.randomUUID().toString(), profileName, selectedSoundUri, pattern)
val configuration = store.load()
store.save(configuration.copy(profiles = configuration.profiles.filterNot { it.id == profile.id } + profile))
dialog.dismiss()
refresh()
}
if (existing != null) {
dialog.getButton(android.content.DialogInterface.BUTTON_NEUTRAL).setOnClickListener {
val configuration = store.load()
if (configuration.rules.any { it.profileId == existing.id }) {
Toast.makeText(context, "Remove or change rules using this profile first", Toast.LENGTH_LONG).show()
return@setOnClickListener
}
store.save(configuration.copy(profiles = configuration.profiles.filterNot { it.id == existing.id }))
dialog.dismiss()
refresh()
}
}
}
private fun parsePattern(text: String): List<Long>? = text.split(',')
.map { it.trim() }
.filter { it.isNotEmpty() }
.map { it.toLongOrNull() ?: return null }
.takeIf { it.all { value -> value >= 0 } }
?: emptyList()
private fun profileSummary(profile: AlertProfile): String = buildList {
if (profile.soundUri != null) add("Sound")
if (profile.vibrationPattern.any { it > 0 }) add("Vibration")
}.joinToString(" + ")
private fun dp(value: Int) = (value * resources.displayMetrics.density).toInt()
}