Skip to content

Commit 043614d

Browse files
committed
[NDGL-89] feat: 내 여행 탭 - 다가오는 여행 목록 섹션 추가
1 parent 9f72d9b commit 043614d

9 files changed

Lines changed: 533 additions & 2 deletions

File tree

core/ui/src/main/res/drawable/img_empty_suitcase.xml

Lines changed: 108 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
package com.yapp.ndgl.data.travel.api
22

33
import com.yapp.ndgl.data.core.model.BaseResponse
4+
import com.yapp.ndgl.data.travel.model.UpcomingTravelList
45
import com.yapp.ndgl.data.travel.model.UpcomingTravelResponse
56
import retrofit2.http.GET
7+
import retrofit2.http.Query
68

79
interface UserTravelApi {
810
@GET("/api/v1/travels/upcoming")
911
suspend fun getUpcomingTravel(): BaseResponse<UpcomingTravelResponse>
12+
13+
@GET("/api/v1/travels/upcoming/list")
14+
suspend fun getUpcomingTravelList(
15+
@Query("page") page: Int? = null,
16+
@Query("size") size: Int? = null,
17+
): BaseResponse<UpcomingTravelList>
1018
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.yapp.ndgl.data.travel.model
2+
3+
import com.yapp.ndgl.data.core.serializer.LocalDateSerializer
4+
import kotlinx.serialization.Serializable
5+
import java.time.LocalDate
6+
7+
@Serializable
8+
data class UpcomingTravelList(
9+
val content: List<UpcomingTravel>,
10+
val hasNext: Boolean,
11+
) {
12+
@Serializable
13+
data class UpcomingTravel(
14+
val id: Long,
15+
val title: String,
16+
val country: String,
17+
val city: String,
18+
@Serializable(with = LocalDateSerializer::class)
19+
val startDate: LocalDate,
20+
@Serializable(with = LocalDateSerializer::class)
21+
val endDate: LocalDate,
22+
val nights: Int,
23+
val days: Int,
24+
val templateId: Long,
25+
val thumbnail: String? = null,
26+
val profileImage: String? = null,
27+
)
28+
}

data/travel/src/main/java/com/yapp/ndgl/data/travel/repository/UserTravelRepository.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.yapp.ndgl.data.travel.repository
33
import com.yapp.ndgl.data.core.model.error.HttpResponseException
44
import com.yapp.ndgl.data.core.model.getData
55
import com.yapp.ndgl.data.travel.api.UserTravelApi
6+
import com.yapp.ndgl.data.travel.model.UpcomingTravelList
67
import com.yapp.ndgl.data.travel.model.UpcomingTravelResponse
78
import java.net.HttpURLConnection
89
import javax.inject.Inject
@@ -23,4 +24,8 @@ class UserTravelRepository @Inject constructor(
2324
}
2425
}
2526
}
27+
28+
suspend fun getUpcomingTravelList(): UpcomingTravelList {
29+
return userTravelApi.getUpcomingTravelList().getData()
30+
}
2631
}

feature/travel/src/main/java/com/yapp/ndgl/feature/travel/mytravel/MyTravelContract.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,14 @@ import com.yapp.ndgl.core.base.UiIntent
66
import com.yapp.ndgl.core.base.UiSideEffect
77
import com.yapp.ndgl.core.base.UiState
88
import com.yapp.ndgl.data.travel.model.PlaceCategory
9+
import kotlinx.collections.immutable.ImmutableList
10+
import kotlinx.collections.immutable.persistentListOf
911
import java.time.LocalDate
1012

1113
@Immutable
1214
data class MyTravelState(
1315
val upcomingTravel: UpcomingTravel? = null,
16+
val upcomingTravels: ImmutableList<UpcomingTravelItem> = persistentListOf(),
1417
) : UiState {
1518
@Stable
1619
sealed class UpcomingTravel {
@@ -40,23 +43,36 @@ data class MyTravelState(
4043
) : UpcomingTravel()
4144
}
4245

46+
@Immutable
4347
data class TravelPlace(
4448
val placeId: String,
4549
val category: PlaceCategory,
4650
val estimatedDuration: Int,
4751
val name: String,
4852
val thumbnailUrl: String,
4953
)
54+
55+
@Immutable
56+
data class UpcomingTravelItem(
57+
val travelId: Long,
58+
val title: String,
59+
val startDate: LocalDate,
60+
val endDate: LocalDate,
61+
val imageUrl: String,
62+
val dDay: Int,
63+
)
5064
}
5165

5266
sealed interface MyTravelIntent : UiIntent {
5367
data class ClickTravel(val travelId: Long) : MyTravelIntent
5468
data class ClickTravelDetail(val travelId: Long) : MyTravelIntent
5569
data class ClickPlaceDetail(val placeId: String) : MyTravelIntent
70+
data object ClickFindNewTravel : MyTravelIntent
5671
}
5772

5873
sealed interface MyTravelSideEffect : UiSideEffect {
5974
data class NavigateToFollowTravel(val travelId: Long, val days: Int) : MyTravelSideEffect
6075
data class NavigateToTravelDetail(val travelId: Long) : MyTravelSideEffect
6176
data class NavigateToTravelPlace(val placeId: String) : MyTravelSideEffect
77+
data object NavigateToPopularTravelList : MyTravelSideEffect
6278
}

feature/travel/src/main/java/com/yapp/ndgl/feature/travel/mytravel/MyTravelScreen.kt

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.yapp.ndgl.feature.travel.mytravel
22

