From c1ca5cc583af2fd3af408217269344e292fd7543 Mon Sep 17 00:00:00 2001 From: bryantran24 <158430748+bryantran24@users.noreply.github.com> Date: Fri, 14 Nov 2025 16:40:48 -0500 Subject: [PATCH 1/4] Map type button --- .../rpi/shuttletracker/ui/maps/MapsScreen.kt | 77 +++++++++++-------- 1 file changed, 45 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/edu/rpi/shuttletracker/ui/maps/MapsScreen.kt b/app/src/main/java/edu/rpi/shuttletracker/ui/maps/MapsScreen.kt index b91ea64..1861fbe 100644 --- a/app/src/main/java/edu/rpi/shuttletracker/ui/maps/MapsScreen.kt +++ b/app/src/main/java/edu/rpi/shuttletracker/ui/maps/MapsScreen.kt @@ -28,6 +28,7 @@ import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.DirectionsBus +import androidx.compose.material.icons.outlined.Layers import androidx.compose.material.icons.outlined.LocationDisabled import androidx.compose.material.icons.outlined.MyLocation import androidx.compose.material.icons.outlined.Refresh @@ -87,6 +88,7 @@ import com.google.android.gms.maps.model.MapStyleOptions import com.google.maps.android.compose.Circle import com.google.maps.android.compose.GoogleMap import com.google.maps.android.compose.MapProperties +import com.google.maps.android.compose.MapType import com.google.maps.android.compose.MapUiSettings import com.google.maps.android.compose.Marker import com.google.maps.android.compose.Polyline @@ -204,9 +206,6 @@ fun MapsScreen( .fillMaxSize(), ) { Column( - modifier = - Modifier - .fillMaxSize(), verticalArrangement = Arrangement.spacedBy(10.dp), ) { // navigates to announcements @@ -268,6 +267,7 @@ fun BusMap( var selectedStop by remember { mutableStateOf(null) } val isDark = mapsUIState.themeMode.isDarkTheme(isSystemInDarkTheme()) + var mapType by remember { mutableStateOf(MapType.NORMAL) } GoogleMap( modifier = Modifier.fillMaxSize(), @@ -282,6 +282,7 @@ fun BusMap( LatLng(42.72095724005504, -73.70196321825452), LatLng(42.741173465236876, -73.6543446409232), ), + mapType = mapType, isBuildingEnabled = true, minZoomPreference = 14f, isMyLocationEnabled = mapLocationEnabled, @@ -348,37 +349,49 @@ fun BusMap( .padding(horizontal = 10.dp), contentAlignment = Alignment.TopEnd, ) { - ActionButton( - icon = - if (mapLocationEnabled) { - Icons.Outlined.MyLocation - } else { - Icons.Outlined.LocationDisabled - }, + Column( + verticalArrangement = Arrangement.spacedBy(10.dp), ) { - // finds current position and moves to there - LocationServices.getFusedLocationProviderClient(context).lastLocation - .addOnSuccessListener { location: Location? -> - if (location == null) return@addOnSuccessListener - - coroutineScope.launch { - cameraPositionState.animate( - update = - CameraUpdateFactory.newCameraPosition( - CameraPosition.builder() - .target( - LatLng( - location.latitude, - location.longitude, - ), - ).tilt(0f) - .zoom(cameraPositionState.position.zoom) - .build(), - ), - durationMs = 1000, - ) + ActionButton( + icon = + if (mapLocationEnabled) { + Icons.Outlined.MyLocation + } else { + Icons.Outlined.LocationDisabled + }, + ) { + // finds current position and moves to there + LocationServices.getFusedLocationProviderClient(context).lastLocation + .addOnSuccessListener { location: Location? -> + if (location == null) return@addOnSuccessListener + + coroutineScope.launch { + cameraPositionState.animate( + update = + CameraUpdateFactory.newCameraPosition( + CameraPosition.builder() + .target( + LatLng( + location.latitude, + location.longitude, + ), + ).tilt(0f) + .zoom(cameraPositionState.position.zoom) + .build(), + ), + durationMs = 1000, + ) + } } - } + } + ActionButton(icon = Icons.Outlined.Layers) { + mapType = + if (mapType == MapType.NORMAL) { + MapType.HYBRID + } else { + MapType.NORMAL + } + } } } } From aa82199baf96ac6d62d1dcffdd75bfb7a2b5050e Mon Sep 17 00:00:00 2001 From: bryantran24 <158430748+bryantran24@users.noreply.github.com> Date: Fri, 12 Dec 2025 16:44:43 -0500 Subject: [PATCH 2/4] Refactor Mapscreen buttons --- .../rpi/shuttletracker/ui/maps/MapsScreen.kt | 272 ++++++++++-------- 1 file changed, 148 insertions(+), 124 deletions(-) diff --git a/app/src/main/java/edu/rpi/shuttletracker/ui/maps/MapsScreen.kt b/app/src/main/java/edu/rpi/shuttletracker/ui/maps/MapsScreen.kt index aa442cf..1fad000 100644 --- a/app/src/main/java/edu/rpi/shuttletracker/ui/maps/MapsScreen.kt +++ b/app/src/main/java/edu/rpi/shuttletracker/ui/maps/MapsScreen.kt @@ -16,7 +16,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.DirectionsBus import androidx.compose.material.icons.outlined.Layers import androidx.compose.material.icons.outlined.LocationDisabled import androidx.compose.material.icons.outlined.MyLocation @@ -119,45 +118,23 @@ fun MapsScreen( }, ) { padding -> - BusMap(mapsUIState = mapsUiState, padding = padding, bottomSheetOnChange = { - bottomSheetLoaded = it - }) + BusMap( + mapsUIState = mapsUiState, + padding = padding, + bottomSheetOnChange = { bottomSheetLoaded = it }, + onScheduleClick = { + navigator.navigate(ScheduleScreenDestination()) + }, + onSettingsClick = { + navigator.navigate(SettingsScreenDestination()) + }, + ) StopInfoBottomSheet( stop = bottomSheetLoaded, mapsUIState = mapsUiState, onDismiss = { bottomSheetLoaded = null }, ) - - Box( - modifier = - Modifier - .padding(padding) - .padding(horizontal = 10.dp) - .fillMaxSize(), - ) { - Column( - verticalArrangement = Arrangement.spacedBy(10.dp), - ) { - // navigates to announcements -// ActionButton( -// icon = Icons.Outlined.Notifications, -// badgeCount = mapsUiState.totalAnnouncements - mapsUiState.notificationsRead, -// ) { -// navigator.navigate(AnnouncementsScreenDestination()) -// } - - // navigates to the schedule - ActionButton(icon = Icons.Outlined.Schedule) { - navigator.navigate(ScheduleScreenDestination()) - } - - // navigates to settings - ActionButton(icon = Icons.Outlined.Settings) { - navigator.navigate(SettingsScreenDestination()) - } - } - } } } @@ -166,12 +143,18 @@ fun MapsScreen( * * @param mapsUIState: The UI state of the view from the view-model * @param padding: Padding needed for the map content padding + * @param buttonSheetOnChange: Callback invoked when a stop is selected/deselected + * to close/open the stop bottom sheet + * @param onScheduleClick: Callback invoked when the user taps the Schedule button. + * @param onSettingsClick: Callback invoked when the user taps the Settings button. * */ @Composable fun BusMap( mapsUIState: MapsUIState, padding: PaddingValues, bottomSheetOnChange: (Stop?) -> Unit, + onScheduleClick: () -> Unit, + onSettingsClick: () -> Unit, ) { val context = LocalContext.current val coroutineScope = rememberCoroutineScope() @@ -200,98 +183,86 @@ fun BusMap( val isDark = mapsUIState.themeMode.isDarkTheme(isSystemInDarkTheme()) var mapType by remember { mutableStateOf(MapType.NORMAL) } - GoogleMap( - modifier = Modifier.fillMaxSize(), - // makes sure the items drawn (current location and compass) are clickable - contentPadding = padding, - cameraPositionState = cameraPositionState, - // auto dark theme - properties = - MapProperties( - latLngBoundsForCameraTarget = - LatLngBounds( - LatLng(42.72095724005504, -73.70196321825452), - LatLng(42.741173465236876, -73.6543446409232), - ), - mapType = mapType, - isBuildingEnabled = true, - minZoomPreference = 14f, - isMyLocationEnabled = mapLocationEnabled, - mapStyleOptions = - if (isDark) { - MapStyleOptions.loadRawResourceStyle(context, R.raw.map_dark) - } else { - MapStyleOptions("[]") - }, - ), - // removes the zoom control which was covered by the FAB - uiSettings = - MapUiSettings( - zoomControlsEnabled = false, - myLocationButtonEnabled = false, - ), - ) { - // creates the stops - mapsUIState.routes.forEach { (_, route) -> - route.stopDetails.forEach { (_, stop) -> - StopCircle( - stop = stop, - selected = stop.name == selectedStop?.name, - onSelected = { s -> - selectedStop = s - bottomSheetOnChange(s) - }, - ) + Box(modifier = Modifier.fillMaxSize()) { + GoogleMap( + modifier = Modifier.fillMaxSize(), + // makes sure the items drawn (current location and compass) are clickable + contentPadding = padding, + cameraPositionState = cameraPositionState, + // auto dark theme + properties = + MapProperties( + latLngBoundsForCameraTarget = + LatLngBounds( + LatLng(42.72095724005504, -73.70196321825452), + LatLng(42.741173465236876, -73.6543446409232), + ), + mapType = mapType, + isBuildingEnabled = true, + minZoomPreference = 14f, + isMyLocationEnabled = mapLocationEnabled, + mapStyleOptions = + if (isDark) { + MapStyleOptions.loadRawResourceStyle(context, R.raw.map_dark) + } else { + MapStyleOptions("[]") + }, + ), + // removes the zoom control which was covered by the FAB + uiSettings = + MapUiSettings( + zoomControlsEnabled = false, + myLocationButtonEnabled = false, + ), + ) { + // creates the stops + mapsUIState.routes.forEach { (_, route) -> + route.stopDetails.forEach { (_, stop) -> + StopCircle( + stop = stop, + selected = stop.name == selectedStop?.name, + onSelected = { s -> + selectedStop = s + bottomSheetOnChange(s) + }, + ) + } } - } - - // creates the bus markers - mapsUIState.buses.forEach { - BusMarker( - bus = it, - colorBlindMode = mapsUIState.colorBlindMode, - ) - } - // draws the paths - mapsUIState.routes.forEach { (_, route) -> - val points = route.latLng() - if (points.isNotEmpty()) { - Polyline( - points = points, - color = - Color( - android.graphics.Color.valueOf( - route.color.toColorInt(), - ).toArgb(), - ), + // creates the bus markers + mapsUIState.buses.forEach { + BusMarker( + bus = it, + colorBlindMode = mapsUIState.colorBlindMode, ) } + + // draws the paths + mapsUIState.routes.forEach { (_, route) -> + val points = route.latLng() + if (points.isNotEmpty()) { + Polyline( + points = points, + color = + Color( + android.graphics.Color.valueOf( + route.color.toColorInt(), + ).toArgb(), + ), + ) + } + } } - } - // Icon to recenter the user on the map to their location - // makes sure its in the top right - Box( - modifier = - Modifier - .fillMaxSize() - .padding(padding) - .padding(horizontal = 10.dp), - contentAlignment = Alignment.TopEnd, - ) { - Column( - verticalArrangement = Arrangement.spacedBy(10.dp), - ) { - ActionButton( - icon = - if (mapLocationEnabled) { - Icons.Outlined.MyLocation - } else { - Icons.Outlined.LocationDisabled - }, - ) { - // finds current position and moves to there + MapButtonsOverlay( + modifier = + Modifier + .padding(padding) + .padding(horizontal = 10.dp), + mapLocationEnabled = mapLocationEnabled, + onScheduleClick = onScheduleClick, + onSettingsClick = onSettingsClick, + onRecenterClick = { LocationServices.getFusedLocationProviderClient(context).lastLocation .addOnSuccessListener { location: Location? -> if (location == null) return@addOnSuccessListener @@ -314,16 +285,16 @@ fun BusMap( ) } } - } - ActionButton(icon = Icons.Outlined.Layers) { + }, + onToggleMapTypeClick = { mapType = if (mapType == MapType.NORMAL) { MapType.HYBRID } else { MapType.NORMAL } - } - } + }, + ) } } @@ -534,3 +505,56 @@ fun StopInfoBottomSheet( } } } + +@Composable +fun MapButtonsOverlay( + modifier: Modifier = Modifier, + mapLocationEnabled: Boolean, + onScheduleClick: () -> Unit, + onSettingsClick: () -> Unit, + onRecenterClick: () -> Unit, + onToggleMapTypeClick: () -> Unit, +) { + Box( + modifier = modifier.fillMaxSize(), + ) { + // Left side (Schedule, Settings) + Column( + modifier = + Modifier + .align(Alignment.TopStart), + verticalArrangement = Arrangement.spacedBy(10.dp), + ) { + ActionButton(icon = Icons.Outlined.Schedule) { + onScheduleClick() + } + + ActionButton(icon = Icons.Outlined.Settings) { + onSettingsClick() + } + } + + // Right side (Location, Layers) + Column( + modifier = + Modifier + .align(Alignment.TopEnd), + verticalArrangement = Arrangement.spacedBy(10.dp), + ) { + ActionButton( + icon = + if (mapLocationEnabled) { + Icons.Outlined.MyLocation + } else { + Icons.Outlined.LocationDisabled + }, + ) { + onRecenterClick() + } + + ActionButton(icon = Icons.Outlined.Layers) { + onToggleMapTypeClick() + } + } + } +} From 3a15462b707c43244be048572f1715d725078cad Mon Sep 17 00:00:00 2001 From: bryantran24 <158430748+bryantran24@users.noreply.github.com> Date: Fri, 12 Dec 2025 18:26:50 -0500 Subject: [PATCH 3/4] Update map type icon and colorblind mode Disabled colorblind mode --- .../rpi/shuttletracker/ui/maps/MapsScreen.kt | 14 +++- .../ui/settings/SettingsScreen.kt | 72 +++++++++---------- .../shuttletracker/ui/util/SettingsItem.kt | 6 +- app/src/main/res/values/strings.xml | 1 + 4 files changed, 52 insertions(+), 41 deletions(-) diff --git a/app/src/main/java/edu/rpi/shuttletracker/ui/maps/MapsScreen.kt b/app/src/main/java/edu/rpi/shuttletracker/ui/maps/MapsScreen.kt index 1fad000..7c9bd03 100644 --- a/app/src/main/java/edu/rpi/shuttletracker/ui/maps/MapsScreen.kt +++ b/app/src/main/java/edu/rpi/shuttletracker/ui/maps/MapsScreen.kt @@ -16,6 +16,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Layers import androidx.compose.material.icons.outlined.Layers import androidx.compose.material.icons.outlined.LocationDisabled import androidx.compose.material.icons.outlined.MyLocation @@ -143,7 +144,7 @@ fun MapsScreen( * * @param mapsUIState: The UI state of the view from the view-model * @param padding: Padding needed for the map content padding - * @param buttonSheetOnChange: Callback invoked when a stop is selected/deselected + * @param bottonSheetOnChange: Callback invoked when a stop is selected/deselected * to close/open the stop bottom sheet * @param onScheduleClick: Callback invoked when the user taps the Schedule button. * @param onSettingsClick: Callback invoked when the user taps the Settings button. @@ -254,12 +255,20 @@ fun BusMap( } } + val mapTypeIcon = + if (mapType == MapType.NORMAL) { + Icons.Outlined.Layers + } else { + Icons.Filled.Layers + } + MapButtonsOverlay( modifier = Modifier .padding(padding) .padding(horizontal = 10.dp), mapLocationEnabled = mapLocationEnabled, + mapTypeIcon = mapTypeIcon, onScheduleClick = onScheduleClick, onSettingsClick = onSettingsClick, onRecenterClick = { @@ -510,6 +519,7 @@ fun StopInfoBottomSheet( fun MapButtonsOverlay( modifier: Modifier = Modifier, mapLocationEnabled: Boolean, + mapTypeIcon: ImageVector, onScheduleClick: () -> Unit, onSettingsClick: () -> Unit, onRecenterClick: () -> Unit, @@ -552,7 +562,7 @@ fun MapButtonsOverlay( onRecenterClick() } - ActionButton(icon = Icons.Outlined.Layers) { + ActionButton(icon = mapTypeIcon) { onToggleMapTypeClick() } } diff --git a/app/src/main/java/edu/rpi/shuttletracker/ui/settings/SettingsScreen.kt b/app/src/main/java/edu/rpi/shuttletracker/ui/settings/SettingsScreen.kt index 2052c50..a915f7d 100644 --- a/app/src/main/java/edu/rpi/shuttletracker/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/edu/rpi/shuttletracker/ui/settings/SettingsScreen.kt @@ -1,10 +1,10 @@ package edu.rpi.shuttletracker.ui.settings import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.outlined.ArrowBack import androidx.compose.material.icons.outlined.Code @@ -31,7 +31,6 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.ramcosta.composedestinations.annotation.Destination @@ -75,46 +74,47 @@ fun SettingsScreen( }, modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), ) { padding -> - Column( - modifier = - Modifier - .padding(padding), + LazyColumn( + modifier = Modifier.padding(padding), ) { - ColorBlindSettingItem( - colorBlindMode = settingsUiState.colorBlindMode, - updateColorBlindMode = viewModel::updateColorBlindMode, - ) - - ThemeModeSettingItem( - themeMode = settingsUiState.themeMode, - updateThemeMode = viewModel::updateThemeMode, - ) +// item { +// ColorBlindSettingItem( +// colorBlindMode = settingsUiState.colorBlindMode, +// updateColorBlindMode = viewModel::updateColorBlindMode, +// ) +// } + item { + ThemeModeSettingItem( + themeMode = settingsUiState.themeMode, + updateThemeMode = viewModel::updateThemeMode, + ) + } if (settingsUiState.devOptionState) { + item { + SettingsItem( + Icons.Outlined.Code, + stringResource(R.string.dev_options), + onClick = { navigator.navigate(DevMenuScreenDestination()) }, + ) + } + } + + item { SettingsItem( - Icons.Outlined.Code, - stringResource(R.string.dev_options), - onClick = { navigator.navigate(DevMenuScreenDestination()) }, + Icons.Outlined.RestartAlt, + stringResource(R.string.redo_setup), + onClick = { navigator.navigate(SetupScreenDestination(true)) }, ) } -// SettingsItem( -// Icons.Outlined.Timeline, -// stringResource(R.string.analytics), -// onClick = { navigator.navigate(AnalyticsScreenDestination()) }, -// ) - - SettingsItem( - Icons.Outlined.RestartAlt, - "Redo Setup", - onClick = { navigator.navigate(SetupScreenDestination(true)) }, - ) - - SettingsItem( - Icons.Outlined.Info, - stringResource(R.string.about), - onClick = { navigator.navigate(AboutScreenDestination()) }, - ) + item { + SettingsItem( + Icons.Outlined.Info, + stringResource(R.string.about), + onClick = { navigator.navigate(AboutScreenDestination()) }, + ) + } } } } @@ -144,7 +144,7 @@ fun ThemeModeSettingItem( SettingsItem( icon = Icons.Outlined.Contrast, stringResource(R.string.app_theme), - bottomPadding = 0.dp, + hasBottomSpacing = false, ) val themeOptions = listOf(ThemeMode.System, ThemeMode.Light, ThemeMode.Dark) diff --git a/app/src/main/java/edu/rpi/shuttletracker/ui/util/SettingsItem.kt b/app/src/main/java/edu/rpi/shuttletracker/ui/util/SettingsItem.kt index b619918..1edf050 100644 --- a/app/src/main/java/edu/rpi/shuttletracker/ui/util/SettingsItem.kt +++ b/app/src/main/java/edu/rpi/shuttletracker/ui/util/SettingsItem.kt @@ -13,13 +13,13 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp /** * @param icon: Icon to show with the setting * @param title: Title of the setting * @param description: Any subtitle to show with the setting + * @param hasBottomSpacing: Adds bottom padding if true, else no padding * @param onClick: What happens when the setting tile is clicked * @param actions: any other composable such as switches to display with the setting * */ @@ -28,7 +28,7 @@ fun SettingsItem( icon: ImageVector? = null, title: String, description: String = "", - bottomPadding: Dp = 10.dp, + hasBottomSpacing: Boolean = true, onClick: () -> Unit = {}, useLargeAction: Boolean = false, actions: @Composable () -> Unit = {}, @@ -40,7 +40,7 @@ fun SettingsItem( .fillMaxWidth() .padding( top = 10.dp, - bottom = bottomPadding, + bottom = if (hasBottomSpacing) 10.dp else 0.dp, start = 20.dp, end = 20.dp, ), diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7b7e9e8..2e2ecad 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -135,6 +135,7 @@ Dev options unlocked in %1$d Developer Options App Theme + Redo Setup Analytics From 3bed8b59b71883bb05284b2a43cb8575835d2d54 Mon Sep 17 00:00:00 2001 From: bryantran24 <158430748+bryantran24@users.noreply.github.com> Date: Fri, 12 Dec 2025 21:32:55 -0500 Subject: [PATCH 4/4] Save maptype preference --- .../repositories/UserPreferencesRepository.kt | 16 ++++++++++ .../rpi/shuttletracker/ui/maps/MapsScreen.kt | 31 +++++++----------- .../shuttletracker/ui/maps/MapsViewModel.kt | 32 ++++++++++++++++++- 3 files changed, 58 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/edu/rpi/shuttletracker/data/repositories/UserPreferencesRepository.kt b/app/src/main/java/edu/rpi/shuttletracker/data/repositories/UserPreferencesRepository.kt index 7a416af..b9c58a4 100644 --- a/app/src/main/java/edu/rpi/shuttletracker/data/repositories/UserPreferencesRepository.kt +++ b/app/src/main/java/edu/rpi/shuttletracker/data/repositories/UserPreferencesRepository.kt @@ -8,6 +8,7 @@ import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.floatPreferencesKey import androidx.datastore.preferences.core.intPreferencesKey import androidx.datastore.preferences.core.stringPreferencesKey +import com.google.maps.android.compose.MapType import dagger.Lazy import dagger.hilt.android.qualifiers.ApplicationContext import edu.rpi.shuttletracker.R @@ -43,6 +44,7 @@ class UserPreferencesRepository private val ALLOW_ANALYTICS = booleanPreferencesKey("allow_analytics") private val DEV_OPTIONS_ACTIVE = booleanPreferencesKey("dev_options_active") private val THEME_MODE = stringPreferencesKey("theme_mode") + private val MAP_TYPE = stringPreferencesKey("map_type") } suspend fun getUserId(): String = @@ -80,6 +82,20 @@ class UserPreferencesRepository apiRepository.get().sendAnalytics(Event(colorBlindModeToggled = colorBlindMode)) } + fun getMapType(): Flow = + dataStore.data.map { + when (it[MAP_TYPE]) { + MapType.HYBRID.name -> MapType.HYBRID + else -> MapType.NORMAL + } + } + + suspend fun saveMapType(mapType: MapType) { + dataStore.edit { + it[MAP_TYPE] = mapType.name + } + } + fun getPrivacyPolicyAccepted(): Flow = dataStore.data.map { it[PRIVACY_POLICY_ACCEPTED] ?: false diff --git a/app/src/main/java/edu/rpi/shuttletracker/ui/maps/MapsScreen.kt b/app/src/main/java/edu/rpi/shuttletracker/ui/maps/MapsScreen.kt index 7c9bd03..0670ef6 100644 --- a/app/src/main/java/edu/rpi/shuttletracker/ui/maps/MapsScreen.kt +++ b/app/src/main/java/edu/rpi/shuttletracker/ui/maps/MapsScreen.kt @@ -123,12 +123,9 @@ fun MapsScreen( mapsUIState = mapsUiState, padding = padding, bottomSheetOnChange = { bottomSheetLoaded = it }, - onScheduleClick = { - navigator.navigate(ScheduleScreenDestination()) - }, - onSettingsClick = { - navigator.navigate(SettingsScreenDestination()) - }, + onScheduleClick = { navigator.navigate(ScheduleScreenDestination()) }, + onSettingsClick = { navigator.navigate(SettingsScreenDestination()) }, + onToggleMapTypeClick = { viewModel.toggleMapType() }, ) StopInfoBottomSheet( @@ -146,8 +143,10 @@ fun MapsScreen( * @param padding: Padding needed for the map content padding * @param bottonSheetOnChange: Callback invoked when a stop is selected/deselected * to close/open the stop bottom sheet - * @param onScheduleClick: Callback invoked when the user taps the Schedule button. - * @param onSettingsClick: Callback invoked when the user taps the Settings button. + * @param onScheduleClick: Callback invoked when the user taps the Schedule button + * @param onSettingsClick: Callback invoked when the user taps the Settings button + * @param onToggleMapTypeClick: Callback invoked when user taps the MapType button + * * */ @Composable fun BusMap( @@ -156,6 +155,7 @@ fun BusMap( bottomSheetOnChange: (Stop?) -> Unit, onScheduleClick: () -> Unit, onSettingsClick: () -> Unit, + onToggleMapTypeClick: () -> Unit, ) { val context = LocalContext.current val coroutineScope = rememberCoroutineScope() @@ -182,7 +182,6 @@ fun BusMap( var selectedStop by remember { mutableStateOf(null) } val isDark = mapsUIState.themeMode.isDarkTheme(isSystemInDarkTheme()) - var mapType by remember { mutableStateOf(MapType.NORMAL) } Box(modifier = Modifier.fillMaxSize()) { GoogleMap( @@ -190,7 +189,6 @@ fun BusMap( // makes sure the items drawn (current location and compass) are clickable contentPadding = padding, cameraPositionState = cameraPositionState, - // auto dark theme properties = MapProperties( latLngBoundsForCameraTarget = @@ -198,7 +196,7 @@ fun BusMap( LatLng(42.72095724005504, -73.70196321825452), LatLng(42.741173465236876, -73.6543446409232), ), - mapType = mapType, + mapType = mapsUIState.mapType, isBuildingEnabled = true, minZoomPreference = 14f, isMyLocationEnabled = mapLocationEnabled, @@ -256,7 +254,7 @@ fun BusMap( } val mapTypeIcon = - if (mapType == MapType.NORMAL) { + if (mapsUIState.mapType == MapType.NORMAL) { Icons.Outlined.Layers } else { Icons.Filled.Layers @@ -295,14 +293,7 @@ fun BusMap( } } }, - onToggleMapTypeClick = { - mapType = - if (mapType == MapType.NORMAL) { - MapType.HYBRID - } else { - MapType.NORMAL - } - }, + onToggleMapTypeClick = onToggleMapTypeClick, ) } } diff --git a/app/src/main/java/edu/rpi/shuttletracker/ui/maps/MapsViewModel.kt b/app/src/main/java/edu/rpi/shuttletracker/ui/maps/MapsViewModel.kt index 7961939..e59b99b 100644 --- a/app/src/main/java/edu/rpi/shuttletracker/ui/maps/MapsViewModel.kt +++ b/app/src/main/java/edu/rpi/shuttletracker/ui/maps/MapsViewModel.kt @@ -3,6 +3,7 @@ package edu.rpi.shuttletracker.ui.maps import androidx.compose.runtime.Immutable import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.google.maps.android.compose.MapType import com.haroldadmin.cnradapter.NetworkResponse import dagger.hilt.android.lifecycle.HiltViewModel import edu.rpi.shuttletracker.data.models.Bus @@ -31,7 +32,7 @@ class MapsViewModel @Inject constructor( private val apiRepository: ApiRepository, - userPreferencesRepository: UserPreferencesRepository, + private val userPreferencesRepository: UserPreferencesRepository, ) : ViewModel() { // represents the ui state of the view private val _mapsUiState = MutableStateFlow(MapsUIState()) @@ -63,6 +64,14 @@ class MapsViewModel } }.launchIn(viewModelScope) + userPreferencesRepository.getMapType() + .flowOn(Dispatchers.Default) + .onEach { mapType -> + _mapsUiState.update { + it.copy(mapType = mapType) + } + }.launchIn(viewModelScope) + userPreferencesRepository.getMaxStopDist() .flowOn(Dispatchers.Default) .onEach { minStopDist -> @@ -158,6 +167,26 @@ class MapsViewModel } } + fun updateMapType(mapType: MapType) { + viewModelScope.launch { + userPreferencesRepository.saveMapType(mapType) + _mapsUiState.update { + it.copy(mapType = mapType) + } + } + } + + fun toggleMapType() { + val next = + if (mapsUiState.value.mapType == MapType.NORMAL) { + MapType.HYBRID + } else { + MapType.NORMAL + } + + updateMapType(next) + } + /** * Reads the network response and maps it to correct place * */ @@ -201,4 +230,5 @@ data class MapsUIState( val colorBlindMode: Boolean = false, val minStopDist: Float = 50f, val themeMode: ThemeMode = ThemeMode.System, + val mapType: MapType = MapType.NORMAL, )