Skip to content

Commit 3eabf44

Browse files
authored
Merge pull request #118 from depromeet/feat/itinerary-2nd-mvp#65
[#105] 디자인 QA 반영완료
2 parents 95726d1 + 38c481b commit 3eabf44

File tree

12 files changed

+302
-126
lines changed

12 files changed

+302
-126
lines changed

app/build.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ android {
3737
applicationId = "com.depromeet.team6"
3838
minSdk = 26
3939
targetSdk = 34
40-
versionCode = 9
41-
versionName = "1.0.8"
40+
versionCode = 10
41+
versionName = "1.1.0"
4242

4343
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
4444
buildConfigField("String", "KAKAO_NATIVE_APP_KEY", properties["kakao.native.app.key"].toString())

app/src/main/java/com/depromeet/team6/data/dataremote/model/response/transits/ResponseCourseSearchDto.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ data class ResponseCourseSearchDto(
1313
@SerialName("totalWalkTime") val totalWalkTime: Int,
1414
@SerialName("transferCount") val transferCount: Int,
1515
@SerialName("totalDistance") val totalDistance: Float,
16-
// @SerialName("totalWalkDistance") val totalWalkDistance: Float,
1716
@SerialName("pathType") val pathType: Int,
1817
@SerialName("legs") val legs: List<Leg>
1918
)

app/src/main/java/com/depromeet/team6/data/mapper/todomain/ResponseCourseSearchDtoMapper.kt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import com.depromeet.team6.data.dataremote.model.response.transits.ResponseCours
44
import com.depromeet.team6.domain.model.Address
55
import com.depromeet.team6.domain.model.course.CourseInfo
66
import com.depromeet.team6.domain.model.course.LegInfo
7+
import com.depromeet.team6.domain.model.course.Station
78
import com.depromeet.team6.domain.model.course.TransportType
89

910
fun List<ResponseCourseSearchDto>.toDomain(): List<CourseInfo> = filter { response ->
@@ -50,7 +51,16 @@ fun List<ResponseCourseSearchDto>.toDomain(): List<CourseInfo> = filter { respon
5051
lon = leg.end.lon.toDouble(),
5152
address = ""
5253
),
53-
passShape = passShape ?: ""
54+
passShape = passShape ?: "",
55+
passStopList = leg.passStopList?.mapIndexedNotNull { idx, it ->
56+
if (it.lon == null || it.lat == null) return@mapIndexedNotNull null
57+
Station(
58+
index = idx,
59+
stationName = it.stationName,
60+
lon = it.lon,
61+
lat = it.lat
62+
)
63+
} ?: emptyList()
5464
)
5565
}
5666

app/src/main/java/com/depromeet/team6/domain/model/course/LegInfo.kt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,13 @@ data class LegInfo(
1111
val distance: Int, // 구간 이동거리 (m)
1212
val startPoint: Address,
1313
val endPoint: Address,
14-
val passShape: String
14+
val passShape: String,
15+
val passStopList: List<Station> = emptyList()
16+
)
17+
18+
data class Station(
19+
val index: Int,
20+
val stationName: String = "(알 수 없음)",
21+
val lon: String,
22+
val lat: String
1523
)

app/src/main/java/com/depromeet/team6/presentation/ui/itinerary/ItineraryContract.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.depromeet.team6.presentation.ui.itinerary
22

3+
import android.util.SparseArray
34
import com.depromeet.team6.domain.model.Address
5+
import com.depromeet.team6.domain.model.RealTimeBusArrival
46
import com.depromeet.team6.domain.model.course.CourseInfo
57
import com.depromeet.team6.presentation.util.base.UiEvent
68
import com.depromeet.team6.presentation.util.base.UiSideEffect
@@ -11,14 +13,15 @@ class ItineraryContract {
1113
data class ItineraryUiState(
1214
val courseDataLoadState: LoadState = LoadState.Idle,
1315
val itineraryInfo: CourseInfo? = null,
16+
val busArrivalStatus: SparseArray<RealTimeBusArrival> = SparseArray(),
1417
val departurePoint: Address? = null,
1518
val destinationPoint: Address? = null
1619
) : UiState
1720

1821
sealed interface ItinerarySideEffect : UiSideEffect
1922

2023
sealed class ItineraryEvent : UiEvent {
21-
data object RegisterAlarm : ItineraryEvent()
2224
data class LoadLegsResult(val result: CourseInfo) : ItineraryEvent()
25+
data object RefreshButtonClicked : ItineraryEvent()
2326
}
2427
}

app/src/main/java/com/depromeet/team6/presentation/ui/itinerary/ItineraryScreen.kt

Lines changed: 68 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,23 @@ package com.depromeet.team6.presentation.ui.itinerary
22

33
import androidx.compose.foundation.background
44
import androidx.compose.foundation.layout.Arrangement
5+
import androidx.compose.foundation.layout.Box
56
import androidx.compose.foundation.layout.Column
67
import androidx.compose.foundation.layout.PaddingValues
78
import androidx.compose.foundation.layout.Spacer
89
import androidx.compose.foundation.layout.fillMaxSize
910
import androidx.compose.foundation.layout.fillMaxWidth
1011
import androidx.compose.foundation.layout.height
12+
import androidx.compose.foundation.layout.offset
1113
import androidx.compose.foundation.layout.padding
14+
import androidx.compose.foundation.layout.size
1215
import androidx.compose.foundation.rememberScrollState
16+
import androidx.compose.foundation.shape.CircleShape
1317
import androidx.compose.foundation.verticalScroll
1418
import androidx.compose.runtime.Composable
1519
import androidx.compose.runtime.LaunchedEffect
1620
import androidx.compose.runtime.getValue
21+
import androidx.compose.ui.Alignment
1722
import androidx.compose.ui.Modifier
1823
import androidx.compose.ui.graphics.Color
1924
import androidx.compose.ui.platform.LocalContext
@@ -27,6 +32,7 @@ import com.depromeet.team6.domain.model.course.LegInfo
2732
import com.depromeet.team6.presentation.model.bus.BusArrivalParameter
2833
import com.depromeet.team6.presentation.model.itinerary.FocusedMarkerParameter
2934
import com.depromeet.team6.presentation.ui.common.AtchaCommonBottomSheet
35+
import com.depromeet.team6.presentation.ui.home.component.RefreshLottieButton
3036
import com.depromeet.team6.presentation.ui.itinerary.component.ItineraryDetail
3137
import com.depromeet.team6.presentation.ui.itinerary.component.ItineraryMap
3238
import com.depromeet.team6.presentation.ui.itinerary.component.ItinerarySummary
@@ -62,10 +68,6 @@ fun ItineraryRoute(
6268
}
6369
}
6470

