diff --git a/presentation/src/main/java/com/example/presentation/ui/InitScreen.kt b/presentation/src/main/java/com/example/presentation/ui/InitScreen.kt index 3a4dd1e..342b1b5 100644 --- a/presentation/src/main/java/com/example/presentation/ui/InitScreen.kt +++ b/presentation/src/main/java/com/example/presentation/ui/InitScreen.kt @@ -16,7 +16,6 @@ fun InitScreen( onCallStoreChanged: (String) -> Unit, onSplashScreenShowAble: (Boolean) -> Unit, navController: NavHostController, - searchText: String?, isFirstRun: Boolean, isOnboardingScreenShowAble: Boolean, onOnboardingScreenShowAble: (Boolean) -> Unit, @@ -29,7 +28,6 @@ fun InitScreen( onCallStoreChanged, onSplashScreenShowAble, navController, - searchText, mapViewModel ) diff --git a/presentation/src/main/java/com/example/presentation/ui/MainActivity.kt b/presentation/src/main/java/com/example/presentation/ui/MainActivity.kt index fa0c11c..d86de5d 100644 --- a/presentation/src/main/java/com/example/presentation/ui/MainActivity.kt +++ b/presentation/src/main/java/com/example/presentation/ui/MainActivity.kt @@ -24,7 +24,6 @@ import com.example.presentation.ui.map.MapViewModel import com.example.presentation.ui.navigation.Screen import com.example.presentation.ui.search.SearchScreen import com.example.presentation.ui.theme.Android_KCSTheme -import com.example.presentation.util.MainConstants.SEARCH_KEY import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.launch @@ -64,16 +63,10 @@ class MainActivity : ComponentActivity() { composable( route = Screen.Main.route ) { - val searchText = remember { - navController.previousBackStackEntry?.savedStateHandle?.get( - SEARCH_KEY - ) - } InitScreen( onCallStoreChanged, onSplashScreenShowAble, navController, - searchText, isFirstRun, isOnboardingScreenShowAble, onOnboardingScreenShowAble, diff --git a/presentation/src/main/java/com/example/presentation/ui/MainScreen.kt b/presentation/src/main/java/com/example/presentation/ui/MainScreen.kt index 4e1c14a..f76e486 100644 --- a/presentation/src/main/java/com/example/presentation/ui/MainScreen.kt +++ b/presentation/src/main/java/com/example/presentation/ui/MainScreen.kt @@ -10,6 +10,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavHostController import com.example.domain.model.map.ShowMoreCount @@ -22,12 +23,15 @@ import com.example.presentation.ui.map.MapViewModel import com.example.presentation.ui.map.NaverMapScreen import com.example.presentation.ui.map.call.StoreCallDialog import com.example.presentation.ui.map.filter.FilterComponent +import com.example.presentation.ui.map.filter.FilterViewModel import com.example.presentation.ui.map.list.StoreListBottomSheet import com.example.presentation.ui.map.reload.ReloadOrShowMoreButton import com.example.presentation.ui.map.summary.DimScreen import com.example.presentation.ui.map.summary.StoreSummaryBottomSheet +import com.example.presentation.ui.search.ReSearchButtonComponent import com.example.presentation.ui.search.StoreSearchComponent import com.example.presentation.util.MainConstants +import com.example.presentation.util.MainConstants.SEARCH_KEY import com.example.presentation.util.MainConstants.UN_MARKER import com.example.presentation.util.MapScreenType import com.naver.maps.map.compose.ExperimentalNaverMapApi @@ -38,8 +42,8 @@ fun MainScreen( onCallStoreChanged: (String) -> Unit, onSplashScreenShowAble: (Boolean) -> Unit, navController: NavHostController, - searchText: String?, - mapViewModel: MapViewModel + mapViewModel: MapViewModel, + filterViewModel : FilterViewModel = hiltViewModel() ) { val (clickedStoreInfo, onStoreInfoChanged) = remember { mutableStateOf( @@ -69,10 +73,6 @@ fun MainScreen( mutableStateOf(false) } - val (isKindFilterClicked, onKindFilterChanged) = remember { mutableStateOf(false) } - val (isGreatFilterClicked, onGreatFilterChanged) = remember { mutableStateOf(false) } - val (isSafeFilterClicked, onSafeFilterChanged) = remember { mutableStateOf(false) } - val (screenCoordinate, onScreenChanged) = remember { mutableStateOf( ScreenCoordinate( @@ -117,6 +117,8 @@ fun MainScreen( val (isReloadOrShowMoreShowAble, onReloadOrShowMoreChanged) = remember { mutableStateOf(false) } + val (isReSearchButtonClicked, onReSearchButtonChanged) = remember { mutableStateOf(false) } + val (isScreenCoordinateChanged, onGetNewScreenCoordinateChanged) = remember { mutableStateOf( false @@ -132,6 +134,14 @@ fun MainScreen( } val (isBackPressed, onBackPressedChanged) = remember { mutableStateOf(false) } + val isSearchTextExist = navController.previousBackStackEntry?.savedStateHandle?.contains( + SEARCH_KEY + ) ?: false + + val searchText = navController.previousBackStackEntry?.savedStateHandle?.get( + SEARCH_KEY + ) + NaverMapScreen( isMarkerClicked, onBottomSheetChanged, @@ -161,38 +171,45 @@ fun MainScreen( isBackPressed, onBackPressedChanged, mapViewModel, - navController + navController, + isReSearchButtonClicked ) if (isReloadOrShowMoreShowAble) { - ReloadOrShowMoreButton( - isMarkerClicked, - currentSummaryInfoHeight, - isMapGestured, - onShowMoreCountChanged, - onReloadButtonChanged, - onMarkerChanged, - onBottomSheetChanged, - isLoading, - showMoreCount, - mapViewModel - ) + if (isSearchTextExist) { + ReSearchButtonComponent( + isMarkerClicked, + currentSummaryInfoHeight, + isMapGestured, + onReSearchButtonChanged, + onMarkerChanged, + onBottomSheetChanged, + isLoading, + ) + } else { + ReloadOrShowMoreButton( + isMarkerClicked, + currentSummaryInfoHeight, + isMapGestured, + onShowMoreCountChanged, + onReloadButtonChanged, + onMarkerChanged, + onBottomSheetChanged, + isLoading, + showMoreCount, + mapViewModel + ) + } } StoreSearchComponent( searchText, onSearchComponentChanged, - onSearchTerminationButtonChanged + onSearchTerminationButtonChanged, + mapViewModel ) FilterComponent( - isKindFilterClicked, - onKindFilterChanged, - isGreatFilterClicked, - onGreatFilterChanged, - isSafeFilterClicked, - onSafeFilterChanged, - mapViewModel, onFilterStateChanged ) @@ -238,8 +255,20 @@ fun MainScreen( onCallDialogChanged(false) } + val mapCenterCoordinate by mapViewModel.mapCenterCoordinate.collectAsStateWithLifecycle() + if (isReSearchButtonClicked && isScreenCoordinateChanged) { + filterViewModel.updateIsFilteredMarker(false) + mapViewModel.searchStore( + mapCenterCoordinate.longitude, + mapCenterCoordinate.latitude, + searchText ?: "" + ) + onReSearchButtonChanged(false) + onGetNewScreenCoordinateChanged(false) + } + if ((isReloadButtonClicked && isScreenCoordinateChanged)) { - mapViewModel.updateIsFilteredMarker(false) + filterViewModel.updateIsFilteredMarker(false) onErrorToastChanged("") mapViewModel.getStoreDetail( nwLong = screenCoordinate.northWest.longitude, @@ -277,7 +306,7 @@ fun MainScreen( @Composable fun PressBack( mapViewModel: MapViewModel, - onBackPressedChanged: (Boolean) -> Unit + onBackPressedChanged: (Boolean) -> Unit, ) { val mapScreenType by mapViewModel.mapScreenType.collectAsStateWithLifecycle() val context = LocalContext.current @@ -285,6 +314,7 @@ fun PressBack( BackHandler { if (mapScreenType == MapScreenType.SEARCH) { + mapViewModel.updateIsSearchTerminated(true) onBackPressedChanged(true) mapViewModel.updateMapScreenType(MapScreenType.MAIN) } else { diff --git a/presentation/src/main/java/com/example/presentation/ui/map/MapViewModel.kt b/presentation/src/main/java/com/example/presentation/ui/map/MapViewModel.kt index 01a827d..f2f4cd2 100644 --- a/presentation/src/main/java/com/example/presentation/ui/map/MapViewModel.kt +++ b/presentation/src/main/java/com/example/presentation/ui/map/MapViewModel.kt @@ -13,10 +13,7 @@ import com.example.domain.util.Resource import com.example.presentation.model.Coordinate import com.example.presentation.model.LocationTrackingButton import com.example.presentation.util.MainConstants.FAIL_TO_LOAD_DATA -import com.example.presentation.util.MainConstants.GREAT_STORE import com.example.presentation.util.MainConstants.INITIALIZE_ABLE -import com.example.presentation.util.MainConstants.KIND_STORE -import com.example.presentation.util.MainConstants.SAFE_STORE import com.example.presentation.util.MapScreenType import com.example.presentation.util.UiState import com.naver.maps.geometry.LatLng @@ -38,8 +35,6 @@ class MapViewModel @Inject constructor( private val _ableToShowSplashScreen = MutableStateFlow(true) val ableToShowSplashScreen: StateFlow = _ableToShowSplashScreen - private val filterSet = mutableSetOf() - private val _storeDetailModelData = MutableStateFlow>>>(UiState.Loading) val storeDetailModelData: StateFlow>>> = @@ -80,9 +75,6 @@ class MapViewModel @Inject constructor( private val _isSearchTerminated = MutableStateFlow(false) val isSearchTerminated: StateFlow = _isSearchTerminated - private val _isFilteredMarker = MutableStateFlow(false) - val isFilteredMarker: StateFlow = _isFilteredMarker - fun showMoreStore(count: Int) { val newItem: List = when (val uiState = _storeDetailModelData.value) { is UiState.Success -> uiState.data.getOrNull(count) ?: emptyList() @@ -97,19 +89,6 @@ class MapViewModel @Inject constructor( _ableToShowSplashScreen.value = false } - fun getFilterSet(): Set { - return if (filterSet.isEmpty()) setOf(SAFE_STORE, GREAT_STORE, KIND_STORE) - else filterSet.toSet() - } - - fun updateFilterSet(certificationName: String, isClicked: Boolean) { - if (isClicked) { - filterSet.add(certificationName) - } else { - filterSet.remove(certificationName) - } - } - fun setLocationTrackingMode(): LocationTrackingButton { return if (isLocationPermissionGranted.value) { LocationTrackingButton.FOLLOW @@ -243,8 +222,4 @@ class MapViewModel @Inject constructor( fun updateIsSearchTerminated(isTerminated: Boolean) { _isSearchTerminated.value = isTerminated } - - fun updateIsFilteredMarker(isFilteredMarker: Boolean) { - _isFilteredMarker.value = isFilteredMarker - } } \ No newline at end of file diff --git a/presentation/src/main/java/com/example/presentation/ui/map/NaverMapScreen.kt b/presentation/src/main/java/com/example/presentation/ui/map/NaverMapScreen.kt index c409a2b..654b1e3 100644 --- a/presentation/src/main/java/com/example/presentation/ui/map/NaverMapScreen.kt +++ b/presentation/src/main/java/com/example/presentation/ui/map/NaverMapScreen.kt @@ -18,6 +18,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavHostController import com.example.domain.model.map.ShowMoreCount @@ -28,6 +29,7 @@ import com.example.presentation.model.LocationTrackingButton import com.example.presentation.model.ScreenCoordinate import com.example.presentation.model.StoreDetail import com.example.presentation.model.StoreType +import com.example.presentation.ui.map.filter.FilterViewModel import com.example.presentation.ui.map.location.CurrentLocationComponent import com.example.presentation.ui.map.marker.StoreMarker import com.example.presentation.ui.map.reload.setReloadButtonBottomPadding @@ -91,7 +93,9 @@ fun NaverMapScreen( isBackPressed: Boolean, onBackPressedChanged: (Boolean) -> Unit, mapViewModel: MapViewModel, - navController: NavHostController + navController: NavHostController, + isReSearchButtonClicked: Boolean, + filterViewModel: FilterViewModel = hiltViewModel() ) { val cameraPositionState = rememberCameraPositionState {} @@ -157,7 +161,7 @@ fun NaverMapScreen( if (isInitializationLocation && mapViewModel.ableToShowSplashScreen.value) { onSplashScreenShowAble(false) } - mapViewModel.updateIsFilteredMarker(true) + filterViewModel.updateIsFilteredMarker(true) onLoadingChanged(false) onCurrentMapChanged(false) onShowMoreCountChanged(ShowMoreCount(0, state.data.size)) @@ -187,11 +191,12 @@ fun NaverMapScreen( when (val state = searchStore) { is UiState.Loading -> { - // Todo : 검색 시 로딩 뷰 구현 + onLoadingChanged(true) } is UiState.Success -> { - mapViewModel.updateIsFilteredMarker(true) + onLoadingChanged(false) + filterViewModel.updateIsFilteredMarker(true) onCurrentMapChanged(false) onReloadOrShowMoreChanged(false) @@ -221,7 +226,8 @@ fun NaverMapScreen( } } - val isFilteredMarker by mapViewModel.isFilteredMarker.collectAsStateWithLifecycle() + val isFilteredMarker by filterViewModel.isFilteredMarker.collectAsStateWithLifecycle() + if (isFilteredMarker) { FilteredMarkers( mapViewModel, @@ -248,6 +254,15 @@ fun NaverMapScreen( onLocationButtonChanged(LocationTrackingButton.NO_FOLLOW) } } + if (isReSearchButtonClicked) { + mapViewModel.updateMapCenterCoordinate( + Coordinate( + cameraPositionState.position.target.latitude, + cameraPositionState.position.target.longitude + ) + ) + onGetNewScreenCoordinateChanged(true) + } if (isReloadButtonClicked) { GetScreenCoordinate(cameraPositionState, onScreenChanged) onGetNewScreenCoordinateChanged(true) @@ -260,7 +275,9 @@ fun NaverMapScreen( ) ) mapViewModel.updateMapZoomLevel(cameraPositionState.position.zoom) + mapViewModel.updateMapScreenType(MapScreenType.MAIN) navController.navigate(Screen.Search.route) + filterViewModel.updateAllFilterUnClicked() onSearchComponentChanged(false) } if (isBackPressed) { @@ -320,13 +337,14 @@ fun FilteredMarkers( onStoreInfoChanged: (StoreDetail) -> Unit, clickedMarkerId: Long, onMarkerChanged: (Long) -> Unit, + filterViewModel: FilterViewModel = hiltViewModel() ) { val storeDetailData by mapViewModel.flattenedStoreDetailList.collectAsStateWithLifecycle() storeDetailData.filter { info -> - mapViewModel.getFilterSet().intersect(info.certificationName.toSet()).isNotEmpty() + filterViewModel.getFilterSet().intersect(info.certificationName.toSet()).isNotEmpty() }.forEach { info -> val storeType = - when (mapViewModel.getFilterSet().intersect(info.certificationName.toSet()) + when (filterViewModel.getFilterSet().intersect(info.certificationName.toSet()) .last()) { KIND_STORE -> StoreType.KIND GREAT_STORE -> StoreType.GREAT @@ -496,7 +514,8 @@ private fun CheckSearchTerminationButtonClicked( onSearchTerminationButtonChanged: (Boolean) -> Unit, onReloadButtonChanged: (Boolean) -> Unit, mapCenterCoordinate: Coordinate, - mapZoomLevel: Double + mapZoomLevel: Double, + filterViewModel: FilterViewModel = hiltViewModel() ) { if (isSearchTerminationButtonClicked) { mapViewModel.updateMapCenterCoordinate( @@ -520,6 +539,7 @@ private fun CheckSearchTerminationButtonClicked( LaunchedEffect(key1 = isSearchTerminated) { if (isSearchTerminated) { + filterViewModel.updateAllFilterUnClicked() movePrevCamera(cameraPositionState, mapCenterCoordinate, mapZoomLevel) onReloadButtonChanged(true) mapViewModel.updateIsSearchTerminated(false) diff --git a/presentation/src/main/java/com/example/presentation/ui/map/filter/FilterComponent.kt b/presentation/src/main/java/com/example/presentation/ui/map/filter/FilterComponent.kt index 3d813f1..7e44de2 100644 --- a/presentation/src/main/java/com/example/presentation/ui/map/filter/FilterComponent.kt +++ b/presentation/src/main/java/com/example/presentation/ui/map/filter/FilterComponent.kt @@ -1,5 +1,6 @@ package com.example.presentation.ui.map.filter +import android.annotation.SuppressLint import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues @@ -27,8 +28,9 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight 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.example.presentation.model.StoreType -import com.example.presentation.ui.map.MapViewModel import com.example.presentation.ui.theme.Black import com.example.presentation.ui.theme.Blue import com.example.presentation.ui.theme.White @@ -36,44 +38,39 @@ import com.example.presentation.util.MainConstants.DEFAULT_MARGIN import com.example.presentation.util.MainConstants.SEARCH_TEXT_FIELD_HEIGHT import com.example.presentation.util.MainConstants.SEARCH_TEXT_FIELD_TOP_PADDING +@SuppressLint("StateFlowValueCalledInComposition") @Composable fun FilterComponent( - isKindFilterClicked: Boolean, - onKindFilterChanged: (Boolean) -> Unit, - isGreatFilterClicked: Boolean, - onGreatFilterChanged: (Boolean) -> Unit, - isSafeFilterClicked: Boolean, - onSafeFilterChanged: (Boolean) -> Unit, - mapViewModel: MapViewModel, onFilterStateChanged: (Boolean) -> Unit, + filterViewModel: FilterViewModel = hiltViewModel() ) { Row( modifier = Modifier .fillMaxHeight() .fillMaxWidth() - .padding(top = (SEARCH_TEXT_FIELD_HEIGHT + SEARCH_TEXT_FIELD_TOP_PADDING + 8).dp, start = DEFAULT_MARGIN.dp), + .padding( + top = (SEARCH_TEXT_FIELD_HEIGHT + SEARCH_TEXT_FIELD_TOP_PADDING + 8).dp, + start = DEFAULT_MARGIN.dp + ), verticalAlignment = Alignment.Top ) { + val isKindClicked = filterViewModel.isKindFilterClicked.collectAsStateWithLifecycle() + val isGreatClicked = filterViewModel.isGreatFilterClicked.collectAsStateWithLifecycle() + val isSafeClicked = filterViewModel.isSafeFilterClicked.collectAsStateWithLifecycle() FilterButton( storeType = StoreType.KIND, - isKindFilterClicked, - onKindFilterChanged, - mapViewModel, + isKindClicked.value, onFilterStateChanged ) FilterButton( storeType = StoreType.GREAT, - isGreatFilterClicked, - onGreatFilterChanged, - mapViewModel, + isGreatClicked.value, onFilterStateChanged ) FilterButton( storeType = StoreType.SAFE, - isSafeFilterClicked, - onSafeFilterChanged, - mapViewModel, + isSafeClicked.value, onFilterStateChanged ) } @@ -84,16 +81,19 @@ fun FilterComponent( fun FilterButton( storeType: StoreType, isFilterClicked: Boolean, - onFilterChanged: (Boolean) -> Unit, - mapViewModel: MapViewModel, - onFilterStateChanged: (Boolean) -> Unit + onFilterStateChanged: (Boolean) -> Unit, + filterViewModel: FilterViewModel = hiltViewModel(), ) { val certificationName = stringResource(id = storeType.storeTypeName).replace(" ", "") CompositionLocalProvider(LocalMinimumInteractiveComponentEnforcement provides false) { Button( onClick = { - mapViewModel.updateFilterSet(certificationName, isFilterClicked.not()) - onFilterChanged(isFilterClicked.not()) + filterViewModel.updateFilterSet(certificationName, isFilterClicked.not()) + when (storeType) { + StoreType.KIND -> filterViewModel.updateKindFilterClicked() + StoreType.SAFE -> filterViewModel.updateSafeFilterClicked() + StoreType.GREAT -> filterViewModel.updateGreatFilterClicked() + } onFilterStateChanged(true) }, modifier = Modifier diff --git a/presentation/src/main/java/com/example/presentation/ui/map/filter/FilterViewModel.kt b/presentation/src/main/java/com/example/presentation/ui/map/filter/FilterViewModel.kt new file mode 100644 index 0000000..4c1403c --- /dev/null +++ b/presentation/src/main/java/com/example/presentation/ui/map/filter/FilterViewModel.kt @@ -0,0 +1,67 @@ +package com.example.presentation.ui.map.filter + +import androidx.lifecycle.ViewModel +import com.example.presentation.util.MainConstants +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import javax.inject.Inject + + +@HiltViewModel +class FilterViewModel @Inject constructor() : ViewModel() { + + private val _isFilteredMarker = MutableStateFlow(false) + val isFilteredMarker: StateFlow = _isFilteredMarker + + private val filterSet = mutableSetOf() + + private val _isKindFilterClicked = MutableStateFlow(false) + val isKindFilterClicked: StateFlow get() = _isKindFilterClicked + + private val _isGreatFilterClicked = MutableStateFlow(false) + val isGreatFilterClicked: StateFlow get() = _isGreatFilterClicked + + private val _isSafeFilterClicked = MutableStateFlow(false) + val isSafeFilterClicked: StateFlow get() = _isSafeFilterClicked + + fun getFilterSet(): Set { + return if (filterSet.isEmpty()) setOf( + MainConstants.SAFE_STORE, + MainConstants.GREAT_STORE, + MainConstants.KIND_STORE + ) + else filterSet.toSet() + } + + fun updateFilterSet(certificationName: String, isClicked: Boolean) { + if (isClicked) { + filterSet.add(certificationName) + } else { + filterSet.remove(certificationName) + } + } + + fun updateAllFilterUnClicked() { + filterSet.clear() + _isKindFilterClicked.value = false + _isGreatFilterClicked.value = false + _isSafeFilterClicked.value = false + } + + fun updateKindFilterClicked() { + _isKindFilterClicked.value = _isKindFilterClicked.value.not() + } + + fun updateGreatFilterClicked() { + _isGreatFilterClicked.value = _isGreatFilterClicked.value.not() + } + + fun updateSafeFilterClicked() { + _isSafeFilterClicked.value = _isSafeFilterClicked.value.not() + } + + fun updateIsFilteredMarker(isFilteredMarker: Boolean) { + _isFilteredMarker.value = isFilteredMarker + } +} \ No newline at end of file diff --git a/presentation/src/main/java/com/example/presentation/ui/map/list/StoreListBottomSheet.kt b/presentation/src/main/java/com/example/presentation/ui/map/list/StoreListBottomSheet.kt index 008dae5..f7abd6f 100644 --- a/presentation/src/main/java/com/example/presentation/ui/map/list/StoreListBottomSheet.kt +++ b/presentation/src/main/java/com/example/presentation/ui/map/list/StoreListBottomSheet.kt @@ -25,6 +25,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight 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.example.presentation.R import com.example.presentation.mapper.toUiModel @@ -33,6 +34,7 @@ import com.example.presentation.model.StoreDetail import com.example.presentation.ui.component.BottomSheetDragHandle import com.example.presentation.ui.component.EmptyScreen import com.example.presentation.ui.map.MapViewModel +import com.example.presentation.ui.map.filter.FilterViewModel import com.example.presentation.ui.theme.Black import com.example.presentation.ui.theme.DarkGray import com.example.presentation.ui.theme.SemiLightGray @@ -145,7 +147,8 @@ fun StoreListContent( onStoreInfoChanged: (StoreDetail) -> Unit, onMarkerChanged: (Long) -> Unit, onListItemChanged: (Boolean) -> Unit, - viewModel: MapViewModel + viewModel: MapViewModel, + filterViewModel: FilterViewModel = hiltViewModel() ) { val storeDetailData by viewModel.flattenedStoreDetailList.collectAsStateWithLifecycle() @@ -160,7 +163,7 @@ fun StoreListContent( LazyColumn { itemsIndexed( storeDetailData.filter { - viewModel.getFilterSet().intersect(it.certificationName.toSet()) + filterViewModel.getFilterSet().intersect(it.certificationName.toSet()) .isNotEmpty() } ) { _, item -> diff --git a/presentation/src/main/java/com/example/presentation/ui/search/ReSearchComponent.kt b/presentation/src/main/java/com/example/presentation/ui/search/ReSearchComponent.kt new file mode 100644 index 0000000..f2efd94 --- /dev/null +++ b/presentation/src/main/java/com/example/presentation/ui/search/ReSearchComponent.kt @@ -0,0 +1,124 @@ +package com.example.presentation.ui.search + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +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.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.presentation.R +import com.example.presentation.ui.map.reload.LoadingAnimation +import com.example.presentation.ui.map.reload.setReloadButtonBottomPadding +import com.example.presentation.ui.theme.Black +import com.example.presentation.ui.theme.Blue +import com.example.presentation.ui.theme.White +import com.example.presentation.util.MainConstants.UN_MARKER + +@Composable +fun ReSearchButtonComponent( + isMarkerClicked: Boolean, + currentSummaryInfoHeight: Dp, + isMapGestured: Boolean, + onReSearchButtonChanged: (Boolean) -> Unit, + onMarkerChanged: (Long) -> Unit, + onBottomSheetChanged: (Boolean) -> Unit, + isLoading: Boolean, +) { + Column( + modifier = Modifier + .fillMaxHeight() + .fillMaxWidth() + .padding( + bottom = setReloadButtonBottomPadding( + isMarkerClicked, + currentSummaryInfoHeight + ) + ), + verticalArrangement = Arrangement.Bottom, + horizontalAlignment = Alignment.CenterHorizontally + ) { + if (isMapGestured) { + ReSearchButton( + onReSearchButtonChanged, + onMarkerChanged, + onBottomSheetChanged, + isLoading, + ) + } + } +} + + +@Composable +fun ReSearchButton( + onReSearchButtonChanged: (Boolean) -> Unit, + onMarkerChanged: (Long) -> Unit, + onBottomSheetChanged: (Boolean) -> Unit, + isLoading: Boolean, +) { + Button( + onClick = { + onMarkerChanged(UN_MARKER) + onBottomSheetChanged(false) + onReSearchButtonChanged(true) + }, + modifier = Modifier + .size(width = 145.dp, height = 35.dp), + contentPadding = PaddingValues(horizontal = 0.dp, vertical = 0.dp), + colors = ButtonDefaults.buttonColors( + containerColor = White, + contentColor = Black + ), + shape = RoundedCornerShape(30.dp), + elevation = ButtonDefaults.buttonElevation(defaultElevation = 4.dp) + ) { + Row( + modifier = Modifier.align(Alignment.CenterVertically) + ) { + if (isLoading) { + LoadingAnimation() + } else { + ReSearchByKeyword() + } + } + } +} + +@Composable +private fun ReSearchByKeyword() { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = ImageVector.vectorResource(id = R.drawable.reload), + tint = Blue, + contentDescription = "ReSearch", + modifier = Modifier.size(13.dp) + ) + Spacer(modifier = Modifier.width(6.dp)) + Text( + text = stringResource(R.string.search_by_keyword_on_current_map), + fontSize = 11.sp, + fontWeight = FontWeight.Medium + ) + } +} \ No newline at end of file diff --git a/presentation/src/main/java/com/example/presentation/ui/search/SearchScreen.kt b/presentation/src/main/java/com/example/presentation/ui/search/SearchScreen.kt index 6202a54..b9609a8 100644 --- a/presentation/src/main/java/com/example/presentation/ui/search/SearchScreen.kt +++ b/presentation/src/main/java/com/example/presentation/ui/search/SearchScreen.kt @@ -54,6 +54,7 @@ import com.example.domain.model.search.SearchWord import com.example.presentation.R import com.example.presentation.ui.component.EmptyScreen import com.example.presentation.ui.map.MapViewModel +import com.example.presentation.ui.map.filter.FilterViewModel import com.example.presentation.ui.navigation.Screen import com.example.presentation.ui.theme.Black import com.example.presentation.ui.theme.DarkGray @@ -83,7 +84,7 @@ fun SearchScreen( ) { SearchAppBar(navController, mapViewModel) SearchDivider(6) - RecentSearchList(onDeleteAllDialogVisibleChanged) + RecentSearchList(onDeleteAllDialogVisibleChanged, navController) if (isDeleteAllDialogVisible) { DeleteAllDialog(onDeleteAllDialogVisibleChanged) @@ -132,6 +133,7 @@ fun SearchTextField( navController: NavHostController, mapViewModel: MapViewModel, searchViewModel: SearchViewModel = hiltViewModel(), + filterViewModel: FilterViewModel = hiltViewModel() ) { var searchText by remember { mutableStateOf("") } @@ -195,10 +197,12 @@ fun SearchTextField( textStyle = TextStyle(color = Black, fontSize = 14.sp, fontWeight = Medium), modifier = Modifier.focusRequester(focusRequester), keyboardActions = KeyboardActions(onDone = { + filterViewModel.updateAllFilterUnClicked() + if (searchText.isNotBlank()) { insertSearchWord(searchText, searchViewModel) - mapViewModel.updateIsFilteredMarker(false) + filterViewModel.updateIsFilteredMarker(false) mapViewModel.searchStore( mapCenterCoordinate.longitude, mapCenterCoordinate.latitude, @@ -235,13 +239,16 @@ fun insertSearchWord(keyword: String, viewModel: SearchViewModel) { @Composable private fun BackArrow(navController: NavHostController, mapViewModel: MapViewModel) { + val mapScreenType by mapViewModel.mapScreenType.collectAsStateWithLifecycle() Image( imageVector = ImageVector.vectorResource(id = R.drawable.arrow), contentDescription = "Arrow", modifier = Modifier .size(18.dp) .clickable { - mapViewModel.updateIsSearchTerminated(true) + if (mapScreenType == MapScreenType.MAIN) { + mapViewModel.updateIsSearchTerminated(true) + } navController.popBackStack() } ) @@ -250,10 +257,11 @@ private fun BackArrow(navController: NavHostController, mapViewModel: MapViewMod @Composable fun RecentSearchList( onDeleteAllDialogVisibleChanged: (Boolean) -> Unit, - viewModel: SearchViewModel = hiltViewModel() + navController: NavHostController, + searchViewModel: SearchViewModel = hiltViewModel() ) { - viewModel.getRecentSearchWord() - val recentSearchWords by viewModel.recentSearchWords.collectAsStateWithLifecycle() + searchViewModel.getRecentSearchWord() + val recentSearchWords by searchViewModel.recentSearchWords.collectAsStateWithLifecycle() TitleText(recentSearchWords, onDeleteAllDialogVisibleChanged) @@ -263,7 +271,7 @@ fun RecentSearchList( } else { LazyColumn { itemsIndexed(recentSearchWords) { idx, item -> - RecentSearchItem(item) + RecentSearchItem(item, navController) SearchDivider(1) } } @@ -300,12 +308,37 @@ fun TitleText(exampleItems: List, onDeleteAllDialogVisibleChanged: ( } @Composable -fun RecentSearchItem(searchWord: SearchWord, viewModel: SearchViewModel = hiltViewModel()) { +fun RecentSearchItem( + searchWord: SearchWord, + navController: NavHostController, + mapViewModel: MapViewModel = hiltViewModel(), + searchViewModel: SearchViewModel = hiltViewModel(), + filterViewModel: FilterViewModel = hiltViewModel() +) { + val mapCenterCoordinate by mapViewModel.mapCenterCoordinate.collectAsStateWithLifecycle() + val mapScreenType by mapViewModel.mapScreenType.collectAsStateWithLifecycle() Row( modifier = Modifier .fillMaxWidth() .height(53.dp) - .padding(horizontal = DEFAULT_MARGIN.dp), + .padding(horizontal = DEFAULT_MARGIN.dp) + .clickable { + // TODO : 최근 검색어 클릭 시 검색 구현 + filterViewModel.updateAllFilterUnClicked() + insertSearchWord(searchWord.keyword, searchViewModel) + + filterViewModel.updateIsFilteredMarker(false) + mapViewModel.searchStore( + mapCenterCoordinate.longitude, + mapCenterCoordinate.latitude, + searchWord.keyword + ) + navController.currentBackStackEntry?.savedStateHandle?.set( + key = SEARCH_KEY, + value = searchWord.keyword + ) + mapViewModel.updateMapScreenType(MapScreenType.SEARCH) + }, verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween ) { @@ -336,7 +369,7 @@ fun RecentSearchItem(searchWord: SearchWord, viewModel: SearchViewModel = hiltVi modifier = Modifier .size(16.dp) .clickable { - viewModel.deleteSearchWordById(searchWord.id) + searchViewModel.deleteSearchWordById(searchWord.id) } ) } diff --git a/presentation/src/main/java/com/example/presentation/ui/search/StoreSearchComponent.kt b/presentation/src/main/java/com/example/presentation/ui/search/StoreSearchComponent.kt index 322edfb..3642742 100644 --- a/presentation/src/main/java/com/example/presentation/ui/search/StoreSearchComponent.kt +++ b/presentation/src/main/java/com/example/presentation/ui/search/StoreSearchComponent.kt @@ -15,6 +15,7 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape 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.draw.shadow @@ -26,19 +27,25 @@ import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.example.presentation.R +import com.example.presentation.ui.map.MapViewModel import com.example.presentation.ui.theme.MediumGray import com.example.presentation.ui.theme.White import com.example.presentation.util.MainConstants.DEFAULT_MARGIN import com.example.presentation.util.MainConstants.SEARCH_TEXT_FIELD_HEIGHT import com.example.presentation.util.MainConstants.SEARCH_TEXT_FIELD_TOP_PADDING +import com.example.presentation.util.MapScreenType @Composable fun StoreSearchComponent( searchText: String?, onSearchComponentChanged: (Boolean) -> Unit, - onSearchTerminationButtonChanged: (Boolean) -> Unit + onSearchTerminationButtonChanged: (Boolean) -> Unit, + mapViewModel: MapViewModel ) { + val mapScreenType by mapViewModel.mapScreenType.collectAsStateWithLifecycle() + Row( modifier = Modifier .padding( @@ -61,11 +68,11 @@ fun StoreSearchComponent( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween ) { - if (searchText.isNullOrBlank()) { + if (mapScreenType == MapScreenType.MAIN) { SearchPlaceHolderText(stringResource(R.string.search_placeholder_text), MediumGray) SearchSuffixImage(R.drawable.search) } else { - SearchPlaceHolderText(searchText, Black) + SearchPlaceHolderText(searchText ?: "", Black) SearchSuffixImage( R.drawable.delete, onSearchTerminationButtonChanged diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index 7cd01fc..1476884 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -11,6 +11,7 @@ 클립보드에 복사되었어요. tel:%s 현 지도에서 검색 + 현재 키워드로 재검색 영업 중 영업 종료 휴무일