Merge pull request #169 from trixidis/feat/reset_data_button

feat(settings): Add option to reset all stats
This commit is contained in:
Nishant Mishra
2025-12-17 19:44:28 +05:30
committed by GitHub
11 changed files with 205 additions and 7 deletions

View File

@@ -60,4 +60,7 @@ interface StatDao {
@Query("SELECT date FROM stat ORDER BY date DESC LIMIT 1")
suspend fun getLastDate(): LocalDate?
@Query("DELETE FROM stat")
suspend fun clearAll()
}

View File

@@ -33,6 +33,8 @@ interface StatRepository {
fun getLastNDaysAverageFocusTimes(n: Int): Flow<StatFocusTime?>
suspend fun getLastDate(): LocalDate?
suspend fun deleteAllStats()
}
/**
@@ -108,4 +110,8 @@ class AppStatRepository(
statDao.getLastNDaysAvgFocusTimes(n)
override suspend fun getLastDate(): LocalDate? = statDao.getLastDate()
override suspend fun deleteAllStats() =
statDao.clearAll()
}

View File

@@ -0,0 +1,107 @@
package org.nsh07.pomodoro.ui.settingsScreen
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme.typography
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import org.nsh07.pomodoro.R
import org.nsh07.pomodoro.ui.theme.TomatoTheme
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun ResetDataDialog(
modifier: Modifier = Modifier,
onDismiss: () -> Unit,
resetData: () -> Unit
) {
Dialog(
onDismissRequest = onDismiss,
) {
Card(
modifier = modifier
.clickable(onClick = onDismiss),
shape = RoundedCornerShape(16.dp),
) {
Column(modifier = Modifier.padding(24.dp)) {
Icon(
painter = painterResource(R.drawable.clear),
contentDescription = null,
modifier = Modifier
.align(Alignment.CenterHorizontally)
.size(40.dp)
)
Spacer(Modifier.height(16.dp))
Text(
text = stringResource(R.string.reset_data),
textAlign = TextAlign.Center,
style = typography.headlineSmall,
modifier = Modifier.align(Alignment.CenterHorizontally)
)
Spacer(Modifier.height(16.dp))
Text(
text = stringResource(R.string.reset_data_dialog_text),
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(24.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.End
) {
TextButton(onClick = onDismiss) {
Text(stringResource(android.R.string.cancel))
}
Spacer(modifier = Modifier.width(8.dp))
Button(
onClick = resetData,
shapes = ButtonDefaults.shapes(),
) {
Text(stringResource(android.R.string.ok))
}
}
}
}
}
}
@Preview
@Composable
fun PreviewResetDataDialog() {
TomatoTheme {
Surface {
ResetDataDialog(
onDismiss = { },
resetData = { }
)
}
}
}

View File

@@ -27,9 +27,11 @@ import androidx.compose.animation.slideOutHorizontally
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
@@ -42,6 +44,7 @@ import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SliderState
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
@@ -170,6 +173,14 @@ private fun SettingsScreen(
setShowSheet = { showLocaleSheet = it }
)
if(settingsState.isShowingEraseDataDialog){
ResetDataDialog(resetData = {
onAction(SettingsAction.EraseData)
}, onDismiss = {
onAction(SettingsAction.CancelEraseData)
})
}
NavDisplay(
backStack = backStack,
onBack = backStack::removeLastOrNull,
@@ -294,6 +305,20 @@ private fun SettingsScreen(
}
item { Spacer(Modifier.height(12.dp)) }
item {
Box(
modifier = Modifier.fillMaxWidth(),
contentAlignment = Alignment.Center
){
TextButton(
onClick = { onAction(SettingsAction.AskEraseData) },
) {
Text(stringResource(R.string.reset_data))
}
}
}
}
}
}

View File

@@ -33,4 +33,7 @@ sealed interface SettingsAction {
data class SaveAlarmSound(val uri: Uri?) : SettingsAction
data class SaveTheme(val theme: String) : SettingsAction
data class SaveColorScheme(val color: Color) : SettingsAction
data object AskEraseData : SettingsAction
data object CancelEraseData : SettingsAction
data object EraseData : SettingsAction
}

View File

@@ -35,6 +35,7 @@ data class SettingsState(
val singleProgressBar: Boolean = false,
val autostartNextSession: Boolean = false,
val secureAod: Boolean = true,
val isShowingEraseDataDialog: Boolean = false,
val focusTime: Long = 25 * 60 * 1000L,
val shortBreakTime: Long = 5 * 60 * 1000L,

View File

@@ -47,6 +47,7 @@ import kotlinx.coroutines.launch
import org.nsh07.pomodoro.TomatoApplication
import org.nsh07.pomodoro.billing.BillingManager
import org.nsh07.pomodoro.data.PreferenceRepository
import org.nsh07.pomodoro.data.StatRepository
import org.nsh07.pomodoro.data.StateRepository
import org.nsh07.pomodoro.service.ServiceHelper
import org.nsh07.pomodoro.ui.Screen
@@ -59,6 +60,7 @@ class SettingsViewModel(
private val billingManager: BillingManager,
private val preferenceRepository: PreferenceRepository,
private val stateRepository: StateRepository,
private val statRepository: StatRepository,
private val serviceHelper: ServiceHelper,
private val time: MutableStateFlow<Long>
) : ViewModel() {
@@ -120,6 +122,25 @@ class SettingsViewModel(
is SettingsAction.SaveTheme -> saveTheme(action.theme)
is SettingsAction.SaveBlackTheme -> saveBlackTheme(action.enabled)
is SettingsAction.SaveAodEnabled -> saveAodEnabled(action.enabled)
is SettingsAction.AskEraseData -> askEraseData()
is SettingsAction.CancelEraseData -> cancelEraseData()
is SettingsAction.EraseData -> deleteStats()
}
}
private fun cancelEraseData() {
viewModelScope.launch(Dispatchers.IO) {
_settingsState.update { currentState ->
currentState.copy(isShowingEraseDataDialog = false)
}
}
}
private fun askEraseData() {
viewModelScope.launch(Dispatchers.IO) {
_settingsState.update { currentState ->
currentState.copy(isShowingEraseDataDialog = true)
}
}
}
@@ -137,6 +158,17 @@ class SettingsViewModel(
}
}
private fun deleteStats() {
viewModelScope.launch(Dispatchers.IO) {
serviceHelper.startService(TimerAction.ResetTimer)
statRepository.deleteAllStats()
_settingsState.update {
it.copy(isShowingEraseDataDialog = false)
}
}
}
fun runTextFieldFlowCollection() {
focusFlowCollectionJob = viewModelScope.launch(Dispatchers.IO) {
snapshotFlow { focusTimeTextFieldState.text }
@@ -458,6 +490,7 @@ class SettingsViewModel(
val appPreferenceRepository = application.container.appPreferenceRepository
val serviceHelper = application.container.serviceHelper
val stateRepository = application.container.stateRepository
val statRepository = application.container.appStatRepository
val time = application.container.time
SettingsViewModel(
@@ -465,7 +498,8 @@ class SettingsViewModel(
preferenceRepository = appPreferenceRepository,
serviceHelper = serviceHelper,
stateRepository = stateRepository,
time = time
statRepository = statRepository,
time = time,
)
}
}

View File

@@ -143,17 +143,21 @@ fun StatsScreen(
val lastMonthSummaryAnalysisModelProducer = remember { CartesianChartModelProducer() }
LaunchedEffect(lastWeekAverageFocusTimes) {
lastWeekSummaryAnalysisModelProducer.runTransaction {
columnSeries {
series(lastWeekAverageFocusTimes)
if (lastWeekAverageFocusTimes.isNotEmpty()) {
lastWeekSummaryAnalysisModelProducer.runTransaction {
columnSeries {
series(lastWeekAverageFocusTimes)
}
}
}
}
LaunchedEffect(lastMonthAverageFocusTimes) {
lastMonthSummaryAnalysisModelProducer.runTransaction {
columnSeries {
series(lastMonthAverageFocusTimes)
if (lastMonthAverageFocusTimes.isNotEmpty()) {
lastMonthSummaryAnalysisModelProducer.runTransaction {
columnSeries {
series(lastMonthAverageFocusTimes)
}
}
}
}

View File

@@ -30,6 +30,7 @@ import com.patrykandpatrick.vico.core.common.data.ExtraStore
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
@@ -42,6 +43,7 @@ import java.time.LocalDate
import java.time.format.DateTimeFormatter
import java.time.format.TextStyle
import java.util.Locale
import kotlin.collections.isNotEmpty
class StatsViewModel(
private val statRepository: StatRepository
@@ -66,6 +68,9 @@ class StatsViewModel(
val lastWeekSummaryChartData: StateFlow<Pair<CartesianChartModelProducer, ExtraStore.Key<List<String>>>> =
statRepository.getLastNDaysStatsSummary(7)
.filter {list ->
list.isNotEmpty()
}
.map { list ->
// reversing is required because we need ascending order while the DB returns descending order
val reversed = list.reversed()
@@ -108,6 +113,9 @@ class StatsViewModel(
val lastMonthSummaryChartData: StateFlow<Pair<CartesianChartModelProducer, ExtraStore.Key<List<String>>>> =
statRepository.getLastNDaysStatsSummary(30)
.filter {list ->
list.isNotEmpty()
}
.map { list ->
val reversed = list.reversed()
val keys = reversed.map { it.date.dayOfMonth.toString() }
@@ -144,6 +152,9 @@ class StatsViewModel(
val lastYearSummaryChartData: StateFlow<Pair<CartesianChartModelProducer, ExtraStore.Key<List<String>>>> =
statRepository.getLastNDaysStatsSummary(365)
.filter {list ->
list.isNotEmpty()
}
.map { list ->
val reversed = list.reversed()
val keys = reversed.map { it.date.format(yearDayFormatter) }

View File

@@ -86,4 +86,6 @@
<string name="session_only_progress">Progression de la session uniquement</string>
<string name="session_only_progress_desc">Montre uniquement la progression de la session actuelle, au lieu de la séquence entière.</string>
<string name="media_volume_for_alarm_desc">Fonctionne uniquement avec un casque. Si le casque est déconnecté, l\'alarme retentit via le haut-parleur au volume des médias.</string>
<string name="reset_data">Réinitialiser les données</string>
<string name="reset_data_dialog_text">Êtes-vous sûr de vouloir réinitialiser toutes vos données ?</string>
</resources>

View File

@@ -113,4 +113,6 @@
<string name="secure_aod_desc">Automatically lock your device after a timeout, while keeping the AOD visible</string>
<string name="timer_reset_message">Timer reset</string>
<string name="undo">Undo</string>
<string name="reset_data">Reset stats</string>
<string name="reset_data_dialog_text">Are you sure you want to reset all your stats?</string>
</resources>