diff --git a/.gitignore b/.gitignore index 9f17c67..4029285 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,7 @@ google-services.json # Android Profiling *.hprof + +# OS Generated Files +.DS_Store +**/.DS_Store \ No newline at end of file diff --git a/app/.DS_Store b/app/.DS_Store deleted file mode 100644 index a049e3c..0000000 Binary files a/app/.DS_Store and /dev/null differ diff --git a/app/src/main/java/com/cornellappdev/transit/ui/components/home/AddFavoritesButton.kt b/app/src/main/java/com/cornellappdev/transit/ui/components/home/AddFavoritesButton.kt index f2f1072..59b49f6 100644 --- a/app/src/main/java/com/cornellappdev/transit/ui/components/home/AddFavoritesButton.kt +++ b/app/src/main/java/com/cornellappdev/transit/ui/components/home/AddFavoritesButton.kt @@ -1,6 +1,7 @@ package com.cornellappdev.transit.ui.components.home import android.R.attr.text +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -35,7 +36,7 @@ fun AddFavoritesButton( ), modifier = modifier .fillMaxWidth() - .height(40.dp) + .height(40.dp), ) { Icon( diff --git a/app/src/main/java/com/cornellappdev/transit/ui/components/home/EcosystemBottomSheetContent.kt b/app/src/main/java/com/cornellappdev/transit/ui/components/home/EcosystemBottomSheetContent.kt index 316db37..1015ac1 100644 --- a/app/src/main/java/com/cornellappdev/transit/ui/components/home/EcosystemBottomSheetContent.kt +++ b/app/src/main/java/com/cornellappdev/transit/ui/components/home/EcosystemBottomSheetContent.kt @@ -1,5 +1,6 @@ package com.cornellappdev.transit.ui.components.home +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row @@ -12,8 +13,12 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.AnnotatedString @@ -22,6 +27,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.cornellappdev.transit.R import com.cornellappdev.transit.models.Place import com.cornellappdev.transit.models.ecosystem.DayOperatingHours @@ -29,6 +35,7 @@ import com.cornellappdev.transit.models.ecosystem.DetailedEcosystemPlace import com.cornellappdev.transit.models.ecosystem.Eatery import com.cornellappdev.transit.models.ecosystem.StaticPlaces import com.cornellappdev.transit.networking.ApiResponse +import com.cornellappdev.transit.ui.theme.FavoritesDividerGray import com.cornellappdev.transit.ui.theme.robotoFamily import com.cornellappdev.transit.ui.viewmodels.FilterState import com.cornellappdev.transit.ui.viewmodels.HomeViewModel @@ -43,6 +50,7 @@ import com.cornellappdev.transit.util.ecosystem.toPlace * @param staticPlaces Collection of all places to populate filters with * @param navigateToPlace Function called to navigate to route options */ +@OptIn(ExperimentalMaterial3Api::class) @Composable fun EcosystemBottomSheetContent( filters: List, @@ -54,7 +62,10 @@ fun EcosystemBottomSheetContent( navigateToPlace: (Place) -> Unit, onDetailsClick: (DetailedEcosystemPlace) -> Unit, onFavoriteStarClick: (Place) -> Unit, + showFilterSheet: Boolean, + onFilterSheetShow: () -> Unit, onAddFavoritesClick: () -> Unit, + homeViewModel: HomeViewModel = hiltViewModel(), ) { Column(modifier = modifier) { Row( @@ -76,7 +87,7 @@ fun EcosystemBottomSheetContent( ) } - LazyRow { + LazyRow(modifier = Modifier.padding(bottom = 12.dp)) { items(filters) { BottomSheetFilterItem( imageResId = it.iconId, @@ -95,9 +106,35 @@ fun EcosystemBottomSheetContent( navigateToPlace = navigateToPlace, onDetailsClick = onDetailsClick, onFavoriteStarClick = onFavoriteStarClick, + onFilterButtonClick = onFilterSheetShow, onAddFavoritesClick = onAddFavoritesClick ) } + + val selectedFilters by homeViewModel.selectedFavoritesFilters.collectAsStateWithLifecycle() + + if (showFilterSheet) { + ModalBottomSheet( + onDismissRequest = { + homeViewModel.cancelFavoritesFilters() + }, + dragHandle = null + ) { + FavoritesFilterBottomSheet( + onCancelClicked = { + homeViewModel.cancelFavoritesFilters() + }, + onApplyClicked = { + homeViewModel.applyFavoritesFilters() + }, + filters = homeViewModel.favoritesFilterList, + selectedFilters = selectedFilters, + onFilterToggle = { filter -> + homeViewModel.toggleFavoritesFilter(filter) + } + ) + } + } } @Composable @@ -109,43 +146,74 @@ private fun BottomSheetFilteredContent( navigateToPlace: (Place) -> Unit, onDetailsClick: (DetailedEcosystemPlace) -> Unit, onFavoriteStarClick: (Place) -> Unit, - onAddFavoritesClick: () -> Unit + onAddFavoritesClick: () -> Unit, + onFilterButtonClick: () -> Unit ) { - LazyColumn( - contentPadding = PaddingValues(start = 24.dp, end = 24.dp, top = 20.dp, bottom = 90.dp), - modifier = Modifier.fillMaxSize() - ) { - when (currentFilter) { - FilterState.FAVORITES -> { - favoriteList(favorites, navigateToPlace, onAddFavoritesClick) + val appliedFilters by homeViewModel.appliedFavoritesFilters.collectAsStateWithLifecycle() + Column { + if (currentFilter == FilterState.FAVORITES) { + Column(modifier = Modifier.padding(horizontal = 12.dp)) { + HorizontalDivider( + modifier = Modifier.padding(vertical = 8.dp), + color = FavoritesDividerGray + ) + FilterRow( + selectedFilters = appliedFilters, + onFilterClick = onFilterButtonClick, + onRemoveFilter = { filter -> homeViewModel.removeAppliedFilter(filter) } + ) + if (appliedFilters.isNotEmpty()) { + Spacer(modifier = Modifier.height(8.dp)) + } } + } + val isFilterBarHidden = currentFilter == FilterState.FAVORITES && appliedFilters.isEmpty() + LazyColumn( + contentPadding = PaddingValues( + start = 12.dp, + end = 12.dp, + top = if (isFilterBarHidden) 0.dp else 8.dp, + bottom = 120.dp // Makes bottom content visible with padding at the end + ), + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.spacedBy(20.dp) + ) { + when (currentFilter) { + FilterState.FAVORITES -> { + favoriteList( + favorites, + navigateToPlace, + onAddFavoritesClick + ) + } - FilterState.PRINTERS -> { - printerList(staticPlaces, navigateToPlace) - } + FilterState.PRINTERS -> { + printerList(staticPlaces, navigateToPlace) + } - FilterState.GYMS -> { - gymList(staticPlaces, navigateToPlace) - } + FilterState.GYMS -> { + gymList(staticPlaces, navigateToPlace) + } - FilterState.EATERIES -> { - eateryList( - eateriesApiResponse = staticPlaces.eateries, - onDetailsClick = onDetailsClick, - favorites = favorites, - onFavoriteStarClick = onFavoriteStarClick, - operatingHoursToString = homeViewModel::isOpenAnnotatedStringFromOperatingHours - ) - } + FilterState.EATERIES -> { + eateryList( + eateriesApiResponse = staticPlaces.eateries, + onDetailsClick = onDetailsClick, + favorites = favorites, + onFavoriteStarClick = onFavoriteStarClick, + operatingHoursToString = homeViewModel::isOpenAnnotatedStringFromOperatingHours + ) + } - FilterState.LIBRARIES -> { - libraryList( - staticPlaces, - navigateToPlace, - onDetailsClick, - favorites, - onFavoriteStarClick, - ) + FilterState.LIBRARIES -> { + libraryList( + staticPlaces, + navigateToPlace, + onDetailsClick, + favorites, + onFavoriteStarClick, + ) + } } } } @@ -160,8 +228,8 @@ private fun LazyListScope.favoriteList( onAddFavoritesClick: () -> Unit ) { item { + Spacer(modifier = Modifier.height(8.dp)) AddFavoritesButton(onAddFavoritesClick = onAddFavoritesClick) - Spacer(Modifier.height(20.dp)) } items(favorites.toList()) { BottomSheetLocationCard( @@ -170,7 +238,6 @@ private fun LazyListScope.favoriteList( ) { //TODO: Eatery } - Spacer(Modifier.height(10.dp)) } } @@ -196,7 +263,6 @@ private fun LazyListScope.gymList( ) { //TODO: Eatery } - Spacer(Modifier.height(10.dp)) } } @@ -271,7 +337,6 @@ private fun LazyListScope.eateryList( ) { onDetailsClick(it) } - Spacer(Modifier.height(10.dp)) } } } @@ -307,7 +372,6 @@ private fun LazyListScope.libraryList( ) { navigateToDetails(it) } - Spacer(Modifier.height(10.dp)) } } } @@ -337,6 +401,8 @@ private fun PreviewEcosystemBottomSheet() { navigateToPlace = {}, onDetailsClick = {}, onFavoriteStarClick = {}, - onAddFavoritesClick = {} + onAddFavoritesClick = {}, + showFilterSheet = true, + onFilterSheetShow = {} ) } \ No newline at end of file diff --git a/app/src/main/java/com/cornellappdev/transit/ui/components/home/FavoritesFilterBottomSheet.kt b/app/src/main/java/com/cornellappdev/transit/ui/components/home/FavoritesFilterBottomSheet.kt new file mode 100644 index 0000000..df9f350 --- /dev/null +++ b/app/src/main/java/com/cornellappdev/transit/ui/components/home/FavoritesFilterBottomSheet.kt @@ -0,0 +1,145 @@ +package com.cornellappdev.transit.ui.components.home + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontStyle +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.transit.ui.theme.DetailsHeaderGray +import com.cornellappdev.transit.ui.theme.SecondaryText +import com.cornellappdev.transit.ui.theme.TransitBlue +import com.cornellappdev.transit.ui.theme.robotoFamily +import com.cornellappdev.transit.ui.viewmodels.FavoritesFilterSheetState + +@Composable +fun FavoritesFilterBottomSheet( + onCancelClicked: () -> Unit, + onApplyClicked: () -> Unit, + filters: List, + selectedFilters: Set, + onFilterToggle: (FavoritesFilterSheetState) -> Unit, +) { + Column( + modifier = Modifier + .fillMaxWidth() + .background(color = DetailsHeaderGray) + .padding(top = 24.dp, bottom = 16.dp, start = 24.dp, end = 24.dp), + verticalArrangement = Arrangement.spacedBy(20.dp) + ) { + Row( + modifier = Modifier + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "Location Type", + fontFamily = robotoFamily, + fontStyle = FontStyle.Normal, + fontWeight = FontWeight.SemiBold, + fontSize = 18.sp, + color = SecondaryText + ) + } + + LazyVerticalGrid( + columns = GridCells.Fixed(3), + horizontalArrangement = Arrangement.spacedBy(12.dp), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + items(filters) { + FavoritesFilterSheetItem( + iconId = it.iconId, + label = it.label, + isActive = it in selectedFilters, + itemOnClick = { onFilterToggle(it) } + ) + } + } + + // Footer with Cancel and Apply buttons + Row( + modifier = Modifier + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + FooterButton( + onClick = onCancelClicked, + text = "Cancel", + modifier = Modifier.weight(0.5f) + ) + Spacer(modifier = Modifier.width(16.dp)) + FooterButton(onClick = onApplyClicked, text = "Apply", modifier = Modifier.weight(0.5f)) + } + } +} + +@Composable +private fun FooterButton( + onClick: () -> Unit, + text: String, + modifier: Modifier = Modifier +) { + val isCancel = text == "Cancel" + Button( + onClick = onClick, + colors = ButtonDefaults.buttonColors( + containerColor = if (isCancel) Color.White else TransitBlue, + contentColor = if (isCancel) TransitBlue else Color.White + ), + shape = RoundedCornerShape(16.dp), + border = if (isCancel) BorderStroke(1.dp, TransitBlue) else null, + modifier = modifier.height(40.dp) + ) { + Text( + text = text, + style = TextStyle(letterSpacing = 0.sp), + fontFamily = robotoFamily, + fontStyle = FontStyle.Normal, + fontSize = 16.sp, + fontWeight = FontWeight.SemiBold + ) + } +} + +@Preview(showBackground = true) +@Composable +private fun FavoritesFilterBottomSheetPreview() { + FavoritesFilterBottomSheet( + onCancelClicked = {}, + onApplyClicked = {}, + filters = listOf( + FavoritesFilterSheetState.EATERIES, + FavoritesFilterSheetState.OTHER, + FavoritesFilterSheetState.LIBRARIES, + FavoritesFilterSheetState.PRINTERS, + FavoritesFilterSheetState.GYMS + ), + selectedFilters = setOf( + FavoritesFilterSheetState.EATERIES, + FavoritesFilterSheetState.LIBRARIES + ), + onFilterToggle = {} + ) +} diff --git a/app/src/main/java/com/cornellappdev/transit/ui/components/home/FavoritesFilterSheetItem.kt b/app/src/main/java/com/cornellappdev/transit/ui/components/home/FavoritesFilterSheetItem.kt new file mode 100644 index 0000000..c8a201e --- /dev/null +++ b/app/src/main/java/com/cornellappdev/transit/ui/components/home/FavoritesFilterSheetItem.kt @@ -0,0 +1,107 @@ +package com.cornellappdev.transit.ui.components.home + +import androidx.annotation.DrawableRes +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +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 com.cornellappdev.transit.R +import com.cornellappdev.transit.ui.theme.MetadataGray +import com.cornellappdev.transit.ui.theme.MutedTransitBlue +import com.cornellappdev.transit.ui.theme.TransitBlue +import com.cornellappdev.transit.ui.theme.robotoFamily + +/** + * Card for each filter on home bottom sheet + * @param iconId The icon for the item + * @param label The label for the item + * @param isActive Whether the filter is selected + */ +@Composable +fun FavoritesFilterSheetItem( + @DrawableRes iconId: Int, + label: String, + isActive: Boolean, + itemOnClick: () -> Unit, +) { + Column( + modifier = Modifier + .fillMaxWidth() + .height(90.dp) + .border( + width = 1.dp, + color = if (isActive) TransitBlue else MetadataGray, + shape = RoundedCornerShape(8.dp) + ) + .background( + color = if (isActive) MutedTransitBlue + else Color.Transparent, + shape = RoundedCornerShape(8.dp) + ) + .clickable(onClick = itemOnClick) + .padding(vertical = 16.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Icon( + painterResource(id = iconId), + contentDescription = label, + modifier = Modifier + .size(34.dp), + tint = if (isActive) TransitBlue else Color.Unspecified + ) + + Spacer(modifier = Modifier.height(8.dp)) + + Text( + label, + color = if (isActive) TransitBlue else MetadataGray, + textAlign = TextAlign.Center, + fontSize = 14.sp, + fontWeight = FontWeight.SemiBold, + fontFamily = robotoFamily, + style = TextStyle(letterSpacing = 0.sp), + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } +} + +@Preview(showBackground = true) +@Composable +private fun ActiveFavoritesFilterSheetItemPreview() { + FavoritesFilterSheetItem( + iconId = R.drawable.eatery_filter_icon, + label = "Eateries", + isActive = true + ) {} +} + +@Preview(showBackground = true) +@Composable +private fun InactiveFavoritesFilterSheetItemPreview() { + FavoritesFilterSheetItem( + iconId = R.drawable.eatery_filter_icon, + label = "Eateries", + isActive = false + ) {} +} diff --git a/app/src/main/java/com/cornellappdev/transit/ui/components/home/FilterButton.kt b/app/src/main/java/com/cornellappdev/transit/ui/components/home/FilterButton.kt new file mode 100644 index 0000000..fc143e7 --- /dev/null +++ b/app/src/main/java/com/cornellappdev/transit/ui/components/home/FilterButton.kt @@ -0,0 +1,60 @@ +package com.cornellappdev.transit.ui.components.home + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontStyle +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.transit.R +import com.cornellappdev.transit.ui.theme.SecondaryText +import com.cornellappdev.transit.ui.theme.robotoFamily + +@Composable +fun FilterButton( + onFilterClick: () -> Unit, + modifier: Modifier = Modifier, +) { + Button( + onClick = onFilterClick, + colors = ButtonDefaults.buttonColors( + containerColor = Color.Transparent, + contentColor = SecondaryText + ), + contentPadding = PaddingValues(horizontal = 8.dp), + modifier = modifier, + ) { + Icon( + painter = painterResource(R.drawable.filter_icon), + contentDescription = "Filter Icon", + modifier = modifier.size(20.dp) + ) + + Spacer(modifier = Modifier.width(4.dp)) + + Text( + text = "Filter", + fontSize = 16.sp, + fontFamily = robotoFamily, + fontStyle = FontStyle.Normal, + fontWeight = FontWeight.Normal, + ) + } +} + +@Preview(showBackground = true) +@Composable +private fun FilterButtonPreview() { + FilterButton(onFilterClick = {}) +} \ No newline at end of file diff --git a/app/src/main/java/com/cornellappdev/transit/ui/components/home/FilterRow.kt b/app/src/main/java/com/cornellappdev/transit/ui/components/home/FilterRow.kt new file mode 100644 index 0000000..f35d048 --- /dev/null +++ b/app/src/main/java/com/cornellappdev/transit/ui/components/home/FilterRow.kt @@ -0,0 +1,115 @@ +package com.cornellappdev.transit.ui.components.home + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Close +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +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.transit.ui.theme.TransitBlue +import com.cornellappdev.transit.ui.theme.robotoFamily +import com.cornellappdev.transit.ui.viewmodels.FavoritesFilterSheetState + +@OptIn(ExperimentalLayoutApi::class) +@Composable +fun FilterRow( + selectedFilters: Set, + onFilterClick: () -> Unit, + onRemoveFilter: (FavoritesFilterSheetState) -> Unit, + modifier: Modifier = Modifier +) { + FlowRow( + modifier = modifier, + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterVertically), + ) { + FilterButton( + onFilterClick = onFilterClick, + modifier = Modifier.align(Alignment.CenterVertically) + ) + + // Selected filter labels + if (selectedFilters.isNotEmpty()) { + selectedFilters.forEach { filter -> + FilterLabel( + text = filter.label, + onRemove = { onRemoveFilter(filter) }, + modifier = Modifier.align(Alignment.CenterVertically) + ) + } + } + } +} + +@Composable +private fun FilterLabel( + text: String, + onRemove: () -> Unit, + modifier: Modifier = Modifier +) { + Surface( + modifier = modifier, + shape = RoundedCornerShape(40.dp), + border = BorderStroke(1.dp, TransitBlue), + color = Color.Transparent + ) { + Row( + modifier = Modifier.padding(start = 14.dp, top = 10.dp, bottom = 10.dp, end = 8.dp), + horizontalArrangement = Arrangement.spacedBy(4.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = text, + fontSize = 16.sp, + fontWeight = FontWeight.Normal, + fontFamily = robotoFamily, + color = TransitBlue + ) + IconButton( + onClick = onRemove, + modifier = Modifier.size(20.dp) + ) { + Icon( + imageVector = Icons.Default.Close, + tint = TransitBlue, + contentDescription = "Remove filter", + modifier = Modifier.size(20.dp) + ) + } + } + + } +} + +@Preview(showBackground = true) +@Composable +private fun FilterRowPreview() { + FilterRow( + selectedFilters = setOf(FavoritesFilterSheetState.EATERIES), + onFilterClick = {}, + onRemoveFilter = {} + ) + +} + +@Preview(showBackground = true) +@Composable +private fun FilterLabelPreview() { + FilterLabel("Eateries", {}) +} \ No newline at end of file diff --git a/app/src/main/java/com/cornellappdev/transit/ui/components/home/RoundedImagePlaceCard.kt b/app/src/main/java/com/cornellappdev/transit/ui/components/home/RoundedImagePlaceCard.kt index 9fa4ab0..d659ba7 100644 --- a/app/src/main/java/com/cornellappdev/transit/ui/components/home/RoundedImagePlaceCard.kt +++ b/app/src/main/java/com/cornellappdev/transit/ui/components/home/RoundedImagePlaceCard.kt @@ -43,7 +43,6 @@ fun RoundedImagePlaceCard( ) { Column( modifier = Modifier - .padding(horizontal = 24.dp, vertical = 10.dp) .clickable { onClick() } ) { Column( diff --git a/app/src/main/java/com/cornellappdev/transit/ui/screens/HomeScreen.kt b/app/src/main/java/com/cornellappdev/transit/ui/screens/HomeScreen.kt index fa59363..ac1c5b0 100644 --- a/app/src/main/java/com/cornellappdev/transit/ui/screens/HomeScreen.kt +++ b/app/src/main/java/com/cornellappdev/transit/ui/screens/HomeScreen.kt @@ -39,7 +39,6 @@ import androidx.compose.material3.rememberBottomSheetScaffoldState import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -170,24 +169,27 @@ fun HomeScreen( rememberBottomSheetScaffoldState(filterSheetState) // Main search bar flow - val searchBarValue = homeViewModel.searchBarUiState.collectAsState().value + val searchBarValue = homeViewModel.searchBarUiState.collectAsStateWithLifecycle().value // Favorited locations - val favorites = favoritesViewModel.favoritesStops.collectAsState().value + val favorites = favoritesViewModel.favoritesStops.collectAsStateWithLifecycle().value // Add search bar - val addSearchBarValue = homeViewModel.addSearchQuery.collectAsState().value + val addSearchBarValue = homeViewModel.addSearchQuery.collectAsStateWithLifecycle().value // Add search bar query response - val placeQueryResponse = homeViewModel.placeQueryFlow.collectAsState().value + val placeQueryResponse = homeViewModel.placeQueryFlow.collectAsStateWithLifecycle().value - val filterStateValue = homeViewModel.filterState.collectAsState().value + val filterStateValue = homeViewModel.filterState.collectAsStateWithLifecycle().value - val staticPlaces = homeViewModel.staticPlacesFlow.collectAsState().value + val staticPlaces = homeViewModel.staticPlacesFlow.collectAsStateWithLifecycle().value // Main search bar active/inactive var searchActive by remember { mutableStateOf(false) } + // Favorite filter bottom sheet state + val showFilterSheet by homeViewModel.showFilterSheet.collectAsStateWithLifecycle() + // Intercept clicks outside of search bar and disable search @Composable fun Modifier.onTapDisableSearch(): Modifier { @@ -339,8 +341,9 @@ fun HomeScreen( onFavoriteStarClick = favoritesViewModel::toggleFavorite, onAddFavoritesClick = { homeViewModel.toggleAddFavoritesSheet(true) - } - + }, + showFilterSheet = showFilterSheet, + onFilterSheetShow = { homeViewModel.openFilterSheet() } ) } } diff --git a/app/src/main/java/com/cornellappdev/transit/ui/theme/Color.kt b/app/src/main/java/com/cornellappdev/transit/ui/theme/Color.kt index 7230a12..7f49b9f 100644 --- a/app/src/main/java/com/cornellappdev/transit/ui/theme/Color.kt +++ b/app/src/main/java/com/cornellappdev/transit/ui/theme/Color.kt @@ -11,6 +11,7 @@ val PurpleGray40 = Color(0xFF625b71) val Pink40 = Color(0xFF7D5260) val TransitBlue = Color(0xff079ddc) +val MutedTransitBlue = Color(0xFFD6F3FF) val PrimaryText = Color(0xff212121) val TextButtonGray = Color(0xff6A737D) val SecondaryText = Color(0xff616161) @@ -25,4 +26,6 @@ val DetailsDividerGray = Color(0xffc6c6c8) val FavoritesYellow = Color(0xFFFEC50E) +val FavoritesDividerGray = Color(0xffDADADA) + val Gray05 = Color(0xFF586069) \ No newline at end of file diff --git a/app/src/main/java/com/cornellappdev/transit/ui/viewmodels/FavoritesFilterSheetState.kt b/app/src/main/java/com/cornellappdev/transit/ui/viewmodels/FavoritesFilterSheetState.kt new file mode 100644 index 0000000..c66c3fc --- /dev/null +++ b/app/src/main/java/com/cornellappdev/transit/ui/viewmodels/FavoritesFilterSheetState.kt @@ -0,0 +1,11 @@ +package com.cornellappdev.transit.ui.viewmodels + +import androidx.annotation.DrawableRes +import com.cornellappdev.transit.R +enum class FavoritesFilterSheetState(@DrawableRes val iconId: Int, val label: String) { + PRINTERS(iconId = R.drawable.printer_filter_icon, label = "Printers"), + GYMS(iconId = R.drawable.gym_filter_icon, label = "Gyms"), + EATERIES(iconId = R.drawable.eatery_filter_icon, label = "Eateries"), + LIBRARIES(iconId = R.drawable.library_filter_icon, label = "Libraries"), + OTHER(iconId = R.drawable.other_filter_icon, label = "Other"), +} \ No newline at end of file diff --git a/app/src/main/java/com/cornellappdev/transit/ui/viewmodels/FilterState.kt b/app/src/main/java/com/cornellappdev/transit/ui/viewmodels/FilterState.kt index 2ab1fef..2482fd0 100644 --- a/app/src/main/java/com/cornellappdev/transit/ui/viewmodels/FilterState.kt +++ b/app/src/main/java/com/cornellappdev/transit/ui/viewmodels/FilterState.kt @@ -11,5 +11,5 @@ enum class FilterState(@DrawableRes val iconId: Int, val label: String) { PRINTERS(iconId = R.drawable.printer_icon, label = "Printers"), GYMS(iconId = R.drawable.gym_icon, label = "Gyms"), EATERIES(iconId = R.drawable.eatery_icon, label = "Eateries"), - LIBRARIES(iconId = R.drawable.library_icon, label = "Libraries") + LIBRARIES(iconId = R.drawable.library_icon, label = "Libraries"), } \ No newline at end of file diff --git a/app/src/main/java/com/cornellappdev/transit/ui/viewmodels/HomeViewModel.kt b/app/src/main/java/com/cornellappdev/transit/ui/viewmodels/HomeViewModel.kt index 3b9aaea..5d034b9 100644 --- a/app/src/main/java/com/cornellappdev/transit/ui/viewmodels/HomeViewModel.kt +++ b/app/src/main/java/com/cornellappdev/transit/ui/viewmodels/HomeViewModel.kt @@ -90,6 +90,13 @@ class HomeViewModel @Inject constructor( val filterState: MutableStateFlow = MutableStateFlow(FilterState.FAVORITES) + private val _showFilterSheet = MutableStateFlow(false) + val showFilterSheet: StateFlow = _showFilterSheet.asStateFlow() + + fun toggleFilterSheet(show: Boolean) { + _showFilterSheet.value = show + } + val staticPlacesFlow = combine( routeRepository.printerFlow, @@ -121,6 +128,55 @@ class HomeViewModel @Inject constructor( _showAddFavoritesSheet.value = show } + val favoritesFilterList = listOf( + FavoritesFilterSheetState.GYMS, + FavoritesFilterSheetState.EATERIES, + FavoritesFilterSheetState.LIBRARIES, + FavoritesFilterSheetState.PRINTERS, + FavoritesFilterSheetState.OTHER + ) + + private val _selectedFavoritesFilters = + MutableStateFlow>(emptySet()) + val selectedFavoritesFilters: StateFlow> = + _selectedFavoritesFilters.asStateFlow() + + private val _appliedFavoritesFilters = + MutableStateFlow>(emptySet()) + val appliedFavoritesFilters: StateFlow> = + _appliedFavoritesFilters.asStateFlow() + + fun toggleFavoritesFilter(filter: FavoritesFilterSheetState) { + _selectedFavoritesFilters.value = if (filter in _selectedFavoritesFilters.value) { + _selectedFavoritesFilters.value - filter + } else { + _selectedFavoritesFilters.value + filter + } + } + + fun applyFavoritesFilters() { + // Save the current selection as applied filters + _appliedFavoritesFilters.value = _selectedFavoritesFilters.value + toggleFilterSheet(false) + } + + fun removeAppliedFilter(filter: FavoritesFilterSheetState) { + _selectedFavoritesFilters.value = _selectedFavoritesFilters.value - filter + _appliedFavoritesFilters.value = _appliedFavoritesFilters.value - filter + } + + fun cancelFavoritesFilters() { + // Restore the previously applied filters + _selectedFavoritesFilters.value = _appliedFavoritesFilters.value + toggleFilterSheet(false) + } + + fun openFilterSheet() { + // Initialize selected filters with currently applied filters + _selectedFavoritesFilters.value = _appliedFavoritesFilters.value + toggleFilterSheet(true) + } + init { userPreferenceRepository.favoritesFlow.onEach { diff --git a/app/src/main/res/drawable/eatery_filter_icon.xml b/app/src/main/res/drawable/eatery_filter_icon.xml new file mode 100644 index 0000000..7be303e --- /dev/null +++ b/app/src/main/res/drawable/eatery_filter_icon.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/filter_icon.xml b/app/src/main/res/drawable/filter_icon.xml new file mode 100644 index 0000000..1ba42db --- /dev/null +++ b/app/src/main/res/drawable/filter_icon.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/gym_filter_icon.xml b/app/src/main/res/drawable/gym_filter_icon.xml new file mode 100644 index 0000000..23cae57 --- /dev/null +++ b/app/src/main/res/drawable/gym_filter_icon.xml @@ -0,0 +1,14 @@ + + + + + + diff --git a/app/src/main/res/drawable/library_filter_icon.xml b/app/src/main/res/drawable/library_filter_icon.xml new file mode 100644 index 0000000..8100a5d --- /dev/null +++ b/app/src/main/res/drawable/library_filter_icon.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/other_filter_icon.xml b/app/src/main/res/drawable/other_filter_icon.xml new file mode 100644 index 0000000..b5a1b30 --- /dev/null +++ b/app/src/main/res/drawable/other_filter_icon.xml @@ -0,0 +1,13 @@ + + + diff --git a/app/src/main/res/drawable/printer_filter_icon.xml b/app/src/main/res/drawable/printer_filter_icon.xml new file mode 100644 index 0000000..092db40 --- /dev/null +++ b/app/src/main/res/drawable/printer_filter_icon.xml @@ -0,0 +1,12 @@ + + + +