feat: Implement the picked alarm sound to be played

#36
This commit is contained in:
Nishant Mishra
2025-09-15 23:23:05 +05:30
parent fdc1c3f6c5
commit 9c33406151
5 changed files with 71 additions and 28 deletions

View File

@@ -7,6 +7,8 @@
package org.nsh07.pomodoro.data package org.nsh07.pomodoro.data
import android.net.Uri
import android.provider.Settings
import androidx.compose.material3.ColorScheme import androidx.compose.material3.ColorScheme
import androidx.compose.material3.lightColorScheme import androidx.compose.material3.lightColorScheme
@@ -18,12 +20,17 @@ interface TimerRepository {
var focusTime: Long var focusTime: Long
var shortBreakTime: Long var shortBreakTime: Long
var longBreakTime: Long var longBreakTime: Long
var sessionLength: Int var sessionLength: Int
var timerFrequency: Float var timerFrequency: Float
var alarmEnabled: Boolean var alarmEnabled: Boolean
var vibrateEnabled: Boolean var vibrateEnabled: Boolean
var colorScheme: ColorScheme var colorScheme: ColorScheme
var alarmSoundUri: Uri?
} }
/** /**
@@ -38,4 +45,6 @@ class AppTimerRepository : TimerRepository {
override var alarmEnabled = true override var alarmEnabled = true
override var vibrateEnabled = true override var vibrateEnabled = true
override var colorScheme = lightColorScheme() override var colorScheme = lightColorScheme()
override var alarmSoundUri: Uri? =
Settings.System.DEFAULT_ALARM_ALERT_URI ?: Settings.System.DEFAULT_RINGTONE_URI
} }

View File

@@ -10,7 +10,6 @@ import android.os.SystemClock
import android.os.VibrationEffect import android.os.VibrationEffect
import android.os.Vibrator import android.os.Vibrator
import android.os.VibratorManager import android.os.VibratorManager
import android.provider.Settings
import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.graphics.toArgb
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
@@ -57,11 +56,7 @@ class TimerService : Service() {
private val scope = CoroutineScope(Dispatchers.IO + job) private val scope = CoroutineScope(Dispatchers.IO + job)
private val skipScope = CoroutineScope(Dispatchers.IO + job) private val skipScope = CoroutineScope(Dispatchers.IO + job)
private val alarm by lazy { private var alarm: MediaPlayer? = null
MediaPlayer.create(
this, Settings.System.DEFAULT_ALARM_ALERT_URI ?: Settings.System.DEFAULT_RINGTONE_URI
)
}
private val vibrator by lazy { private val vibrator by lazy {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
@@ -78,6 +73,11 @@ class TimerService : Service() {
return null return null
} }
override fun onCreate() {
super.onCreate()
alarm = MediaPlayer.create(this, timerRepository.alarmSoundUri)
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
when (intent?.action) { when (intent?.action) {
Actions.TOGGLE.toString() -> { Actions.TOGGLE.toString() -> {
@@ -94,6 +94,8 @@ class TimerService : Service() {
Actions.SKIP.toString() -> skipTimer(true) Actions.SKIP.toString() -> skipTimer(true)
Actions.STOP_ALARM.toString() -> stopAlarm() Actions.STOP_ALARM.toString() -> stopAlarm()
Actions.UPDATE_ALARM_TONE.toString() -> updateAlarmTone()
} }
return super.onStartCommand(intent, flags, startId) return super.onStartCommand(intent, flags, startId)
} }
@@ -315,7 +317,7 @@ class TimerService : Service() {
} }
fun startAlarm() { fun startAlarm() {
if (timerRepository.alarmEnabled) alarm.start() if (timerRepository.alarmEnabled) alarm?.start()
if (timerRepository.vibrateEnabled) { if (timerRepository.vibrateEnabled) {
if (!vibrator.hasVibrator()) { if (!vibrator.hasVibrator()) {
@@ -330,8 +332,8 @@ class TimerService : Service() {
fun stopAlarm() { fun stopAlarm() {
if (timerRepository.alarmEnabled) { if (timerRepository.alarmEnabled) {
alarm.pause() alarm?.pause()
alarm.seekTo(0) alarm?.seekTo(0)
} }
if (timerRepository.vibrateEnabled) { if (timerRepository.vibrateEnabled) {
@@ -351,6 +353,11 @@ class TimerService : Service() {
) )
} }
fun updateAlarmTone() {
alarm?.release()
alarm = MediaPlayer.create(this, timerRepository.alarmSoundUri)
}
suspend fun saveTimeToDb() { suspend fun saveTimeToDb() {
when (timerState.value.timerMode) { when (timerState.value.timerMode) {
TimerMode.FOCUS -> statRepository.addFocusTime( TimerMode.FOCUS -> statRepository.addFocusTime(
@@ -379,10 +386,11 @@ class TimerService : Service() {
job.cancel() job.cancel()
saveTimeToDb() saveTimeToDb()
notificationManager.cancel(1) notificationManager.cancel(1)
alarm?.release()
} }
} }
enum class Actions { enum class Actions {
TOGGLE, SKIP, RESET, STOP_ALARM TOGGLE, SKIP, RESET, STOP_ALARM, UPDATE_ALARM_TONE
} }
} }

View File

@@ -74,6 +74,7 @@ import androidx.compose.ui.unit.sp
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import org.nsh07.pomodoro.R import org.nsh07.pomodoro.R
import org.nsh07.pomodoro.service.TimerService
import org.nsh07.pomodoro.ui.settingsScreen.viewModel.SettingsViewModel import org.nsh07.pomodoro.ui.settingsScreen.viewModel.SettingsViewModel
import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTopBar import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTopBar
import org.nsh07.pomodoro.ui.theme.CustomColors.listItemColors import org.nsh07.pomodoro.ui.theme.CustomColors.listItemColors
@@ -89,6 +90,7 @@ fun SettingsScreenRoot(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
viewModel: SettingsViewModel = viewModel(factory = SettingsViewModel.Factory) viewModel: SettingsViewModel = viewModel(factory = SettingsViewModel.Factory)
) { ) {
val context = LocalContext.current
val focusTimeInputFieldState = rememberSaveable(saver = TextFieldState.Saver) { val focusTimeInputFieldState = rememberSaveable(saver = TextFieldState.Saver) {
viewModel.focusTimeTextFieldState viewModel.focusTimeTextFieldState
} }
@@ -99,8 +101,9 @@ fun SettingsScreenRoot(
viewModel.longBreakTimeTextFieldState viewModel.longBreakTimeTextFieldState
} }
val alarmEnabled = viewModel.alarmEnabled.collectAsStateWithLifecycle() val alarmEnabled by viewModel.alarmEnabled.collectAsStateWithLifecycle()
val vibrateEnabled = viewModel.vibrateEnabled.collectAsStateWithLifecycle() val vibrateEnabled by viewModel.vibrateEnabled.collectAsStateWithLifecycle()
val alarmSound by viewModel.alarmSound.collectAsStateWithLifecycle()
val sessionsSliderState = rememberSaveable( val sessionsSliderState = rememberSaveable(
saver = SliderState.Saver( saver = SliderState.Saver(
@@ -116,10 +119,18 @@ fun SettingsScreenRoot(
shortBreakTimeInputFieldState = shortBreakTimeInputFieldState, shortBreakTimeInputFieldState = shortBreakTimeInputFieldState,
longBreakTimeInputFieldState = longBreakTimeInputFieldState, longBreakTimeInputFieldState = longBreakTimeInputFieldState,
sessionsSliderState = sessionsSliderState, sessionsSliderState = sessionsSliderState,
alarmEnabled = alarmEnabled.value, alarmEnabled = alarmEnabled,
vibrateEnabled = vibrateEnabled.value, vibrateEnabled = vibrateEnabled,
alarmSound = alarmSound,
onAlarmEnabledChange = viewModel::saveAlarmEnabled, onAlarmEnabledChange = viewModel::saveAlarmEnabled,
onVibrateEnabledChange = viewModel::saveVibrateEnabled, onVibrateEnabledChange = viewModel::saveVibrateEnabled,
onAlarmSoundChanged = {
viewModel.saveAlarmSound(it)
Intent(context, TimerService::class.java).apply {
action = TimerService.Actions.RESET.toString()
context.startService(this)
}
},
modifier = modifier modifier = modifier
) )
} }
@@ -133,8 +144,10 @@ private fun SettingsScreen(
sessionsSliderState: SliderState, sessionsSliderState: SliderState,
alarmEnabled: Boolean, alarmEnabled: Boolean,
vibrateEnabled: Boolean, vibrateEnabled: Boolean,
alarmSound: Uri?,
onAlarmEnabledChange: (Boolean) -> Unit, onAlarmEnabledChange: (Boolean) -> Unit,
onVibrateEnabledChange: (Boolean) -> Unit, onVibrateEnabledChange: (Boolean) -> Unit,
onAlarmSoundChanged: (Uri?) -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
@@ -142,15 +155,13 @@ private fun SettingsScreen(
checkedIconColor = colorScheme.primary, checkedIconColor = colorScheme.primary,
) )
var selectedSoundUri by remember { mutableStateOf<Uri?>(Settings.System.DEFAULT_ALARM_ALERT_URI) } var selectedSoundUri by remember { mutableStateOf(alarmSound) }
var selectedSoundName by remember { mutableStateOf("...") } var selectedSoundName by remember { mutableStateOf("...") }
val context = LocalContext.current val context = LocalContext.current
// 2. The Activity Result Launcher
val ringtonePickerLauncher = rememberLauncherForActivityResult( val ringtonePickerLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult() contract = ActivityResultContracts.StartActivityForResult()
) { result -> ) { result ->
// This block is executed when the user picks a sound and returns to the app
if (result.resultCode == Activity.RESULT_OK) { if (result.resultCode == Activity.RESULT_OK) {
val uri = val uri =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
@@ -163,6 +174,7 @@ private fun SettingsScreen(
result.data?.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI) result.data?.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI)
} }
selectedSoundUri = uri selectedSoundUri = uri
onAlarmSoundChanged(uri)
} }
} }
@@ -424,8 +436,10 @@ fun SettingsScreenPreview() {
sessionsSliderState = rememberSliderState(value = 3f, steps = 3, valueRange = 1f..5f), sessionsSliderState = rememberSliderState(value = 3f, steps = 3, valueRange = 1f..5f),
alarmEnabled = true, alarmEnabled = true,
vibrateEnabled = true, vibrateEnabled = true,
alarmSound = Settings.System.DEFAULT_ALARM_ALERT_URI,
onAlarmEnabledChange = {}, onAlarmEnabledChange = {},
onVibrateEnabledChange = {}, onVibrateEnabledChange = {},
onAlarmSoundChanged = {},
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
) )
} }

View File

@@ -7,6 +7,7 @@
package org.nsh07.pomodoro.ui.settingsScreen.viewModel package org.nsh07.pomodoro.ui.settingsScreen.viewModel
import android.net.Uri
import androidx.compose.foundation.text.input.TextFieldState import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.SliderState import androidx.compose.material3.SliderState
@@ -47,6 +48,9 @@ class SettingsViewModel(
onValueChangeFinished = ::updateSessionLength onValueChangeFinished = ::updateSessionLength
) )
private val _alarmSound = MutableStateFlow(timerRepository.alarmSoundUri)
val alarmSound: StateFlow<Uri?> = _alarmSound.asStateFlow()
private val _alarmEnabled: MutableStateFlow<Boolean> = private val _alarmEnabled: MutableStateFlow<Boolean> =
MutableStateFlow(timerRepository.alarmEnabled) MutableStateFlow(timerRepository.alarmEnabled)
val alarmEnabled: StateFlow<Boolean> = _alarmEnabled.asStateFlow() val alarmEnabled: StateFlow<Boolean> = _alarmEnabled.asStateFlow()
@@ -119,6 +123,11 @@ class SettingsViewModel(
} }
} }
fun saveAlarmSound(uri: Uri?) {
timerRepository.alarmSoundUri = uri
_alarmSound.value = uri
}
companion object { companion object {
val Factory: ViewModelProvider.Factory = viewModelFactory { val Factory: ViewModelProvider.Factory = viewModelFactory {
initializer { initializer {

View File

@@ -77,17 +77,20 @@ class TimerViewModel(
timerRepository.sessionLength timerRepository.sessionLength
) )
timerRepository.alarmEnabled = (preferenceRepository.getIntPreference("alarm_enabled") timerRepository.alarmEnabled = (
?: preferenceRepository.saveIntPreference( preferenceRepository.getIntPreference("alarm_enabled")
"alarm_enabled", ?: preferenceRepository.saveIntPreference(
1 "alarm_enabled",
)) == 1 1
timerRepository.vibrateEnabled = )
(preferenceRepository.getIntPreference("vibrate_enabled") ) == 1
?: preferenceRepository.saveIntPreference( timerRepository.vibrateEnabled = (
"vibrate_enabled", preferenceRepository.getIntPreference("vibrate_enabled")
1 ?: preferenceRepository.saveIntPreference(
)) == 1 "vibrate_enabled",
1
)
) == 1
resetTimer() resetTimer()