Skip to content

Commit 510b52f

Browse files
committed
[NDGL-86] feature: 교통수단 변경 BottomSheet 제작 및 변경 로직 추가
1 parent 06ab78c commit 510b52f

7 files changed

Lines changed: 334 additions & 113 deletions

File tree

core/ui/src/main/res/values/strings.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,4 +106,9 @@
106106
<string name="place_detail_modal_change_description">%s -> %s</string>
107107
<string name="place_detail_modal_change_confirm">변경하기</string>
108108
<string name="place_detail_modal_change_cancel">아니요</string>
109+
110+
<!-- Transport Bottom Sheet -->
111+
<string name="transport_bottom_sheet_title">이동수단 변경</string>
112+
<string name="transport_bottom_sheet_button">확인</string>
113+
109114
</resources>

core/util/src/main/java/com/yapp/ndgl/core/util/IntUtil.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,21 @@ package com.yapp.ndgl.core.util
22

33
import java.text.NumberFormat
44
import java.util.Locale
5+
import java.util.Locale.getDefault
56

67
fun Int.formatDecimal(): String = NumberFormat.getInstance(Locale.US).format(this)
8+
9+
fun Int.formatDistance(): String {
10+
return when {
11+
this >= 1000 -> {
12+
val km = this / 1000.0
13+
if (km % 1 == 0.0) {
14+
"${km.toInt()}km"
15+
} else {
16+
String.format(getDefault(), "%.1fkm", km)
17+
}
18+
}
19+
20+
else -> "${this}m"
21+
}
22+
}

feature/travel/src/main/java/com/yapp/ndgl/feature/travel/traveldetail/TravelDetailContract.kt

Lines changed: 11 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import com.yapp.ndgl.core.base.UiSideEffect
99
import com.yapp.ndgl.core.base.UiState
1010
import com.yapp.ndgl.core.ui.R
1111
import com.yapp.ndgl.core.ui.theme.NDGLTheme
12-
import java.util.Locale.getDefault
1312
import kotlin.time.Duration
1413
import kotlin.time.Duration.Companion.hours
1514

@@ -23,11 +22,10 @@ data class TravelDetailState(
2322
val showDeleteModal: Boolean = false,
2423
val showCancelEditModal: Boolean = false,
2524
val showTimelineBottomSheet: Boolean = false,
26-
val startTime: Duration? = null,
27-
val endTime: Duration? = null,
2825
val selectedPlace: TravelPlace? = null,
2926
val showPlaceBottomSheet: Boolean = false,
3027
val showTimeBottomSheet: Boolean = false,
28+
val showTransportBottomSheet: Boolean = false,
3129
val showCostModal: Boolean = false,
3230
val showMemoModal: Boolean = false,
3331
) : UiState
@@ -65,13 +63,14 @@ data class Budget(
6563
}
6664

6765
data class Itinerary(
68-
val budget: Budget = Budget(0),
66+
val startTime: Duration? = null,
67+
val endTime: Duration? = null,
6968
val places: List<TravelPlace> = emptyList(),
70-
val transportSegments: List<TransportSegment> = emptyList(),
7169
) {
7270
val totalDuration: Duration
73-
get() = places.fold(0.hours) { acc, place -> acc + place.duration } +
74-
transportSegments.fold(0.hours) { acc, segment -> acc + segment.duration }
71+
get() = places.fold(0.hours) { acc, place ->
72+
acc + place.duration + (place.transportToNext?.duration ?: 0.hours)
73+
}
7574
}
7675

