diff --git a/app/src/main/java/com/marcus/meditationtimer/MainActivity.kt b/app/src/main/java/com/marcus/meditationtimer/MainActivity.kt index 6bda5d7..3e5e6f7 100644 --- a/app/src/main/java/com/marcus/meditationtimer/MainActivity.kt +++ b/app/src/main/java/com/marcus/meditationtimer/MainActivity.kt @@ -36,6 +36,7 @@ import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.ui.draw.clip +import androidx.compose.ui.text.style.TextDecoration import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults @@ -187,6 +188,7 @@ private fun TimerTab() { var durationMin by rememberSaveable { mutableIntStateOf(20) } val settings = remember { context.getSharedPreferences("settings", android.content.Context.MODE_PRIVATE) } var intervalMin by rememberSaveable { mutableIntStateOf(settings.getInt("interval_min", 0)) } + var gongKey by rememberSaveable { mutableStateOf(settings.getString("gong_sound", "bowl") ?: "bowl") } val phase = timer.phase val selectedTotalMs = durationMin * 60_000L @@ -222,7 +224,15 @@ private fun TimerTab() { settings.edit().putInt("interval_min", it).apply() }, ) - Spacer(Modifier.height(20.dp)) + Spacer(Modifier.height(6.dp)) + GongSoundRow( + soundKey = gongKey, + onChange = { + gongKey = it + settings.edit().putString("gong_sound", it).apply() + }, + ) + Spacer(Modifier.height(16.dp)) Button( onClick = { TimerService.start(context, selectedTotalMs, intervalMin) }, colors = ButtonDefaults.buttonColors(containerColor = Gold, contentColor = DeepBlue), @@ -274,27 +284,79 @@ private val INTERVAL_OPTIONS = listOf(0, 5, 10, 15, 20, 30) @Composable private fun IntervalBellRow(intervalMin: Int, onChange: (Int) -> Unit) { + // Collapsed by default — a small underlined label that expands the options. + var expanded by rememberSaveable { mutableStateOf(false) } Column(horizontalAlignment = Alignment.CenterHorizontally) { Text( - "INTERVAL BELL", - color = Color.White.copy(alpha = 0.35f), - fontSize = 11.sp, - letterSpacing = 2.sp, + text = if (intervalMin == 0) "Interval Timer" else "Interval Timer · ${intervalMin}m", + color = Gold.copy(alpha = if (expanded) 0.9f else 0.55f), + fontSize = 13.sp, + textDecoration = TextDecoration.Underline, + modifier = Modifier + .clickable { expanded = !expanded } + .padding(horizontal = 12.dp, vertical = 6.dp), ) - Spacer(Modifier.height(8.dp)) - Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { - INTERVAL_OPTIONS.forEach { min -> - val selected = intervalMin == min - Text( - text = if (min == 0) "Off" else "${min}m", - color = if (selected) DeepBlue else Gold.copy(alpha = 0.7f), - fontSize = 13.sp, - modifier = Modifier - .clip(RoundedCornerShape(14.dp)) - .background(if (selected) Gold else Color.White.copy(alpha = 0.06f)) - .clickable { onChange(min) } - .padding(horizontal = 12.dp, vertical = 6.dp), - ) + if (expanded) { + Spacer(Modifier.height(6.dp)) + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + INTERVAL_OPTIONS.forEach { min -> + val selected = intervalMin == min + Text( + text = if (min == 0) "Off" else "${min}m", + color = if (selected) DeepBlue else Gold.copy(alpha = 0.7f), + fontSize = 13.sp, + modifier = Modifier + .clip(RoundedCornerShape(14.dp)) + .background(if (selected) Gold else Color.White.copy(alpha = 0.06f)) + .clickable { + onChange(min) + expanded = false + } + .padding(horizontal = 12.dp, vertical = 6.dp), + ) + } + } + } + } +} + +@Composable +private fun GongSoundRow(soundKey: String, onChange: (String) -> Unit) { + var expanded by rememberSaveable { mutableStateOf(false) } + val context = LocalContext.current + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Text( + text = "Gong Sound · ${GONG_SOUNDS.firstOrNull { it.first == soundKey }?.second ?: "Bowl"}", + color = Gold.copy(alpha = if (expanded) 0.9f else 0.55f), + fontSize = 13.sp, + textDecoration = TextDecoration.Underline, + modifier = Modifier + .clickable { expanded = !expanded } + .padding(horizontal = 12.dp, vertical = 6.dp), + ) + if (expanded) { + Spacer(Modifier.height(6.dp)) + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + GONG_SOUNDS.forEach { (key, label) -> + val selected = soundKey == key + Text( + text = label, + color = if (selected) DeepBlue else Gold.copy(alpha = 0.7f), + fontSize = 13.sp, + modifier = Modifier + .clip(RoundedCornerShape(14.dp)) + .background(if (selected) Gold else Color.White.copy(alpha = 0.06f)) + .clickable { + onChange(key) + // Preview the chosen gong immediately. + android.media.MediaPlayer.create(context, gongRes(key))?.apply { + setOnCompletionListener { it.release() } + start() + } + } + .padding(horizontal = 12.dp, vertical = 6.dp), + ) + } } } } diff --git a/app/src/main/java/com/marcus/meditationtimer/Sounds.kt b/app/src/main/java/com/marcus/meditationtimer/Sounds.kt new file mode 100644 index 0000000..3675d81 --- /dev/null +++ b/app/src/main/java/com/marcus/meditationtimer/Sounds.kt @@ -0,0 +1,18 @@ +package com.marcus.meditationtimer + +/** Selectable session-gong sounds (key → label). Key persisted in settings. */ +val GONG_SOUNDS = listOf( + "bowl" to "Bowl", + "deep" to "Deep", + "bright" to "Bright", + "bell" to "Bell", + "twin" to "Twin", +) + +fun gongRes(key: String): Int = when (key) { + "deep" -> R.raw.gong_deep + "bright" -> R.raw.gong_bright + "bell" -> R.raw.gong_bell + "twin" -> R.raw.gong_twin + else -> R.raw.gong +} diff --git a/app/src/main/java/com/marcus/meditationtimer/TimerService.kt b/app/src/main/java/com/marcus/meditationtimer/TimerService.kt index d17cc50..ad0016f 100644 --- a/app/src/main/java/com/marcus/meditationtimer/TimerService.kt +++ b/app/src/main/java/com/marcus/meditationtimer/TimerService.kt @@ -69,6 +69,7 @@ class TimerService : Service() { private var pauseCount = 0 private var intervalMin = 0 private var nextBellElapsedMs = 0L + private var gongResId = R.raw.gong override fun onBind(intent: Intent?) = null @@ -89,6 +90,9 @@ class TimerService : Service() { pauseCount = 0 intervalMin = intent.getIntExtra(EXTRA_INTERVAL_MIN, 0) nextBellElapsedMs = if (intervalMin > 0) intervalMin * 60_000L else Long.MAX_VALUE + gongResId = gongRes( + getSharedPreferences("settings", MODE_PRIVATE).getString("gong_sound", "bowl") ?: "bowl" + ) startForeground(NOTIF_ID, buildNotification(remainingMs, paused = false)) acquireWakeLock(totalMs) playGong() @@ -185,7 +189,7 @@ class TimerService : Service() { private fun playGong(onComplete: (() -> Unit)? = null) { player?.release() - player = MediaPlayer.create(this, R.raw.gong)?.apply { + player = MediaPlayer.create(this, gongResId)?.apply { setOnCompletionListener { it.release() if (player == it) player = null diff --git a/app/src/main/res/raw/gong_bell.wav b/app/src/main/res/raw/gong_bell.wav new file mode 100644 index 0000000..94f4030 Binary files /dev/null and b/app/src/main/res/raw/gong_bell.wav differ diff --git a/app/src/main/res/raw/gong_bright.wav b/app/src/main/res/raw/gong_bright.wav new file mode 100644 index 0000000..d42f4c9 Binary files /dev/null and b/app/src/main/res/raw/gong_bright.wav differ diff --git a/app/src/main/res/raw/gong_deep.wav b/app/src/main/res/raw/gong_deep.wav new file mode 100644 index 0000000..ea81d81 Binary files /dev/null and b/app/src/main/res/raw/gong_deep.wav differ diff --git a/app/src/main/res/raw/gong_twin.wav b/app/src/main/res/raw/gong_twin.wav new file mode 100644 index 0000000..91da111 Binary files /dev/null and b/app/src/main/res/raw/gong_twin.wav differ