diff --git a/shared/src/commonMain/kotlin/at/techbee/spectacled/screens/core/domain/IcalEntry.kt b/shared/src/commonMain/kotlin/at/techbee/spectacled/screens/core/domain/IcalEntry.kt index 49e8918f..272df030 100644 --- a/shared/src/commonMain/kotlin/at/techbee/spectacled/screens/core/domain/IcalEntry.kt +++ b/shared/src/commonMain/kotlin/at/techbee/spectacled/screens/core/domain/IcalEntry.kt @@ -177,6 +177,11 @@ data class IcalEntry( ) } + // Ignores bookkeeping the app/sync layer maintains on its own (row id, sync state, CalDAV + // etag/href, last-modified stamp) - true if nothing a user could see or edit differs. + fun hasSameContentAs(other: IcalEntry): Boolean = + this.copy(id = other.id, lastModified = other.lastModified, syncState = other.syncState, etag = other.etag, href = other.href) == other + fun getProgressTriState() = when { status == Status.IN_PROCESS -> ToggleableState.Indeterminate status == Status.COMPLETED -> ToggleableState.On diff --git a/shared/src/commonMain/kotlin/at/techbee/spectacled/screens/details/presentation/DetailsViewModel.kt b/shared/src/commonMain/kotlin/at/techbee/spectacled/screens/details/presentation/DetailsViewModel.kt index 4aacad7f..994d6f77 100644 --- a/shared/src/commonMain/kotlin/at/techbee/spectacled/screens/details/presentation/DetailsViewModel.kt +++ b/shared/src/commonMain/kotlin/at/techbee/spectacled/screens/details/presentation/DetailsViewModel.kt @@ -91,11 +91,20 @@ class DetailsViewModel( .debounce(500L.milliseconds) // Wait for 500ms pause in typing .distinctUntilChanged { old, new -> old.lastModified == new.lastModified } // Only save if last modified changed .collect { entry -> - if(!_state.value.isLoading + val current = _state.value + if(!current.isLoading && entry.calendarId != 0L && entry.syncState != SyncState.SYNCED - && entry.syncState != SyncState.LOCAL_NEW) // untouched new entry: don't create a row for it at all - saveIcalEntry(entry.syncState) + && entry.syncState != SyncState.LOCAL_NEW) { // untouched new entry: don't create a row for it at all + // onUndo() bumps lastModified to restore a prior checkpoint even though its + // content already matches that checkpoint - that save must still go through + // so the restore is actually persisted (saveIcalEntry() itself already skips + // re-recording it on the backstack, via undoRestoreMarker). + val isUndoRestore = current.undoRestoreMarker != null && current.undoRestoreMarker == entry.lastModified + val lastCheckpoint = current.changeBackstack.lastOrNull() ?: current.originalIcalEntry + if(isUndoRestore || !entry.hasSameContentAs(lastCheckpoint)) + saveIcalEntry(entry.syncState) + } } } diff --git a/shared/src/commonMain/kotlin/at/techbee/spectacled/screens/details/presentation/components/TimePickerBottomSheet.kt b/shared/src/commonMain/kotlin/at/techbee/spectacled/screens/details/presentation/components/TimePickerBottomSheet.kt index 23ad1e41..b4db862b 100644 --- a/shared/src/commonMain/kotlin/at/techbee/spectacled/screens/details/presentation/components/TimePickerBottomSheet.kt +++ b/shared/src/commonMain/kotlin/at/techbee/spectacled/screens/details/presentation/components/TimePickerBottomSheet.kt @@ -26,6 +26,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -64,7 +65,16 @@ fun TimePickerBottomSheet( ) var showTimezoneDropdown by mutableStateOf(false) + // rememberTimePickerState() seeds hour/minute from the entry's current time, and + // LaunchedEffect runs immediately on first composition - without this guard, just opening + // the sheet (without touching the picker) would fire onTimeUpdated with that seeded value. + var hasHandledInitialTime by remember { mutableStateOf(false) } + LaunchedEffect(timePickerState.minute, timePickerState.hour) { + if (!hasHandledInitialTime) { + hasHandledInitialTime = true + return@LaunchedEffect + } val newLocalDateTime = LocalDateTime( year = localDateTime.year, month = localDateTime.month,