65-
LaunchedEffect(Unit) {
66-
// viewModel.getLegs()
67-
}
68-
6971
when (uiState.courseDataLoadState) {
7072
LoadState.Idle -> {}
7173
LoadState.Success -> ItineraryScreen(
@@ -74,6 +76,7 @@ fun ItineraryRoute(
7476
uiState = uiState,
7577
focusedMarkerParam = focusedMarkerParam,
7678
onBackPressed = onBackPressed,
79+
onRefreshButtonClick = { viewModel.setEvent(ItineraryContract.ItineraryEvent.RefreshButtonClicked) },
7780
modifier = Modifier
7881
.fillMaxSize()
7982
.padding(paddingValues = padding)
@@ -92,61 +95,75 @@ fun ItineraryScreen(
9295
uiState: ItineraryContract.ItineraryUiState = ItineraryContract.ItineraryUiState(),
9396
focusedMarkerParam: FocusedMarkerParameter? = null,
9497
navigateToBusCourse: (BusArrivalParameter) -> Unit = {},
98+
onRefreshButtonClick: () -> Unit = {},
9599
onBackPressed: () -> Unit = {}
96100
) {
97101
val sheetScrollState = rememberScrollState()
98102
val itineraryInfo = uiState.itineraryInfo!!
99103

100-
AtchaCommonBottomSheet(
101-
modifier = modifier,
102-
mainContent = {
103-
ItineraryMap(
104-
marginTop = marginTop,
105-
legs = itineraryInfo.legs,
106-
currentLocation = LatLng(37.5665, 126.9780),
107-
departurePoint = uiState.departurePoint!!,
108-
destinationPoint = uiState.destinationPoint!!,
109-
onBackPressed = onBackPressed,
110-
focusedMarkerParameter = focusedMarkerParam
111-
)
112-
},
113-
sheetContent = {
114-
Column(
115-
modifier = modifier
116-
.fillMaxSize()
117-
.padding(horizontal = 16.dp)
118-
// .nestedScroll(rememberNestedScrollInteropConnection())
119-
.verticalScroll(sheetScrollState),
120-
verticalArrangement = Arrangement.Top
121-
) {
122-
ItinerarySummary(
123-
modifier = Modifier
124-
.padding(horizontal = 16.dp),
125-
totalTimeMinute = itineraryInfo.totalTime / 60,
126-
boardingTime = itineraryInfo.boardingTime,
127-
legs = itineraryInfo.legs
128-
)
129-
Spacer(
130-
modifier = Modifier
131-
.fillMaxWidth()
132-
.height(31.dp) // 8 + 1 + 22
133-
.padding(top = 8.dp, bottom = 22.dp) // mimic the spacing
134-
.background(Color(0x0AFFFFFF)) // applies to the 1.dp middle line
135-
)
136-
ItineraryDetail(
137-
modifier = Modifier
138-
.padding(horizontal = 16.dp),
139-
courseInfo = itineraryInfo,
104+
Box() {
105+
AtchaCommonBottomSheet(
106+
modifier = Modifier,
107+
mainContent = {
108+
ItineraryMap(
109+
marginTop = marginTop,
110+
legs = itineraryInfo.legs,
111+
currentLocation = LatLng(37.5665, 126.9780),
140112
departurePoint = uiState.departurePoint!!,
141113
destinationPoint = uiState.destinationPoint!!,
142-
onClickBusInfo = navigateToBusCourse
114+
onBackPressed = onBackPressed,
115+
focusedMarkerParameter = focusedMarkerParam
143116
)
144-
Spacer(Modifier.height(marginBottom))
145-
}
146-
},
147-
sheetScrollState = sheetScrollState,
148-
marginBottom = marginBottom
149-
)
117+
},
118+
sheetContent = {
119+
Column(
120+
modifier = Modifier
121+
.fillMaxSize()
122+
// .nestedScroll(rememberNestedScrollInteropConnection())
123+
.verticalScroll(sheetScrollState),
124+
verticalArrangement = Arrangement.Top
125+
) {
126+
ItinerarySummary(
127+
modifier = Modifier
128+
.padding(horizontal = 16.dp),
129+
totalTimeMinute = itineraryInfo.totalTime / 60,
130+
boardingTime = itineraryInfo.boardingTime,
131+
legs = itineraryInfo.legs
132+
)
133+
Spacer(
134+
modifier = Modifier
135+
.fillMaxWidth()
136+
.height(31.dp) // 8 + 1 + 22
137+
.padding(top = 8.dp, bottom = 22.dp) // mimic the spacing
138+
.background(Color(0x0AFFFFFF)) // applies to the 1.dp middle line
139+
)
140+
ItineraryDetail(
141+
modifier = Modifier
142+
.padding(horizontal = 16.dp),
143+
courseInfo = itineraryInfo,
144+
busArrivalStatus = uiState.busArrivalStatus,
145+
departurePoint = uiState.departurePoint!!,
146+
destinationPoint = uiState.destinationPoint!!,
147+
onClickBusInfo = navigateToBusCourse
148+
)
149+
Spacer(Modifier.height(marginBottom))
150+
}
151+
},
152+
sheetScrollState = sheetScrollState,
153+
marginBottom = marginBottom
154+
)
155+
156+
RefreshLottieButton(
157+
modifier = Modifier
158+
.size(48.dp)
159+
.align(Alignment.BottomEnd)
160+
// TODO : 레이아웃 수정
161+
.offset(x = -16.dp, y = -50.dp)
162+
.background(shape = CircleShape, color = Color(0xff48484B))
163+
.padding(7.5.dp),
164+
onClick = onRefreshButtonClick
165+
)
166+
}
150167
}
151168

152169
@Preview

app/src/main/java/com/depromeet/team6/presentation/ui/itinerary/ItineraryViewModel.kt

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,26 @@
11
package com.depromeet.team6.presentation.ui.itinerary
22

3+
import android.util.SparseArray
4+
import androidx.lifecycle.viewModelScope
35
import com.depromeet.team6.domain.model.Address
6+
import com.depromeet.team6.domain.model.RealTimeBusArrival
47
import com.depromeet.team6.domain.model.course.CourseInfo
8+
import com.depromeet.team6.domain.model.course.TransportType
9+
import com.depromeet.team6.domain.usecase.GetBusArrivalUseCase
510
import com.depromeet.team6.presentation.util.base.BaseViewModel
611
import com.depromeet.team6.presentation.util.view.LoadState
712
import com.google.gson.Gson
813
import dagger.hilt.android.lifecycle.HiltViewModel
14+
import kotlinx.coroutines.async
15+
import kotlinx.coroutines.awaitAll
16+
import kotlinx.coroutines.launch
17+
import timber.log.Timber
918
import javax.inject.Inject
1019

1120
@HiltViewModel
12-
class ItineraryViewModel @Inject constructor() : BaseViewModel<ItineraryContract.ItineraryUiState, ItineraryContract.ItinerarySideEffect, ItineraryContract.ItineraryEvent>() {
21+
class ItineraryViewModel @Inject constructor(
22+
private val getBusArrivalUseCase: GetBusArrivalUseCase
23+
) : BaseViewModel<ItineraryContract.ItineraryUiState, ItineraryContract.ItinerarySideEffect, ItineraryContract.ItineraryEvent>() {
1324
override fun createInitialState(): ItineraryContract.ItineraryUiState = ItineraryContract.ItineraryUiState()
1425

1526
override suspend fun handleEvent(event: ItineraryContract.ItineraryEvent) {
@@ -22,7 +33,7 @@ class ItineraryViewModel @Inject constructor() : BaseViewModel<ItineraryContract
2233
)
2334
}
2435
}
25-
ItineraryContract.ItineraryEvent.RegisterAlarm -> TODO()
36+
ItineraryContract.ItineraryEvent.RefreshButtonClicked -> getRemainingBusArrivalTimes()
2637
}
2738
}
2839

@@ -39,9 +50,40 @@ class ItineraryViewModel @Inject constructor() : BaseViewModel<ItineraryContract
3950
itineraryInfo = courseInfo
4051
)
4152
}
53+
getRemainingBusArrivalTimes()
54+
}
55+
56+
private fun getRemainingBusArrivalTimes() {
57+
val newBusArrivalStatus = SparseArray<RealTimeBusArrival>()
58+
59+
viewModelScope.launch {
60+
val deferredList = currentState.itineraryInfo!!.legs.mapIndexedNotNull { idx, leg ->
61+
if (leg.transportType != TransportType.BUS) return@mapIndexedNotNull null
62+
63+
async {
64+
val result = getBusArrivalUseCase(
65+
routeName = leg.routeName!!,
66+
stationName = leg.startPoint.name,
67+
lat = leg.startPoint.lat,
68+
lon = leg.startPoint.lon
69+
)
70+
result.mapCatching {
71+
Timber.d("busArrivalStatusAPIResult : $it")
72+
idx to it.realTimeBusArrival[0]
73+
}.getOrNull()
74+
}
75+
}
76+
// 모든 API 호출이 끝난 뒤 결과를 처리
77+
val results = deferredList.awaitAll().filterNotNull()
78+
for ((idx, arrival) in results) {
79+
newBusArrivalStatus.put(idx, arrival)
80+
}
81+
setState {
82+
copy(
83+
busArrivalStatus = newBusArrivalStatus
84+
)
85+
}
86+
}
87+
Timber.d("busArrivalStatus ViewModel : ${currentState.busArrivalStatus}")
4288
}
43-
// fun getLegs() {
44-
// val mockData = getCourseSearchResults()
45-
// setEvent(ItineraryContract.ItineraryEvent.LoadLegsResult(mockData[0]))
46-
// }
4789
}