33
import androidx.compose.foundation.background
44
import androidx.compose.foundation.layout.Arrangement
5+
import androidx.compose.foundation.layout.PaddingValues
56
import androidx.compose.foundation.layout.fillMaxSize
67
import androidx.compose.foundation.layout.fillMaxWidth
78
import androidx.compose.foundation.layout.padding
@@ -13,6 +14,7 @@ import androidx.compose.runtime.getValue
1314
import androidx.compose.ui.Alignment
1415
import androidx.compose.ui.Modifier
1516
import androidx.compose.ui.tooling.preview.Preview
17+
import androidx.compose.ui.unit.dp
1618
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
1719
import com.yapp.ndgl.core.ui.R
1820
import com.yapp.ndgl.core.ui.designsystem.NDGLNavigationBar
@@ -37,6 +39,9 @@ internal fun MyTravelRoute(
3739
onPlaceClick = { placeId ->
3840
viewModel.onIntent(MyTravelIntent.ClickPlaceDetail(placeId = placeId))
3941
},
42+
onNewTravelFindClick = {
43+
viewModel.onIntent(MyTravelIntent.ClickFindNewTravel)
44+
},
4045
)
4146

4247
viewModel.collectSideEffect { sideEffect ->
@@ -53,6 +58,10 @@ internal fun MyTravelRoute(
5358
is MyTravelSideEffect.NavigateToTravelPlace -> navigateToTravelPlace(
5459
sideEffect.placeId,
5560
)
61+
62+
MyTravelSideEffect.NavigateToPopularTravelList -> {
63+
// FIXME: navigate to popular travel list
64+
}
5665
}
5766
}
5867
}
@@ -62,6 +71,7 @@ private fun MyTravelScreen(
6271
state: MyTravelState,
6372
onTravelClick: (Long) -> Unit,
6473
onPlaceClick: (String) -> Unit,
74+
onNewTravelFindClick: () -> Unit,
6575
) {
6676
Scaffold(
6777
modifier = Modifier.fillMaxSize(),
@@ -89,7 +99,11 @@ private fun MyTravelScreen(
8999
modifier = Modifier
90100
.padding(innerPadding)
91101
.fillMaxSize(),
92-
verticalArrangement = Arrangement.Center,
102+
contentPadding = PaddingValues(
103+
top = 20.dp,
104+
bottom = 100.dp,
105+
),
106+
verticalArrangement = Arrangement.spacedBy(40.dp),
93107
horizontalAlignment = Alignment.CenterHorizontally,
94108
) {
95109
if (state.upcomingTravel != null) {
@@ -102,6 +116,13 @@ private fun MyTravelScreen(
102116
)
103117
}
104118
}
119+
item {
120+
UpcomingTravelListSection(
121+
upcomingTravels = state.upcomingTravels,
122+
onUserTravelClick = onTravelClick,
123+
onNewTravelFindClick = onNewTravelFindClick,
124+
)
125+
}
105126
}
106127
}
107128
}
@@ -114,6 +135,7 @@ private fun MyTravelScreenPreview() {
114135
state = MyTravelState(),
115136
onTravelClick = {},
116137
onPlaceClick = {},
138+
onNewTravelFindClick = {},
117139
)
118140
}
119141
}

feature/travel/src/main/java/com/yapp/ndgl/feature/travel/mytravel/MyTravelViewModel.kt

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import com.yapp.ndgl.core.base.BaseViewModel
55
import com.yapp.ndgl.core.util.suspendRunCatching
66
import com.yapp.ndgl.data.travel.repository.UserTravelRepository
77
import dagger.hilt.android.lifecycle.HiltViewModel
8+
import kotlinx.collections.immutable.persistentListOf
9+
import kotlinx.collections.immutable.toImmutableList
810
import kotlinx.coroutines.launch
911
import java.time.LocalDate
1012
import java.time.temporal.ChronoUnit
@@ -77,14 +79,35 @@ class MyTravelViewModel @Inject constructor(
7779
}
7880

7981
private fun loadUpcomingTravelList() {
80-
// FIXME: 다가오는 여행 목록 조회
82+
viewModelScope.launch {
83+
suspendRunCatching { userTravelRepository.getUpcomingTravelList() }
84+
.onSuccess { result ->
85+
val today = LocalDate.now()
86+
val travels = result.content.map { travel ->
87+
val dDay = ChronoUnit.DAYS.between(today, travel.startDate).toInt()
88+
MyTravelState.UpcomingTravelItem(
89+
travelId = travel.id,
90+
title = travel.title,
91+
startDate = travel.startDate,
92+
endDate = travel.endDate,
93+
imageUrl = travel.thumbnail ?: "",
94+
dDay = dDay,
95+
)
96+
}.toImmutableList()
97+
reduce { copy(upcomingTravels = travels) }
98+
}
99+
.onFailure {
100+
reduce { copy(upcomingTravels = persistentListOf()) }
101+
}
102+
}
81103
}
82104

83105
override suspend fun handleIntent(intent: MyTravelIntent) {
84106
when (intent) {
85107
is MyTravelIntent.ClickTravel -> postNavigateToFollowTravel(travelId = intent.travelId)
86108
is MyTravelIntent.ClickTravelDetail -> postNavigateToTravelDetail(travelId = intent.travelId)
87109
is MyTravelIntent.ClickPlaceDetail -> postNavigateToPlaceDetail(placeId = intent.placeId)
110+
MyTravelIntent.ClickFindNewTravel -> postNavigateToPopularTravelList()
88111
}
89112
}
90113

@@ -99,4 +122,8 @@ class MyTravelViewModel @Inject constructor(
99122
private fun postNavigateToPlaceDetail(placeId: String) {
100123
postSideEffect(MyTravelSideEffect.NavigateToTravelPlace(placeId = placeId))
101124
}
125+
126+
private fun postNavigateToPopularTravelList() {
127+
postSideEffect(MyTravelSideEffect.NavigateToPopularTravelList)
128+
}
102129
}

0 commit comments

Comments
 (0)