feat: Add vibration when timer completes, reduce loop frequency when app is in background

This commit is contained in:
Nishant Mishra
2025-09-15 10:53:16 +05:30
parent 25323e9d19
commit 66dd419de4
4 changed files with 59 additions and 9 deletions

View File

@@ -6,6 +6,7 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.POST_PROMOTED_NOTIFICATIONS" /> <uses-permission android:name="android.permission.POST_PROMOTED_NOTIFICATIONS" />
<uses-permission android:name="android.permission.VIBRATE" />
<application <application
android:name=".TomatoApplication" android:name=".TomatoApplication"

View File

@@ -18,6 +18,10 @@ class MainActivity : ComponentActivity() {
private val timerViewModel: TimerViewModel by viewModels(factoryProducer = { TimerViewModel.Factory }) private val timerViewModel: TimerViewModel by viewModels(factoryProducer = { TimerViewModel.Factory })
private val statsViewModel: StatsViewModel by viewModels(factoryProducer = { StatsViewModel.Factory }) private val statsViewModel: StatsViewModel by viewModels(factoryProducer = { StatsViewModel.Factory })
private val appContainer by lazy {
(application as TomatoApplication).container
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
enableEdgeToEdge() enableEdgeToEdge()
@@ -29,6 +33,18 @@ class MainActivity : ComponentActivity() {
} }
} }
override fun onStop() {
super.onStop()
// Reduce the timer loop frequency when not visible to save battery power
appContainer.appTimerRepository.timerFrequency = 1f
}
override fun onStart() {
super.onStart()
// Increase the timer loop frequency again when visible to make the progress smoother
appContainer.appTimerRepository.timerFrequency = 10f
}
companion object { companion object {
val screens = listOf( val screens = listOf(
NavItem( NavItem(

View File

@@ -16,6 +16,7 @@ interface TimerRepository {
var shortBreakTime: Long var shortBreakTime: Long
var longBreakTime: Long var longBreakTime: Long
var sessionLength: Int var sessionLength: Int
var timerFrequency: Float
} }
/** /**
@@ -26,4 +27,5 @@ class AppTimerRepository : TimerRepository {
override var shortBreakTime = 5 * 60 * 1000L override var shortBreakTime = 5 * 60 * 1000L
override var longBreakTime = 15 * 60 * 1000L override var longBreakTime = 15 * 60 * 1000L
override var sessionLength = 4 override var sessionLength = 4
override var timerFrequency: Float = 10f
} }

View File

@@ -7,6 +7,9 @@ import android.media.MediaPlayer
import android.os.Build import android.os.Build
import android.os.IBinder import android.os.IBinder
import android.os.SystemClock import android.os.SystemClock
import android.os.VibrationEffect
import android.os.Vibrator
import android.os.VibratorManager
import android.provider.Settings import android.provider.Settings
import androidx.compose.material3.ColorScheme import androidx.compose.material3.ColorScheme
import androidx.compose.material3.lightColorScheme import androidx.compose.material3.lightColorScheme
@@ -62,7 +65,23 @@ 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 lateinit var alarm: MediaPlayer private val alarm by lazy {
MediaPlayer.create(
this,
Settings.System.DEFAULT_ALARM_ALERT_URI ?: Settings.System.DEFAULT_RINGTONE_URI
)
}
private val vibrator by lazy {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val vibratorManager =
getSystemService(VIBRATOR_MANAGER_SERVICE) as VibratorManager
vibratorManager.defaultVibrator
} else {
@Suppress("DEPRECATION")
getSystemService(VIBRATOR_SERVICE) as Vibrator
}
}
private var cs: ColorScheme = lightColorScheme() private var cs: ColorScheme = lightColorScheme()
@@ -80,11 +99,6 @@ class TimerService : Service() {
_time = appContainer.time _time = appContainer.time
timerState = _timerState.asStateFlow() timerState = _timerState.asStateFlow()
alarm = MediaPlayer.create(
this,
Settings.System.DEFAULT_ALARM_ALERT_URI ?: Settings.System.DEFAULT_RINGTONE_URI
)
} }
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
@@ -147,7 +161,8 @@ class TimerService : Service() {
else -> timerRepository.longBreakTime - (SystemClock.elapsedRealtime() - startTime - pauseDuration).toInt() else -> timerRepository.longBreakTime - (SystemClock.elapsedRealtime() - startTime - pauseDuration).toInt()
} }
iterations = (iterations + 1) % 10 iterations =
(iterations + 1) % timerRepository.timerFrequency.toInt().coerceAtLeast(1)
if (iterations == 0) showTimerNotification(time.toInt()) if (iterations == 0) showTimerNotification(time.toInt())
@@ -165,7 +180,7 @@ class TimerService : Service() {
} }
} }
delay(100) delay((1000f / timerRepository.timerFrequency).toLong())
} }
} }
} }
@@ -254,7 +269,7 @@ class TimerService : Service() {
) )
if (complete) { if (complete) {
alarm.start() startAlarm()
_timerState.update { currentState -> _timerState.update { currentState ->
currentState.copy(alarmRinging = true) currentState.copy(alarmRinging = true)
} }
@@ -328,9 +343,25 @@ class TimerService : Service() {
} }
} }
fun startAlarm() {
alarm.start()
if (!vibrator.hasVibrator()) {
return
}
val vibrationPattern = longArrayOf(0, 1000, 1000, 1000)
val repeat = 2
val effect = VibrationEffect.createWaveform(vibrationPattern, repeat)
vibrator.vibrate(effect)
}
fun stopAlarm() { fun stopAlarm() {
alarm.pause() alarm.pause()
alarm.seekTo(0) alarm.seekTo(0)
vibrator.cancel()
_timerState.update { currentState -> _timerState.update { currentState ->
currentState.copy(alarmRinging = false) currentState.copy(alarmRinging = false)
} }