Merge pull request #169 from trixidis/feat/reset_data_button
feat(settings): Add option to reset all stats
This commit is contained in:
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
}
|
||||
@@ -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 = { }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) }
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user