diff --git a/app/src/main/java/org/nsh07/pomodoro/data/AppContainer.kt b/app/src/main/java/org/nsh07/pomodoro/data/AppContainer.kt index 9f2849b..2d45910 100644 --- a/app/src/main/java/org/nsh07/pomodoro/data/AppContainer.kt +++ b/app/src/main/java/org/nsh07/pomodoro/data/AppContainer.kt @@ -10,14 +10,14 @@ package org.nsh07.pomodoro.data import android.content.Context interface AppContainer { - val appPreferencesRepository: AppPreferenceRepository + val appPreferenceRepository: AppPreferenceRepository val appStatRepository: AppStatRepository val appTimerRepository: AppTimerRepository } class DefaultAppContainer(context: Context) : AppContainer { - override val appPreferencesRepository: AppPreferenceRepository by lazy { + override val appPreferenceRepository: AppPreferenceRepository by lazy { AppPreferenceRepository(AppDatabase.getDatabase(context).preferenceDao()) } diff --git a/app/src/main/java/org/nsh07/pomodoro/data/PreferenceRepository.kt b/app/src/main/java/org/nsh07/pomodoro/data/PreferenceRepository.kt index 9b9fa0a..05a7791 100644 --- a/app/src/main/java/org/nsh07/pomodoro/data/PreferenceRepository.kt +++ b/app/src/main/java/org/nsh07/pomodoro/data/PreferenceRepository.kt @@ -4,7 +4,7 @@ import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -interface PreferencesRepository { +interface PreferenceRepository { suspend fun saveIntPreference(key: String, value: Int): Int suspend fun getIntPreference(key: String): Int? @@ -15,7 +15,7 @@ interface PreferencesRepository { class AppPreferenceRepository( private val preferenceDao: PreferenceDao, private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO -) : PreferencesRepository { +) : PreferenceRepository { override suspend fun saveIntPreference(key: String, value: Int): Int = withContext(ioDispatcher) { preferenceDao.insertIntPreference(IntPreference(key, value)) diff --git a/app/src/main/java/org/nsh07/pomodoro/data/Stat.kt b/app/src/main/java/org/nsh07/pomodoro/data/Stat.kt index f4125f0..f2c92f6 100644 --- a/app/src/main/java/org/nsh07/pomodoro/data/Stat.kt +++ b/app/src/main/java/org/nsh07/pomodoro/data/Stat.kt @@ -1,8 +1,8 @@ /* - * Copyright (c) 2025 Nishant Mishra + * Copyright (c) 2025 Nishant Mishra * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ package org.nsh07.pomodoro.data @@ -14,6 +14,9 @@ import androidx.room.PrimaryKey data class Stat( @PrimaryKey val date: String, - val focusTime: Int, - val breakTime: Int + val focusTimeQ1: Long, + val focusTimeQ2: Long, + val focusTimeQ3: Long, + val focusTimeQ4: Long, + val breakTime: Long ) diff --git a/app/src/main/java/org/nsh07/pomodoro/data/StatDao.kt b/app/src/main/java/org/nsh07/pomodoro/data/StatDao.kt index 1448a3a..5680a1c 100644 --- a/app/src/main/java/org/nsh07/pomodoro/data/StatDao.kt +++ b/app/src/main/java/org/nsh07/pomodoro/data/StatDao.kt @@ -18,11 +18,20 @@ interface StatDao { @Insert(onConflict = REPLACE) suspend fun insertStat(stat: Stat) - @Query("UPDATE stat SET focusTime = focusTime + :focusTime WHERE date = :date") - suspend fun addFocusTime(date: String, focusTime: Int) + @Query("UPDATE stat SET focusTimeQ1 = focusTimeQ1 + :focusTime WHERE date = :date") + suspend fun addFocusTimeQ1(date: String, focusTime: Long) + + @Query("UPDATE stat SET focusTimeQ2 = focusTimeQ2 + :focusTime WHERE date = :date") + suspend fun addFocusTimeQ2(date: String, focusTime: Long) + + @Query("UPDATE stat SET focusTimeQ3 = focusTimeQ3 + :focusTime WHERE date = :date") + suspend fun addFocusTimeQ3(date: String, focusTime: Long) + + @Query("UPDATE stat SET focusTimeQ4 = focusTimeQ4 + :focusTime WHERE date = :date") + suspend fun addFocusTimeQ4(date: String, focusTime: Long) @Query("UPDATE stat SET breakTime = breakTime + :breakTime WHERE date = :date") - suspend fun addBreakTime(date: String, breakTime: Int) + suspend fun addBreakTime(date: String, breakTime: Long) @Query("SELECT * FROM stat WHERE date = :date") fun getStat(date: String): Flow diff --git a/app/src/main/java/org/nsh07/pomodoro/data/StatRepository.kt b/app/src/main/java/org/nsh07/pomodoro/data/StatRepository.kt index 8f615ff..08c3b32 100644 --- a/app/src/main/java/org/nsh07/pomodoro/data/StatRepository.kt +++ b/app/src/main/java/org/nsh07/pomodoro/data/StatRepository.kt @@ -12,11 +12,12 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.withContext import java.time.LocalDate +import java.time.LocalTime interface StatRepository { - suspend fun addFocusTime(focusTime: Int) + suspend fun addFocusTime(focusTime: Long) - suspend fun addBreakTime(breakTime: Int) + suspend fun addBreakTime(breakTime: Long) fun getTodayStat(): Flow @@ -27,21 +28,55 @@ class AppStatRepository( private val statDao: StatDao, private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO ) : StatRepository { - override suspend fun addFocusTime(focusTime: Int) = withContext(ioDispatcher) { + override suspend fun addFocusTime(focusTime: Long) = withContext(ioDispatcher) { val currentDate = LocalDate.now().toString() + val currentTime = LocalTime.now().toSecondOfDay() + val secondsInDay = 24 * 60 * 60 + if (statDao.statExists(currentDate)) { - statDao.addFocusTime(currentDate, focusTime) + when (currentTime) { + in 0..(secondsInDay / 4) -> + statDao.addFocusTimeQ1(currentDate, focusTime) + + in (secondsInDay / 4)..(secondsInDay / 2) -> + statDao.addFocusTimeQ2(currentDate, focusTime) + + in (secondsInDay / 2)..(3 * secondsInDay / 4) -> + statDao.addFocusTimeQ3(currentDate, focusTime) + + else -> statDao.addFocusTimeQ4(currentDate, focusTime) + } } else { - statDao.insertStat(Stat(currentDate, focusTime, 0)) + when (currentTime) { + in 0..(secondsInDay / 4) -> + statDao.insertStat( + Stat(currentDate, focusTime, 0, 0, 0, 0) + ) + + in (secondsInDay / 4)..(secondsInDay / 2) -> + statDao.insertStat( + Stat(currentDate, 0, focusTime, 0, 0, 0) + ) + + in (secondsInDay / 2)..(3 * secondsInDay / 4) -> + statDao.insertStat( + Stat(currentDate, 0, 0, focusTime, 0, 0) + ) + + else -> + statDao.insertStat( + Stat(currentDate, 0, 0, 0, focusTime, 0) + ) + } } } - override suspend fun addBreakTime(breakTime: Int) = withContext(ioDispatcher) { + override suspend fun addBreakTime(breakTime: Long) = withContext(ioDispatcher) { val currentDate = LocalDate.now().toString() if (statDao.statExists(currentDate)) { statDao.addBreakTime(currentDate, breakTime) } else { - statDao.insertStat(Stat(currentDate, 0, breakTime)) + statDao.insertStat(Stat(currentDate, 0, 0, 0, 0, breakTime)) } } diff --git a/app/src/main/java/org/nsh07/pomodoro/data/TimerRepository.kt b/app/src/main/java/org/nsh07/pomodoro/data/TimerRepository.kt index ba7b33d..d4d1ad3 100644 --- a/app/src/main/java/org/nsh07/pomodoro/data/TimerRepository.kt +++ b/app/src/main/java/org/nsh07/pomodoro/data/TimerRepository.kt @@ -1,15 +1,22 @@ +/* + * Copyright (c) 2025 Nishant Mishra + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.nsh07.pomodoro.data interface TimerRepository { - var focusTime: Int - var shortBreakTime: Int - var longBreakTime: Int + var focusTime: Long + var shortBreakTime: Long + var longBreakTime: Long var sessionLength: Int } class AppTimerRepository : TimerRepository { - override var focusTime = 25 * 60 * 1000 - override var shortBreakTime = 5 * 60 * 1000 - override var longBreakTime = 15 * 60 * 1000 + override var focusTime = 25 * 60 * 1000L + override var shortBreakTime = 5 * 60 * 1000L + override var longBreakTime = 15 * 60 * 1000L override var sessionLength = 4 } \ No newline at end of file diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsViewModel.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsViewModel.kt index 52c9407..c3454e5 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsViewModel.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsViewModel.kt @@ -1,3 +1,10 @@ +/* + * Copyright (c) 2025 Nishant Mishra + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.nsh07.pomodoro.ui.settingsScreen.viewModel import androidx.compose.foundation.text.input.TextFieldState @@ -46,7 +53,7 @@ class SettingsViewModel( timerRepository.focusTime = preferenceRepository.saveIntPreference( "focus_time", it.toString().toInt() * 60 * 1000 - ) + ).toLong() } } } @@ -58,7 +65,7 @@ class SettingsViewModel( timerRepository.shortBreakTime = preferenceRepository.saveIntPreference( "short_break_time", it.toString().toInt() * 60 * 1000 - ) + ).toLong() } } } @@ -70,7 +77,7 @@ class SettingsViewModel( timerRepository.longBreakTime = preferenceRepository.saveIntPreference( "long_break_time", it.toString().toInt() * 60 * 1000 - ) + ).toLong() } } } @@ -89,7 +96,7 @@ class SettingsViewModel( val Factory: ViewModelProvider.Factory = viewModelFactory { initializer { val application = (this[APPLICATION_KEY] as TomatoApplication) - val appPreferenceRepository = application.container.appPreferencesRepository + val appPreferenceRepository = application.container.appPreferenceRepository val appTimerRepository = application.container.appTimerRepository SettingsViewModel( diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/viewModel/TimerState.kt b/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/viewModel/TimerState.kt index e100237..6ed54bc 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/viewModel/TimerState.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/viewModel/TimerState.kt @@ -1,9 +1,16 @@ +/* + * Copyright (c) 2025 Nishant Mishra + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.nsh07.pomodoro.ui.timerScreen.viewModel data class TimerState( val timerMode: TimerMode = TimerMode.FOCUS, val timeStr: String = "25:00", - val totalTime: Int = 25 * 60, + val totalTime: Long = 25 * 60, val timerRunning: Boolean = false, val nextTimerMode: TimerMode = TimerMode.SHORT_BREAK, val nextTimeStr: String = "5:00" diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/viewModel/TimerViewModel.kt b/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/viewModel/TimerViewModel.kt index b266105..e30c471 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/viewModel/TimerViewModel.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/viewModel/TimerViewModel.kt @@ -1,3 +1,10 @@ +/* + * Copyright (c) 2025 Nishant Mishra + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.nsh07.pomodoro.ui.timerScreen.viewModel import android.os.SystemClock @@ -17,30 +24,18 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import org.nsh07.pomodoro.TomatoApplication -import org.nsh07.pomodoro.data.AppPreferenceRepository +import org.nsh07.pomodoro.data.PreferenceRepository +import org.nsh07.pomodoro.data.StatRepository import org.nsh07.pomodoro.data.TimerRepository import org.nsh07.pomodoro.utils.millisecondsToStr @OptIn(FlowPreview::class) class TimerViewModel( - private val preferenceRepository: AppPreferenceRepository, + private val preferenceRepository: PreferenceRepository, + private val statRepository: StatRepository, private val timerRepository: TimerRepository ) : ViewModel() { - init { - viewModelScope.launch(Dispatchers.IO) { - timerRepository.focusTime = preferenceRepository.getIntPreference("focus_time") - ?: preferenceRepository.saveIntPreference("focus_time", timerRepository.focusTime) - timerRepository.shortBreakTime = preferenceRepository.getIntPreference("short_break_time") - ?: preferenceRepository.saveIntPreference("short_break_time", timerRepository.shortBreakTime) - timerRepository.longBreakTime = preferenceRepository.getIntPreference("long_break_time") - ?: preferenceRepository.saveIntPreference("long_break_time", timerRepository.longBreakTime) - timerRepository.sessionLength = preferenceRepository.getIntPreference("session_length") - ?: preferenceRepository.saveIntPreference("session_length", timerRepository.sessionLength) - - resetTimer() - } - } - + // TODO: Document code private val _timerState = MutableStateFlow( TimerState( totalTime = timerRepository.focusTime, @@ -48,68 +43,106 @@ class TimerViewModel( nextTimeStr = millisecondsToStr(timerRepository.shortBreakTime) ) ) + val timerState: StateFlow = _timerState.asStateFlow() var timerJob: Job? = null - private val _time = MutableStateFlow(timerRepository.focusTime) - val time: StateFlow = _time.asStateFlow() + val time: StateFlow = _time.asStateFlow() private var cycles = 0 + private var startTime = 0L private var pauseTime = 0L private var pauseDuration = 0L - fun resetTimer() { - _time.update { timerRepository.focusTime } - cycles = 0 - startTime = 0L - pauseTime = 0L - pauseDuration = 0L + init { + viewModelScope.launch(Dispatchers.IO) { + timerRepository.focusTime = + preferenceRepository.getIntPreference("focus_time")?.toLong() + ?: preferenceRepository.saveIntPreference( + "focus_time", + timerRepository.focusTime.toInt() + ).toLong() + timerRepository.shortBreakTime = + preferenceRepository.getIntPreference("short_break_time")?.toLong() + ?: preferenceRepository.saveIntPreference( + "short_break_time", + timerRepository.shortBreakTime.toInt() + ).toLong() + timerRepository.longBreakTime = + preferenceRepository.getIntPreference("long_break_time")?.toLong() + ?: preferenceRepository.saveIntPreference( + "long_break_time", + timerRepository.longBreakTime.toInt() + ).toLong() + timerRepository.sessionLength = preferenceRepository.getIntPreference("session_length") + ?: preferenceRepository.saveIntPreference( + "session_length", + timerRepository.sessionLength + ) - _timerState.update { currentState -> - currentState.copy( - timerMode = TimerMode.FOCUS, - timeStr = millisecondsToStr(time.value), - totalTime = time.value, - nextTimerMode = TimerMode.SHORT_BREAK, - nextTimeStr = millisecondsToStr(timerRepository.shortBreakTime) - ) + resetTimer() } } - fun skipTimer() { - startTime = 0L - pauseTime = 0L - pauseDuration = 0L - cycles = (cycles + 1) % (timerRepository.sessionLength * 2) - - if (cycles % 2 == 0) { + fun resetTimer() { + viewModelScope.launch { + saveTimeToDb() _time.update { timerRepository.focusTime } + cycles = 0 + startTime = 0L + pauseTime = 0L + pauseDuration = 0L + _timerState.update { currentState -> currentState.copy( timerMode = TimerMode.FOCUS, timeStr = millisecondsToStr(time.value), totalTime = time.value, - nextTimerMode = if (cycles == 6) TimerMode.LONG_BREAK else TimerMode.SHORT_BREAK, - nextTimeStr = if (cycles == 6) millisecondsToStr( - timerRepository.longBreakTime - ) else millisecondsToStr( - timerRepository.shortBreakTime - ) + nextTimerMode = TimerMode.SHORT_BREAK, + nextTimeStr = millisecondsToStr(timerRepository.shortBreakTime) ) } - } else { - val long = cycles == (timerRepository.sessionLength * 2) - 1 - _time.update { if (long) timerRepository.longBreakTime else timerRepository.shortBreakTime } + } + } - _timerState.update { currentState -> - currentState.copy( - timerMode = if (long) TimerMode.LONG_BREAK else TimerMode.SHORT_BREAK, - timeStr = millisecondsToStr(time.value), - totalTime = time.value, - nextTimerMode = TimerMode.FOCUS, - nextTimeStr = millisecondsToStr(timerRepository.focusTime) - ) + fun skipTimer() { + viewModelScope.launch { + saveTimeToDb() + startTime = 0L + pauseTime = 0L + pauseDuration = 0L + + cycles = (cycles + 1) % (timerRepository.sessionLength * 2) + + if (cycles % 2 == 0) { + _time.update { timerRepository.focusTime } + _timerState.update { currentState -> + currentState.copy( + timerMode = TimerMode.FOCUS, + timeStr = millisecondsToStr(time.value), + totalTime = time.value, + nextTimerMode = if (cycles == 6) TimerMode.LONG_BREAK else TimerMode.SHORT_BREAK, + nextTimeStr = if (cycles == 6) millisecondsToStr( + timerRepository.longBreakTime + ) else millisecondsToStr( + timerRepository.shortBreakTime + ) + ) + } + } else { + val long = cycles == (timerRepository.sessionLength * 2) - 1 + _time.update { if (long) timerRepository.longBreakTime else timerRepository.shortBreakTime } + + _timerState.update { currentState -> + currentState.copy( + timerMode = if (long) TimerMode.LONG_BREAK else TimerMode.SHORT_BREAK, + timeStr = millisecondsToStr(time.value), + totalTime = time.value, + nextTimerMode = TimerMode.FOCUS, + nextTimeStr = millisecondsToStr(timerRepository.focusTime) + ) + } } } } @@ -164,15 +197,27 @@ class TimerViewModel( } } + suspend fun saveTimeToDb() { + when (timerState.value.timerMode) { + TimerMode.FOCUS -> statRepository + .addFocusTime((timerState.value.totalTime - time.value)) + + else -> statRepository + .addBreakTime((timerState.value.totalTime - time.value)) + } + } + companion object { val Factory: ViewModelProvider.Factory = viewModelFactory { initializer { val application = (this[APPLICATION_KEY] as TomatoApplication) - val appPreferenceRepository = application.container.appPreferencesRepository + val appPreferenceRepository = application.container.appPreferenceRepository + val appStatRepository = application.container.appStatRepository val appTimerRepository = application.container.appTimerRepository TimerViewModel( preferenceRepository = appPreferenceRepository, + statRepository = appStatRepository, timerRepository = appTimerRepository ) } diff --git a/app/src/main/java/org/nsh07/pomodoro/utils/Utils.kt b/app/src/main/java/org/nsh07/pomodoro/utils/Utils.kt index 1bb75c2..eccac42 100644 --- a/app/src/main/java/org/nsh07/pomodoro/utils/Utils.kt +++ b/app/src/main/java/org/nsh07/pomodoro/utils/Utils.kt @@ -1,9 +1,16 @@ +/* + * Copyright (c) 2025 Nishant Mishra + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.nsh07.pomodoro.utils import java.util.Locale import kotlin.math.ceil -fun millisecondsToStr(t: Int): String { +fun millisecondsToStr(t: Long): String { val min = (ceil(t / 1000.0).toInt() / 60) val sec = (ceil(t / 1000.0).toInt() % 60) return String.format(locale = Locale.getDefault(), "%02d:%02d", min, sec)