From b42f89ff9b8c2d010062ba143515a4d335990f60 Mon Sep 17 00:00:00 2001 From: Caleb Shim Date: Tue, 14 Apr 2026 18:45:51 -0400 Subject: [PATCH 1/5] Add missing previews --- .../details/EateryDetailsStickyHeader.kt | 6 +- .../android/eatery/ui/screens/AboutScreen.kt | 9 + .../eatery/ui/screens/CompareMenusScreen.kt | 61 ++++++- .../eatery/ui/screens/EateryDetailScreen.kt | 6 + .../android/eatery/ui/screens/LegalScreen.kt | 9 + .../eatery/ui/screens/NearestScreen.kt | 53 +++++- .../ui/screens/NotificationsHomeScreen.kt | 10 +- .../ui/screens/NotificationsSettingsScreen.kt | 10 +- .../eatery/ui/screens/PrivacyScreen.kt | 78 +++++--- .../eatery/ui/screens/ProfileScreen.kt | 58 +++++- .../android/eatery/ui/screens/SearchScreen.kt | 129 +++++++++++--- .../eatery/ui/screens/SettingsScreen.kt | 43 ++++- .../eatery/ui/screens/SupportScreen.kt | 64 +++++-- .../eatery/ui/screens/UpcomingMenuScreen.kt | 116 ++++++++++-- .../android/eatery/util/PreviewData.kt | 168 ++++++++++++++++++ 15 files changed, 723 insertions(+), 97 deletions(-) diff --git a/app/src/main/java/com/cornellappdev/android/eatery/ui/components/details/EateryDetailsStickyHeader.kt b/app/src/main/java/com/cornellappdev/android/eatery/ui/components/details/EateryDetailsStickyHeader.kt index 312b2616..324aaf46 100644 --- a/app/src/main/java/com/cornellappdev/android/eatery/ui/components/details/EateryDetailsStickyHeader.kt +++ b/app/src/main/java/com/cornellappdev/android/eatery/ui/components/details/EateryDetailsStickyHeader.kt @@ -33,8 +33,6 @@ import com.cornellappdev.android.eatery.data.models.Event import com.cornellappdev.android.eatery.data.models.MenuCategory import com.cornellappdev.android.eatery.ui.theme.GrayFive import com.cornellappdev.android.eatery.ui.theme.GrayZero -import com.cornellappdev.android.eatery.util.AppStorePopupRepository -import com.cornellappdev.android.eatery.util.appStorePopupRepository import kotlinx.coroutines.launch @Composable @@ -45,7 +43,7 @@ fun EateryDetailsStickyHeader( listState: LazyListState, startIndex: Int, onItemClick: (Int) -> Unit, - appStorePopupRepository: AppStorePopupRepository = appStorePopupRepository(), + onRequestRatingPopup: () -> Unit = {}, ) { val rowState = rememberLazyListState() val rowCoroutine = rememberCoroutineScope() @@ -68,7 +66,7 @@ fun EateryDetailsStickyHeader( if (selectedIndex >= 4) { // They've scrolled decently far down and have interacted with this menu, we can // request a review - appStorePopupRepository.requestRatingPopup() + onRequestRatingPopup() } rowState.animateScrollToItem(selectedIndex) } diff --git a/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/AboutScreen.kt b/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/AboutScreen.kt index 875df922..aee42c4b 100644 --- a/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/AboutScreen.kt +++ b/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/AboutScreen.kt @@ -45,6 +45,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.cornellappdev.android.eatery.R @@ -53,6 +54,7 @@ import com.cornellappdev.android.eatery.ui.theme.EateryBlueTypography import com.cornellappdev.android.eatery.ui.theme.GrayFive import com.cornellappdev.android.eatery.ui.theme.GrayOne import com.cornellappdev.android.eatery.ui.theme.GraySix +import com.cornellappdev.android.eatery.util.EateryPreview import kotlinx.coroutines.launch @Composable @@ -325,3 +327,10 @@ fun CreditsRow(position: TeamPosition) { }) } } + +@Preview(showBackground = true) +@Composable +private fun AboutScreenPreview() = EateryPreview { + AboutScreen() +} + diff --git a/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/CompareMenusScreen.kt b/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/CompareMenusScreen.kt index 18a3f834..fafabf65 100644 --- a/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/CompareMenusScreen.kt +++ b/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/CompareMenusScreen.kt @@ -58,6 +58,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel @@ -77,6 +78,11 @@ import com.cornellappdev.android.eatery.ui.theme.Green import com.cornellappdev.android.eatery.ui.theme.Red import com.cornellappdev.android.eatery.ui.theme.Yellow import com.cornellappdev.android.eatery.ui.viewmodels.CompareMenusViewModel +import com.cornellappdev.android.eatery.ui.viewmodels.state.ReportUiState +import com.cornellappdev.android.eatery.util.AppStorePopupRepository +import com.cornellappdev.android.eatery.util.EateryPreview +import com.cornellappdev.android.eatery.util.PreviewData +import com.cornellappdev.android.eatery.util.appStorePopupRepository import kotlinx.coroutines.launch import java.math.BigDecimal @@ -88,6 +94,7 @@ import java.math.BigDecimal fun CompareMenusScreen( eateryIds: List, compareMenusViewModel: CompareMenusViewModel = hiltViewModel(), + appStorePopupRepository: AppStorePopupRepository = appStorePopupRepository(), onEateryClick: (eatery: Eatery) -> Unit, ) { val eateryIdsKey = remember(eateryIds) { eateryIds.hashCode() } @@ -97,8 +104,32 @@ fun CompareMenusScreen( val uiState by compareMenusViewModel.uiState.collectAsStateWithLifecycle() val reportState by compareMenusViewModel.reportState.collectAsStateWithLifecycle() - val eateries = uiState.eateries - val events = uiState.events + + CompareMenusScreenContent( + eateries = uiState.eateries, + events = uiState.events, + reportState = reportState, + onSendReport = compareMenusViewModel::sendReport, + onClearReportState = compareMenusViewModel::clearReportState, + onRequestRatingPopup = { appStorePopupRepository.requestRatingPopup() }, + onEateryClick = onEateryClick, + ) +} + +@OptIn( + ExperimentalFoundationApi::class, + ExperimentalMaterial3Api::class, +) +@Composable +private fun CompareMenusScreenContent( + eateries: List, + events: List, + reportState: ReportUiState, + onSendReport: (String, String, Int?) -> Unit, + onClearReportState: () -> Unit, + onRequestRatingPopup: () -> Unit, + onEateryClick: (Eatery) -> Unit, +) { val modalBottomSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) val coroutineScope = rememberCoroutineScope() var showBottomSheet by rememberSaveable { mutableStateOf(false) } @@ -108,7 +139,7 @@ fun CompareMenusScreen( coroutineScope.launch { modalBottomSheetState.hide() showBottomSheet = false - compareMenusViewModel.clearReportState() + onClearReportState() } } val openBottomSheet: (BottomSheetContent) -> Unit = { content -> @@ -196,8 +227,8 @@ fun CompareMenusScreen( issue = issue, eateryId = it, reportState = reportState, - sendReport = compareMenusViewModel::sendReport, - clearReportState = compareMenusViewModel::clearReportState, + sendReport = onSendReport, + clearReportState = onClearReportState, hide = closeBottomSheet ) } @@ -214,6 +245,7 @@ fun CompareMenusScreen( firstPagerState, events, onOpenSheet = openBottomSheet, + onRequestRatingPopup = onRequestRatingPopup, onEateryClick ) TitlePager(eateries, secondPagerState) @@ -224,6 +256,23 @@ fun CompareMenusScreen( } +@Preview(showBackground = true) +@Composable +private fun CompareMenusScreenPreview() = EateryPreview { + val previewState = PreviewData.compareMenusPreviewState() + + CompareMenusScreenContent( + eateries = previewState.eateries, + events = previewState.events, + reportState = ReportUiState.Idle, + onSendReport = { _, _, _ -> }, + onClearReportState = {}, + onRequestRatingPopup = {}, + onEateryClick = {} + ) +} + + @Composable @OptIn( ExperimentalFoundationApi::class, @@ -234,6 +283,7 @@ private fun MenuPager( firstPagerState: PagerState, events: List, onOpenSheet: (BottomSheetContent) -> Unit, + onRequestRatingPopup: () -> Unit, onEateryClick: (eatery: Eatery) -> Unit ) { val coroutineScope = rememberCoroutineScope() @@ -328,6 +378,7 @@ private fun MenuPager( fullMenuList, listState, 0, + onRequestRatingPopup = onRequestRatingPopup, onItemClick = { index -> coroutineScope.launch { listState.animateScrollToItem(index) diff --git a/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/EateryDetailScreen.kt b/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/EateryDetailScreen.kt index 0fea5643..267ad7d7 100644 --- a/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/EateryDetailScreen.kt +++ b/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/EateryDetailScreen.kt @@ -122,8 +122,10 @@ import com.cornellappdev.android.eatery.ui.viewmodels.EateryDetailViewState import com.cornellappdev.android.eatery.ui.viewmodels.MealViewState import com.cornellappdev.android.eatery.ui.viewmodels.state.EateryApiResponse import com.cornellappdev.android.eatery.ui.viewmodels.state.ReportUiState +import com.cornellappdev.android.eatery.util.AppStorePopupRepository import com.cornellappdev.android.eatery.util.EateryPreview import com.cornellappdev.android.eatery.util.PreviewData +import com.cornellappdev.android.eatery.util.appStorePopupRepository import com.cornellappdev.android.eatery.util.fromOffsetToDayOfWeek import com.cornellappdev.android.eatery.util.toMealTypeDisplayName import com.cornellappdev.android.eatery.util.toReadableFullName @@ -137,6 +139,7 @@ import java.time.format.DateTimeFormatter @Composable fun EateryDetailScreen( eateryDetailViewModel: EateryDetailViewModel = hiltViewModel(), + appStorePopupRepository: AppStorePopupRepository = appStorePopupRepository(), onCompareMenusClick: (selectedEateriesIds: List) -> Unit, ) { val viewState = eateryDetailViewModel.eateryDetailViewState.collectAsStateWithLifecycle().value @@ -162,6 +165,7 @@ fun EateryDetailScreen( onResetSelectedEvent = eateryDetailViewModel::resetSelectedEvent, onSearchQueryChange = eateryDetailViewModel::setSearchQuery, onToggleFavoriteMenuItem = eateryDetailViewModel::toggleFavoriteMenuItem, + onRequestRatingPopup = { appStorePopupRepository.requestRatingPopup() } ) } @@ -180,6 +184,7 @@ fun EateryDetailScreenContent( onResetSelectedEvent: () -> Unit, onSearchQueryChange: (String) -> Unit, onToggleFavoriteMenuItem: (String) -> Unit, + onRequestRatingPopup: () -> Unit = {}, ) { val shimmer = rememberShimmer(ShimmerBounds.View) val context = LocalContext.current @@ -856,6 +861,7 @@ fun EateryDetailScreenContent( fullMenuList, listState, 5, + onRequestRatingPopup = onRequestRatingPopup, onItemClick = { index -> // The first category title has an item index of 8 // ideal is listState.animateScrollToItem(index + 8, scrollOffset = -400) diff --git a/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/LegalScreen.kt b/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/LegalScreen.kt index f4a7ccdd..8b85478c 100644 --- a/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/LegalScreen.kt +++ b/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/LegalScreen.kt @@ -13,6 +13,7 @@ import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.cornellappdev.android.eatery.R @@ -21,6 +22,7 @@ import com.cornellappdev.android.eatery.ui.components.settings.SettingsOption import com.cornellappdev.android.eatery.ui.theme.EateryBlue import com.cornellappdev.android.eatery.ui.theme.EateryBlueTypography import com.cornellappdev.android.eatery.ui.theme.GraySix +import com.cornellappdev.android.eatery.util.EateryPreview @Composable fun LegalScreen() { @@ -68,3 +70,10 @@ fun LegalScreen() { ) } } + +@Preview(showBackground = true) +@Composable +private fun LegalScreenPreview() = EateryPreview { + LegalScreen() +} + diff --git a/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/NearestScreen.kt b/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/NearestScreen.kt index 8fef807f..81499401 100644 --- a/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/NearestScreen.kt +++ b/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/NearestScreen.kt @@ -22,6 +22,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel @@ -33,6 +34,8 @@ import com.cornellappdev.android.eatery.ui.theme.EateryBlue import com.cornellappdev.android.eatery.ui.theme.EateryBlueTypography import com.cornellappdev.android.eatery.ui.theme.GrayTwo import com.cornellappdev.android.eatery.ui.viewmodels.NearestViewModel +import com.cornellappdev.android.eatery.util.EateryPreview +import com.cornellappdev.android.eatery.util.PreviewData import com.valentinilk.shimmer.ShimmerBounds import com.valentinilk.shimmer.rememberShimmer @@ -46,6 +49,24 @@ fun NearestScreen( ) { rememberShimmer(ShimmerBounds.View) val uiState = nearestViewModel.uiState.collectAsStateWithLifecycle().value + + NearestScreenContent( + uiState = uiState, + onEateryClick = onEateryClick, + onFavoriteClick = { eatery, isFavorite -> + if (eatery.id != null && eatery.name != null) { + nearestViewModel.setFavorite(eatery.id, eatery.name, isFavorite) + } + } + ) +} + +@Composable +private fun NearestScreenContent( + uiState: NearestViewModel.NearestUiState, + onEateryClick: (Eatery) -> Unit, + onFavoriteClick: (Eatery, Boolean) -> Unit, +) { val nearestEateries = uiState.nearestEateries val favorites = uiState.favoriteEateries @@ -109,9 +130,7 @@ fun NearestScreen( eatery = eatery, isFavorite = favorites.contains(eatery), onFavoriteClick = { - if (eatery.id != null && eatery.name != null) { - nearestViewModel.setFavorite(eatery.id, eatery.name, it) - } + onFavoriteClick(eatery, it) }) { onEateryClick(it) } @@ -124,3 +143,31 @@ fun NearestScreen( } } } + +@Preview(showBackground = true) +@Composable +private fun NearestScreenPreview() = EateryPreview { + val eateries = listOf( + PreviewData.mockEatery(1).copy(name = "Okenshields"), + PreviewData.mockEatery(2).copy(name = "Becker House Dining Room") + ) + NearestScreenContent( + uiState = NearestViewModel.NearestUiState( + nearestEateries = eateries, + favoriteEateries = listOf(eateries.first()) + ), + onEateryClick = {}, + onFavoriteClick = { _, _ -> } + ) +} + +@Preview(showBackground = true) +@Composable +private fun NearestScreenEmptyPreview() = EateryPreview { + NearestScreenContent( + uiState = NearestViewModel.NearestUiState(), + onEateryClick = {}, + onFavoriteClick = { _, _ -> } + ) +} + diff --git a/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/NotificationsHomeScreen.kt b/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/NotificationsHomeScreen.kt index 40ba8a5e..9812d3e2 100644 --- a/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/NotificationsHomeScreen.kt +++ b/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/NotificationsHomeScreen.kt @@ -8,11 +8,13 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.cornellappdev.android.eatery.R import com.cornellappdev.android.eatery.ui.components.notifications.FavoriteItemRow import com.cornellappdev.android.eatery.ui.theme.EateryBlue import com.cornellappdev.android.eatery.ui.theme.EateryBlueTypography +import com.cornellappdev.android.eatery.util.EateryPreview @Composable fun NotificationsHomeScreen( @@ -65,4 +67,10 @@ fun NotificationsHomeScreen( } } } -} \ No newline at end of file +} + +@Preview(showBackground = true) +@Composable +private fun NotificationsHomeScreenPreview() = EateryPreview { + NotificationsHomeScreen() +} diff --git a/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/NotificationsSettingsScreen.kt b/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/NotificationsSettingsScreen.kt index 703c6526..0538b473 100644 --- a/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/NotificationsSettingsScreen.kt +++ b/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/NotificationsSettingsScreen.kt @@ -11,6 +11,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.cornellappdev.android.eatery.R @@ -18,6 +19,7 @@ import com.cornellappdev.android.eatery.ui.components.settings.SwitchOption import com.cornellappdev.android.eatery.ui.theme.EateryBlue import com.cornellappdev.android.eatery.ui.theme.EateryBlueTypography import com.cornellappdev.android.eatery.ui.theme.GraySix +import com.cornellappdev.android.eatery.util.EateryPreview @Composable fun NotificationsSettingsScreen() { @@ -81,4 +83,10 @@ fun NotificationsSettingsScreen() { } ) } -} \ No newline at end of file +} + +@Preview(showBackground = true) +@Composable +private fun NotificationsSettingsScreenPreview() = EateryPreview { + NotificationsSettingsScreen() +} diff --git a/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/PrivacyScreen.kt b/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/PrivacyScreen.kt index 03fd9333..34fbe5ff 100644 --- a/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/PrivacyScreen.kt +++ b/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/PrivacyScreen.kt @@ -17,6 +17,7 @@ import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel @@ -28,6 +29,7 @@ import com.cornellappdev.android.eatery.ui.theme.EateryBlue import com.cornellappdev.android.eatery.ui.theme.EateryBlueTypography import com.cornellappdev.android.eatery.ui.theme.GraySix import com.cornellappdev.android.eatery.ui.viewmodels.PrivacyViewModel +import com.cornellappdev.android.eatery.util.EateryPreview import com.google.firebase.analytics.FirebaseAnalytics @Composable @@ -35,6 +37,40 @@ fun PrivacyScreen(privacyViewModel: PrivacyViewModel = hiltViewModel()) { val context = LocalContext.current val uriCurrent = LocalUriHandler.current + PrivacyScreenContent( + analyticsDisabled = privacyViewModel.analyticsDisabled, + onOpenLocationSettings = { + context.startActivity( + Intent( + android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS, + Uri.fromParts("package", context.packageName, null) + ) + ) + }, + onOpenNotificationSettings = { + val intent = Intent(android.provider.Settings.ACTION_APP_NOTIFICATION_SETTINGS) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + intent.putExtra("app_package", context.packageName) + intent.putExtra("app_uid", context.applicationInfo.uid) + intent.putExtra("android.provider.extra.APP_PACKAGE", context.packageName) + context.startActivity(intent) + }, + onAnalyticsPreferenceChange = { switched -> + privacyViewModel.setAnalyticsDisabled(switched) + FirebaseAnalytics.getInstance(context).setAnalyticsCollectionEnabled(!switched) + }, + onOpenPrivacyPolicy = { uriCurrent.openUri("https://www.cornellappdev.com/privacy") } + ) +} + +@Composable +private fun PrivacyScreenContent( + analyticsDisabled: Boolean, + onOpenLocationSettings: () -> Unit, + onOpenNotificationSettings: () -> Unit, + onAnalyticsPreferenceChange: (Boolean) -> Unit, + onOpenPrivacyPolicy: () -> Unit, +) { Column( modifier = Modifier .padding(top = 36.dp, start = 16.dp, end = 16.dp) @@ -60,14 +96,7 @@ fun PrivacyScreen(privacyViewModel: PrivacyViewModel = hiltViewModel()) { SettingsOption( title = stringResource(R.string.privacy_location_access_title), description = stringResource(R.string.privacy_location_access_description), - onClick = { - context.startActivity( - Intent( - android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS, - Uri.fromParts("package", context.packageName, null) - ) - ) - }, + onClick = onOpenLocationSettings, trailingIcon = { Icon( imageVector = Icons.Outlined.ArrowOutward, @@ -79,16 +108,7 @@ fun PrivacyScreen(privacyViewModel: PrivacyViewModel = hiltViewModel()) { SettingsOption( title = stringResource(R.string.privacy_notification_access_title), description = stringResource(R.string.privacy_notification_access_description), - onClick = { - val intent = Intent(android.provider.Settings.ACTION_APP_NOTIFICATION_SETTINGS) - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - - intent.putExtra("app_package", context.packageName) - intent.putExtra("app_uid", context.applicationInfo.uid) - intent.putExtra("android.provider.extra.APP_PACKAGE", context.packageName) - - context.startActivity(intent) - }, + onClick = onOpenNotificationSettings, trailingIcon = { Icon( imageVector = Icons.Outlined.ArrowOutward, @@ -107,17 +127,14 @@ fun PrivacyScreen(privacyViewModel: PrivacyViewModel = hiltViewModel()) { SwitchOption( title = stringResource(R.string.privacy_share_with_cornell_appdev_title), description = stringResource(R.string.privacy_share_with_cornell_appdev_description), - initialValue = !privacyViewModel.analyticsDisabled, - onCheckedChange = { switched -> - privacyViewModel.setAnalyticsDisabled(switched) - FirebaseAnalytics.getInstance(context).setAnalyticsCollectionEnabled(!switched) - } + initialValue = !analyticsDisabled, + onCheckedChange = onAnalyticsPreferenceChange ) SettingsLineSeparator() SettingsOption( title = stringResource(R.string.privacy_policy_title), - onClick = { uriCurrent.openUri("https://www.cornellappdev.com/privacy") }, + onClick = onOpenPrivacyPolicy, trailingIcon = { Icon( imageVector = Icons.Outlined.ArrowOutward, @@ -128,3 +145,16 @@ fun PrivacyScreen(privacyViewModel: PrivacyViewModel = hiltViewModel()) { ) } } + +@Preview(showBackground = true) +@Composable +private fun PrivacyScreenPreview() = EateryPreview { + PrivacyScreenContent( + analyticsDisabled = false, + onOpenLocationSettings = {}, + onOpenNotificationSettings = {}, + onAnalyticsPreferenceChange = {}, + onOpenPrivacyPolicy = {} + ) +} + diff --git a/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/ProfileScreen.kt b/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/ProfileScreen.kt index 389dac64..9f048445 100644 --- a/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/ProfileScreen.kt +++ b/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/ProfileScreen.kt @@ -2,6 +2,7 @@ package com.cornellappdev.android.eatery.ui.screens import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.runtime.Composable +import androidx.compose.ui.tooling.preview.Preview import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.cornellappdev.android.eatery.data.models.AccountBalances @@ -11,6 +12,7 @@ import com.cornellappdev.android.eatery.ui.components.login.AccountPage import com.cornellappdev.android.eatery.ui.components.login.LoginPage import com.cornellappdev.android.eatery.ui.viewmodels.LoginViewModel import com.cornellappdev.android.eatery.ui.viewmodels.state.DisplayTransaction +import com.cornellappdev.android.eatery.util.EateryPreview @OptIn(ExperimentalAnimationApi::class) @@ -82,4 +84,58 @@ private fun ProfileScreenContent( updateAccountFilter = updateAccountFilter ) } -} \ No newline at end of file +} + +@Preview(showBackground = true) +@Composable +private fun ProfileScreenLoginPreview() = EateryPreview { + ProfileScreenContent( + isLoginState = true, + accountTypeBalance = AccountBalances(), + loading = false, + onLoginPressed = {}, + onSuccess = {}, + onBackClick = {}, + onModalHidden = {}, + accountFilter = TransactionAccountType.BRBS, + filterText = "", + onSettingsClicked = {}, + filteredTransactions = emptyList(), + onQueryChanged = {}, + updateAccountFilter = {} + ) +} + +@Preview(showBackground = true) +@Composable +private fun ProfileScreenAccountPreview() = EateryPreview { + ProfileScreenContent( + isLoginState = false, + accountTypeBalance = AccountBalances( + brbBalance = 120.35, + cityBucksBalance = 42.0, + laundryBalance = 8.0, + mealSwipes = 10 + ), + loading = false, + onLoginPressed = {}, + onSuccess = {}, + onBackClick = {}, + onModalHidden = {}, + accountFilter = TransactionAccountType.BRBS, + filterText = "", + onSettingsClicked = {}, + filteredTransactions = listOf( + DisplayTransaction( + id = "1", + amount = -12.75, + accountType = TransactionAccountType.BRBS, + location = "Okenshields", + formattedDate = "Apr 14" + ) + ), + onQueryChanged = {}, + updateAccountFilter = {} + ) +} + diff --git a/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/SearchScreen.kt b/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/SearchScreen.kt index 7e906955..d301a48a 100644 --- a/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/SearchScreen.kt +++ b/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/SearchScreen.kt @@ -50,6 +50,7 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle @@ -65,6 +66,9 @@ import com.cornellappdev.android.eatery.ui.theme.EateryBlueTypography import com.cornellappdev.android.eatery.ui.theme.GrayZero import com.cornellappdev.android.eatery.ui.viewmodels.SearchViewModel import com.cornellappdev.android.eatery.ui.viewmodels.state.EateryApiResponse +import com.cornellappdev.android.eatery.ui.viewmodels.state.NetworkUiError +import com.cornellappdev.android.eatery.util.EateryPreview +import com.cornellappdev.android.eatery.util.PreviewData import com.cornellappdev.android.eatery.util.popIn import com.cornellappdev.android.eatery.util.popOut import com.skydoves.landscapist.ImageOptions @@ -83,6 +87,55 @@ fun SearchScreen( searchViewModel: SearchViewModel = hiltViewModel(), onEateryClick: (eatery: Eatery) -> Unit, onFavoriteClick: () -> Unit +) { + val uiState = searchViewModel.uiState.collectAsStateWithLifecycle().value + + SearchScreenContent( + query = uiState.query, + filters = uiState.filters, + searchResponse = uiState.searchResponse, + favoriteEateries = uiState.favoriteEateries, + recentSearches = uiState.recentSearches, + error = uiState.error, + searchScreenFilters = searchViewModel.searchScreenFilters, + onClearError = searchViewModel::clearError, + onQueryEateries = searchViewModel::queryEateries, + onAddPaymentMethodFilters = searchViewModel::addPaymentMethodFilters, + onToggleFilter = searchViewModel::toggleFilter, + onAddFavorite = searchViewModel::addFavorite, + onRemoveFavorite = searchViewModel::removeFavorite, + onAddRecentSearch = searchViewModel::addRecentSearch, + observeEatery = { eateryId -> + searchViewModel.observeEatery(eateryId).collectAsStateWithLifecycle().value + }, + onEateryClick = onEateryClick, + onFavoriteClick = onFavoriteClick, + ) +} + +@OptIn( + ExperimentalMaterial3Api::class, + ExperimentalFoundationApi::class +) +@Composable +private fun SearchScreenContent( + query: String, + filters: List, + searchResponse: EateryApiResponse>, + favoriteEateries: List, + recentSearches: List, + error: NetworkUiError?, + searchScreenFilters: List, + onClearError: () -> Unit, + onQueryEateries: (String) -> Unit, + onAddPaymentMethodFilters: (List) -> Unit, + onToggleFilter: (Filter) -> Unit, + onAddFavorite: (Int, String) -> Unit, + onRemoveFavorite: (Int, String) -> Unit, + onAddRecentSearch: (Int?) -> Unit, + observeEatery: @Composable (Int) -> EateryApiResponse, + onEateryClick: (Eatery) -> Unit, + onFavoriteClick: () -> Unit, ) { val selectedPaymentMethodFilters = remember { mutableStateListOf() } val focusRequester = remember { FocusRequester() } @@ -90,16 +143,9 @@ fun SearchScreen( var showPaymentMethodSheet by rememberSaveable { mutableStateOf(false) } val coroutineScope = rememberCoroutineScope() - val uiState = searchViewModel.uiState.collectAsStateWithLifecycle().value - val query = uiState.query - val favorites = uiState.favoriteEateries - val recentSearches = uiState.recentSearches - val filters = uiState.filters - val searchResponse = uiState.searchResponse - NetworkErrorToast( - error = uiState.error, - onErrorShown = searchViewModel::clearError + error = error, + onErrorShown = onClearError ) // Automatically brings the search bar into focus when the view is composed @@ -115,7 +161,7 @@ fun SearchScreen( DisposableEffect(Unit) { onDispose { // Handles the case where filters reset as well (by adding an empty list). - searchViewModel.addPaymentMethodFilters(selectedPaymentMethodFilters) + onAddPaymentMethodFilters(selectedPaymentMethodFilters) } } } @@ -149,7 +195,7 @@ fun SearchScreen( SearchBar( searchText = query, onSearchTextChange = { - searchViewModel.queryEateries(it) + onQueryEateries(it) }, placeholderText = stringResource(R.string.search_placeholder_grub), modifier = Modifier.padding( @@ -177,9 +223,9 @@ fun SearchScreen( FilterRow( currentFiltersSelected = filters, onFilterClicked = { filter -> - searchViewModel.toggleFilter(filter) + onToggleFilter(filter) }, - filters = searchViewModel.searchScreenFilters, + filters = searchScreenFilters, ) } } @@ -204,7 +250,7 @@ fun SearchScreen( .fillMaxWidth() ) { AnimatedVisibility( - visible = favorites.isNotEmpty(), + visible = favoriteEateries.isNotEmpty(), enter = popIn(), exit = popOut() ) { @@ -248,7 +294,7 @@ fun SearchScreen( horizontalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier.padding(start = 16.dp) ) { - items(items = favorites, key = { eatery -> + items(items = favoriteEateries, key = { eatery -> eatery.id ?: eatery.hashCode() }) { eatery -> FavoriteItem(eatery, onEateryClick) @@ -267,9 +313,7 @@ fun SearchScreen( ) } items(recentSearches) { eateryId -> - val eateryResponse = - searchViewModel.observeEatery(eateryId) - .collectAsStateWithLifecycle().value + val eateryResponse = observeEatery(eateryId) if (eateryResponse is EateryApiResponse.Success) { Box( Modifier.padding( @@ -279,25 +323,25 @@ fun SearchScreen( val eatery = eateryResponse.data EateryCard( eatery = eatery, - isFavorite = favorites.any { favoriteEatery -> + isFavorite = favoriteEateries.any { favoriteEatery -> favoriteEatery.id == eatery.id }, onFavoriteClick = { if (eatery.id != null && eatery.name != null) { if (it) { - searchViewModel.addFavorite( + onAddFavorite( eatery.id, eatery.name ) } else { - searchViewModel.removeFavorite( + onRemoveFavorite( eatery.id, eatery.name ) } } }) { - searchViewModel.addRecentSearch(it.id) + onAddRecentSearch(it.id) onEateryClick(it) } } @@ -315,22 +359,22 @@ fun SearchScreen( ) { EateryCard( eatery = eatery, - isFavorite = favorites.any { favoriteEatery -> + isFavorite = favoriteEateries.any { favoriteEatery -> favoriteEatery.id == eatery.id }, onFavoriteClick = { if (eatery.id != null && eatery.name != null) { if (it) { - searchViewModel.addFavorite(eatery.id, eatery.name) + onAddFavorite(eatery.id, eatery.name) } else { - searchViewModel.removeFavorite( + onRemoveFavorite( eatery.id, eatery.name ) } } }) { - searchViewModel.addRecentSearch(it.id) + onAddRecentSearch(it.id) onEateryClick(it) } } @@ -339,6 +383,39 @@ fun SearchScreen( } } +@Preview(showBackground = true) +@Composable +private fun SearchScreenPreview() = EateryPreview { + SearchScreenContent( + query = "Ok", + filters = emptyList(), + searchResponse = EateryApiResponse.Success( + listOf( + PreviewData.mockEatery(1).copy(name = "Okenshields"), + PreviewData.mockEatery(2).copy(name = "Becker House Dining Room") + ) + ), + favoriteEateries = listOf(PreviewData.mockEatery(1).copy(name = "Okenshields")), + recentSearches = emptyList(), + error = null, + searchScreenFilters = listOf( + Filter.FromEateryFilter.North, + Filter.FromEateryFilter.West, + Filter.FromEateryFilter.Central + ), + onClearError = {}, + onQueryEateries = {}, + onAddPaymentMethodFilters = {}, + onToggleFilter = {}, + onAddFavorite = { _, _ -> }, + onRemoveFavorite = { _, _ -> }, + onAddRecentSearch = {}, + observeEatery = { EateryApiResponse.Pending }, + onEateryClick = {}, + onFavoriteClick = {} + ) +} + /** * Each favorited item within the favorited list diff --git a/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/SettingsScreen.kt b/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/SettingsScreen.kt index d58b481d..7916a1f2 100644 --- a/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/SettingsScreen.kt +++ b/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/SettingsScreen.kt @@ -33,6 +33,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import com.cornellappdev.android.eatery.R @@ -45,6 +46,7 @@ import com.cornellappdev.android.eatery.ui.theme.EateryBlueTypography import com.cornellappdev.android.eatery.ui.theme.GrayFive import com.cornellappdev.android.eatery.ui.theme.GrayZero import com.cornellappdev.android.eatery.ui.viewmodels.SettingsViewModel +import com.cornellappdev.android.eatery.util.EateryPreview import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class) @@ -52,6 +54,22 @@ import kotlinx.coroutines.launch fun SettingsScreen( settingsViewModel: SettingsViewModel = hiltViewModel(), destinations: HashMap Unit> +) { + SettingsScreenContent( + destinations = destinations, + onLogout = { + settingsViewModel.onLogout(onDone = { + destinations[Routes.PROFILE]?.invoke() + }) + } + ) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun SettingsScreenContent( + destinations: Map Unit>, + onLogout: () -> Unit, ) { // To sign out, setIsLoggedIn to false and transition back to profileView with autoLogin false val coroutineScope = rememberCoroutineScope() @@ -263,9 +281,7 @@ fun SettingsScreen( ) { Button( onClick = { - settingsViewModel.onLogout(onDone = { - destinations[Routes.PROFILE]?.invoke() - }) + onLogout() }, shape = RoundedCornerShape(25.dp), colors = ButtonDefaults.buttonColors( @@ -286,3 +302,24 @@ fun SettingsScreen( } } } + +private fun previewDestinations(): Map Unit> = hashMapOf( + Routes.ABOUT to {}, + Routes.FAVORITES to {}, + Routes.NOTIFICATIONS_SETTING to {}, + Routes.NOTIFICATIONS_HOME to {}, + Routes.LEGAL to {}, + Routes.PRIVACY to {}, + Routes.SUPPORT to {}, + Routes.PROFILE to {} +) + +@Preview(showBackground = true) +@Composable +private fun SettingsScreenPreview() = EateryPreview { + SettingsScreenContent( + destinations = previewDestinations(), + onLogout = {} + ) +} + diff --git a/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/SupportScreen.kt b/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/SupportScreen.kt index 6c3c966e..45e046e9 100644 --- a/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/SupportScreen.kt +++ b/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/SupportScreen.kt @@ -44,6 +44,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.core.net.toUri @@ -60,17 +61,48 @@ import com.cornellappdev.android.eatery.ui.theme.GrayFive import com.cornellappdev.android.eatery.ui.theme.GraySix import com.cornellappdev.android.eatery.ui.theme.GrayZero import com.cornellappdev.android.eatery.ui.viewmodels.SupportViewModel +import com.cornellappdev.android.eatery.ui.viewmodels.state.ReportUiState +import com.cornellappdev.android.eatery.util.EateryPreview import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class) @Composable fun SupportScreen(supportViewModel: SupportViewModel = hiltViewModel()) { - val coroutineScope = rememberCoroutineScope() val context = LocalContext.current val reportingIssueSubject = stringResource(R.string.support_reporting_issue_subject) val orderFoodSubject = stringResource(R.string.support_order_food_subject) val emailChooserTitle = stringResource(R.string.support_email_chooser_title) val reportState by supportViewModel.reportState.collectAsStateWithLifecycle() + + SupportScreenContent( + reportState = reportState, + clearReportState = supportViewModel::clearReportState, + sendReport = supportViewModel::sendReport, + onSendSupportEmail = { + val email = Intent(Intent.ACTION_SENDTO) + email.data = + "mailto:team@cornellappdev.com?subject=${Uri.encode(reportingIssueSubject)}".toUri() + context.startActivity(Intent.createChooser(email, emailChooserTitle)) + }, + onSendOrderFoodEmail = { + val email = Intent(Intent.ACTION_SENDTO) + email.data = + "mailto:dining@cornell.edu?subject=${Uri.encode(orderFoodSubject)}".toUri() + context.startActivity(Intent.createChooser(email, emailChooserTitle)) + } + ) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun SupportScreenContent( + reportState: ReportUiState, + clearReportState: () -> Unit, + sendReport: (String, String) -> Unit, + onSendSupportEmail: () -> Unit, + onSendOrderFoodEmail: () -> Unit, +) { + val coroutineScope = rememberCoroutineScope() val modalBottomSheetState = rememberModalBottomSheetState( skipPartiallyExpanded = true, ) @@ -80,7 +112,7 @@ fun SupportScreen(supportViewModel: SupportViewModel = hiltViewModel()) { if (showReportSheet) { ModalBottomSheet( onDismissRequest = { - supportViewModel.clearReportState() + clearReportState() showReportSheet = false }, sheetState = modalBottomSheetState, @@ -96,9 +128,9 @@ fun SupportScreen(supportViewModel: SupportViewModel = hiltViewModel()) { eateryId = null, reportState = reportState, sendReport = { issue, report, _ -> - supportViewModel.sendReport(issue, report) + sendReport(issue, report) }, - clearReportState = supportViewModel::clearReportState, + clearReportState = clearReportState, ) { coroutineScope.launch { modalBottomSheetState.hide() @@ -163,10 +195,7 @@ fun SupportScreen(supportViewModel: SupportViewModel = hiltViewModel()) { } TextButton(modifier = Modifier.align(Alignment.CenterHorizontally), onClick = { - val email = Intent(Intent.ACTION_SENDTO) - email.data = - "mailto:team@cornellappdev.com?subject=${Uri.encode(reportingIssueSubject)}".toUri() - context.startActivity(Intent.createChooser(email, emailChooserTitle)) + onSendSupportEmail() }) { Text( text = stringResource(R.string.support_email_us), @@ -233,11 +262,7 @@ fun SupportScreen(supportViewModel: SupportViewModel = hiltViewModel()) { } } ) { - val email = Intent(Intent.ACTION_SENDTO) - email.data = - "mailto:dining@cornell.edu?subject=${Uri.encode(orderFoodSubject)}".toUri() - - context.startActivity(Intent.createChooser(email, emailChooserTitle)) + onSendOrderFoodEmail() } } } @@ -324,3 +349,16 @@ fun FAQCreation( } SettingsLineSeparator() } + +@Preview(showBackground = true) +@Composable +private fun SupportScreenPreview() = EateryPreview { + SupportScreenContent( + reportState = ReportUiState.Idle, + clearReportState = {}, + sendReport = { _, _ -> }, + onSendSupportEmail = {}, + onSendOrderFoodEmail = {} + ) +} + diff --git a/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/UpcomingMenuScreen.kt b/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/UpcomingMenuScreen.kt index b2fa9e8f..a48e2db5 100644 --- a/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/UpcomingMenuScreen.kt +++ b/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/UpcomingMenuScreen.kt @@ -37,6 +37,7 @@ import androidx.compose.ui.graphics.Color.Companion.White import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel @@ -53,9 +54,12 @@ import com.cornellappdev.android.eatery.ui.components.upcoming.UpcomingLoadingIt import com.cornellappdev.android.eatery.ui.components.upcoming.UpcomingLoadingItem.Companion.CreateUpcomingLoadingItem import com.cornellappdev.android.eatery.ui.theme.EateryBlue import com.cornellappdev.android.eatery.ui.theme.EateryBlueTypography +import com.cornellappdev.android.eatery.ui.viewmodels.UpcomingMenusViewState import com.cornellappdev.android.eatery.ui.viewmodels.UpcomingViewModel import com.cornellappdev.android.eatery.ui.viewmodels.state.EateryApiResponse import com.cornellappdev.android.eatery.util.AppStorePopupRepository +import com.cornellappdev.android.eatery.util.EateryPreview +import com.cornellappdev.android.eatery.util.PreviewData import com.cornellappdev.android.eatery.util.appStorePopupRepository import com.valentinilk.shimmer.ShimmerBounds import com.valentinilk.shimmer.rememberShimmer @@ -73,12 +77,42 @@ fun UpcomingMenuScreen( upcomingViewModel: UpcomingViewModel = hiltViewModel(), appStorePopupRepository: AppStorePopupRepository = appStorePopupRepository(), onEateryClick: (Int) -> Unit, +) { + val viewState = upcomingViewModel.viewStateFlow.collectAsStateWithLifecycle().value + + UpcomingMenuScreenContent( + viewState = viewState, + upcomingMenuFilters = upcomingViewModel.upcomingMenuFilters, + onMealFilterChanged = upcomingViewModel::onMealFilterChanged, + onToggleFilterClicked = upcomingViewModel::onToggleFilterClicked, + onResetFiltersClicked = upcomingViewModel::onResetFiltersClicked, + onSelectDayOffset = upcomingViewModel::selectDayOffset, + onPingEateries = upcomingViewModel::pingEateries, + onEateryClick = onEateryClick, + onEateryCardContract = { appStorePopupRepository.requestRatingPopup() }, + ) +} + +@OptIn( + ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class, + ExperimentalAnimationApi::class +) +@Composable +private fun UpcomingMenuScreenContent( + viewState: UpcomingMenusViewState, + upcomingMenuFilters: List, + onMealFilterChanged: (MealFilter) -> Unit, + onToggleFilterClicked: (Filter) -> Unit, + onResetFiltersClicked: () -> Unit, + onSelectDayOffset: (Int) -> Unit, + onPingEateries: () -> Unit, + onEateryClick: (Int) -> Unit, + onEateryCardContract: () -> Unit, ) { val modalBottomSheetState = rememberModalBottomSheetState( skipPartiallyExpanded = true ) var showMealBottomSheet by rememberSaveable { mutableStateOf(false) } - val viewState = upcomingViewModel.viewStateFlow.collectAsStateWithLifecycle().value val coroutineScope = rememberCoroutineScope() val shimmer = rememberShimmer(ShimmerBounds.View) @@ -97,7 +131,7 @@ fun UpcomingMenuScreen( MealBottomSheet( isVisible = modalBottomSheetState.isVisible, selectedMeal = viewState.mealFilter, - onSubmit = upcomingViewModel::onMealFilterChanged, + onSubmit = onMealFilterChanged, hide = { coroutineScope.launch { modalBottomSheetState.hide() @@ -119,12 +153,12 @@ fun UpcomingMenuScreen( innerListState = innerListState, isFirstVisible = isFirstVisible, selectedDay = viewState.selectedDay, - selectDayOffset = upcomingViewModel::selectDayOffset, + selectDayOffset = onSelectDayOffset, showModalBottomSheet = { showMealBottomSheet = true }, mealFilter = viewState.mealFilter, - upcomingMenuFilters = upcomingViewModel.upcomingMenuFilters, + upcomingMenuFilters = upcomingMenuFilters, selectedFilters = viewState.selectedFilters, - onToggleFilterClicked = upcomingViewModel::onToggleFilterClicked, + onToggleFilterClicked = onToggleFilterClicked, filterRowState = filterRowState ) { if (menus.data.isEmpty()) { @@ -138,7 +172,7 @@ fun UpcomingMenuScreen( modifier = Modifier.align( Alignment.Center ), - resetFilters = upcomingViewModel::onResetFiltersClicked + resetFilters = onResetFiltersClicked ) } Spacer(modifier = Modifier.height(12.dp)) @@ -158,9 +192,7 @@ fun UpcomingMenuScreen( selectEatery = { onEateryClick(eatery.eateryId) }, - onEateryCardContract = { - appStorePopupRepository.requestRatingPopup() - } + onEateryCardContract = onEateryCardContract ) Spacer(modifier = Modifier.height(12.dp)) } @@ -174,12 +206,12 @@ fun UpcomingMenuScreen( innerListState = innerListState, isFirstVisible = isFirstVisible, selectedDay = viewState.selectedDay, - selectDayOffset = upcomingViewModel::selectDayOffset, + selectDayOffset = onSelectDayOffset, showModalBottomSheet = { showMealBottomSheet = true }, mealFilter = viewState.mealFilter, - upcomingMenuFilters = upcomingViewModel.upcomingMenuFilters, + upcomingMenuFilters = upcomingMenuFilters, selectedFilters = viewState.selectedFilters, - onToggleFilterClicked = upcomingViewModel::onToggleFilterClicked, + onToggleFilterClicked = onToggleFilterClicked, filterRowState = filterRowState ) { items(UpcomingLoadingItem.upcomingItems) { item -> @@ -196,21 +228,21 @@ fun UpcomingMenuScreen( UpcomingMenuHeader(isFirstVisible) CalendarWeekSelector( selectedDay = viewState.selectedDay, - selectDayOffset = upcomingViewModel::selectDayOffset + selectDayOffset = onSelectDayOffset ) UpcomingFilterRow( showModalBottomSheet = { showMealBottomSheet = true }, mealFilter = viewState.mealFilter, - upcomingMenuFilters = upcomingViewModel.upcomingMenuFilters, + upcomingMenuFilters = upcomingMenuFilters, selectedFilters = viewState.selectedFilters, - onToggleFilterClicked = upcomingViewModel::onToggleFilterClicked, + onToggleFilterClicked = onToggleFilterClicked, filterRowState = filterRowState ) Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { - ErrorContent(onTryAgain = upcomingViewModel::pingEateries) + ErrorContent(onTryAgain = onPingEateries) } } } @@ -218,6 +250,58 @@ fun UpcomingMenuScreen( } } +@Preview(showBackground = true) +@Composable +private fun UpcomingMenuScreenPreview() = EateryPreview { + val previewState = PreviewData.upcomingMenuPreviewState() + UpcomingMenuScreenContent( + viewState = previewState.viewState, + upcomingMenuFilters = previewState.upcomingMenuFilters, + onMealFilterChanged = {}, + onToggleFilterClicked = {}, + onResetFiltersClicked = {}, + onSelectDayOffset = {}, + onPingEateries = {}, + onEateryClick = {}, + onEateryCardContract = {} + ) +} + +@Preview(showBackground = true) +@Composable +private fun UpcomingMenuScreenEmptyPreview() = EateryPreview { + val previewState = PreviewData.upcomingMenuEmptyPreviewState() + UpcomingMenuScreenContent( + viewState = previewState.viewState, + upcomingMenuFilters = previewState.upcomingMenuFilters, + onMealFilterChanged = {}, + onToggleFilterClicked = {}, + onResetFiltersClicked = {}, + onSelectDayOffset = {}, + onPingEateries = {}, + onEateryClick = {}, + onEateryCardContract = {} + ) +} + +@Preview(showBackground = true) +@Composable +private fun UpcomingMenuScreenErrorPreview() = EateryPreview { + val previewState = PreviewData.upcomingMenuErrorPreviewState() + UpcomingMenuScreenContent( + viewState = previewState.viewState, + upcomingMenuFilters = previewState.upcomingMenuFilters, + onMealFilterChanged = {}, + onToggleFilterClicked = {}, + onResetFiltersClicked = {}, + onSelectDayOffset = {}, + onPingEateries = {}, + onEateryClick = {}, + onEateryCardContract = {} + ) +} + + @Composable private fun UpcomingMenuShell( innerListState: LazyListState, diff --git a/app/src/main/java/com/cornellappdev/android/eatery/util/PreviewData.kt b/app/src/main/java/com/cornellappdev/android/eatery/util/PreviewData.kt index 7e2e52ce..2a654307 100644 --- a/app/src/main/java/com/cornellappdev/android/eatery/util/PreviewData.kt +++ b/app/src/main/java/com/cornellappdev/android/eatery/util/PreviewData.kt @@ -1,10 +1,41 @@ package com.cornellappdev.android.eatery.util import com.cornellappdev.android.eatery.data.models.Eatery +import com.cornellappdev.android.eatery.data.models.EateryStatus +import com.cornellappdev.android.eatery.data.models.Event +import com.cornellappdev.android.eatery.data.models.MenuCategory +import com.cornellappdev.android.eatery.data.models.MenuItem +import com.cornellappdev.android.eatery.ui.components.general.Filter +import com.cornellappdev.android.eatery.ui.components.general.MenuCategoryViewState +import com.cornellappdev.android.eatery.ui.components.general.MenuItemViewState +import com.cornellappdev.android.eatery.ui.components.upcoming.EateryHours +import com.cornellappdev.android.eatery.ui.components.upcoming.MenuCardViewState +import com.cornellappdev.android.eatery.ui.theme.Green +import com.cornellappdev.android.eatery.ui.theme.Orange +import com.cornellappdev.android.eatery.ui.viewmodels.EateriesSection +import com.cornellappdev.android.eatery.ui.viewmodels.UpcomingMenusViewState +import com.cornellappdev.android.eatery.ui.viewmodels.state.EateryApiResponse +import java.time.LocalDateTime import kotlin.random.Random object PreviewData { + data class CompareMenusPreviewState( + val eateries: List, + val events: List, + ) + + data class UpcomingMenuPreviewState( + val viewState: UpcomingMenusViewState, + val upcomingMenuFilters: List, + ) + + private val defaultUpcomingMenuFilters = listOf( + Filter.FromEateryFilter.North, + Filter.FromEateryFilter.Central, + Filter.FromEateryFilter.West + ) + fun mockEatery(id:Int = Random.nextInt()) = Eatery( id = id, @@ -12,4 +43,141 @@ object PreviewData { location = "Test Location", ) + fun compareMenusPreviewState(): CompareMenusPreviewState { + val okenshieldsEvent = mockEvent( + id = 1, + eateryId = 1, + items = listOf("Chicken Tikka Masala", "Roasted Vegetables") + ) + val beckerEvent = mockEvent( + id = 2, + eateryId = 2, + items = listOf("Baked Ziti", "Garden Salad") + ) + + return CompareMenusPreviewState( + eateries = listOf( + mockEatery(1).copy(name = "Okenshields", events = listOf(okenshieldsEvent)), + mockEatery(2).copy(name = "Becker House Dining Room", events = listOf(beckerEvent)) + ), + events = listOf(okenshieldsEvent, beckerEvent) + ) + } + + fun upcomingMenuPreviewState(): UpcomingMenuPreviewState { + return UpcomingMenuPreviewState( + viewState = UpcomingMenusViewState( + menus = EateryApiResponse.Success( + listOf( + EateriesSection( + header = "North", + menuCards = listOf( + MenuCardViewState( + eateryId = 1, + name = "Morrison Dining", + eateryHours = EateryHours( + startTime = "11:00 AM", + endTime = "2:30 PM" + ), + eateryStatus = EateryStatus( + statusText = "Open", + statusColor = Green + ), + menu = listOf( + MenuCategoryViewState( + category = "Lunch", + items = listOf( + MenuItemViewState( + item = MenuItem(name = "Chicken Tikka Masala"), + isFavorite = true + ), + MenuItemViewState( + item = MenuItem(name = "Roasted Vegetables"), + isFavorite = false + ) + ) + ) + ) + ) + ) + ), + EateriesSection( + header = "West", + menuCards = listOf( + MenuCardViewState( + eateryId = 2, + name = "Becker House Dining Room", + eateryHours = EateryHours( + startTime = "5:00 PM", + endTime = "8:00 PM" + ), + eateryStatus = EateryStatus( + statusText = "Closing Soon", + statusColor = Orange + ), + menu = listOf( + MenuCategoryViewState( + category = "Dinner", + items = listOf( + MenuItemViewState( + item = MenuItem(name = "Baked Ziti"), + isFavorite = false + ), + MenuItemViewState( + item = MenuItem(name = "Garden Salad"), + isFavorite = true + ) + ) + ) + ) + ) + ) + ) + ) + ) + ), + upcomingMenuFilters = defaultUpcomingMenuFilters + ) + } + + fun upcomingMenuEmptyPreviewState(): UpcomingMenuPreviewState { + return UpcomingMenuPreviewState( + viewState = UpcomingMenusViewState( + menus = EateryApiResponse.Success(emptyList()) + ), + upcomingMenuFilters = defaultUpcomingMenuFilters + ) + } + + fun upcomingMenuErrorPreviewState(): UpcomingMenuPreviewState { + return UpcomingMenuPreviewState( + viewState = UpcomingMenusViewState( + menus = EateryApiResponse.Error + ), + upcomingMenuFilters = defaultUpcomingMenuFilters + ) + } + + private fun mockEvent( + id: Int, + eateryId: Int, + items: List, + ): Event { + val start = LocalDateTime.now().minusMinutes(30) + val end = LocalDateTime.now().plusHours(1) + return Event( + id = id, + eateryId = eateryId, + type = "Lunch", + startTimestamp = start, + endTimestamp = end, + menu = mutableListOf( + MenuCategory( + name = "Entrees", + items = items.map { MenuItem(name = it) } + ) + ) + ) + } + } From e69822d19476449d876727d73902fffc03ae5255 Mon Sep 17 00:00:00 2001 From: Caleb Shim Date: Tue, 14 Apr 2026 18:55:28 -0400 Subject: [PATCH 2/5] Nit fixes --- .../eatery/ui/screens/CompareMenusScreen.kt | 2 +- .../eatery/ui/screens/ProfileScreen.kt | 4 +- .../eatery/ui/screens/SupportScreen.kt | 251 +++++++++--------- 3 files changed, 132 insertions(+), 125 deletions(-) diff --git a/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/CompareMenusScreen.kt b/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/CompareMenusScreen.kt index fafabf65..e030e46e 100644 --- a/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/CompareMenusScreen.kt +++ b/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/CompareMenusScreen.kt @@ -246,7 +246,7 @@ private fun CompareMenusScreenContent( events, onOpenSheet = openBottomSheet, onRequestRatingPopup = onRequestRatingPopup, - onEateryClick + onEateryClick = onEateryClick ) TitlePager(eateries, secondPagerState) } diff --git a/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/ProfileScreen.kt b/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/ProfileScreen.kt index 9f048445..05271242 100644 --- a/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/ProfileScreen.kt +++ b/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/ProfileScreen.kt @@ -86,7 +86,7 @@ private fun ProfileScreenContent( } } -@Preview(showBackground = true) +@Preview(name = "Profile - Login", showBackground = true) @Composable private fun ProfileScreenLoginPreview() = EateryPreview { ProfileScreenContent( @@ -106,7 +106,7 @@ private fun ProfileScreenLoginPreview() = EateryPreview { ) } -@Preview(showBackground = true) +@Preview(name = "Profile - Account", showBackground = true) @Composable private fun ProfileScreenAccountPreview() = EateryPreview { ProfileScreenContent( diff --git a/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/SupportScreen.kt b/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/SupportScreen.kt index 45e046e9..faffd825 100644 --- a/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/SupportScreen.kt +++ b/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/SupportScreen.kt @@ -79,20 +79,27 @@ fun SupportScreen(supportViewModel: SupportViewModel = hiltViewModel()) { clearReportState = supportViewModel::clearReportState, sendReport = supportViewModel::sendReport, onSendSupportEmail = { - val email = Intent(Intent.ACTION_SENDTO) - email.data = - "mailto:team@cornellappdev.com?subject=${Uri.encode(reportingIssueSubject)}".toUri() + val email = createMailToIntent( + recipient = "team@cornellappdev.com", + subject = reportingIssueSubject + ) context.startActivity(Intent.createChooser(email, emailChooserTitle)) }, onSendOrderFoodEmail = { - val email = Intent(Intent.ACTION_SENDTO) - email.data = - "mailto:dining@cornell.edu?subject=${Uri.encode(orderFoodSubject)}".toUri() + val email = createMailToIntent( + recipient = "dining@cornell.edu", + subject = orderFoodSubject + ) context.startActivity(Intent.createChooser(email, emailChooserTitle)) } ) } +private fun createMailToIntent(recipient: String, subject: String): Intent = + Intent(Intent.ACTION_SENDTO).apply { + data = "mailto:$recipient?subject=${Uri.encode(subject)}".toUri() + } + @OptIn(ExperimentalMaterial3Api::class) @Composable private fun SupportScreenContent( @@ -141,130 +148,130 @@ private fun SupportScreenContent( } } - Column( - modifier = Modifier - .padding(start = 16.dp, end = 16.dp) - .fillMaxSize() - .then(Modifier.statusBarsPadding()) - ) { - Text( - text = stringResource(R.string.support_title), - color = EateryBlue, - style = EateryBlueTypography.h2, - modifier = Modifier.padding(top = 7.dp) - ) - Text( - text = stringResource(R.string.support_description), - style = TextStyle(fontWeight = FontWeight.Medium, fontSize = 18.sp), - color = GraySix, - modifier = Modifier.padding(top = 7.dp, bottom = 24.dp) - ) + Column( + modifier = Modifier + .padding(start = 16.dp, end = 16.dp) + .fillMaxSize() + .then(Modifier.statusBarsPadding()) + ) { + Text( + text = stringResource(R.string.support_title), + color = EateryBlue, + style = EateryBlueTypography.h2, + modifier = Modifier.padding(top = 7.dp) + ) + Text( + text = stringResource(R.string.support_description), + style = TextStyle(fontWeight = FontWeight.Medium, fontSize = 18.sp), + color = GraySix, + modifier = Modifier.padding(top = 7.dp, bottom = 24.dp) + ) - Text( - text = stringResource(R.string.support_make_eatery_better), - color = Color.Black, - style = EateryBlueTypography.h4, - modifier = Modifier.padding(bottom = 12.dp) - ) - Text( - text = stringResource(R.string.support_make_eatery_better_description), - color = GrayFive, - style = EateryBlueTypography.subtitle2, - modifier = Modifier.padding(bottom = 12.dp) - ) - Button( - shape = RoundedCornerShape(24.dp), - modifier = Modifier - .height(48.dp) - .fillMaxWidth(), - onClick = { - issue = null - showReportSheet = true - }, - colors = ButtonDefaults.buttonColors( - containerColor = EateryBlue, - contentColor = Color.White - ) - ) { - Icon(imageVector = Icons.Default.Report, Icons.Default.Report.name) - Text( - text = stringResource(R.string.report_an_issue), - style = EateryBlueTypography.h5, - modifier = Modifier.padding(start = 8.dp) - ) - } + Text( + text = stringResource(R.string.support_make_eatery_better), + color = Color.Black, + style = EateryBlueTypography.h4, + modifier = Modifier.padding(bottom = 12.dp) + ) + Text( + text = stringResource(R.string.support_make_eatery_better_description), + color = GrayFive, + style = EateryBlueTypography.subtitle2, + modifier = Modifier.padding(bottom = 12.dp) + ) + Button( + shape = RoundedCornerShape(24.dp), + modifier = Modifier + .height(48.dp) + .fillMaxWidth(), + onClick = { + issue = null + showReportSheet = true + }, + colors = ButtonDefaults.buttonColors( + containerColor = EateryBlue, + contentColor = Color.White + ) + ) { + Icon(imageVector = Icons.Default.Report, Icons.Default.Report.name) + Text( + text = stringResource(R.string.report_an_issue), + style = EateryBlueTypography.h5, + modifier = Modifier.padding(start = 8.dp) + ) + } - TextButton(modifier = Modifier.align(Alignment.CenterHorizontally), onClick = { - onSendSupportEmail() - }) { - Text( - text = stringResource(R.string.support_email_us), - style = EateryBlueTypography.button, - color = EateryBlue - ) - Spacer(Modifier.size(ButtonDefaults.IconSpacing)) - Icon( - Icons.Outlined.ArrowOutward, - null, - tint = EateryBlue - ) - } + TextButton(modifier = Modifier.align(Alignment.CenterHorizontally), onClick = { + onSendSupportEmail() + }) { + Text( + text = stringResource(R.string.support_email_us), + style = EateryBlueTypography.button, + color = EateryBlue + ) + Spacer(Modifier.size(ButtonDefaults.IconSpacing)) + Icon( + Icons.Outlined.ArrowOutward, + null, + tint = EateryBlue + ) + } - Text( - text = stringResource(R.string.support_faqs_heading), - style = EateryBlueTypography.h4, - color = Color.Black, - modifier = Modifier.padding(top = 20.dp) - ) - FAQCreation( - title = stringResource(R.string.support_faq_wrong_menus_title), - dropdownText = stringResource(id = R.string.wrong_empty_menus), - action = { - ReportButton() - } - ) { - issue = Issue.ITEM - showReportSheet = true - } + Text( + text = stringResource(R.string.support_faqs_heading), + style = EateryBlueTypography.h4, + color = Color.Black, + modifier = Modifier.padding(top = 20.dp) + ) + FAQCreation( + title = stringResource(R.string.support_faq_wrong_menus_title), + dropdownText = stringResource(id = R.string.wrong_empty_menus), + action = { + ReportButton() + } + ) { + issue = Issue.ITEM + showReportSheet = true + } - FAQCreation( - title = stringResource(R.string.support_faq_closed_hours_title), - dropdownText = stringResource(id = R.string.eatery_closed_when_open), - action = { - ReportButton() - } - ) { - issue = Issue.HOURS - showReportSheet = true - } + FAQCreation( + title = stringResource(R.string.support_faq_closed_hours_title), + dropdownText = stringResource(id = R.string.eatery_closed_when_open), + action = { + ReportButton() + } + ) { + issue = Issue.HOURS + showReportSheet = true + } - FAQCreation( - title = stringResource(R.string.support_faq_wait_times_title), - dropdownText = stringResource(id = R.string.wait_time_longer), - action = { - ReportButton() - } - ) { - issue = Issue.WAIT_TIMES - showReportSheet = true - } + FAQCreation( + title = stringResource(R.string.support_faq_wait_times_title), + dropdownText = stringResource(id = R.string.wait_time_longer), + action = { + ReportButton() + } + ) { + issue = Issue.WAIT_TIMES + showReportSheet = true + } - FAQCreation( - title = stringResource(R.string.support_faq_order_title), - dropdownText = stringResource(id = R.string.order_on_eatery), - action = { - Row { - Text( - text = stringResource(R.string.support_faq_order_email_prompt), - style = EateryBlueTypography.subtitle2, - color = EateryBlue, - ) - } - } - ) { - onSendOrderFoodEmail() + FAQCreation( + title = stringResource(R.string.support_faq_order_title), + dropdownText = stringResource(id = R.string.order_on_eatery), + action = { + Row { + Text( + text = stringResource(R.string.support_faq_order_email_prompt), + style = EateryBlueTypography.subtitle2, + color = EateryBlue, + ) } } + ) { + onSendOrderFoodEmail() + } + } } @Composable From e9496d2581f06e1ee8d74b0a0474561099701176 Mon Sep 17 00:00:00 2001 From: Caleb Shim Date: Tue, 14 Apr 2026 18:58:44 -0400 Subject: [PATCH 3/5] Add HomeScreen preview --- .../android/eatery/ui/screens/HomeScreen.kt | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/HomeScreen.kt b/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/HomeScreen.kt index 757bbbb6..29e7b6ab 100644 --- a/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/HomeScreen.kt +++ b/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/HomeScreen.kt @@ -94,6 +94,7 @@ import com.cornellappdev.android.eatery.ui.viewmodels.HomeViewModel import com.cornellappdev.android.eatery.ui.viewmodels.state.EateryApiResponse import com.cornellappdev.android.eatery.util.EateryPreview import com.cornellappdev.android.eatery.util.LocationHandler +import com.cornellappdev.android.eatery.util.PreviewData import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.rememberMultiplePermissionsState import com.valentinilk.shimmer.ShimmerBounds @@ -502,6 +503,36 @@ private fun PreviewErrorContent() = EateryPreview { ErrorContent(onTryAgain = {}) } +@Preview(showBackground = true) +@Composable +private fun PreviewRegularContent() = EateryPreview { + val eateries = listOf( + PreviewData.mockEatery(1).copy(name = "Okenshields"), + PreviewData.mockEatery(2).copy(name = "Becker House Dining Room"), + PreviewData.mockEatery(3).copy(name = "Morrison Dining") + ) + val favorites = listOf(eateries.first()) + + LazyColumn(modifier = Modifier + .fillMaxSize() + .background(Color.White)) { + regularContent( + eateriesApiResponse = EateryApiResponse.Success(eateries), + selectedFilters = emptyList(), + favorites = favorites, + onFavoriteClick = { _, _ -> }, + onEateryClick = {}, + onResetFilters = {}, + lastFavorite = favorites.firstOrNull(), + onFavoriteExpand = {}, + isGridView = false, + onListClick = {}, + onGridClick = {}, + nearestEateries = eateries + ) + } +} + private fun LazyListScope.regularContent( eateriesApiResponse: EateryApiResponse.Success>, selectedFilters: List, From 03a40a47b3c7d420abd0abdab324b6a1bef4fe6a Mon Sep 17 00:00:00 2001 From: Caleb Shim Date: Wed, 15 Apr 2026 00:29:48 -0400 Subject: [PATCH 4/5] Extract strings --- .../android/eatery/ui/screens/SupportScreen.kt | 17 +++++++++++------ app/src/main/res/values/strings.xml | 3 +++ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/SupportScreen.kt b/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/SupportScreen.kt index faffd825..0f59616a 100644 --- a/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/SupportScreen.kt +++ b/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/SupportScreen.kt @@ -69,6 +69,9 @@ import kotlinx.coroutines.launch @Composable fun SupportScreen(supportViewModel: SupportViewModel = hiltViewModel()) { val context = LocalContext.current + val supportRecipient = stringResource(R.string.support_team_email_recipient) + val orderFoodRecipient = stringResource(R.string.support_order_food_email_recipient) + val mailToTemplate = stringResource(R.string.support_mailto_template) val reportingIssueSubject = stringResource(R.string.support_reporting_issue_subject) val orderFoodSubject = stringResource(R.string.support_order_food_subject) val emailChooserTitle = stringResource(R.string.support_email_chooser_title) @@ -80,24 +83,26 @@ fun SupportScreen(supportViewModel: SupportViewModel = hiltViewModel()) { sendReport = supportViewModel::sendReport, onSendSupportEmail = { val email = createMailToIntent( - recipient = "team@cornellappdev.com", - subject = reportingIssueSubject + recipient = supportRecipient, + subject = reportingIssueSubject, + mailToTemplate = mailToTemplate ) context.startActivity(Intent.createChooser(email, emailChooserTitle)) }, onSendOrderFoodEmail = { val email = createMailToIntent( - recipient = "dining@cornell.edu", - subject = orderFoodSubject + recipient = orderFoodRecipient, + subject = orderFoodSubject, + mailToTemplate = mailToTemplate ) context.startActivity(Intent.createChooser(email, emailChooserTitle)) } ) } -private fun createMailToIntent(recipient: String, subject: String): Intent = +private fun createMailToIntent(recipient: String, subject: String, mailToTemplate: String): Intent = Intent(Intent.ACTION_SENDTO).apply { - data = "mailto:$recipient?subject=${Uri.encode(subject)}".toUri() + data = mailToTemplate.format(recipient, Uri.encode(subject)).toUri() } @OptIn(ExperimentalMaterial3Api::class) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7dca51cd..b2b611f7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -74,6 +74,9 @@ Choose an Email client : Ordering Food on Eatery Eatery - Reporting an Issue + team@cornellappdev.com + dining@cornell.edu + mailto:%1$s?subject=%2$s Upcoming Menus Menus From ddfb6d18466becb76c3fdaa1d352a9db3b1f0370 Mon Sep 17 00:00:00 2001 From: Caleb Shim Date: Wed, 15 Apr 2026 00:42:49 -0400 Subject: [PATCH 5/5] Use spacers --- .../android/eatery/ui/screens/SupportScreen.kt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/SupportScreen.kt b/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/SupportScreen.kt index 94361eaf..c580cb7a 100644 --- a/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/SupportScreen.kt +++ b/app/src/main/java/com/cornellappdev/android/eatery/ui/screens/SupportScreen.kt @@ -159,31 +159,32 @@ private fun SupportScreenContent( .fillMaxSize() .then(Modifier.statusBarsPadding()) ) { + Spacer(modifier = Modifier.height(7.dp)) Text( text = stringResource(R.string.support_title), color = EateryBlue, style = EateryBlueTypography.h2, - modifier = Modifier.padding(top = 7.dp) ) + Spacer(modifier = Modifier.height(7.dp)) Text( text = stringResource(R.string.support_description), style = TextStyle(fontWeight = FontWeight.Medium, fontSize = 18.sp), color = GraySix, - modifier = Modifier.padding(top = 7.dp, bottom = 24.dp) ) + Spacer(modifier = Modifier.height(24.dp)) Text( text = stringResource(R.string.support_make_eatery_better), color = Color.Black, style = EateryBlueTypography.h4, - modifier = Modifier.padding(bottom = 12.dp) ) + Spacer(modifier = Modifier.height(12.dp)) Text( text = stringResource(R.string.support_make_eatery_better_description), color = GrayFive, style = EateryBlueTypography.subtitle2, - modifier = Modifier.padding(bottom = 12.dp) ) + Spacer(modifier = Modifier.height(12.dp)) Button( shape = RoundedCornerShape(24.dp), modifier = Modifier @@ -222,11 +223,11 @@ private fun SupportScreenContent( ) } + Spacer(modifier = Modifier.height(20.dp)) Text( text = stringResource(R.string.support_faqs_heading), style = EateryBlueTypography.h4, color = Color.Black, - modifier = Modifier.padding(top = 20.dp) ) FAQCreation( title = stringResource(R.string.support_faq_wrong_menus_title),