Skip to content

Commit 2b43ad8

Browse files
authored
Merge pull request #23 from YAPP-Github/feature/NDGL-68
[NDGL-68] 따라가기 여행 템플릿 관련 API 연동
2 parents 83c6ce4 + 846981b commit 2b43ad8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+801
-585
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ google-services.json
3535

3636
# Claude Code local settings
3737
.claude/settings.local.json
38+
.mcp.json
3839

3940
# Android Profiling
4041
*.hprof

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@
9595
<string name="place_detail_tab_info">정보</string>
9696
<string name="place_detail_tab_photo">사진</string>
9797
<string name="place_detail_add_schedule">일정 추가하기</string>
98+
<string name="place_detail_review_format">리뷰 %s</string>
9899
<string name="place_detail_website">웹사이트 보기</string>
99100
<string name="place_detail_menu">메뉴</string>
100101
<string name="place_detail_plan_b_message">아래에서 플랜 B를 알아봐요!</string>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.yapp.ndgl.core.util
2+
3+
import java.util.Locale
4+
5+
private val countryCodeRegex = Regex("^[A-Za-z]{2}$")
6+
7+
fun String.toCountryName(): String {
8+
if (!matches(countryCodeRegex)) return ""
9+
val locale = Locale.Builder().setRegion(this).build()
10+
return locale.displayCountry
11+
}

data/travel/src/main/java/com/yapp/ndgl/data/travel/api/TravelTemplateApi.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ package com.yapp.ndgl.data.travel.api
33
import com.yapp.ndgl.data.core.model.BaseResponse
44
import com.yapp.ndgl.data.travel.model.PopularTravelTemplates
55
import com.yapp.ndgl.data.travel.model.RecommendTravelTemplates
6+
import com.yapp.ndgl.data.travel.model.TravelTemplateContentInfo
7+
import com.yapp.ndgl.data.travel.model.TravelTemplateItinerary
68
import retrofit2.http.GET
9+
import retrofit2.http.Path
710
import retrofit2.http.Query
811

