diff --git a/app/src/main/java/com/app/whakaara/activities/MainActivity.kt b/app/src/main/java/com/app/whakaara/activities/MainActivity.kt index eccca03d..accc34cc 100644 --- a/app/src/main/java/com/app/whakaara/activities/MainActivity.kt +++ b/app/src/main/java/com/app/whakaara/activities/MainActivity.kt @@ -78,11 +78,21 @@ class MainActivity : ComponentActivity() { override fun onResume() { super.onResume() - viewModel.recreateTimer() + with(viewModel) { + recreateTimer() + recreateStopwatch() + cancelStopwatchNotification() + cancelTimerNotification() + } } override fun onPause() { super.onPause() - viewModel.saveTimerStateForRecreation() + with(viewModel) { + saveTimerStateForRecreation() + saveStopwatchStateForRecreation() + startStopwatchNotification() + startTimerNotification() + } } } diff --git a/app/src/main/java/com/app/whakaara/data/datastore/PreferencesDataStore.kt b/app/src/main/java/com/app/whakaara/data/datastore/PreferencesDataStore.kt index 87c6c10e..29b72261 100644 --- a/app/src/main/java/com/app/whakaara/data/datastore/PreferencesDataStore.kt +++ b/app/src/main/java/com/app/whakaara/data/datastore/PreferencesDataStore.kt @@ -9,9 +9,15 @@ import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.longPreferencesKey import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore +import com.app.whakaara.state.Lap +import com.app.whakaara.state.StopwatchState import com.app.whakaara.state.TimerStateDataStore import com.app.whakaara.ui.theme.darkGreen import com.app.whakaara.utils.GeneralUtils +import com.app.whakaara.utils.constants.DateUtilsConstants.STOPWATCH_STARTING_TIME +import com.app.whakaara.utils.constants.GeneralConstants.ZERO_MILLIS +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.flow.map import javax.inject.Inject @@ -29,10 +35,20 @@ class PreferencesDataStore @Inject constructor( private object PreferencesKeys { val COLOUR_BACKGROUND_KEY = stringPreferencesKey("colour_background") val COLOUR_TEXT_KEY = stringPreferencesKey("colour_text") + val TIMER_FINISH_KEY = longPreferencesKey("timer_finish") val TIMER_ACTIVE_KEY = booleanPreferencesKey("timer_active") val TIMER_PAUSED_KEY = booleanPreferencesKey("timer_paused") val TIMER_TIME_STAMP = longPreferencesKey("timer_time_stamp") + + // TODO: Shift to Room + val STOPWATCH_TIME_MILLIS = longPreferencesKey("stopwatch_time") + val STOPWATCH_LAST_TIMESTAMP = longPreferencesKey("stopwatch_timestamp") + val STOPWATCH_FORMATTED_TIME = stringPreferencesKey("stopwatch_formattedTime") + val STOPWATCH_IS_ACTIVE = booleanPreferencesKey("stopwatch_is_active") + val STOPWATCH_IS_START = booleanPreferencesKey("stopwatch_is_start") + val STOPWATCH_IS_PAUSED = booleanPreferencesKey("stopwatch_is_paused") + val STOPWATCH_LAP_LIST = stringPreferencesKey("stopwatch_lap_list") } //region colour @@ -55,10 +71,10 @@ class PreferencesDataStore @Inject constructor( //region timer val readTimerStatus = preferencesDataStore.data.map { preferences -> TimerStateDataStore( - remainingTimeInMillis = preferences[PreferencesKeys.TIMER_FINISH_KEY] ?: 0L, + remainingTimeInMillis = preferences[PreferencesKeys.TIMER_FINISH_KEY] ?: ZERO_MILLIS, isActive = preferences[PreferencesKeys.TIMER_ACTIVE_KEY] ?: false, isPaused = preferences[PreferencesKeys.TIMER_PAUSED_KEY] ?: false, - timeStamp = preferences[PreferencesKeys.TIMER_TIME_STAMP] ?: 0L + timeStamp = preferences[PreferencesKeys.TIMER_TIME_STAMP] ?: ZERO_MILLIS ) } @@ -71,4 +87,45 @@ class PreferencesDataStore @Inject constructor( } } //endregion + + //region stopwatch + val readStopwatchState = preferencesDataStore.data.map { preferences -> + val lapString = preferences[PreferencesKeys.STOPWATCH_LAP_LIST] ?: "[]" + val list: ArrayList = Gson().fromJson(lapString, object : TypeToken>() {}.type) + StopwatchState( + timeMillis = preferences[PreferencesKeys.STOPWATCH_TIME_MILLIS] ?: ZERO_MILLIS, + lastTimeStamp = preferences[PreferencesKeys.STOPWATCH_LAST_TIMESTAMP] ?: ZERO_MILLIS, + formattedTime = preferences[PreferencesKeys.STOPWATCH_FORMATTED_TIME] ?: STOPWATCH_STARTING_TIME, + isActive = preferences[PreferencesKeys.STOPWATCH_IS_ACTIVE] ?: false, + isStart = preferences[PreferencesKeys.STOPWATCH_IS_START] ?: true, + isPaused = preferences[PreferencesKeys.STOPWATCH_IS_PAUSED] ?: false, + lapList = list + ) + } + + suspend fun saveStopwatchState(state: StopwatchState) { + val list = Gson().toJson(state.lapList) + preferencesDataStore.edit { preferences -> + preferences[PreferencesKeys.STOPWATCH_TIME_MILLIS] = state.timeMillis + preferences[PreferencesKeys.STOPWATCH_LAST_TIMESTAMP] = state.lastTimeStamp + preferences[PreferencesKeys.STOPWATCH_FORMATTED_TIME] = state.formattedTime + preferences[PreferencesKeys.STOPWATCH_IS_ACTIVE] = state.isActive + preferences[PreferencesKeys.STOPWATCH_IS_START] = state.isStart + preferences[PreferencesKeys.STOPWATCH_IS_PAUSED] = state.isPaused + preferences[PreferencesKeys.STOPWATCH_LAP_LIST] = list + } + } + + suspend fun clearStopwatchState() { + preferencesDataStore.edit { preferences -> + preferences.remove(PreferencesKeys.STOPWATCH_TIME_MILLIS) + preferences.remove(PreferencesKeys.STOPWATCH_LAST_TIMESTAMP) + preferences.remove(PreferencesKeys.STOPWATCH_FORMATTED_TIME) + preferences.remove(PreferencesKeys.STOPWATCH_IS_ACTIVE) + preferences.remove(PreferencesKeys.STOPWATCH_IS_START) + preferences.remove(PreferencesKeys.STOPWATCH_IS_PAUSED) + preferences.remove(PreferencesKeys.STOPWATCH_LAP_LIST) + } + } + //endregion } diff --git a/app/src/main/java/com/app/whakaara/logic/MainViewModel.kt b/app/src/main/java/com/app/whakaara/logic/MainViewModel.kt index 53e03cca..d0fd8878 100644 --- a/app/src/main/java/com/app/whakaara/logic/MainViewModel.kt +++ b/app/src/main/java/com/app/whakaara/logic/MainViewModel.kt @@ -204,13 +204,47 @@ class MainViewModel @Inject constructor( stopwatchManagerWrapper.pauseStopwatch() } - fun resetStopwatch() { + fun resetStopwatch() = viewModelScope.launch(Dispatchers.IO) { stopwatchManagerWrapper.resetStopwatch() + preferencesDatastore.clearStopwatchState() } fun lapStopwatch() { stopwatchManagerWrapper.lapStopwatch() } + + fun saveStopwatchStateForRecreation() = viewModelScope.launch(Dispatchers.IO) { + if (stopwatchState.value.isActive || stopwatchState.value.isPaused) { + preferencesDatastore.saveStopwatchState( + state = stopwatchState.value + ) + } + } + + fun recreateStopwatch() = viewModelScope.launch(Dispatchers.Main) { + if (stopwatchState.value == StopwatchState()) { + val state = preferencesDatastore.readStopwatchState.first() + if (state.isActive) { + stopwatchManagerWrapper.recreateStopwatchActive(state = state) + preferencesDatastore.clearStopwatchState() + } else if (state.isPaused) { + stopwatchManagerWrapper.recreateStopwatchPaused(state = state) + preferencesDatastore.clearStopwatchState() + } + } + } + + fun startStopwatchNotification() { + if (stopwatchState.value.isActive) { + stopwatchManagerWrapper.createStopwatchNotification() + } else if (stopwatchState.value.isPaused) { + stopwatchManagerWrapper.pauseStopwatchNotification() + } + } + + fun cancelStopwatchNotification() { + stopwatchManagerWrapper.cancelNotification() + } //endregion //region timer @@ -250,22 +284,26 @@ class MainViewModel @Inject constructor( } fun recreateTimer() = viewModelScope.launch(Dispatchers.Main) { - val status = preferencesDatastore.readTimerStatus.first() - val difference = System.currentTimeMillis() - status.timeStamp - if (status.remainingTimeInMillis > 0 && timerState.value.isStart && (status.remainingTimeInMillis > difference)) { - if (status.isActive) { - timerManagerWrapper.recreateActiveTimer( - milliseconds = status.remainingTimeInMillis - difference - ) - } else if (status.isPaused) { - timerManagerWrapper.recreatePausedTimer( - milliseconds = status.remainingTimeInMillis - difference - ) - } + if (!timerState.value.isStart) { + val status = preferencesDatastore.readTimerStatus.first() + with(status) { + val difference = System.currentTimeMillis() - timeStamp + if (remainingTimeInMillis > 0 && timerState.value.isStart && (remainingTimeInMillis > difference)) { + if (isActive) { + timerManagerWrapper.recreateActiveTimer( + milliseconds = remainingTimeInMillis - difference + ) + } else if (isPaused) { + timerManagerWrapper.recreatePausedTimer( + milliseconds = remainingTimeInMillis - difference + ) + } - preferencesDatastore.saveTimerData( - state = TimerStateDataStore() - ) + preferencesDatastore.saveTimerData( + state = TimerStateDataStore() + ) + } + } } } @@ -281,5 +319,17 @@ class MainViewModel @Inject constructor( ) } } + + fun startTimerNotification() { + if (timerState.value.isTimerPaused) { + timerManagerWrapper.pauseTimerNotificationCountdown() + } else if (timerState.value.isTimerActive) { + timerManagerWrapper.startTimerNotificationCountdown(milliseconds = timerState.value.currentTime + Calendar.getInstance().timeInMillis) + } + } + + fun cancelTimerNotification() { + timerManagerWrapper.cancelNotification() + } // endregion } diff --git a/app/src/main/java/com/app/whakaara/logic/StopwatchManagerWrapper.kt b/app/src/main/java/com/app/whakaara/logic/StopwatchManagerWrapper.kt index 2a91035d..75c6289d 100644 --- a/app/src/main/java/com/app/whakaara/logic/StopwatchManagerWrapper.kt +++ b/app/src/main/java/com/app/whakaara/logic/StopwatchManagerWrapper.kt @@ -7,12 +7,14 @@ import android.content.Context import android.content.Intent import androidx.core.app.NotificationCompat import com.app.whakaara.R +import com.app.whakaara.data.datastore.PreferencesDataStore import com.app.whakaara.receiver.StopwatchReceiver import com.app.whakaara.state.Lap import com.app.whakaara.state.StopwatchState import com.app.whakaara.utils.DateUtils import com.app.whakaara.utils.PendingIntentUtils import com.app.whakaara.utils.constants.DateUtilsConstants.STOPWATCH_STARTING_TIME +import com.app.whakaara.utils.constants.GeneralConstants.MAX_NUMBER_OF_LAPS import com.app.whakaara.utils.constants.GeneralConstants.TIMER_START_DELAY_MILLIS import com.app.whakaara.utils.constants.GeneralConstants.ZERO_MILLIS import com.app.whakaara.utils.constants.NotificationUtilsConstants.INTENT_REQUEST_CODE @@ -26,6 +28,7 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import javax.inject.Inject import javax.inject.Named @@ -34,7 +37,8 @@ class StopwatchManagerWrapper @Inject constructor( private val notificationManager: NotificationManager, @Named("stopwatch") private val stopwatchNotificationBuilder: NotificationCompat.Builder, - private val coroutineScope: CoroutineScope + private val coroutineScope: CoroutineScope, + private val preferencesDataStore: PreferencesDataStore ) { val stopwatchState = MutableStateFlow(StopwatchState()) @@ -45,19 +49,13 @@ class StopwatchManagerWrapper @Inject constructor( coroutineScope.launch { val lastTimeStamp = System.currentTimeMillis() - val setWhen = if (stopwatchState.value.isStart) { - lastTimeStamp - } else { - lastTimeStamp - stopwatchState.value.timeMillis - } - - createStopwatchNotification(milliseconds = setWhen) stopwatchState.update { it.copy( lastTimeStamp = lastTimeStamp, isActive = true, - isStart = false + isStart = false, + isPaused = false ) } @@ -78,17 +76,18 @@ class StopwatchManagerWrapper @Inject constructor( } fun pauseStopwatch() { + cancelNotification() stopwatchState.update { it.copy( - isActive = false + isActive = false, + isPaused = true ) } - pauseStopwatchNotification() } fun resetStopwatch() { - coroutineScope.coroutineContext.cancelChildren() cancelNotification() + coroutineScope.coroutineContext.cancelChildren() stopwatchState.update { it.copy( timeMillis = ZERO_MILLIS, @@ -96,34 +95,117 @@ class StopwatchManagerWrapper @Inject constructor( formattedTime = STOPWATCH_STARTING_TIME, isActive = false, isStart = true, + isPaused = false, lapList = mutableListOf() ) } } fun lapStopwatch() { - val diff: Long - val current = stopwatchState.value.timeMillis - diff = if (stopwatchState.value.lapList.isEmpty()) { - current - } else { - current - stopwatchState.value.lapList.last().time + if (stopwatchState.value.lapList.size < MAX_NUMBER_OF_LAPS) { + val diff: Long + val current = stopwatchState.value.timeMillis + diff = if (stopwatchState.value.lapList.isEmpty()) { + current + } else { + current - stopwatchState.value.lapList.last().time + } + val nextLap = Lap( + time = current, + diff = diff + ) + val newList = stopwatchState.value.lapList.plus(nextLap).toMutableList() + stopwatchState.update { + it.copy( + lapList = newList + ) + } } - val nextLap = Lap( - time = current, - diff = diff - ) - val newList = stopwatchState.value.lapList.plus(nextLap).toMutableList() + } + + fun recreateStopwatchActive( + state: StopwatchState + ) { + val current = System.currentTimeMillis() + val difference = current - state.lastTimeStamp + val time = difference + state.timeMillis stopwatchState.update { it.copy( - lapList = newList + lastTimeStamp = current, + timeMillis = time, + formattedTime = DateUtils.formatTimeForStopwatch( + millis = time + ), + lapList = state.lapList + ) + } + startStopwatch() + } + + fun recreateStopwatchActiveFromReceiver( + state: StopwatchState + ) { + if (stopwatchState.value != StopwatchState()) { + startStopwatch() + } else { + stopwatchState.update { + state.copy( + isStart = false, + isActive = false, + isPaused = false + ) + } + startStopwatch() + } + runBlocking { + preferencesDataStore.saveStopwatchState(stopwatchState.value) + } + } + + fun recreateStopwatchPausedFromReceiver( + state: StopwatchState + ) { + if (stopwatchState.value != StopwatchState() || !state.isActive) { + pauseStopwatch() + } else { + val current = System.currentTimeMillis() + val difference = current - state.lastTimeStamp + val time = difference + state.timeMillis + recreateStopwatchPaused( + state = state.copy( + lastTimeStamp = current, + timeMillis = time, + formattedTime = DateUtils.formatTimeForStopwatch( + millis = time + ), + isPaused = true, + isActive = false, + isStart = false + ) ) } + + runBlocking { + preferencesDataStore.saveStopwatchState(stopwatchState.value) + } } - private fun createStopwatchNotification( - milliseconds: Long + fun recreateStopwatchPaused( + state: StopwatchState ) { + stopwatchState.update { + state + } + } + + fun createStopwatchNotification() { + val lastTimeStamp = System.currentTimeMillis() + val setWhen = if (stopwatchState.value.isStart) { + lastTimeStamp + } else { + lastTimeStamp - stopwatchState.value.timeMillis + } + val pauseReceiverIntent = app.applicationContext.getTimerReceiverIntent(intentAction = STOPWATCH_RECEIVER_ACTION_PAUSE) val stopReceiverIntent = app.applicationContext.getTimerReceiverIntent(intentAction = STOPWATCH_RECEIVER_ACTION_STOP) @@ -144,7 +226,7 @@ class StopwatchManagerWrapper @Inject constructor( STOPWATCH_NOTIFICATION_ID, stopwatchNotificationBuilder.apply { clearActions() - setWhen(milliseconds) + setWhen(setWhen) setUsesChronometer(true) setSubText(app.applicationContext.getString(R.string.stopwatch_notification_sub_text)) addAction(0, app.applicationContext.getString(R.string.notification_timer_pause_action_label), pauseReceiverPendingIntent) @@ -153,7 +235,7 @@ class StopwatchManagerWrapper @Inject constructor( ) } - private fun pauseStopwatchNotification() { + fun pauseStopwatchNotification() { val startReceiverIntent = app.applicationContext.getTimerReceiverIntent(intentAction = STOPWATCH_RECEIVER_ACTION_START) val stopReceiverIntent = app.applicationContext.getTimerReceiverIntent(intentAction = STOPWATCH_RECEIVER_ACTION_STOP) @@ -177,13 +259,14 @@ class StopwatchManagerWrapper @Inject constructor( clearActions() setUsesChronometer(false) setSubText(app.applicationContext.getString(R.string.notification_sub_text_pasused)) + setWhen(System.currentTimeMillis()) addAction(0, app.applicationContext.getString(R.string.notification_timer_play_action_label), playReceiverPendingIntent) addAction(0, app.applicationContext.getString(R.string.notification_timer_stop_action_label), stopReceiverPendingIntent) }.build() ) } - private fun cancelNotification() { + fun cancelNotification() { notificationManager.cancel(STOPWATCH_NOTIFICATION_ID) } diff --git a/app/src/main/java/com/app/whakaara/logic/TimerManagerWrapper.kt b/app/src/main/java/com/app/whakaara/logic/TimerManagerWrapper.kt index 8ab8b384..26ce13f2 100644 --- a/app/src/main/java/com/app/whakaara/logic/TimerManagerWrapper.kt +++ b/app/src/main/java/com/app/whakaara/logic/TimerManagerWrapper.kt @@ -33,6 +33,7 @@ import com.app.whakaara.utils.constants.NotificationUtilsConstants.TIMER_NOTIFIC import com.app.whakaara.utils.constants.NotificationUtilsConstants.TIMER_RECEIVER_ACTION_PAUSE import com.app.whakaara.utils.constants.NotificationUtilsConstants.TIMER_RECEIVER_ACTION_START import com.app.whakaara.utils.constants.NotificationUtilsConstants.TIMER_RECEIVER_ACTION_STOP +import com.app.whakaara.utils.constants.NotificationUtilsConstants.TIMER_RECEIVER_CURRENT_TIME_EXTRA import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.MutableStateFlow @@ -190,7 +191,7 @@ class TimerManagerWrapper @Inject constructor( fun pauseTimer() { if (!timerState.value.isTimerPaused) { cancelTimerAlarm() - pauseTimerNotificationCountdown() +// pauseTimerNotificationCountdown() countDownTimerUtil.cancel() timerState.update { it.copy( @@ -261,7 +262,7 @@ class TimerManagerWrapper @Inject constructor( milliseconds, pendingIntent ) - startTimerNotificationCountdown(milliseconds = milliseconds) +// startTimerNotificationCountdown(milliseconds = milliseconds) } private fun getStartReceiverIntent( @@ -278,7 +279,7 @@ class TimerManagerWrapper @Inject constructor( putExtra(INTENT_ALARM_ID, alarmId) } - private fun startTimerNotificationCountdown( + fun startTimerNotificationCountdown( milliseconds: Long ) { val pauseReceiverIntent = app.applicationContext.getTimerReceiverIntent(intentAction = TIMER_RECEIVER_ACTION_PAUSE) @@ -315,8 +316,8 @@ class TimerManagerWrapper @Inject constructor( ) } - private fun pauseTimerNotificationCountdown() { - val startTimerReceiverIntent = app.applicationContext.getTimerReceiverIntent(intentAction = TIMER_RECEIVER_ACTION_START) + fun pauseTimerNotificationCountdown() { + val startTimerReceiverIntent = app.applicationContext.getTimerReceiverIntent(intentAction = TIMER_RECEIVER_ACTION_START).apply { putExtra(TIMER_RECEIVER_CURRENT_TIME_EXTRA, timerState.value.currentTime) } val stopTimerReceiverIntent = app.applicationContext.getTimerReceiverIntent(intentAction = TIMER_RECEIVER_ACTION_STOP) val playTimerReceiverPendingIntent = PendingIntentUtils.getBroadcast( @@ -364,7 +365,7 @@ class TimerManagerWrapper @Inject constructor( alarmManager.cancel(pendingIntent) } - private fun cancelNotification() { + fun cancelNotification() { notificationManager.cancel(TIMER_NOTIFICATION_ID) } diff --git a/app/src/main/java/com/app/whakaara/module/NotificationModule.kt b/app/src/main/java/com/app/whakaara/module/NotificationModule.kt index cb185a5c..1b2f8306 100644 --- a/app/src/main/java/com/app/whakaara/module/NotificationModule.kt +++ b/app/src/main/java/com/app/whakaara/module/NotificationModule.kt @@ -104,7 +104,6 @@ class NotificationModule { setAutoCancel(false) setOngoing(true) setSubText(context.getString(R.string.stopwatch_notification_sub_text)) - setContentTitle(context.getString(R.string.shortcut_stopwatch_short_label)) setVisibility(NotificationCompat.VISIBILITY_PUBLIC) } } @@ -160,8 +159,9 @@ class NotificationModule { notificationManager: NotificationManager, @Named("stopwatch") stopwatchNotificationBuilder: NotificationCompat.Builder, - coroutineScope: CoroutineScope - ): StopwatchManagerWrapper = StopwatchManagerWrapper(app, notificationManager, stopwatchNotificationBuilder, coroutineScope) + coroutineScope: CoroutineScope, + preferencesDataStore: PreferencesDataStore + ): StopwatchManagerWrapper = StopwatchManagerWrapper(app, notificationManager, stopwatchNotificationBuilder, coroutineScope, preferencesDataStore) @Provides @Singleton diff --git a/app/src/main/java/com/app/whakaara/receiver/StopwatchReceiver.kt b/app/src/main/java/com/app/whakaara/receiver/StopwatchReceiver.kt index c0eddece..eac0aab5 100644 --- a/app/src/main/java/com/app/whakaara/receiver/StopwatchReceiver.kt +++ b/app/src/main/java/com/app/whakaara/receiver/StopwatchReceiver.kt @@ -2,11 +2,14 @@ package com.app.whakaara.receiver import android.content.Context import android.content.Intent +import com.app.whakaara.data.datastore.PreferencesDataStore import com.app.whakaara.logic.StopwatchManagerWrapper +import com.app.whakaara.state.StopwatchState import com.app.whakaara.utils.constants.NotificationUtilsConstants.STOPWATCH_RECEIVER_ACTION_PAUSE import com.app.whakaara.utils.constants.NotificationUtilsConstants.STOPWATCH_RECEIVER_ACTION_START import com.app.whakaara.utils.constants.NotificationUtilsConstants.STOPWATCH_RECEIVER_ACTION_STOP import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.flow.first import javax.inject.Inject @AndroidEntryPoint @@ -14,29 +17,49 @@ class StopwatchReceiver : HiltBroadcastReceiver() { @Inject lateinit var stopwatchManagerWrapper: StopwatchManagerWrapper + + @Inject + lateinit var preferencesDatastore: PreferencesDataStore + override fun onReceive(context: Context, intent: Intent) { super.onReceive(context, intent) val actionsList = listOf(STOPWATCH_RECEIVER_ACTION_START, STOPWATCH_RECEIVER_ACTION_PAUSE, STOPWATCH_RECEIVER_ACTION_STOP) if (!actionsList.contains(intent.action)) return goAsync { + val state = preferencesDatastore.readStopwatchState.first() when (intent.action) { - STOPWATCH_RECEIVER_ACTION_START -> startStopwatch() - STOPWATCH_RECEIVER_ACTION_PAUSE -> pauseStopwatch() - STOPWATCH_RECEIVER_ACTION_STOP -> stopStopwatch() + STOPWATCH_RECEIVER_ACTION_START -> { + startStopwatch(state = state) + } + STOPWATCH_RECEIVER_ACTION_PAUSE -> { + pauseStopwatch(state = state) + } + STOPWATCH_RECEIVER_ACTION_STOP -> { + stopStopwatch() + } } } } - private fun startStopwatch() { - stopwatchManagerWrapper.startStopwatch() + private fun startStopwatch(state: StopwatchState) { + with(stopwatchManagerWrapper) { + recreateStopwatchActiveFromReceiver(state = state) + cancelNotification() + createStopwatchNotification() + } } - private fun pauseStopwatch() { - stopwatchManagerWrapper.pauseStopwatch() + private fun pauseStopwatch(state: StopwatchState) { + with(stopwatchManagerWrapper) { + recreateStopwatchPausedFromReceiver(state = state) + cancelNotification() + pauseStopwatchNotification() + } } - private fun stopStopwatch() { + private suspend fun stopStopwatch() { stopwatchManagerWrapper.resetStopwatch() + preferencesDatastore.clearStopwatchState() } } diff --git a/app/src/main/java/com/app/whakaara/receiver/TimerReceiver.kt b/app/src/main/java/com/app/whakaara/receiver/TimerReceiver.kt index 13090e5f..c913d58f 100644 --- a/app/src/main/java/com/app/whakaara/receiver/TimerReceiver.kt +++ b/app/src/main/java/com/app/whakaara/receiver/TimerReceiver.kt @@ -6,7 +6,10 @@ import com.app.whakaara.logic.TimerManagerWrapper import com.app.whakaara.utils.constants.NotificationUtilsConstants.TIMER_RECEIVER_ACTION_PAUSE import com.app.whakaara.utils.constants.NotificationUtilsConstants.TIMER_RECEIVER_ACTION_START import com.app.whakaara.utils.constants.NotificationUtilsConstants.TIMER_RECEIVER_ACTION_STOP +import com.app.whakaara.utils.constants.NotificationUtilsConstants.TIMER_RECEIVER_CURRENT_TIME_EXTRA import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.Dispatchers +import java.util.Calendar import javax.inject.Inject @AndroidEntryPoint @@ -19,10 +22,16 @@ class TimerReceiver : HiltBroadcastReceiver() { val actionsList = listOf(TIMER_RECEIVER_ACTION_PAUSE, TIMER_RECEIVER_ACTION_STOP, TIMER_RECEIVER_ACTION_START) if (!actionsList.contains(intent.action)) return - goAsync { + val currentTime = intent.getLongExtra(TIMER_RECEIVER_CURRENT_TIME_EXTRA, 0L) + + goAsync( + coroutineContext = Dispatchers.Main + ) { when (intent.action) { TIMER_RECEIVER_ACTION_START -> { - startTimer() + startTimer( + currentTime = currentTime + ) } TIMER_RECEIVER_ACTION_PAUSE -> { pauseTimer() @@ -34,12 +43,16 @@ class TimerReceiver : HiltBroadcastReceiver() { } } - private fun startTimer() { + private fun startTimer( + currentTime: Long + ) { timerManagerWrapper.startTimer() + timerManagerWrapper.startTimerNotificationCountdown(milliseconds = currentTime + Calendar.getInstance().timeInMillis) } private fun pauseTimer() { timerManagerWrapper.pauseTimer() + timerManagerWrapper.pauseTimerNotificationCountdown() } private fun stopTimer() { diff --git a/app/src/main/java/com/app/whakaara/state/StopwatchState.kt b/app/src/main/java/com/app/whakaara/state/StopwatchState.kt index 7088132b..52325064 100644 --- a/app/src/main/java/com/app/whakaara/state/StopwatchState.kt +++ b/app/src/main/java/com/app/whakaara/state/StopwatchState.kt @@ -9,6 +9,7 @@ data class StopwatchState( val formattedTime: String = STOPWATCH_STARTING_TIME, val isActive: Boolean = false, val isStart: Boolean = true, + val isPaused: Boolean = false, val lapList: MutableList = mutableListOf() ) diff --git a/app/src/main/java/com/app/whakaara/ui/clock/Stopwatch.kt b/app/src/main/java/com/app/whakaara/ui/clock/Stopwatch.kt index adc081ca..78f30b68 100644 --- a/app/src/main/java/com/app/whakaara/ui/clock/Stopwatch.kt +++ b/app/src/main/java/com/app/whakaara/ui/clock/Stopwatch.kt @@ -10,7 +10,8 @@ import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.FabPosition import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable -import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import com.app.whakaara.state.StopwatchState @@ -20,7 +21,6 @@ import com.app.whakaara.ui.theme.Spacings.spaceMedium import com.app.whakaara.ui.theme.Spacings.spaceNone import com.app.whakaara.ui.theme.ThemePreviews import com.app.whakaara.ui.theme.WhakaaraTheme -import kotlinx.coroutines.launch @Composable fun Stopwatch( @@ -31,7 +31,15 @@ fun Stopwatch( onLap: () -> Unit = {} ) { val listState = rememberLazyListState() - val scope = rememberCoroutineScope() + LaunchedEffect(key1 = stopwatchState.lapList) { + snapshotFlow { listState.firstVisibleItemIndex } + .collect { + if (stopwatchState.lapList.lastIndex >= 0) { + listState.animateScrollToItem(stopwatchState.lapList.lastIndex, 0) + } + } + } + Scaffold( floatingActionButton = { FloatingActionButtonRow( @@ -41,11 +49,6 @@ fun Stopwatch( onPlayPause = if (stopwatchState.isActive) onPause else onStart, onExtraButtonClicked = { onLap() - scope.launch { - if (stopwatchState.lapList.lastIndex >= 0) { - listState.animateScrollToItem(stopwatchState.lapList.lastIndex, 0) - } - } } ) }, diff --git a/app/src/main/java/com/app/whakaara/utils/constants/GeneralConstants.kt b/app/src/main/java/com/app/whakaara/utils/constants/GeneralConstants.kt index be1b12fd..370185d1 100644 --- a/app/src/main/java/com/app/whakaara/utils/constants/GeneralConstants.kt +++ b/app/src/main/java/com/app/whakaara/utils/constants/GeneralConstants.kt @@ -16,4 +16,6 @@ object GeneralConstants { const val ONBOARDING_ROUTE = "onboarding" val DAYS_OF_WEEK = listOf("M", "T", "W", "T", "F", "S", "S") + + const val MAX_NUMBER_OF_LAPS = 100 } diff --git a/app/src/main/java/com/app/whakaara/utils/constants/NotificationUtilsConstants.kt b/app/src/main/java/com/app/whakaara/utils/constants/NotificationUtilsConstants.kt index 8b6f2134..b8bec1c6 100644 --- a/app/src/main/java/com/app/whakaara/utils/constants/NotificationUtilsConstants.kt +++ b/app/src/main/java/com/app/whakaara/utils/constants/NotificationUtilsConstants.kt @@ -34,6 +34,7 @@ object NotificationUtilsConstants { const val TIMER_RECEIVER_ACTION_START = "start_timer" const val TIMER_RECEIVER_ACTION_PAUSE = "pause_timer" const val TIMER_RECEIVER_ACTION_STOP = "stop_timer" + const val TIMER_RECEIVER_CURRENT_TIME_EXTRA = "current_time" const val STOPWATCH_RECEIVER_ACTION_START = "start_stopwatch" const val STOPWATCH_RECEIVER_ACTION_PAUSE = "pause_stopwatch"