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 1f1586659b6d418cd34b3a13f4401c960bfc9e0a
parent 178b003ba0e0edabeb73548c4cacd98e9c7715bf
Author: MTRNord <MTRNord@users.noreply.github.com>
Date:   Mon, 28 Jul 2025 17:42:11 +0200

Refactor the sidebar

Diffstat:
Mapp/build.gradle.kts | 3+++
Mapp/src/main/java/space/midnightthoughts/nordiccalendar/MainActivity.kt | 275++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Mapp/src/main/java/space/midnightthoughts/nordiccalendar/components/AppScaffold.kt | 38++++++++++++--------------------------
Mapp/src/main/java/space/midnightthoughts/nordiccalendar/components/SidebarDrawer.kt | 53+++++++++++++++++++++++++++++++++++++++--------------
4 files changed, 201 insertions(+), 168 deletions(-)

diff --git a/app/build.gradle.kts b/app/build.gradle.kts @@ -21,10 +21,13 @@ android { buildTypes { release { isMinifyEnabled = false + isShrinkResources = false proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) + + signingConfig = signingConfigs.getByName("debug") } } compileOptions { diff --git a/app/src/main/java/space/midnightthoughts/nordiccalendar/MainActivity.kt b/app/src/main/java/space/midnightthoughts/nordiccalendar/MainActivity.kt @@ -31,6 +31,7 @@ import androidx.compose.material3.Icon import androidx.compose.material3.Scaffold 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 @@ -45,6 +46,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavBackStackEntry import androidx.navigation.NavHostController import androidx.navigation.NavType import androidx.navigation.compose.NavHost @@ -103,143 +105,34 @@ class MainActivity : ComponentActivity() { } ) ) { backStackEntry -> - val calendarViewModel: CalendarViewModel = viewModel() - val selectedTab by calendarViewModel.selectedTab.collectAsState() - val calendars by calendarViewModel.calendars.collectAsState() - val selectedCalendars by calendarViewModel.selectedCalendars.collectAsState() - val events by calendarViewModel.events.collectAsState() - val isRefreshing by calendarViewModel.isRefreshing.collectAsState() - val tabArg = backStackEntry.arguments?.getInt("tab") - // Tab nur beim initialen Aufruf setzen, nicht bei jedem Recomposition - val didSetTab = remember { mutableStateOf(false) } - if (!didSetTab.value && tabArg != null) { - calendarViewModel.setTab(tabArg) - didSetTab.value = true - } - AppScaffold( - title = stringResource(R.string.app_name), - calendars = calendars, - selectedCalendars = selectedCalendars, - selectedDestination = "calendar", - onCalendarToggle = { cal -> calendarViewModel.toggleCalendar(cal) }, - onSettingsClick = { navController.navigate(Destinations.Settings.route) }, - onAboutClick = { navController.navigate(Destinations.About.route) }, - onCalendarClick = { navController.navigate(Destinations.Calendar.route + "&tab=$selectedTab") }, - floatingActionButton = { - FloatingActionButton(onClick = { /* Event hinzufügen (später) */ }) { - Icon( - Icons.Default.Add, - contentDescription = stringResource(R.string.event_add) - ) - } - } - ) { innerPadding -> - CalendarScreen( - selectedTab = selectedTab, - onTabSelected = { calendarViewModel.setTab(it) }, - events = events, - modifier = innerPadding, - isRefreshing = isRefreshing, - onRefresh = { calendarViewModel.updateEvents() }, - navController = navController, - viewModel = calendarViewModel, - startMillis = calendarViewModel.run { - javaClass.getDeclaredField("startMillis") - .apply { isAccessible = true }.get(this) as Long - }, - endMillis = calendarViewModel.run { - javaClass.getDeclaredField("endMillis") - .apply { isAccessible = true }.get(this) as Long - } - ) - } + CalendarView( + backStackEntry = backStackEntry, + navController = navController + ) } composable(Destinations.EventDetails.route) { backStackEntry -> - val eventId = backStackEntry.arguments?.getString("eventId") - AppScaffold( - title = stringResource(R.string.event_details_title), - calendars = emptyList(), - selectedCalendars = emptyList(), - selectedDestination = "eventDetails", - onCalendarToggle = {}, - onSettingsClick = { navController.navigate(Destinations.Settings.route) }, - onAboutClick = { navController.navigate(Destinations.About.route) }, - onCalendarClick = { navController.navigate(Destinations.Calendar.route + "&tab=2") }, - onBackClick = { - navController.navigate(Destinations.Calendar.route + "&tab=2") { - popUpTo(Destinations.EventDetails.route) { inclusive = true } - } - } - ) { innerPadding -> - Column( - modifier = innerPadding - .fillMaxSize() - .padding(16.dp), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally - ) { - Text(stringResource(R.string.event_details, eventId ?: "")) - Spacer(modifier = Modifier.height(24.dp)) - // Weitere Event-Details hier - } - } + EventDetailsView( + backStackEntry = backStackEntry, + navController = navController + ) } - composable(Destinations.Settings.route) { - val calendarViewModel: CalendarViewModel = viewModel() - val calendars by calendarViewModel.calendars.collectAsState() - val selectedCalendars by calendarViewModel.selectedCalendars.collectAsState() - AppScaffold( - title = stringResource(R.string.settings), - calendars = calendars, - selectedCalendars = selectedCalendars, - selectedDestination = "settings", - onCalendarToggle = { cal -> calendarViewModel.toggleCalendar(cal) }, - onSettingsClick = { navController.navigate(Destinations.Settings.route) }, - onAboutClick = { navController.navigate(Destinations.About.route) }, - onCalendarClick = { navController.navigate(Destinations.Calendar.route) } - ) { innerPadding -> - Column( - modifier = innerPadding - .fillMaxSize() - ) { - Text( - stringResource(R.string.settings), - modifier = Modifier.padding(16.dp) - ) - // Weitere Einstellungen hier - } - } + composable(Destinations.Settings.route) { backStackEntry -> + SettingsView( + navController = navController, + backStackEntry = backStackEntry + ) } - composable(Destinations.About.route) { - val calendarViewModel: CalendarViewModel = viewModel() - val calendars by calendarViewModel.calendars.collectAsState() - val selectedCalendars by calendarViewModel.selectedCalendars.collectAsState() - AppScaffold( - title = stringResource(R.string.about) + " Nordic Calendar", - calendars = calendars, - selectedCalendars = selectedCalendars, - selectedDestination = "about", - onCalendarToggle = { cal -> calendarViewModel.toggleCalendar(cal) }, - onSettingsClick = { navController.navigate(Destinations.Settings.route) }, - onAboutClick = { navController.navigate(Destinations.About.route) }, - onCalendarClick = { navController.navigate(Destinations.Calendar.route) } - ) { innerPadding -> - Column( - modifier = innerPadding - .fillMaxSize() - ) { - Text( - stringResource(R.string.about) + " Nordic Calendar", - modifier = Modifier.padding(16.dp) - ) - // Weitere Infos hier - } - } + composable(Destinations.About.route) { backStackEntry -> + AboutView( + navController = navController, + backStackEntry = backStackEntry + ) } } } } } + } // Pass Navigation Actions: Create a function to handle navigation and pass it to screens. @@ -334,3 +227,128 @@ 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 tabArg = backStackEntry.arguments?.getInt("tab") + + LaunchedEffect(tabArg) { + if (tabArg != null) { + calendarViewModel.setTab(tabArg) + } + } + + AppScaffold( + title = stringResource(R.string.app_name), + selectedDestination = "calendar", + navController = navController, + calendarViewModel = calendarViewModel, + floatingActionButton = { + FloatingActionButton(onClick = { /* Event hinzufügen (später) */ }) { + Icon( + Icons.Default.Add, + contentDescription = stringResource(R.string.event_add) + ) + } + } + ) { innerPadding -> + CalendarScreen( + selectedTab = selectedTab, + onTabSelected = { calendarViewModel.setTab(it) }, + events = events, + modifier = innerPadding, + isRefreshing = isRefreshing, + onRefresh = { calendarViewModel.updateEvents() }, + navController = navController, + viewModel = calendarViewModel, + startMillis = calendarViewModel.run { + javaClass.getDeclaredField("startMillis") + .apply { isAccessible = true }.get(this) as Long + }, + endMillis = calendarViewModel.run { + javaClass.getDeclaredField("endMillis") + .apply { isAccessible = true }.get(this) as Long + } + ) + } +} + + +@Composable +fun EventDetailsView( + backStackEntry: NavBackStackEntry, + navController: NavHostController +) { + val eventId = backStackEntry.arguments?.getString("eventId") + AppScaffold( + title = stringResource(R.string.event_details_title), + selectedDestination = "eventDetails", + navController = navController, + ) { innerPadding -> + Column( + modifier = innerPadding + .fillMaxSize() + .padding(16.dp), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text(stringResource(R.string.event_details, eventId ?: "")) + Spacer(modifier = Modifier.height(24.dp)) + // Weitere Event-Details hier + } + } +} + +@Composable +fun SettingsView(navController: NavHostController, backStackEntry: NavBackStackEntry) { + val parentEntry = remember(backStackEntry) { + navController.getBackStackEntry(Destinations.Calendar.route) + } + val calendarViewModel: CalendarViewModel = viewModel(parentEntry) + AppScaffold( + title = stringResource(R.string.settings), + selectedDestination = "settings", + navController = navController, + calendarViewModel + ) { innerPadding -> + Column( + modifier = innerPadding + .fillMaxSize() + ) { + Text( + stringResource(R.string.settings), + modifier = Modifier.padding(16.dp) + ) + // Weitere Einstellungen hier + } + } +} + +@Composable +fun AboutView(navController: NavHostController, backStackEntry: NavBackStackEntry) { + val parentEntry = remember(backStackEntry) { + navController.getBackStackEntry(Destinations.Calendar.route) + } + val calendarViewModel: CalendarViewModel = viewModel(parentEntry) + AppScaffold( + title = stringResource(R.string.about) + " Nordic Calendar", + selectedDestination = "about", + navController = navController, + calendarViewModel + ) { innerPadding -> + Column( + modifier = innerPadding + .fillMaxSize() + ) { + Text( + stringResource(R.string.about) + " Nordic Calendar", + modifier = Modifier.padding(16.dp) + ) + // Weitere Infos hier + } + } +} +\ No newline at end of file diff --git a/app/src/main/java/space/midnightthoughts/nordiccalendar/components/AppScaffold.kt b/app/src/main/java/space/midnightthoughts/nordiccalendar/components/AppScaffold.kt @@ -17,23 +17,19 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource +import androidx.navigation.NavController import kotlinx.coroutines.launch import space.midnightthoughts.nordiccalendar.R -import space.midnightthoughts.nordiccalendar.util.Calendar +import space.midnightthoughts.nordiccalendar.viewmodels.CalendarViewModel @OptIn(ExperimentalMaterial3Api::class) @Composable fun AppScaffold( title: String? = null, - calendars: List<Calendar>, - selectedCalendars: List<Calendar>, selectedDestination: String, - onCalendarToggle: (Calendar) -> Unit, - onSettingsClick: () -> Unit, - onAboutClick: () -> Unit, - onCalendarClick: () -> Unit, + navController: NavController, + calendarViewModel: CalendarViewModel? = null, floatingActionButton: (@Composable () -> Unit)? = null, - onBackClick: (() -> Unit)? = null, content: @Composable (Modifier) -> Unit ) { val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed) @@ -42,22 +38,9 @@ fun AppScaffold( drawerState = drawerState, drawerContent = { SidebarDrawer( - calendars = calendars, - selectedCalendars = selectedCalendars, - onCalendarToggle = onCalendarToggle, - onSettingsClick = { - scope.launch { drawerState.close() } - onSettingsClick() - }, - onAboutClick = { - scope.launch { drawerState.close() } - onAboutClick() - }, - onCalendarClick = { - scope.launch { drawerState.close() } - onCalendarClick() - }, - selectedDestination = selectedDestination + selectedDestination = selectedDestination, + calendarViewModel = calendarViewModel, + navController = navController, ) } ) { @@ -66,8 +49,11 @@ fun AppScaffold( TopAppBar( title = { title ?: Text(stringResource(R.string.app_name)) }, navigationIcon = { - if (onBackClick != null) { - IconButton(onClick = onBackClick) { + if (navController.previousBackStackEntry != null) { + IconButton(onClick = { + scope.launch { drawerState.close() } + navController.navigateUp() + }) { Icon( Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(R.string.back) diff --git a/app/src/main/java/space/midnightthoughts/nordiccalendar/components/SidebarDrawer.kt b/app/src/main/java/space/midnightthoughts/nordiccalendar/components/SidebarDrawer.kt @@ -1,5 +1,6 @@ package space.midnightthoughts.nordiccalendar.components +import android.util.Log import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -16,22 +17,32 @@ import androidx.compose.material3.NavigationDrawerItem import androidx.compose.material3.NavigationDrawerItemDefaults import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import kotlinx.coroutines.launch +import space.midnightthoughts.nordiccalendar.Destinations import space.midnightthoughts.nordiccalendar.R -import space.midnightthoughts.nordiccalendar.util.Calendar +import space.midnightthoughts.nordiccalendar.viewmodels.CalendarViewModel @Composable fun SidebarDrawer( - calendars: List<Calendar>, - selectedCalendars: List<Calendar>, - onCalendarToggle: (Calendar) -> Unit, - onSettingsClick: () -> Unit, - onAboutClick: () -> Unit, - onCalendarClick: () -> Unit, + calendarViewModel: CalendarViewModel?, + navController: NavController, selectedDestination: String ) { + val selectedCalendars = calendarViewModel?.selectedCalendars?.collectAsState() + val calendars = calendarViewModel?.calendars?.collectAsState() + Log.d( + "SidebarDrawer", + "Selected Calendars: ${selectedCalendars?.value}, Calendars: ${calendars?.value}" + ) + + val scope = rememberCoroutineScope() + ModalDrawerSheet { Column( modifier = Modifier @@ -41,21 +52,27 @@ fun SidebarDrawer( NavigationDrawerItem( label = { Text(stringResource(R.string.calendar)) }, selected = selectedDestination == "calendar", - onClick = onCalendarClick, + onClick = { + navController.navigate(Destinations.Calendar.route) + }, modifier = Modifier.fillMaxWidth(), colors = NavigationDrawerItemDefaults.colors() ) NavigationDrawerItem( label = { Text(stringResource(R.string.settings)) }, selected = selectedDestination == "settings", - onClick = onSettingsClick, + onClick = { + navController.navigate(Destinations.Settings.route) + }, modifier = Modifier.fillMaxWidth(), colors = NavigationDrawerItemDefaults.colors() ) NavigationDrawerItem( label = { Text(stringResource(R.string.about)) }, selected = selectedDestination == "about", - onClick = onAboutClick, + onClick = { + navController.navigate(Destinations.About.route) + }, modifier = Modifier.fillMaxWidth(), colors = NavigationDrawerItemDefaults.colors() ) @@ -64,18 +81,26 @@ fun SidebarDrawer( Spacer(Modifier.height(8.dp)) Text( stringResource(R.string.calendar_selection), - style = MaterialTheme.typography.titleMedium + style = MaterialTheme.typography.titleMedium, + modifier = + Modifier.padding(start = 8.dp, bottom = 8.dp, end = 8.dp, top = 8.dp) ) Spacer(Modifier.height(8.dp)) - calendars.forEach { calendar -> + calendars?.value?.forEach { calendar -> Row(verticalAlignment = androidx.compose.ui.Alignment.CenterVertically) { Checkbox( - checked = selectedCalendars.contains(calendar), - onCheckedChange = { onCalendarToggle(calendar) } + checked = selectedCalendars?.value?.contains(calendar) == true, + onCheckedChange = { + scope.launch { + calendarViewModel.toggleCalendar(calendar) + } + } ) Text(calendar.name, modifier = Modifier.padding(start = 8.dp)) } } + + } } }