912
interface TravelTemplateApi {
@@ -16,4 +19,15 @@ interface TravelTemplateApi {
1619

1720
@GET("/api/v1/travel-templates/recommend")
1821
suspend fun getRecommendTravelTemplates(): BaseResponse<RecommendTravelTemplates>
22+
23+
@GET("/api/v1/travel-templates/{id}/itinerary")
24+
suspend fun getTravelTemplateItinerary(
25+
@Path("id") id: Long,
26+
@Query("day") day: Int? = null,
27+
): BaseResponse<TravelTemplateItinerary>
28+
29+
@GET("/api/v1/travel-templates/{id}/content-card")
30+
suspend fun getTravelTemplateContentInfo(
31+
@Path("id") id: Long,
32+
): BaseResponse<TravelTemplateContentInfo>
1933
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.yapp.ndgl.data.travel.model
2+
3+
import kotlinx.serialization.SerialName
4+
import kotlinx.serialization.Serializable
5+
6+
@Serializable
7+
enum class TransportCategory {
8+
@SerialName("DRIVING")
9+
DRIVING,
10+
11+
@SerialName("TRANSIT")
12+
TRANSIT,
13+
14+
@SerialName("WALKING")
15+
WALKING,
16+
17+
@SerialName("BICYCLING")
18+
BICYCLING,
19+
20+
@SerialName("TAXI")
21+
TAXI,
22+
23+
@SerialName("TWO_WHEELER")
24+
TWO_WHEELER,
25+
26+
@SerialName("FERRY")
27+
FERRY,
28+
29+
@SerialName("FLIGHT")
30+
FLIGHT,
31+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.yapp.ndgl.data.travel.model
2+
3+
import kotlinx.serialization.SerialName
4+
import kotlinx.serialization.Serializable
5+
6+
@Serializable
7+
data class TravelTemplateContentInfo(
8+
val travelId: Long,
9+
@SerialName("country")
10+
val countryCode: String,
11+
val city: String,
12+
val budgetPerPerson: Int? = null,
13+
val nights: Int,
14+
val days: Int,
15+
val program: ProgramInfo,
16+
) {
17+
@Serializable
18+
data class ProgramInfo(
19+
val title: String,
20+
@SerialName("name")
21+
val creatorName: String,
22+
val thumbnail: String? = null,
23+
val profileImage: String? = null,
24+
val link: String? = null,
25+
val summary: String,
26+
)
27+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.yapp.ndgl.data.travel.model
2+
3+
import kotlinx.serialization.Serializable
4+
5+
@Serializable
6+
data class TravelTemplateItinerary(
7+
val itineraries: List<ItineraryItem>,
8+
) {
9+
@Serializable
10+
data class ItineraryItem(
11+
val id: Long,
12+
val day: Int,
13+
val sequence: Int,
14+
val distanceKm: Double? = null,
15+
val transportation: List<Transportation>? = null,
16+
val travelerTips: List<String>? = null,
17+
val planB: List<PlanBPlace>? = null,
18+
val estimatedDuration: Int,
19+
val place: ItineraryPlace,
20+
)
21+
22+
@Serializable
23+
data class Transportation(
24+
val mode: TransportCategory,
25+
val timeMin: Int,
26+
)
27+
28+
@Serializable
29+
data class PlanBPlace(
30+
val name: String,
31+
val feature: String? = null,
32+
)
33+
34+
@Serializable
35+
data class ItineraryPlace(
36+
val googlePlaceId: String,
37+
val thumbnail: String? = null,
38+
val latitude: Double,
39+
val longitude: Double,
40+
val name: String,
41+
val regularOpeningHours: String? = null,
42+
val googleMapsUri: String? = null,
43+
val category: PlaceCategory,
44+
val priceRange: String? = null,
45+
)
46+
}

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import com.yapp.ndgl.data.core.model.getData
44
import com.yapp.ndgl.data.travel.api.TravelTemplateApi
55
import com.yapp.ndgl.data.travel.model.PopularTravelTemplates
66
import com.yapp.ndgl.data.travel.model.RecommendTravelTemplates
7+
import com.yapp.ndgl.data.travel.model.TravelTemplateContentInfo
8+
import com.yapp.ndgl.data.travel.model.TravelTemplateItinerary
79
import javax.inject.Inject
810
import javax.inject.Singleton
911

@@ -22,4 +24,12 @@ class TravelTemplateRepository @Inject constructor(
2224
suspend fun getRecommendTravelTemplates(): RecommendTravelTemplates {
2325
return travelTemplateApi.getRecommendTravelTemplates().getData()
2426
}
27+
28+
suspend fun getTravelTemplateItinerary(travelId: Long, day: Int): TravelTemplateItinerary {
29+
return travelTemplateApi.getTravelTemplateItinerary(id = travelId, day = day).getData()
30+
}
31+
32+
suspend fun getTravelTemplateContentInfo(travelId: Long): TravelTemplateContentInfo {
33+
return travelTemplateApi.getTravelTemplateContentInfo(id = travelId).getData()
34+
}
2535
}

feature/travel/src/main/java/com/yapp/ndgl/feature/travel/datepicker/DatePickerScreen.kt

Lines changed: 64 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@ package com.yapp.ndgl.feature.travel.datepicker
33
import androidx.compose.foundation.background
44
import androidx.compose.foundation.layout.Box
55
import androidx.compose.foundation.layout.Column
6-
import androidx.compose.foundation.layout.PaddingValues
76
import androidx.compose.foundation.layout.Spacer
87
import androidx.compose.foundation.layout.fillMaxSize
98
import androidx.compose.foundation.layout.fillMaxWidth
109
import androidx.compose.foundation.layout.height
1110
import androidx.compose.foundation.layout.padding
11+
import androidx.compose.foundation.layout.statusBarsPadding
12+
import androidx.compose.material3.Scaffold
1213
import androidx.compose.material3.Text
1314
import androidx.compose.runtime.Composable
1415
import androidx.compose.runtime.getValue
@@ -31,7 +32,6 @@ import kotlinx.datetime.LocalDate
3132
internal fun DatePickerRoute(
3233
viewModel: DatePickerViewModel = hiltViewModel(),
3334
navigateBack: () -> Unit = {},
34-
innerPadding: PaddingValues = PaddingValues(),
3535
) {
3636
val state by viewModel.collectAsState()
3737

@@ -68,7 +68,6 @@ internal fun DatePickerRoute(
6868
clickBackButton = navigateBack,
6969
dismissDialog = ::dismissDialog,
7070
clickTravelButton = ::clickTravelButton,
71-
innerPadding = innerPadding,
7271
)
7372

7473
viewModel.collectSideEffect { sideEffect ->
@@ -90,78 +89,84 @@ private fun DatePickerScreen(
9089
clickBackButton: () -> Unit,
9190
dismissDialog: () -> Unit,
9291
clickTravelButton: () -> Unit,
93-
innerPadding: PaddingValues,
9492
) {
95-
Box(
96-
modifier = Modifier
97-
.fillMaxSize()
98-
.background(NDGLTheme.colors.white)
99-
.padding(innerPadding),
100-
) {
101-
Column(
102-
modifier = Modifier
103-
.fillMaxSize()
104-
.padding(bottom = 16.dp),
105-
) {
93+
Scaffold(
94+
modifier = Modifier.fillMaxSize(),
95+
topBar = {
10696
NDGLNavigationBar(
107-
modifier = Modifier.fillMaxWidth(),
97+
modifier = Modifier
98+
.fillMaxWidth()
99+
.statusBarsPadding(),
108100
headline = stringResource(R.string.date_picker_title),
109101
textAlignType = NDGLNavigationBarAttr.TextAlignType.CENTER,
110102
leadingIcon = R.drawable.ic_28_chevron_left,
111103
onLeadingIconClick = clickBackButton,
112104
)
113-
Spacer(Modifier.height(24.dp))
105+
},
106+
) { innerPadding ->
107+
Box(
108+
modifier = Modifier
109+
.fillMaxSize()
110+
.background(NDGLTheme.colors.white)
111+
.padding(innerPadding),
112+
) {
114113
Column(
115114
modifier = Modifier
116115
.fillMaxSize()
117-
.padding(horizontal = 24.dp),
116+
.padding(top = 24.dp, bottom = 16.dp),
118117
) {
119-
CalendarView(
120-
year = state.currentYear,
121-
month = state.currentMonth,
122-
startDate = state.startDate,
123-
endDate = state.endDate,
124-
onDateSelected = selectDate,
125-
onPreviousMonth = selectPreviousMonth,
126-
onNextMonth = selectNextMonth,
127-
)
118+
Column(
119+
modifier = Modifier
120+
.fillMaxSize()
121+
.padding(horizontal = 24.dp),
122+
) {
123+
CalendarView(
124+
year = state.currentYear,
125+
month = state.currentMonth,
126+
startDate = state.startDate,
127+
endDate = state.endDate,
128+
onDateSelected = selectDate,
129+
onPreviousMonth = selectPreviousMonth,
130+
onNextMonth = selectNextMonth,
131+
)
128132

129-
if (state.isInsufficientDuration) {
130-
Spacer(Modifier.height(24.dp))
131-
Text(
132-
stringResource(
133-
R.string.date_picker_error_insufficient,
134-
),
135-
color = NDGLTheme.colors.red500,
136-
style = NDGLTheme.typography.bodySmMedium,
133+
if (state.isInsufficientDuration) {
134+
Spacer(Modifier.height(24.dp))
135+
Text(
136+
stringResource(
137+
R.string.date_picker_error_insufficient,
138+
),
139+
color = NDGLTheme.colors.red500,
140+
style = NDGLTheme.typography.bodySmMedium,
141+
)
142+
}
143+
Spacer(Modifier.weight(1f))
144+
NDGLCTAButton(
145+
modifier = Modifier
146+
.fillMaxWidth(),
147+
type = NDGLCTAButtonAttr.Type.PRIMARY,
148+
size = NDGLCTAButtonAttr.Size.LARGE,
149+
status = if (state.isDateSelected) {
150+
NDGLCTAButtonAttr.Status.ACTIVE
151+
} else {
152+
NDGLCTAButtonAttr.Status.DISABLED
153+
},
154+
label = stringResource(R.string.date_picker_complete),
155+
onClick = clickCompleteButton,
137156
)
138157
}
139-
Spacer(Modifier.weight(1f))
140-
NDGLCTAButton(
141-
modifier = Modifier
142-
.fillMaxWidth(),
143-
type = NDGLCTAButtonAttr.Type.PRIMARY,
144-
size = NDGLCTAButtonAttr.Size.LARGE,
145-
status = if (state.isDateSelected) {
146-
NDGLCTAButtonAttr.Status.ACTIVE
147-
} else {
148-
NDGLCTAButtonAttr.Status.DISABLED
149-
},
150-
label = stringResource(R.string.date_picker_complete),
151-
onClick = clickCompleteButton,
152-
)
153158
}
154-
}
155159

156-
if (state.showDialog) {
157-
NDGLModal(
158-
onDismissRequest = dismissDialog,
159-
title = stringResource(R.string.date_picker_modal_title),
160-
body = stringResource(R.string.date_picker_modal_body),
161-
negativeButtonText = stringResource(R.string.date_picker_modal_negative),
162-
positiveButtonText = stringResource(R.string.date_picker_modal_positive),
163-
onPositiveButtonClick = clickTravelButton,
164-
)
160+
if (state.showDialog) {
161+
NDGLModal(
162+
onDismissRequest = dismissDialog,
163+
title = stringResource(R.string.date_picker_modal_title),
164+
body = stringResource(R.string.date_picker_modal_body),
165+
negativeButtonText = stringResource(R.string.date_picker_modal_negative),
166+
positiveButtonText = stringResource(R.string.date_picker_modal_positive),
167+
onPositiveButtonClick = clickTravelButton,
168+
)
169+
}
165170
}
166171
}
167172
}
@@ -181,7 +186,6 @@ private fun DatePickerScreenPreview() {
181186
clickBackButton = {},
182187
dismissDialog = {},
183188
clickTravelButton = {},
184-
innerPadding = PaddingValues(),
185189
)
186190
}
187191
}
@@ -206,7 +210,6 @@ private fun DatePickerScreenWithDialogPreview() {
206210
clickBackButton = {},
207211
dismissDialog = {},
208212
clickTravelButton = {},
209-
innerPadding = PaddingValues(),
210213
)
211214
}
212215
}

0 commit comments

Comments
 (0)