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? = 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() }