diff --git a/app/src/main/java/com/eatssu/android/presentation/goodprice/GoodPriceMapActivity.kt b/app/src/main/java/com/eatssu/android/presentation/goodprice/GoodPriceMapActivity.kt index 5e865826d..768a7dc82 100644 --- a/app/src/main/java/com/eatssu/android/presentation/goodprice/GoodPriceMapActivity.kt +++ b/app/src/main/java/com/eatssu/android/presentation/goodprice/GoodPriceMapActivity.kt @@ -5,6 +5,7 @@ import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import com.eatssu.android.analytics.ProvideAnalyticsTracker +import com.eatssu.android.presentation.map.MapExternalNavigator import com.eatssu.common.analytics.AnalyticsTracker import com.eatssu.design_system.theme.EatssuTheme import dagger.hilt.android.AndroidEntryPoint @@ -19,6 +20,9 @@ class GoodPriceMapActivity : ComponentActivity() { @Inject lateinit var analyticsTracker: AnalyticsTracker + @Inject + lateinit var mapExternalNavigator: MapExternalNavigator + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() @@ -27,7 +31,7 @@ class GoodPriceMapActivity : ComponentActivity() { // 애널리틱스 트래커 및 EatssuTheme 적용 ProvideAnalyticsTracker(analyticsTracker) { EatssuTheme { - GoodPriceMapRoute() + GoodPriceMapRoute(mapExternalNavigator = mapExternalNavigator) } } } diff --git a/app/src/main/java/com/eatssu/android/presentation/goodprice/GoodPriceMapScreen.kt b/app/src/main/java/com/eatssu/android/presentation/goodprice/GoodPriceMapScreen.kt index 276ad5432..6606278aa 100644 --- a/app/src/main/java/com/eatssu/android/presentation/goodprice/GoodPriceMapScreen.kt +++ b/app/src/main/java/com/eatssu/android/presentation/goodprice/GoodPriceMapScreen.kt @@ -46,11 +46,18 @@ import com.eatssu.android.R import com.eatssu.android.domain.model.GoodPriceStore import com.eatssu.android.presentation.goodprice.component.GoodPriceFilterRow import com.eatssu.android.presentation.goodprice.component.GoodPriceStoreBottomSheet +import com.eatssu.android.presentation.map.MapDestination +import com.eatssu.android.presentation.map.MapExternalNavigator +import com.eatssu.android.presentation.map.MapProvider import com.eatssu.android.presentation.map.findActivityOrNull +import com.eatssu.android.presentation.map.openMapWithoutResolution import com.eatssu.android.presentation.util.TrackScreenViewEvent +import com.eatssu.android.presentation.util.showToast import com.eatssu.common.UiEvent +import com.eatssu.common.UiText import com.eatssu.common.enums.GoodPriceCategory import com.eatssu.common.enums.ScreenId +import com.eatssu.common.enums.ToastType import com.eatssu.design_system.component.EatSsuSnackbar import com.eatssu.design_system.component.EatSsuSnackbarType import com.eatssu.design_system.theme.Black @@ -86,6 +93,7 @@ private const val LOCATION_PERMISSION_REQUEST_CODE = 2001 @Composable fun GoodPriceMapRoute( viewModel: GoodPriceMapViewModel = viewModel(), + mapExternalNavigator: MapExternalNavigator? = null, showTopBar: Boolean = true, contentPadding: PaddingValues = PaddingValues(), modifier: Modifier = Modifier, @@ -133,7 +141,28 @@ fun GoodPriceMapRoute( showTopBar = showTopBar, contentPadding = contentPadding, onCategorySelected = { viewModel.setCategory(it) }, - onStoreClick = { viewModel.selectStore(it.id) }, + onStoreClick = viewModel::selectStore, + onOpenMap = { provider, store -> + scope.launch { + val destination = MapDestination( + storeName = store.storeName, + latitude = store.latitude, + longitude = store.longitude, + ) + val opened = mapExternalNavigator?.open( + context = context, + provider = provider, + destination = destination, + ) ?: context.openMapWithoutResolution(provider, destination) + + if (!opened) { + context.showToast( + UiText.StringResource(R.string.toast_map_open_failed), + ToastType.ERROR, + ) + } + } + }, onDismissBottomSheet = { viewModel.clearSelectedStore() }, animateCameraTo = { latLng, zoom -> scope.launch { @@ -159,6 +188,7 @@ fun GoodPriceMapScreen( contentPadding: PaddingValues = PaddingValues(), onCategorySelected: (GoodPriceCategory) -> Unit, onStoreClick: (GoodPriceStore) -> Unit, + onOpenMap: (MapProvider, GoodPriceStore) -> Unit, onDismissBottomSheet: () -> Unit, animateCameraTo: (LatLng, Double) -> Unit, modifier: Modifier = Modifier, @@ -333,8 +363,13 @@ fun GoodPriceMapScreen( // 업소 상세 바텀시트 uiState.selectedStoreDetail?.let { detail -> + val selectedStore = uiState.selectedStore ?: return@let + if (selectedStore.id != detail.id) return@let + GoodPriceStoreBottomSheet( storeDetail = detail, + onKakaoMapClick = { onOpenMap(MapProvider.KAKAO, selectedStore) }, + onNaverMapClick = { onOpenMap(MapProvider.NAVER, selectedStore) }, onDismiss = onDismissBottomSheet, ) } diff --git a/app/src/main/java/com/eatssu/android/presentation/goodprice/GoodPriceMapState.kt b/app/src/main/java/com/eatssu/android/presentation/goodprice/GoodPriceMapState.kt index 1127a1d3b..f0de3d251 100644 --- a/app/src/main/java/com/eatssu/android/presentation/goodprice/GoodPriceMapState.kt +++ b/app/src/main/java/com/eatssu/android/presentation/goodprice/GoodPriceMapState.kt @@ -10,6 +10,7 @@ import com.eatssu.common.enums.GoodPriceCategory data class GoodPriceMapState( val stores: List = emptyList(), // 지도에 표시할 업소 목록 val selectedCategory: GoodPriceCategory = GoodPriceCategory.ALL, // 현재 선택된 필터 카테고리 + val selectedStore: GoodPriceStore? = null, // 딥링크에 사용할 선택 업소의 좌표 정보 val selectedStoreDetail: GoodPriceStoreDetail? = null, // 현재 선택되어 바텀시트에 표시할 업소 상세 정보 val isLoading: Boolean = false, // 로딩 상태 ) diff --git a/app/src/main/java/com/eatssu/android/presentation/goodprice/GoodPriceMapViewModel.kt b/app/src/main/java/com/eatssu/android/presentation/goodprice/GoodPriceMapViewModel.kt index 5a0d3fafb..6c7ea0ca0 100644 --- a/app/src/main/java/com/eatssu/android/presentation/goodprice/GoodPriceMapViewModel.kt +++ b/app/src/main/java/com/eatssu/android/presentation/goodprice/GoodPriceMapViewModel.kt @@ -4,6 +4,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.eatssu.android.R import com.eatssu.android.data.model.ApiResult +import com.eatssu.android.domain.model.GoodPriceStore import com.eatssu.android.domain.usecase.goodprice.GetGoodPriceStoreDetailUseCase import com.eatssu.android.domain.usecase.goodprice.GetGoodPriceStoresUseCase import com.eatssu.common.UiEvent @@ -77,21 +78,36 @@ class GoodPriceMapViewModel @Inject constructor( } // 특정 업소 마커 클릭 시 상세 정보 조회 - fun selectStore(storeId: Long) { + fun selectStore(store: GoodPriceStore) { + _uiState.update { + it.copy( + selectedStore = store, + selectedStoreDetail = null, + ) + } + viewModelScope.launch { - when (val result = getGoodPriceStoreDetailUseCase(storeId)) { + when (val result = getGoodPriceStoreDetailUseCase(store.id)) { is ApiResult.Success -> { - _uiState.update { it.copy(selectedStoreDetail = result.data) } + _uiState.update { state -> + if (state.selectedStore?.id == store.id) { + state.copy(selectedStoreDetail = result.data) + } else { + state + } + } } else -> { - // 상세 정보 조회 실패 시에도 동일한 에러 토스트 표시 - _uiEvent.emit( - UiEvent.ShowToast( - UiText.StringResource(R.string.toast_load_good_price_stores_failed), - ToastType.ERROR, + if (_uiState.value.selectedStore?.id == store.id) { + // 상세 정보 조회 실패 시에도 동일한 에러 토스트 표시 + _uiEvent.emit( + UiEvent.ShowToast( + UiText.StringResource(R.string.toast_load_good_price_stores_failed), + ToastType.ERROR, + ) ) - ) + } } } } @@ -99,6 +115,11 @@ class GoodPriceMapViewModel @Inject constructor( // 바텀시트 닫을 때 선택된 업소 정보 초기화 fun clearSelectedStore() { - _uiState.update { it.copy(selectedStoreDetail = null) } + _uiState.update { + it.copy( + selectedStore = null, + selectedStoreDetail = null, + ) + } } } diff --git a/app/src/main/java/com/eatssu/android/presentation/goodprice/component/GoodPriceStoreBottomSheet.kt b/app/src/main/java/com/eatssu/android/presentation/goodprice/component/GoodPriceStoreBottomSheet.kt index 09fb7d4f5..2d42abccc 100644 --- a/app/src/main/java/com/eatssu/android/presentation/goodprice/component/GoodPriceStoreBottomSheet.kt +++ b/app/src/main/java/com/eatssu/android/presentation/goodprice/component/GoodPriceStoreBottomSheet.kt @@ -25,6 +25,7 @@ import androidx.compose.ui.unit.dp import coil.compose.AsyncImage import com.eatssu.android.R import com.eatssu.android.domain.model.GoodPriceStoreDetail +import com.eatssu.android.presentation.map.component.MapLinkButtons import com.eatssu.common.enums.GoodPriceCategory import com.eatssu.design_system.theme.Black import com.eatssu.design_system.theme.EatssuTheme @@ -43,6 +44,8 @@ import com.eatssu.design_system.theme.White @Composable fun GoodPriceStoreBottomSheet( storeDetail: GoodPriceStoreDetail, + onKakaoMapClick: () -> Unit, + onNaverMapClick: () -> Unit, onDismiss: () -> Unit, modifier: Modifier = Modifier, ) { @@ -137,5 +140,10 @@ fun GoodPriceStoreBottomSheet( ) } } + + MapLinkButtons( + onKakaoMapClick = onKakaoMapClick, + onNaverMapClick = onNaverMapClick, + ) } } diff --git a/app/src/main/java/com/eatssu/android/presentation/map/KakaoLocalPlaceResolver.kt b/app/src/main/java/com/eatssu/android/presentation/map/KakaoLocalPlaceResolver.kt index ca7c0e36e..671b54785 100644 --- a/app/src/main/java/com/eatssu/android/presentation/map/KakaoLocalPlaceResolver.kt +++ b/app/src/main/java/com/eatssu/android/presentation/map/KakaoLocalPlaceResolver.kt @@ -3,7 +3,6 @@ package com.eatssu.android.presentation.map import com.eatssu.android.BuildConfig import com.eatssu.android.data.remote.dto.response.KakaoLocalSearchResponse import com.eatssu.android.data.remote.service.KakaoLocalService -import com.eatssu.android.domain.model.PartnershipRestaurant import timber.log.Timber import javax.inject.Inject @@ -11,7 +10,7 @@ class KakaoLocalPlaceResolver @Inject constructor( private val kakaoLocalService: KakaoLocalService, ) { internal suspend fun resolve( - restaurant: PartnershipRestaurant, + destination: MapDestination, preferredPlaceId: String?, ): ResolvedMapPlace? { val restApiKey = BuildConfig.KAKAO_REST_API_KEY.trim() @@ -20,9 +19,9 @@ class KakaoLocalPlaceResolver @Inject constructor( val response = runCatching { kakaoLocalService.searchPlaces( authorization = "KakaoAK $restApiKey", - query = restaurant.storeName, - longitude = restaurant.longitude, - latitude = restaurant.latitude, + query = destination.storeName, + longitude = destination.longitude, + latitude = destination.latitude, ) }.onFailure { throwable -> Timber.w(throwable, "Failed to resolve Kakao local place") diff --git a/app/src/main/java/com/eatssu/android/presentation/map/MapDeepLink.kt b/app/src/main/java/com/eatssu/android/presentation/map/MapDeepLink.kt index cbd975256..a5677e4ab 100644 --- a/app/src/main/java/com/eatssu/android/presentation/map/MapDeepLink.kt +++ b/app/src/main/java/com/eatssu/android/presentation/map/MapDeepLink.kt @@ -55,26 +55,35 @@ internal object MapDeepLink { fun kakaoCoordinateUrl(latitude: Double, longitude: Double): String = "kakaomap://look?p=$latitude,$longitude" - fun serverWebUrl(provider: MapProvider, restaurant: PartnershipRestaurant): String? = + fun serverWebUrl(provider: MapProvider, destination: MapDestination): String? = when (provider) { - MapProvider.NAVER -> restaurant.naverMapUrl - MapProvider.KAKAO -> restaurant.kakaoMapUrl + MapProvider.NAVER -> destination.naverMapUrl + MapProvider.KAKAO -> destination.kakaoMapUrl }?.takeIf { url -> url.isHttpWebUrl() } + fun serverWebUrl(provider: MapProvider, restaurant: PartnershipRestaurant): String? = + serverWebUrl(provider, restaurant.toMapDestination()) + fun fallbackWebUrl( provider: MapProvider, - restaurant: PartnershipRestaurant, + destination: MapDestination, resolvedPlace: ResolvedMapPlace?, ): String = when (provider) { MapProvider.NAVER -> - "https://map.naver.com/p/search/${(resolvedPlace?.name ?: restaurant.storeName).pathEncoded()}" + "https://map.naver.com/p/search/${(resolvedPlace?.name ?: destination.storeName).pathEncoded()}" MapProvider.KAKAO -> resolvedPlace?.let { place -> "https://map.kakao.com/link/map/${place.id}" } ?: "https://map.kakao.com/link/map/" + - "${restaurant.storeName.pathEncoded()},${restaurant.latitude},${restaurant.longitude}" + "${destination.storeName.pathEncoded()},${destination.latitude},${destination.longitude}" } + fun fallbackWebUrl( + provider: MapProvider, + restaurant: PartnershipRestaurant, + resolvedPlace: ResolvedMapPlace?, + ): String = fallbackWebUrl(provider, restaurant.toMapDestination(), resolvedPlace) + fun supportsKakaoPlaceAction(versionName: String?): Boolean = versionName ?.substringBefore('.') @@ -106,36 +115,41 @@ internal object MapDeepLink { internal fun Context.openMapWithoutResolution( provider: MapProvider, - restaurant: PartnershipRestaurant, + destination: MapDestination, ): Boolean { if (!isPackageInstalled(provider.packageName)) { return openWebUrl( - MapDeepLink.serverWebUrl(provider, restaurant) - ?: MapDeepLink.fallbackWebUrl(provider, restaurant, resolvedPlace = null), + MapDeepLink.serverWebUrl(provider, destination) + ?: MapDeepLink.fallbackWebUrl(provider, destination, resolvedPlace = null), ) } val appUrl = when (provider) { MapProvider.NAVER -> MapDeepLink.naverCoordinateUrl( - storeName = restaurant.storeName, - latitude = restaurant.latitude, - longitude = restaurant.longitude, + storeName = destination.storeName, + latitude = destination.latitude, + longitude = destination.longitude, appName = packageName, ) MapProvider.KAKAO -> MapDeepLink.kakaoCoordinateUrl( - latitude = restaurant.latitude, - longitude = restaurant.longitude, + latitude = destination.latitude, + longitude = destination.longitude, ) } return startMapUrl(appUrl, provider) || openWebUrl( - MapDeepLink.serverWebUrl(provider, restaurant) - ?: MapDeepLink.fallbackWebUrl(provider, restaurant, resolvedPlace = null), + MapDeepLink.serverWebUrl(provider, destination) + ?: MapDeepLink.fallbackWebUrl(provider, destination, resolvedPlace = null), ) } +internal fun Context.openMapWithoutResolution( + provider: MapProvider, + restaurant: PartnershipRestaurant, +): Boolean = openMapWithoutResolution(provider, restaurant.toMapDestination()) + internal fun Context.startMapUrl(url: String, provider: MapProvider): Boolean = startMapIntent(mapIntent(url, provider.packageName), provider) diff --git a/app/src/main/java/com/eatssu/android/presentation/map/MapDestination.kt b/app/src/main/java/com/eatssu/android/presentation/map/MapDestination.kt new file mode 100644 index 000000000..fad6fb57a --- /dev/null +++ b/app/src/main/java/com/eatssu/android/presentation/map/MapDestination.kt @@ -0,0 +1,19 @@ +package com.eatssu.android.presentation.map + +import com.eatssu.android.domain.model.PartnershipRestaurant + +internal data class MapDestination( + val storeName: String, + val latitude: Double, + val longitude: Double, + val naverMapUrl: String? = null, + val kakaoMapUrl: String? = null, +) + +internal fun PartnershipRestaurant.toMapDestination(): MapDestination = MapDestination( + storeName = storeName, + latitude = latitude, + longitude = longitude, + naverMapUrl = naverMapUrl, + kakaoMapUrl = kakaoMapUrl, +) diff --git a/app/src/main/java/com/eatssu/android/presentation/map/MapExternalNavigator.kt b/app/src/main/java/com/eatssu/android/presentation/map/MapExternalNavigator.kt index 0e2f1dbee..704dca07c 100644 --- a/app/src/main/java/com/eatssu/android/presentation/map/MapExternalNavigator.kt +++ b/app/src/main/java/com/eatssu/android/presentation/map/MapExternalNavigator.kt @@ -11,9 +11,15 @@ class MapExternalNavigator @Inject constructor( context: Context, provider: MapProvider, restaurant: PartnershipRestaurant, + ): Boolean = open(context, provider, restaurant.toMapDestination()) + + internal suspend fun open( + context: Context, + provider: MapProvider, + destination: MapDestination, ): Boolean { - val serverWebUrl = MapDeepLink.serverWebUrl(provider, restaurant) - val serverKakaoPlaceId = restaurant.kakaoMapUrl + val serverWebUrl = MapDeepLink.serverWebUrl(provider, destination) + val serverKakaoPlaceId = destination.kakaoMapUrl ?.let(MapDeepLink::kakaoPlaceId) val isMapAppInstalled = context.isPackageInstalled(provider.packageName) @@ -41,7 +47,7 @@ class MapExternalNavigator @Inject constructor( !supportsKakaoPlaceAction val resolvedPlace = if (requiresExactPlaceName) { kakaoLocalPlaceResolver.resolve( - restaurant = restaurant, + destination = destination, preferredPlaceId = serverKakaoPlaceId, ) } else { @@ -49,7 +55,7 @@ class MapExternalNavigator @Inject constructor( } val webFallbackUrl = serverWebUrl ?: MapDeepLink.fallbackWebUrl( provider = provider, - restaurant = restaurant, + destination = destination, resolvedPlace = resolvedPlace, ) @@ -62,9 +68,9 @@ class MapExternalNavigator @Inject constructor( appName = context.packageName, ) } ?: MapDeepLink.naverCoordinateUrl( - storeName = restaurant.storeName, - latitude = restaurant.latitude, - longitude = restaurant.longitude, + storeName = destination.storeName, + latitude = destination.latitude, + longitude = destination.longitude, appName = context.packageName, ) @@ -76,13 +82,13 @@ class MapExternalNavigator @Inject constructor( resolvedPlace != null -> MapDeepLink.kakaoSearchUrl( storeName = resolvedPlace.name, - latitude = restaurant.latitude, - longitude = restaurant.longitude, + latitude = destination.latitude, + longitude = destination.longitude, ) else -> MapDeepLink.kakaoCoordinateUrl( - latitude = restaurant.latitude, - longitude = restaurant.longitude, + latitude = destination.latitude, + longitude = destination.longitude, ) } } diff --git a/app/src/main/java/com/eatssu/android/presentation/map/MapFragmentView.kt b/app/src/main/java/com/eatssu/android/presentation/map/MapFragmentView.kt index ad3dff39b..d752317b7 100644 --- a/app/src/main/java/com/eatssu/android/presentation/map/MapFragmentView.kt +++ b/app/src/main/java/com/eatssu/android/presentation/map/MapFragmentView.kt @@ -389,6 +389,7 @@ fun MapRoute( // 1: 착한 가격 탭 (서울시 전역 착한가격업소 지도, 학과 바텀시트 불필요) 1 -> { GoodPriceMapRoute( + mapExternalNavigator = mapExternalNavigator, showTopBar = false, contentPadding = PaddingValues(bottom = dimensionResource(R.dimen.bottom_nav_height)), modifier = Modifier.fillMaxSize(), diff --git a/app/src/main/java/com/eatssu/android/presentation/map/component/MapLinkButtons.kt b/app/src/main/java/com/eatssu/android/presentation/map/component/MapLinkButtons.kt new file mode 100644 index 000000000..ead7a50eb --- /dev/null +++ b/app/src/main/java/com/eatssu/android/presentation/map/component/MapLinkButtons.kt @@ -0,0 +1,100 @@ +package com.eatssu.android.presentation.map.component + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +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.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.eatssu.android.R +import com.eatssu.design_system.theme.EatssuTheme +import com.eatssu.design_system.theme.Gray200 +import com.eatssu.design_system.theme.Gray400 +import com.eatssu.design_system.theme.Gray600 + +@Composable +internal fun MapLinkButtons( + onKakaoMapClick: () -> Unit, + onNaverMapClick: () -> Unit, + modifier: Modifier = Modifier, +) { + Box( + modifier = modifier + .fillMaxWidth() + .height(1.dp) + .background(Gray200), + ) + + Row( + modifier = Modifier + .fillMaxWidth() + .height(64.dp) + .padding(horizontal = 24.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + MapLinkButton( + iconRes = R.drawable.ic_kakao_map, + labelRes = R.string.map_open_kakao, + onClick = onKakaoMapClick, + modifier = Modifier.weight(1f), + ) + + Box( + modifier = Modifier + .width(1.dp) + .height(24.dp) + .background(Gray400), + ) + + MapLinkButton( + iconRes = R.drawable.ic_naver_map, + labelRes = R.string.map_open_naver, + onClick = onNaverMapClick, + modifier = Modifier.weight(1f), + ) + } +} + +@Composable +private fun MapLinkButton( + iconRes: Int, + labelRes: Int, + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { + Row( + modifier = modifier + .fillMaxHeight() + .clickable(onClick = onClick), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + ) { + Image( + painter = painterResource(iconRes), + contentDescription = null, + modifier = Modifier.size(24.dp), + ) + + Spacer(modifier = Modifier.width(8.dp)) + + Text( + text = stringResource(labelRes), + style = EatssuTheme.typography.body2, + color = Gray600, + ) + } +} diff --git a/app/src/main/java/com/eatssu/android/presentation/map/component/MapRestaurantBottomSheet.kt b/app/src/main/java/com/eatssu/android/presentation/map/component/MapRestaurantBottomSheet.kt index 37b66854e..276de0830 100644 --- a/app/src/main/java/com/eatssu/android/presentation/map/component/MapRestaurantBottomSheet.kt +++ b/app/src/main/java/com/eatssu/android/presentation/map/component/MapRestaurantBottomSheet.kt @@ -12,7 +12,6 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column 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.height import androidx.compose.foundation.layout.padding @@ -253,41 +252,10 @@ fun MapRestaurantBottomSheet( } } - Box( - modifier = Modifier - .fillMaxWidth() - .height(1.dp) - .background(Gray200), + MapLinkButtons( + onKakaoMapClick = onKakaoMapClick, + onNaverMapClick = onNaverMapClick, ) - - Row( - modifier = Modifier - .fillMaxWidth() - .height(64.dp) - .padding(horizontal = 24.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - MapLinkButton( - iconRes = R.drawable.ic_kakao_map, - labelRes = R.string.map_open_kakao, - onClick = onKakaoMapClick, - modifier = Modifier.weight(1f), - ) - - Box( - modifier = Modifier - .width(1.dp) - .height(24.dp) - .background(Gray400), - ) - - MapLinkButton( - iconRes = R.drawable.ic_naver_map, - labelRes = R.string.map_open_naver, - onClick = onNaverMapClick, - modifier = Modifier.weight(1f), - ) - } } Box( @@ -318,36 +286,6 @@ fun MapRestaurantBottomSheet( } } -@Composable -private fun MapLinkButton( - iconRes: Int, - labelRes: Int, - onClick: () -> Unit, - modifier: Modifier = Modifier, -) { - Row( - modifier = modifier - .fillMaxHeight() - .clickable(onClick = onClick), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Center, - ) { - Image( - painter = painterResource(iconRes), - contentDescription = null, - modifier = Modifier.size(24.dp), - ) - - Spacer(modifier = Modifier.width(8.dp)) - - Text( - text = stringResource(labelRes), - style = EatssuTheme.typography.body2, - color = Gray600, - ) - } -} - @Preview(showBackground = true) @Composable fun MapRestaurantBottomSheetPreview() { diff --git a/app/src/test/java/com/eatssu/android/presentation/map/MapWebFallbackBehaviorSpec.kt b/app/src/test/java/com/eatssu/android/presentation/map/MapWebFallbackBehaviorSpec.kt index d1f45e18d..0001efde2 100644 --- a/app/src/test/java/com/eatssu/android/presentation/map/MapWebFallbackBehaviorSpec.kt +++ b/app/src/test/java/com/eatssu/android/presentation/map/MapWebFallbackBehaviorSpec.kt @@ -50,4 +50,29 @@ class MapWebFallbackBehaviorSpec : AppBehaviorSpec({ } } } + + given("a good-price store without server map URLs") { + val destination = MapDestination( + storeName = "착한식당 숭실대점", + latitude = 37.496, + longitude = 126.955, + ) + + `when`("neither map app is installed") { + then("generated browser URLs retain the store name and coordinates") { + MapDeepLink.fallbackWebUrl( + provider = MapProvider.NAVER, + destination = destination, + resolvedPlace = null, + ) shouldContain "%EC%B0%A9%ED%95%9C%EC%8B%9D%EB%8B%B9%20%EC%88%AD%EC%8B%A4%EB%8C%80%EC%A0%90" + + MapDeepLink.fallbackWebUrl( + provider = MapProvider.KAKAO, + destination = destination, + resolvedPlace = null, + ) shouldBe "https://map.kakao.com/link/map/" + + "%EC%B0%A9%ED%95%9C%EC%8B%9D%EB%8B%B9%20%EC%88%AD%EC%8B%A4%EB%8C%80%EC%A0%90,37.496,126.955" + } + } + } })