nordiccalendar

A simple Material 3 native calendar app fitting my personal requirements
git clone git://archive.git.mtrnord.blog/MTRNord/nordiccalendar.git
Log | Files | Refs | README | LICENSE

commit 62c27a7176f0eeefe7eb476085a508ac0319f0a5
parent 7367b8d7cb2928e52c1814d0a629dc4933ed5899
Author: MTRNord <MTRNord@users.noreply.github.com>
Date:   Tue, 29 Jul 2025 23:26:08 +0200

Use more derived state

Diffstat:
Mapp/src/main/java/space/midnightthoughts/nordiccalendar/MainActivity.kt | 3+--
Mapp/src/main/java/space/midnightthoughts/nordiccalendar/screens/CalendarScreen.kt | 40++++++++++++++++++++++------------------
Mapp/src/main/java/space/midnightthoughts/nordiccalendar/screens/EventDetailsView.kt | 66++++++++++++++++++++++++++++++++++++++++--------------------------
3 files changed, 63 insertions(+), 46 deletions(-)

diff --git a/app/src/main/java/space/midnightthoughts/nordiccalendar/MainActivity.kt b/app/src/main/java/space/midnightthoughts/nordiccalendar/MainActivity.kt @@ -180,7 +180,7 @@ fun IntroScreen(navController: NavHostController, onFinish: (() -> Unit)? = null val pagerState = rememberPagerState(initialPage = 0, pageCount = { onBoardingData.size }) val coroutineScope = rememberCoroutineScope() - // Keep state if the user has allowed permmissions to hide the finish button using mutable state + // Keep state if the user has allowed permissions to hide the finish button using mutable state val hasPermissions = remember { mutableStateOf(false) } NordicCalendarTheme { @@ -298,7 +298,6 @@ fun CalendarView( @Composable fun AboutView(navController: NavHostController) { - AppScaffold( title = stringResource(R.string.about) + " Nordic Calendar", selectedDestination = "about", diff --git a/app/src/main/java/space/midnightthoughts/nordiccalendar/screens/CalendarScreen.kt b/app/src/main/java/space/midnightthoughts/nordiccalendar/screens/CalendarScreen.kt @@ -44,6 +44,7 @@ import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableLongStateOf import androidx.compose.runtime.remember @@ -88,13 +89,13 @@ fun CalendarScreen( val calendarViewModel: CalendarViewModel = hiltViewModel() val events = remember(calendarViewModel) { calendarViewModel.events - }.collectAsState(initial = emptyList()) + }.collectAsState() val isRefreshing = remember(calendarViewModel) { calendarViewModel.isRefreshing - }.collectAsState(initial = false) + }.collectAsState() val selectedTab = remember(calendarViewModel) { calendarViewModel.selectedTab - }.collectAsState(initial = 0) + }.collectAsState() Log.d( "CalendarScreen", @@ -397,29 +398,30 @@ fun DateRangeHeader( ) { val startMillis = remember(calendarViewModel) { calendarViewModel.startMillis - }.collectAsState(initial = System.currentTimeMillis()) + }.collectAsState() val endMillis = remember(calendarViewModel) { calendarViewModel.endMillis - }.collectAsState(initial = System.currentTimeMillis()) + }.collectAsState() val appLocale = getCurrentAppLocale(LocalContext.current) val dateFormat = DateTimeFormatter.ofPattern("dd.MM.yyyy", appLocale) - val startDate = remember(startMillis.value) { - Date(startMillis.value) + val startDate = remember { + derivedStateOf { Date(startMillis.value) } } - val endDate = remember(endMillis.value) { - Date(endMillis.value) + val endDate = remember { + derivedStateOf { Date(endMillis.value) } } val rangeText = when (selectedTab) { 0, 1 -> "${ - startDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime() + startDate.value.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime() .format(dateFormat) } – ${ - endDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime().format(dateFormat) + endDate.value.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime() + .format(dateFormat) }" - 2 -> startDate.toInstant() + 2 -> startDate.value.toInstant() .atZone(ZoneId.systemDefault()).toLocalDate() .format(dateFormat) @@ -506,11 +508,11 @@ fun EventCard( ) { val appLocale = getCurrentAppLocale(LocalContext.current) val hourFormat = DateTimeFormatter.ofPattern("HH:mm", appLocale) - val startDate = remember(eventStartOverride.takeIf { it > 0L } ?: event.startTime) { - Date(eventStartOverride.takeIf { it > 0L } ?: event.startTime) + val startDate = remember { + derivedStateOf { Date(eventStartOverride.takeIf { it > 0L } ?: event.startTime) } } - val endDate = remember(eventEndOverride.takeIf { it > 0L } ?: event.endTime) { - Date(eventEndOverride.takeIf { it > 0L } ?: event.endTime) + val endDate = remember { + derivedStateOf { Date(eventEndOverride.takeIf { it > 0L } ?: event.endTime) } } val shape = if (noTopCorners && noBottomCorners) { MaterialTheme.shapes.medium.copy( @@ -568,12 +570,14 @@ fun EventCard( Spacer(modifier = Modifier.height(4.dp)) val startTimeText = when { showStartTimeAsMidnight -> "00:00" - else -> startDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime() + else -> startDate.value.toInstant().atZone(ZoneId.systemDefault()) + .toLocalDateTime() .format(hourFormat) } val endTimeText = when { showEndTimeAsMidnight -> "24:00" - else -> endDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime() + else -> endDate.value.toInstant().atZone(ZoneId.systemDefault()) + .toLocalDateTime() .format(hourFormat) } Text( diff --git a/app/src/main/java/space/midnightthoughts/nordiccalendar/screens/EventDetailsView.kt b/app/src/main/java/space/midnightthoughts/nordiccalendar/screens/EventDetailsView.kt @@ -28,6 +28,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment @@ -76,22 +77,30 @@ fun EventDetailsView( val viewModel: EventDetailsViewModel = hiltViewModel() val event = remember(viewModel) { viewModel.event - }.collectAsState(initial = null) - val startDate = remember(event.value?.startTime) { - Date(event.value?.startTime ?: 0L) + }.collectAsState() + val startDate = remember { + derivedStateOf { + Date(event.value?.startTime ?: 0L) + } } - val endDate = remember(event.value?.endTime) { - Date(event.value?.endTime ?: 0L) + val endDate = remember { + derivedStateOf { + Date(event.value?.endTime ?: 0L) + } } - val startCalendar = remember(startDate) { - val calendar = Calendar.getInstance() - calendar.time = startDate - calendar + val startCalendar = remember { + derivedStateOf { + val calendar = Calendar.getInstance() + calendar.time = startDate.value + calendar + } } - val endCalendar = remember(endDate) { - val calendar = Calendar.getInstance() - calendar.time = endDate - calendar + val endCalendar = remember { + derivedStateOf { + val calendar = Calendar.getInstance() + calendar.time = endDate.value + calendar + } } val appLocale = getCurrentAppLocale( @@ -102,8 +111,8 @@ fun EventDetailsView( val duration = remember(startDate, endDate) { // Make DateTimePeriod from start and end date val duration = Duration.between( - startDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(), - endDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime() + startDate.value.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(), + endDate.value.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime() ) // Format the duration as a string language agnostic using android.text.format.formatElapsedTime @@ -121,13 +130,15 @@ fun EventDetailsView( val locationText = event.value?.location?.trim() // Prevent leaking the url to third party services - val isValidUrlInLocation = remember(locationText) { - Patterns.WEB_URL.matcher(locationText ?: "").matches() + val isValidUrlInLocation = remember { + derivedStateOf { + Patterns.WEB_URL.matcher(locationText ?: "").matches() + } } val locationPosition by viewModel.locationPosition.collectAsState() val appLocaleString = appLocale.language LaunchedEffect(locationText, appLocaleString) { - if (!locationText.isNullOrEmpty() && !isValidUrlInLocation) { + if (!locationText.isNullOrEmpty() && !isValidUrlInLocation.value) { viewModel.resolveLocation(locationText, appLocaleString) } } @@ -179,21 +190,23 @@ fun EventDetailsView( ) { Text( // Check if the event is starting and ending on the same day or not - text = if (startCalendar.get(Calendar.DAY_OF_YEAR) == endCalendar.get( + text = if (startCalendar.value.get(Calendar.DAY_OF_YEAR) == endCalendar.value.get( Calendar.DAY_OF_YEAR ) - && startCalendar.get(Calendar.YEAR) == endCalendar.get(Calendar.YEAR) + && startCalendar.value.get(Calendar.YEAR) == endCalendar.value.get( + Calendar.YEAR + ) ) { - startDate.toInstant().atZone(ZoneId.systemDefault()) + startDate.value.toInstant().atZone(ZoneId.systemDefault()) .toLocalDate() .format(dateFormat) } else { stringResource( R.string.event_date_range, - startDate.toInstant().atZone(ZoneId.systemDefault()) + startDate.value.toInstant().atZone(ZoneId.systemDefault()) .toLocalDate() .format(dateFormat), - endDate.toInstant().atZone(ZoneId.systemDefault()) + endDate.value.toInstant().atZone(ZoneId.systemDefault()) .toLocalDate() .format(dateFormat) ) @@ -204,10 +217,11 @@ fun EventDetailsView( Text( text = stringResource( R.string.event_time_range, - startDate.toInstant().atZone(ZoneId.systemDefault()) + startDate.value.toInstant().atZone(ZoneId.systemDefault()) .toLocalTime() .format(timeFormat), - endDate.toInstant().atZone(ZoneId.systemDefault()).toLocalTime() + endDate.value.toInstant().atZone(ZoneId.systemDefault()) + .toLocalTime() .format(timeFormat) ), style = MaterialTheme.typography.titleSmall, @@ -296,7 +310,7 @@ fun EventDetailsView( DividerDefaults.Thickness, color = MaterialTheme.colorScheme.outlineVariant ) - if (locationPosition != null && !isValidUrlInLocation) { + if (locationPosition != null && !isValidUrlInLocation.value) { Column { Text( text = stringResource(R.string.location),