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 a58aff0..a43a5fd 100644 --- a/app/src/main/java/org/nsh07/pomodoro/data/StatDao.kt +++ b/app/src/main/java/org/nsh07/pomodoro/data/StatDao.kt @@ -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() } \ No newline at end of file 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 7d2efbd..614966d 100644 --- a/app/src/main/java/org/nsh07/pomodoro/data/StatRepository.kt +++ b/app/src/main/java/org/nsh07/pomodoro/data/StatRepository.kt @@ -33,6 +33,8 @@ interface StatRepository { fun getLastNDaysAverageFocusTimes(n: Int): Flow 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() + } \ No newline at end of file diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/ResetDataDialog.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/ResetDataDialog.kt new file mode 100644 index 0000000..b0d6729 --- /dev/null +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/ResetDataDialog.kt @@ -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 = { } + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt index 79dbda2..9ab8ae8 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt @@ -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)) + } + } + } } } } diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsAction.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsAction.kt index f7ee061..aa79399 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsAction.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsAction.kt @@ -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 } diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsState.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsState.kt index c6fb3ea..390a8fe 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsState.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsState.kt @@ -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, 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 99cee0e..3f772f2 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 @@ -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 ) : 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, ) } } diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/StatsScreen.kt b/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/StatsScreen.kt index 18195b3..9405552 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/StatsScreen.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/StatsScreen.kt @@ -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) + } } } } diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/viewModel/StatsViewModel.kt b/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/viewModel/StatsViewModel.kt index e5b6cdd..b7aa720 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/viewModel/StatsViewModel.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/viewModel/StatsViewModel.kt @@ -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>>> = 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>>> = 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>>> = statRepository.getLastNDaysStatsSummary(365) + .filter {list -> + list.isNotEmpty() + } .map { list -> val reversed = list.reversed() val keys = reversed.map { it.date.format(yearDayFormatter) } diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 566622e..c3d5877 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -86,4 +86,6 @@ Progression de la session uniquement Montre uniquement la progression de la session actuelle, au lieu de la séquence entière. Fonctionne uniquement avec un casque. Si le casque est déconnecté, l\'alarme retentit via le haut-parleur au volume des médias. + Réinitialiser les données + Êtes-vous sûr de vouloir réinitialiser toutes vos données ? diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3755e19..da46b2d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -113,4 +113,6 @@ Automatically lock your device after a timeout, while keeping the AOD visible Timer reset Undo + Reset stats + Are you sure you want to reset all your stats? \ No newline at end of file