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)