app/src/main/java/com/depromeet/team6/presentation/ui/itinerary/component/ItineraryDetail.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,36 @@
11
package com.depromeet.team6.presentation.ui.itinerary.component
22

3+
import android.util.SparseArray
34
import androidx.compose.foundation.layout.Column
45
import androidx.compose.foundation.layout.padding
56
import androidx.compose.runtime.Composable
67
import androidx.compose.ui.Modifier
78
import androidx.compose.ui.unit.dp
89
import com.depromeet.team6.domain.model.Address
10+
import com.depromeet.team6.domain.model.RealTimeBusArrival
911
import com.depromeet.team6.domain.model.course.CourseInfo
1012
import com.depromeet.team6.presentation.model.bus.BusArrivalParameter
13+
import timber.log.Timber
1114
import java.time.LocalDateTime
1215

1316
@Composable
1417
fun ItineraryDetail(
1518
courseInfo: CourseInfo,
19+
busArrivalStatus: SparseArray<RealTimeBusArrival>,
1620
departurePoint: Address,
1721
destinationPoint: Address,
1822
modifier: Modifier = Modifier,
1923
onClickBusInfo: (BusArrivalParameter) -> Unit = {}
2024
) {
2125
val arrivalDateTime = LocalDateTime.parse(courseInfo.departureTime).plusSeconds(courseInfo.totalTime.toLong())
22-
26+
Timber.d("busArrivalStatus : $busArrivalStatus")
2327
Column(
2428
modifier = modifier
2529
.padding(vertical = 12.dp)
2630
) {
2731
ItineraryInfoDetail(
2832
legs = courseInfo.legs,
33+
busArrivalStatus = busArrivalStatus,
2934
departureTime = courseInfo.departureTime,
3035
departureName = departurePoint.name,
3136
arrivalTime = arrivalDateTime.toString(),

0 commit comments

Comments
 (0)