7776
data class TravelPlace(
@@ -88,6 +87,7 @@ data class TravelPlace(
8887
val placeType: PlaceType,
8988
val userData: UserData = UserData(),
9089
val startTime: Duration,
90+
val transportToNext: TransportSegment? = null,
9191
) {
9292
val duration: Duration
9393
get() = userData.estimatedDuration
@@ -122,22 +122,7 @@ data class TransportSegment(
122122
val type: TransportType,
123123
val duration: Duration,
124124
val distance: Int,
125-
) {
126-
fun formatDistance(): String {
127-
return when {
128-
distance >= 1000 -> {
129-
val km = distance / 1000.0
130-
if (km % 1 == 0.0) {
131-
"${km.toInt()}km"
132-
} else {
133-
String.format(getDefault(), "%.1fkm", km)
134-
}
135-
}
136-
137-
else -> "${distance}m"
138-
}
139-
}
140-
}
125+
)
141126

142127
enum class TransportType(@get:StringRes val labelRes: Int, @get:DrawableRes val iconRes: Int) {
143128
WALK(R.string.transport_type_walk, R.drawable.ic_20_walk),
@@ -164,6 +149,9 @@ sealed interface TravelDetailIntent : UiIntent {
164149
data class ConfirmTimelineSetting(val startTime: Duration) : TravelDetailIntent
165150
data class ReorderPlaces(val dayIndex: Int, val fromIndex: Int, val toIndex: Int) : TravelDetailIntent
166151
data object ConfirmEditMode : TravelDetailIntent
152+
data class ClickTransportSegment(val place: TravelPlace) : TravelDetailIntent
153+
data class ConfirmChangeTransportSegment(val segment: TransportSegment) : TravelDetailIntent
154+
data object DismissTransportBottomSheet : TravelDetailIntent
167155
data class ClickPlaceItem(val place: TravelPlace) : TravelDetailIntent
168156
data class ClickAddTime(val placeId: Int) : TravelDetailIntent
169157
data class ClickAddCost(val placeId: Int) : TravelDetailIntent

feature/travel/src/main/java/com/yapp/ndgl/feature/travel/traveldetail/TravelDetailScreen.kt

Lines changed: 43 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ import com.yapp.ndgl.feature.travel.traveldetail.component.EditablePlaceItem
7171
import com.yapp.ndgl.feature.travel.traveldetail.component.PlaceBottomSheet
7272
import com.yapp.ndgl.feature.travel.traveldetail.component.PlaceItem
7373
import com.yapp.ndgl.feature.travel.traveldetail.component.TimelineContent
74+
import com.yapp.ndgl.feature.travel.traveldetail.component.TransportBottomSheet
7475
import com.yapp.ndgl.feature.travel.traveldetail.component.TransportSegment
7576
import com.yapp.ndgl.feature.travel.traveldetail.component.TravelDetailToolBar
7677
import com.yapp.ndgl.feature.travel.traveldetail.component.TravelMap
@@ -121,6 +122,9 @@ internal fun TravelDetailRoute(
121122
dismissTimelineBottomSheet = { viewModel.onIntent(TravelDetailIntent.DismissTimelineBottomSheet) },
122123
confirmTimelineSetting = { startTime -> viewModel.onIntent(TravelDetailIntent.ConfirmTimelineSetting(startTime)) },
123124
reorderPlaces = { dayIndex, fromIndex, toIndex -> viewModel.onIntent(TravelDetailIntent.ReorderPlaces(dayIndex, fromIndex, toIndex)) },
125+
clickTransportSegment = { place -> viewModel.onIntent(TravelDetailIntent.ClickTransportSegment(place)) },
126+
confirmChangeTransport = { segment -> viewModel.onIntent(TravelDetailIntent.ConfirmChangeTransportSegment(segment)) },
127+
dismissTransportBottomSheet = { viewModel.onIntent(TravelDetailIntent.DismissTransportBottomSheet) },
124128
confirmEditMode = { viewModel.onIntent(TravelDetailIntent.ConfirmEditMode) },
125129
clickPlaceItem = { viewModel.onIntent(TravelDetailIntent.ClickPlaceItem(it)) },
126130
dismissPlaceBottomSheet = { viewModel.onIntent(TravelDetailIntent.DismissPlaceBottomSheet) },
@@ -160,6 +164,9 @@ private fun TravelDetailScreen(
160164
confirmTimelineSetting: (Duration) -> Unit,
161165
reorderPlaces: (Int, Int, Int) -> Unit,
162166
confirmEditMode: () -> Unit,
167+
clickTransportSegment: (TravelPlace) -> Unit,
168+
confirmChangeTransport: (TransportSegment) -> Unit,
169+
dismissTransportBottomSheet: () -> Unit,
163170
clickPlaceItem: (TravelPlace) -> Unit,
164171
clickAddTime: (Int) -> Unit,
165172
clickAddMemo: (Int) -> Unit,
@@ -198,7 +205,6 @@ private fun TravelDetailScreen(
198205
val currentItineraries = if (state.isEditMode) state.tempItineraries else state.itineraries
199206
val currentItinerary = currentItineraries.getOrNull(state.selectedDay - 1)
200207
val currentPlaces = currentItinerary?.places.orEmpty()
201-
val currentTransportSegments = currentItinerary?.transportSegments.orEmpty()
202208

203209
// 헤더(0) + stickyHeader(1) + 맵 아이템(2) = 3개가 장소 아이템 앞에 위치
204210
val placesOffset = 3
@@ -309,7 +315,7 @@ private fun TravelDetailScreen(
309315
)
310316
} else {
311317
TravelDetailToolBar(
312-
startTime = state.startTime,
318+
startTime = itinerary.startTime,
313319
clickStartTimeSetting = clickStartTimeSetting,
314320
clickEditTravel = clickEditTravel,
315321
)
@@ -366,14 +372,6 @@ private fun TravelDetailScreen(
366372
checked = state.selectedPlaceIds.contains(place.id),
367373
onCheck = { checkPlaceItem(place.id) },
368374
)
369-
370-
// if (isDragging) {
371-
// Box(
372-
// modifier = Modifier
373-
// .matchParentSize()
374-
// .background(NDGLTheme.colors.black50.copy(0.9f)),
375-
// )
376-
// }
377375
}
378376
}
379377
} else {
@@ -397,9 +395,9 @@ private fun TravelDetailScreen(
397395

398396
if (index < currentPlaces.size - 1) {
399397
key("transport_${state.selectedDay}_${place.id}") {
400-
currentTransportSegments.getOrNull(index)?.let { segment ->
398+
place.transportToNext?.let { segment ->
401399
Box(modifier = Modifier.padding(horizontal = 24.dp)) {
402-
TransportSegment(segment = segment)
400+
TransportSegment(segment = segment, onClick = { clickTransportSegment(place) })
403401
}
404402
}
405403
}
@@ -498,13 +496,32 @@ private fun TravelDetailScreen(
498496
title = stringResource(R.string.schedule_setting_title),
499497
) {
500498
TimelineContent(
501-
startTime = state.startTime ?: 8.hours,
499+
startTime = currentItinerary?.startTime ?: 8.hours,
502500
totalDuration = state.itineraries.getOrNull(state.selectedDay - 1)?.totalDuration ?: 0.hours,
503501
onConfirm = confirmTimelineSetting,
504502
)
505503
}
506504
}
507505

506+
if (state.showTransportBottomSheet && state.selectedPlace != null && state.selectedPlace.transportToNext != null) {
507+
// TODO: 실제 교통수단 후보로 수정
508+
val mockAvailableTransports = mutableListOf(
509+
TransportSegment(TransportType.WALK, 15.minutes, 1200),
510+
TransportSegment(TransportType.CAR, 10.minutes, 5400),
511+
TransportSegment(TransportType.BUS, 25.minutes, 4800),
512+
TransportSegment(TransportType.TRAIN, 40.minutes, 12000),
513+
)
514+
mockAvailableTransports.remove(state.selectedPlace.transportToNext)
515+
mockAvailableTransports.add(state.selectedPlace.transportToNext)
516+
517+
TransportBottomSheet(
518+
initialTransport = state.selectedPlace.transportToNext,
519+
availableTransports = mockAvailableTransports,
520+
onDismissRequest = dismissTransportBottomSheet,
521+
onConfirm = confirmChangeTransport,
522+
)
523+
}
524+
508525
if (state.showPlaceBottomSheet && state.selectedPlace != null) {
509526
PlaceBottomSheet(
510527
place = state.selectedPlace,
@@ -614,7 +631,6 @@ private fun TravelDetailScreenPreview() {
614631
selectedDay = 1,
615632
itineraries = listOf(
616633
Itinerary(
617-
budget = Budget(300000),
618634
places = listOf(
619635
TravelPlace(
620636
id = 1,
@@ -629,7 +645,8 @@ private fun TravelDetailScreenPreview() {
629645
googleMapsUri = "",
630646
placeType = PlaceType.ATTRACTION,
631647
userData = TravelPlace.UserData(estimatedDuration = 90.minutes),
632-
startTime = 0.hours,
648+
transportToNext = TransportSegment(type = TransportType.CAR, duration = 25.minutes, distance = 3500),
649+
startTime = 8.hours,
633650
),
634651
TravelPlace(
635652
id = 2,
@@ -645,13 +662,7 @@ private fun TravelDetailScreenPreview() {
645662
placeType = PlaceType.RESTAURANT,
646663
userData = TravelPlace.UserData(estimatedDuration = 60.minutes),
647664
startTime = 0.hours,
648-
),
649-
),
650-
transportSegments = listOf(
651-
TransportSegment(
652-
type = TransportType.CAR,
653-
duration = 25.minutes,
654-
distance = 3500,
665+
transportToNext = null,
655666
),
656667
),
657668
),
@@ -689,6 +700,9 @@ private fun TravelDetailScreenPreview() {
689700
confirmCost = { _ -> },
690701
dismissMemoModal = {},
691702
confirmMemo = { _ -> },
703+
clickTransportSegment = {},
704+
confirmChangeTransport = { _ -> },
705+
dismissTransportBottomSheet = {},
692706
)
693707
}
694708
}
@@ -718,7 +732,6 @@ private fun TravelDetailScreenEditModePreview() {
718732
selectedDay = 1,
719733
itineraries = listOf(
720734
Itinerary(
721-
budget = Budget(300000),
722735
places = listOf(
723736
TravelPlace(
724737
id = 1,
@@ -733,6 +746,7 @@ private fun TravelDetailScreenEditModePreview() {
733746
googleMapsUri = "",
734747
placeType = PlaceType.ATTRACTION,
735748
userData = TravelPlace.UserData(estimatedDuration = 90.minutes),
749+
transportToNext = TransportSegment(type = TransportType.CAR, duration = 25.minutes, distance = 3500),
736750
startTime = 0.hours,
737751
),
738752
TravelPlace(
@@ -747,14 +761,9 @@ private fun TravelDetailScreenEditModePreview() {
747761
regularOpeningHours = "11:00~22:00",
748762
googleMapsUri = "",
749763
placeType = PlaceType.RESTAURANT,
750-
userData = TravelPlace.UserData(estimatedDuration = 60.minutes), startTime = 0.hours,
751-
),
752-
),
753-
transportSegments = listOf(
754-
TransportSegment(
755-
type = TransportType.CAR,
756-
duration = 25.minutes,
757-
distance = 3500,
764+
userData = TravelPlace.UserData(estimatedDuration = 60.minutes),
765+
transportToNext = null,
766+
startTime = 8.hours,
758767
),
759768
),
760769
),
@@ -792,6 +801,9 @@ private fun TravelDetailScreenEditModePreview() {
792801
confirmCost = { _ -> },
793802
dismissMemoModal = {},
794803
confirmMemo = { _ -> },
804+
clickTransportSegment = {},
805+
confirmChangeTransport = { _ -> },
806+
dismissTransportBottomSheet = {},
795807
)
796808
}
797809
}

0 commit comments

Comments
 (0)