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 af6e3d3c33e4000bc31fa353cb7e9c642f1b3295
parent 1f1586659b6d418cd34b3a13f4401c960bfc9e0a
Author: MTRNord <MTRNord@users.noreply.github.com>
Date:   Mon, 28 Jul 2025 18:02:38 +0200

Refactor the calendar data slightly

Diffstat:
Mapp/src/main/java/space/midnightthoughts/nordiccalendar/MainActivity.kt | 19++++++++++++-------
Mapp/src/main/java/space/midnightthoughts/nordiccalendar/components/AppScaffold.kt | 14++++++++++++--
Mapp/src/main/java/space/midnightthoughts/nordiccalendar/components/SidebarDrawer.kt | 31+++++++++++++++++++++++++------
Mapp/src/main/java/space/midnightthoughts/nordiccalendar/util/CalendarData.kt | 4+++-
Mapp/src/main/java/space/midnightthoughts/nordiccalendar/viewmodels/CalendarViewModel.kt | 33++++++++++++++++++---------------
5 files changed, 70 insertions(+), 31 deletions(-)

diff --git a/app/src/main/java/space/midnightthoughts/nordiccalendar/MainActivity.kt b/app/src/main/java/space/midnightthoughts/nordiccalendar/MainActivity.kt @@ -33,7 +33,6 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope @@ -231,9 +230,15 @@ fun IntroScreen(navController: NavHostController, onFinish: (() -> Unit)? = null @Composable fun CalendarView(backStackEntry: NavBackStackEntry, navController: NavHostController) { val calendarViewModel: CalendarViewModel = viewModel() - val selectedTab by calendarViewModel.selectedTab.collectAsState() - val events by calendarViewModel.events.collectAsState() - val isRefreshing by calendarViewModel.isRefreshing.collectAsState() + val selectedTab = remember(calendarViewModel) { + calendarViewModel.selectedTab + }.collectAsState(initial = 0) + val events = remember(calendarViewModel) { + calendarViewModel.events + }.collectAsState(initial = emptyList()) + val isRefreshing = remember(calendarViewModel) { + calendarViewModel.isRefreshing + }.collectAsState(initial = false) val tabArg = backStackEntry.arguments?.getInt("tab") LaunchedEffect(tabArg) { @@ -257,11 +262,11 @@ fun CalendarView(backStackEntry: NavBackStackEntry, navController: NavHostContro } ) { innerPadding -> CalendarScreen( - selectedTab = selectedTab, + selectedTab = selectedTab.value, onTabSelected = { calendarViewModel.setTab(it) }, - events = events, + events = events.value, modifier = innerPadding, - isRefreshing = isRefreshing, + isRefreshing = isRefreshing.value, onRefresh = { calendarViewModel.updateEvents() }, navController = navController, viewModel = calendarViewModel, diff --git a/app/src/main/java/space/midnightthoughts/nordiccalendar/components/AppScaffold.kt b/app/src/main/java/space/midnightthoughts/nordiccalendar/components/AppScaffold.kt @@ -14,6 +14,9 @@ import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.material3.rememberDrawerState import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource @@ -34,6 +37,12 @@ fun AppScaffold( ) { val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed) val scope = rememberCoroutineScope() + val isBackButtonVisible by remember { + derivedStateOf { + navController.previousBackStackEntry != null + } + } + ModalNavigationDrawer( drawerState = drawerState, drawerContent = { @@ -41,6 +50,7 @@ fun AppScaffold( selectedDestination = selectedDestination, calendarViewModel = calendarViewModel, navController = navController, + drawerState = drawerState, ) } ) { @@ -49,10 +59,10 @@ fun AppScaffold( TopAppBar( title = { title ?: Text(stringResource(R.string.app_name)) }, navigationIcon = { - if (navController.previousBackStackEntry != null) { + if (isBackButtonVisible) { IconButton(onClick = { scope.launch { drawerState.close() } - navController.navigateUp() + navController.popBackStack() }) { Icon( Icons.AutoMirrored.Filled.ArrowBack, diff --git a/app/src/main/java/space/midnightthoughts/nordiccalendar/components/SidebarDrawer.kt b/app/src/main/java/space/midnightthoughts/nordiccalendar/components/SidebarDrawer.kt @@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material3.Checkbox import androidx.compose.material3.DividerDefaults +import androidx.compose.material3.DrawerState import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ModalDrawerSheet @@ -18,6 +19,8 @@ import androidx.compose.material3.NavigationDrawerItemDefaults import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource @@ -32,13 +35,20 @@ import space.midnightthoughts.nordiccalendar.viewmodels.CalendarViewModel fun SidebarDrawer( calendarViewModel: CalendarViewModel?, navController: NavController, - selectedDestination: String + selectedDestination: String, + drawerState: DrawerState ) { - val selectedCalendars = calendarViewModel?.selectedCalendars?.collectAsState() - val calendars = calendarViewModel?.calendars?.collectAsState() + val calendars = remember(calendarViewModel) { + calendarViewModel?.calendars + }?.collectAsState(initial = emptyList()) + val selectedCalendars = remember(calendars) { + derivedStateOf { + calendars?.value?.filter { it.selected } + } + } Log.d( "SidebarDrawer", - "Selected Calendars: ${selectedCalendars?.value}, Calendars: ${calendars?.value}" + "Selected Calendars: ${selectedCalendars.value}, Calendars: ${calendars?.value}" ) val scope = rememberCoroutineScope() @@ -53,6 +63,9 @@ fun SidebarDrawer( label = { Text(stringResource(R.string.calendar)) }, selected = selectedDestination == "calendar", onClick = { + scope.launch { + drawerState.close() + } navController.navigate(Destinations.Calendar.route) }, modifier = Modifier.fillMaxWidth(), @@ -62,6 +75,9 @@ fun SidebarDrawer( label = { Text(stringResource(R.string.settings)) }, selected = selectedDestination == "settings", onClick = { + scope.launch { + drawerState.close() + } navController.navigate(Destinations.Settings.route) }, modifier = Modifier.fillMaxWidth(), @@ -71,6 +87,9 @@ fun SidebarDrawer( label = { Text(stringResource(R.string.about)) }, selected = selectedDestination == "about", onClick = { + scope.launch { + drawerState.close() + } navController.navigate(Destinations.About.route) }, modifier = Modifier.fillMaxWidth(), @@ -89,10 +108,10 @@ fun SidebarDrawer( calendars?.value?.forEach { calendar -> Row(verticalAlignment = androidx.compose.ui.Alignment.CenterVertically) { Checkbox( - checked = selectedCalendars?.value?.contains(calendar) == true, + checked = selectedCalendars.value?.contains(calendar) == true, onCheckedChange = { scope.launch { - calendarViewModel.toggleCalendar(calendar) + calendarViewModel?.toggleCalendar(calendar) } } ) diff --git a/app/src/main/java/space/midnightthoughts/nordiccalendar/util/CalendarData.kt b/app/src/main/java/space/midnightthoughts/nordiccalendar/util/CalendarData.kt @@ -13,7 +13,9 @@ data class Calendar( val accountName: String, val accountType: String, val syncEvents: Boolean, - val visible: Boolean + val visible: Boolean, + // TODO: Store this somewhere so it is kept across app restarts + val selected: Boolean = true ) data class Event( diff --git a/app/src/main/java/space/midnightthoughts/nordiccalendar/viewmodels/CalendarViewModel.kt b/app/src/main/java/space/midnightthoughts/nordiccalendar/viewmodels/CalendarViewModel.kt @@ -21,9 +21,6 @@ class CalendarViewModel(app: Application) : AndroidViewModel(app) { private val _calendars = MutableStateFlow<List<Calendar>>(emptyList()) val calendars: StateFlow<List<Calendar>> = _calendars.asStateFlow() - private val _selectedCalendars = MutableStateFlow<List<Calendar>>(emptyList()) - val selectedCalendars: StateFlow<List<Calendar>> = _selectedCalendars.asStateFlow() - private val _events = MutableStateFlow<List<Event>>(emptyList()) val events: StateFlow<List<Event>> = _events.asStateFlow() @@ -42,20 +39,23 @@ class CalendarViewModel(app: Application) : AndroidViewModel(app) { viewModelScope.launch { val allCalendars = calendarData.getCalendars(contentResolver) _calendars.value = allCalendars - _selectedCalendars.value = allCalendars.filter { it.visible } updateEvents() isRefreshing.value = false } } fun toggleCalendar(calendar: Calendar) { - val current = _selectedCalendars.value.toMutableList() - if (current.contains(calendar)) { - current.remove(calendar) - } else { - current.add(calendar) + // TODO: Store this somewhere so it is kept across app restarts + Log.d("CalendarViewModel", "Toggling calendar: ${calendar.name}, ID: ${calendar.id}") + + val updatedCalendars = _calendars.value.map { + if (it.id == calendar.id) { + it.copy(selected = !it.selected) + } else { + it + } } - _selectedCalendars.value = current + _calendars.value = updatedCalendars updateEvents() } @@ -137,10 +137,12 @@ class CalendarViewModel(app: Application) : AndroidViewModel(app) { endMillis = getDefaultEndMillis(tab) Log.d( "CalendarViewModel", - "Updating events for selected calendars: ${_selectedCalendars.value.map { it.name }}" + "Updating events for selected calendars: ${ + _calendars.value.filter { it.selected }.map { it.name } + }" ) viewModelScope.launch { - val selectedIds = _selectedCalendars.value.map { it.id } + val selectedIds = _calendars.value.filter { it.selected }.map { it.id } Log.d("CalendarViewModel", "Selected calendar IDs: $selectedIds") _events.value = calendarData.getEventsForCalendars( contentResolver, @@ -238,13 +240,14 @@ class CalendarViewModel(app: Application) : AndroidViewModel(app) { private fun updateEventsCustom() { isRefreshing.value = true - val tab = _selectedTab.value Log.d( "CalendarViewModel", - "Updating events for selected calendars: ${_selectedCalendars.value.map { it.name }}" + "Updating events for selected calendars: ${ + _calendars.value.filter { it.selected }.map { it.name } + }" ) viewModelScope.launch { - val selectedIds = _selectedCalendars.value.map { it.id } + val selectedIds = _calendars.value.filter { it.selected }.map { it.id } Log.d("CalendarViewModel", "Selected calendar IDs: $selectedIds") _events.value = calendarData.getEventsForCalendars( contentResolver,