Skip to content

Commit fa45d9b

Browse files
authored
Merge pull request #68 from YAPP-Github/refactor/#65-map-refactor
[refactor] #65 네컷지도 관련 로직 개선
2 parents 0cf8344 + 8982f52 commit fa45d9b

10 files changed

Lines changed: 237 additions & 293 deletions

File tree

feature/map/impl/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ dependencies {
1010
implementation(projects.feature.map.api)
1111
api(libs.map.sdk)
1212
implementation(libs.naver.map.compose)
13+
implementation(libs.play.services.location)
1314

1415
implementation(libs.kotlinx.collections.immutable)
1516
implementation(libs.coil.compose)

feature/map/impl/src/main/java/com/neki/android/feature/map/impl/MapContract.kt

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
11
package com.neki.android.feature.map.impl
22

3+
import androidx.compose.ui.graphics.ImageBitmap
34
import com.neki.android.core.model.Brand
45
import com.neki.android.core.model.PhotoBooth
56
import com.neki.android.feature.map.impl.const.DirectionApp
67
import kotlinx.collections.immutable.ImmutableList
8+
import kotlinx.collections.immutable.ImmutableMap
79
import kotlinx.collections.immutable.persistentListOf
10+
import kotlinx.collections.immutable.persistentMapOf
811

912
data class MapState(
1013
val isLoading: Boolean = false,
1114
val currentLocLatLng: LocLatLng? = null,
15+
val isCameraOnCurrentLocation: Boolean = false,
16+
val isVisibleRefreshButton: Boolean = false,
1217
val dragLevel: DragLevel = DragLevel.FIRST,
1318
val brands: ImmutableList<Brand> = persistentListOf(),
19+
val brandImageCache: ImmutableMap<String, ImageBitmap> = persistentMapOf(),
1420
val mapMarkers: ImmutableList<PhotoBooth> = persistentListOf(),
1521
val nearbyPhotoBooths: ImmutableList<PhotoBooth> = persistentListOf(),
1622
val isShowInfoDialog: Boolean = false,
@@ -20,14 +26,15 @@ data class MapState(
2026

2127
sealed interface MapIntent {
2228
data object EnterMapScreen : MapIntent
23-
data object NaverMapLoaded : MapIntent
2429

2530
// in 지도
31+
data class UpdateCurrentLocation(val locLatLng: LocLatLng) : MapIntent
32+
data object GrantedLocationPermission : MapIntent
2633
data class LoadPhotoBoothsByBounds(val mapBounds: MapBounds) : MapIntent
2734
data class ClickPhotoBoothMarker(val locLatLng: LocLatLng) : MapIntent
2835
data class ClickRefreshButton(val mapBounds: MapBounds) : MapIntent
2936
data object ClickDirectionIcon : MapIntent
30-
data class UpdateCurrentLocation(val locLatLng: LocLatLng) : MapIntent
37+
data object GestureOnMap : MapIntent
3138

3239
// in 패널
3340
data object ClickCurrentLocationIcon : MapIntent
@@ -51,9 +58,10 @@ sealed interface MapIntent {
5158
}
5259

5360
sealed interface MapEffect {
54-
data object TrackingFollowMode : MapEffect
55-
data object LoadInitialPhotoBooths : MapEffect
56-
data class MoveCameraToPosition(val locLatLng: LocLatLng) : MapEffect
61+
data class MoveCameraToPosition(
62+
val locLatLng: LocLatLng,
63+
val isRequiredLoadPhotoBooths: Boolean = false,
64+
) : MapEffect
5765
data object OpenDirectionBottomSheet : MapEffect
5866
data class ShowToastMessage(val message: String) : MapEffect
5967

feature/map/impl/src/main/java/com/neki/android/feature/map/impl/MapScreen.kt

Lines changed: 44 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ import androidx.compose.runtime.mutableStateOf
1414
import androidx.compose.runtime.remember
1515
import androidx.compose.runtime.rememberCoroutineScope
1616
import androidx.compose.runtime.setValue
17+
import androidx.compose.runtime.snapshotFlow
1718
import androidx.compose.ui.Alignment
1819
import androidx.compose.ui.Modifier
19-
import androidx.compose.ui.graphics.ImageBitmap
2020
import androidx.compose.ui.platform.LocalContext
2121
import androidx.compose.ui.unit.dp
2222
import androidx.compose.ui.window.DialogProperties
@@ -51,7 +51,6 @@ import com.neki.android.feature.map.impl.component.PhotoBoothMarker
5151
import com.neki.android.feature.map.impl.component.ToMapChip
5252
import com.neki.android.feature.map.impl.const.MapConst
5353
import com.neki.android.feature.map.impl.util.DirectionHelper
54-
import com.neki.android.feature.map.impl.util.rememberCachedBrandImage
5554
import kotlinx.coroutines.launch
5655

5756
@OptIn(ExperimentalNaverMapApi::class)
@@ -72,21 +71,17 @@ fun MapRoute(
7271
MapConst.DEFAULT_ZOOM_LEVEL,
7372
)
7473
}
75-
val brandImageCache = rememberCachedBrandImage(uiState.brands)
7674
var isNavigatedToSettings by remember { mutableStateOf(false) }
77-
var isFirstLocationLoaded by remember { mutableStateOf(false) }
7875

79-
fun setFollowMode() {
80-
if (locationTrackingMode != LocationTrackingMode.Follow) {
81-
cameraPositionState.move(CameraUpdate.zoomTo(MapConst.DEFAULT_ZOOM_LEVEL))
82-
locationTrackingMode = LocationTrackingMode.Follow
83-
}
84-
}
76+
val locationPermissionLauncher = rememberLauncherForActivityResult(
77+
contract = ActivityResultContracts.RequestMultiplePermissions(),
78+
) { permissions ->
79+
val isGranted = permissions.values.any { it }
8580

86-
LaunchedEffect(cameraPositionState.cameraUpdateReason, cameraPositionState.isMoving) {
87-
if (!cameraPositionState.isMoving && cameraPositionState.cameraUpdateReason == CameraUpdateReason.LOCATION && !isFirstLocationLoaded) {
88-
isFirstLocationLoaded = true
89-
// 권한 있고, 최초 요청 시
81+
if (isGranted) {
82+
locationTrackingMode = LocationTrackingMode.NoFollow
83+
viewModel.store.onIntent(MapIntent.GrantedLocationPermission)
84+
} else {
9085
cameraPositionState.contentBounds?.let { bounds ->
9186
viewModel.store.onIntent(
9287
MapIntent.LoadPhotoBoothsByBounds(
@@ -99,28 +94,28 @@ fun MapRoute(
9994
),
10095
)
10196
}
102-
}
103-
}
10497

105-
val locationPermissionLauncher = rememberLauncherForActivityResult(
106-
contract = ActivityResultContracts.RequestMultiplePermissions(),
107-
) { permissions ->
108-
val isGranted = permissions.values.any { it }
109-
110-
if (isGranted) {
111-
setFollowMode()
112-
} else {
113-
// 2회 이상 거부
98+
// 영구 거부
11499
if (!LocationPermissionManager.shouldShowLocationRationale(activity)) {
115100
viewModel.store.onIntent(MapIntent.ShowLocationPermissionDialog)
116101
}
117102
}
118103
}
119104

105+
LaunchedEffect(Unit) {
106+
snapshotFlow { cameraPositionState.isMoving to cameraPositionState.cameraUpdateReason }
107+
.collect { (isMoving, reason) ->
108+
if (isMoving && reason == CameraUpdateReason.GESTURE) {
109+
viewModel.store.onIntent(MapIntent.GestureOnMap)
110+
}
111+
}
112+
}
113+
120114
LifecycleResumeEffect(Unit) {
121115
if (isNavigatedToSettings) {
122116
if (LocationPermissionManager.isGrantedLocationPermission(context)) {
123-
setFollowMode()
117+
locationTrackingMode = LocationTrackingMode.NoFollow
118+
viewModel.store.onIntent(MapIntent.GrantedLocationPermission)
124119
}
125120
isNavigatedToSettings = false
126121
}
@@ -129,37 +124,8 @@ fun MapRoute(
129124

130125
viewModel.store.sideEffects.collectWithLifecycle { sideEffect ->
131126
when (sideEffect) {
132-
is MapEffect.LoadInitialPhotoBooths -> {
133-
cameraPositionState.contentBounds?.let { bounds ->
134-
if (!LocationPermissionManager.isGrantedLocationPermission(context)) {
135-
viewModel.store.onIntent(
136-
MapIntent.LoadPhotoBoothsByBounds(
137-
MapBounds(
138-
southWest = LocLatLng(bounds.southWest.latitude, bounds.southWest.longitude),
139-
northWest = LocLatLng(bounds.northWest.latitude, bounds.northWest.longitude),
140-
northEast = LocLatLng(bounds.northEast.latitude, bounds.northEast.longitude),
141-
southEast = LocLatLng(bounds.southEast.latitude, bounds.southEast.longitude),
142-
),
143-
),
144-
)
145-
}
146-
}
147-
}
148-
149-
is MapEffect.TrackingFollowMode -> {
150-
if (LocationPermissionManager.isGrantedLocationPermission(context)) {
151-
setFollowMode()
152-
} else {
153-
viewModel.store.onIntent(MapIntent.RequestLocationPermission)
154-
}
155-
}
156-
157127
is MapEffect.OpenDirectionBottomSheet -> {
158-
if (LocationPermissionManager.isGrantedLocationPermission(context)) {
159-
viewModel.store.onIntent(MapIntent.OpenDirectionBottomSheet)
160-
} else {
161-
viewModel.store.onIntent(MapIntent.RequestLocationPermission)
162-
}
128+
viewModel.store.onIntent(MapIntent.OpenDirectionBottomSheet)
163129
}
164130

165131
is MapEffect.MoveCameraToPosition -> {
@@ -172,6 +138,22 @@ fun MapRoute(
172138
animation = CameraAnimation.Easing,
173139
durationMs = MapConst.DEFAULT_CAMERA_ANIMATION_DURATIONS_MS,
174140
)
141+
142+
if (sideEffect.isRequiredLoadPhotoBooths) {
143+
locationTrackingMode = LocationTrackingMode.NoFollow
144+
cameraPositionState.contentBounds?.let { bounds ->
145+
viewModel.store.onIntent(
146+
MapIntent.LoadPhotoBoothsByBounds(
147+
MapBounds(
148+
southWest = LocLatLng(bounds.southWest.latitude, bounds.southWest.longitude),
149+
northWest = LocLatLng(bounds.northWest.latitude, bounds.northWest.longitude),
150+
northEast = LocLatLng(bounds.northEast.latitude, bounds.northEast.longitude),
151+
southEast = LocLatLng(bounds.southEast.latitude, bounds.southEast.longitude),
152+
),
153+
),
154+
)
155+
}
156+
}
175157
}
176158
}
177159

@@ -203,17 +185,7 @@ fun MapRoute(
203185
uiState = uiState,
204186
onIntent = viewModel.store::onIntent,
205187
locationTrackingMode = locationTrackingMode,
206-
onLocationTrackingModeChange = { locationTrackingMode = it },
207188
cameraPositionState = cameraPositionState,
208-
brandImageCache = brandImageCache,
209-
onMapLoaded = {
210-
if (LocationPermissionManager.isGrantedLocationPermission(context)) {
211-
setFollowMode()
212-
} else {
213-
viewModel.store.onIntent(MapIntent.NaverMapLoaded)
214-
viewModel.store.onIntent(MapIntent.RequestLocationPermission)
215-
}
216-
},
217189
)
218190
}
219191

@@ -223,12 +195,9 @@ fun MapScreen(
223195
uiState: MapState = MapState(),
224196
onIntent: (MapIntent) -> Unit = {},
225197
locationTrackingMode: LocationTrackingMode = LocationTrackingMode.None,
226-
onLocationTrackingModeChange: (LocationTrackingMode) -> Unit = {},
227198
cameraPositionState: CameraPositionState = rememberCameraPositionState {
228199
position = CameraPosition(LatLng(MapConst.DEFAULT_LATITUDE, MapConst.DEFAULT_LONGITUDE), MapConst.DEFAULT_ZOOM_LEVEL)
229200
},
230-
brandImageCache: Map<String, ImageBitmap> = emptyMap(),
231-
onMapLoaded: () -> Unit = {},
232201
) {
233202
val mapProperties = remember(locationTrackingMode) {
234203
MapProperties(
@@ -251,22 +220,15 @@ fun MapScreen(
251220
locationSource = rememberFusedLocationSource(),
252221
properties = mapProperties,
253222
uiSettings = mapUiSettings,
254-
onMapLoaded = onMapLoaded,
255-
onOptionChange = {
256-
cameraPositionState.locationTrackingMode?.let {
257-
onLocationTrackingModeChange(it)
258-
}
259-
},
223+
onMapLoaded = { onIntent(MapIntent.RequestLocationPermission) },
260224
onLocationChange = { location ->
261225
onIntent(MapIntent.UpdateCurrentLocation(LocLatLng(location.latitude, location.longitude)))
262226
},
263227
) {
264228
uiState.mapMarkers.filter { it.isCheckedBrand }.forEach { photoBooth ->
265-
val cachedBitmap = brandImageCache[photoBooth.imageUrl]
266229
PhotoBoothMarker(
267230
photoBooth = photoBooth,
268-
isFocused = photoBooth.isFocused,
269-
cachedBitmap = cachedBitmap,
231+
cachedBitmap = uiState.brandImageCache[photoBooth.imageUrl],
270232
onClick = {
271233
onIntent(MapIntent.ClickPhotoBoothMarker(LocLatLng(photoBooth.latitude, photoBooth.longitude)))
272234
},
@@ -279,14 +241,14 @@ fun MapScreen(
279241
nearbyPhotoBooths = uiState.nearbyPhotoBooths,
280242
dragLevel = uiState.dragLevel,
281243
onDragLevelChanged = { onIntent(MapIntent.ChangeDragLevel(it)) },
244+
isCurrentLocation = uiState.isCameraOnCurrentLocation,
282245
onClickInfoIcon = { onIntent(MapIntent.ClickInfoIcon) },
283-
isCurrentLocation = locationTrackingMode == LocationTrackingMode.Follow,
284246
onClickCurrentLocation = { onIntent(MapIntent.ClickCurrentLocationIcon) },
285247
onClickBrand = { onIntent(MapIntent.ClickVerticalBrand(it)) },
286248
onClickNearPhotoBooth = { onIntent(MapIntent.ClickNearPhotoBooth(it)) },
287249
)
288250

289-
if ((uiState.dragLevel == DragLevel.FIRST || uiState.dragLevel == DragLevel.SECOND) && locationTrackingMode != LocationTrackingMode.Follow) {
251+
if ((uiState.dragLevel == DragLevel.FIRST || uiState.dragLevel == DragLevel.SECOND) && uiState.isVisibleRefreshButton) {
290252
MapRefreshChip(
291253
modifier = Modifier
292254
.align(Alignment.TopCenter)
@@ -321,7 +283,7 @@ fun MapScreen(
321283
PhotoBoothDetailContent(
322284
photoBooth = focusedPhotoBooth,
323285
modifier = Modifier.align(Alignment.BottomCenter),
324-
isCurrentLocation = locationTrackingMode == LocationTrackingMode.Follow,
286+
isCurrentLocation = uiState.isCameraOnCurrentLocation,
325287
onClickCurrentLocation = { onIntent(MapIntent.ClickCurrentLocationIcon) },
326288
onClickCloseCard = { onIntent(MapIntent.ClickClosePhotoBoothCard) },
327289
onClickCard = {
@@ -359,9 +321,7 @@ fun MapScreen(
359321
)
360322
}
361323

362-
val isLoadingLocation = locationTrackingMode == LocationTrackingMode.Follow && uiState.currentLocLatLng == null
363-
364-
if (isLoadingLocation || uiState.isLoading) {
324+
if (uiState.isLoading) {
365325
LoadingDialog(
366326
properties = DialogProperties(
367327
dismissOnBackPress = false,

0 commit comments

Comments
 (0)