Skip to content

Commit c09b983

Browse files
authored
Merge #92 -> develop
[Feat/#92] 산책 게시물 리스트 조회 API
2 parents 4da3ce2 + f85a52b commit c09b983

File tree

13 files changed

+502
-100
lines changed

13 files changed

+502
-100
lines changed

app/src/main/java/com/paw/key/core/designsystem/component/CourseCard.kt

Lines changed: 82 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package com.paw.key.core.designsystem.component
22

3-
import android.util.Log
43
import androidx.compose.foundation.Image
54
import androidx.compose.foundation.background
65
import androidx.compose.foundation.clickable
@@ -16,7 +15,6 @@ import androidx.compose.foundation.layout.padding
1615
import androidx.compose.foundation.layout.size
1716
import androidx.compose.foundation.layout.width
1817
import androidx.compose.foundation.shape.CircleShape
19-
import com.paw.key.R
2018
import androidx.compose.foundation.shape.RoundedCornerShape
2119
import androidx.compose.material3.HorizontalDivider
2220
import androidx.compose.material3.Icon
@@ -39,25 +37,39 @@ import androidx.compose.ui.tooling.preview.Preview
3937
import androidx.compose.ui.unit.dp
4038
import coil.compose.AsyncImage
4139
import coil.request.ImageRequest
40+
import com.paw.key.R
4241
import com.paw.key.core.designsystem.theme.PawKeyTheme
4342
import com.paw.key.core.util.noRippleClickable
44-
import kotlin.String
4543

4644
@Composable
4745
fun CourseCard(
4846
title: String,
49-
petName:String,
47+
petName: String,
5048
date: String,
51-
onCLickItem : () -> Unit,
49+
representativeImageUrl: String? = null, // 추가
50+
petProfileImageUrl: String? = null, // 추가
51+
descriptionTags: List<String> = emptyList(), // 추가
52+
onCLickItem: () -> Unit,
5253
modifier: Modifier = Modifier,
53-
isShared : Boolean = false, // true면 떠진거 false면 닫은거
54-
isRecord : Boolean = false // 기록한 아이템 - true면 하트, false면 공유 아이콘
54+
isShared: Boolean = false,
55+
isRecord: Boolean = false
5556
) {
57+
// 날짜 포맷 변환 함수
58+
fun formatDate(dateString: String): String {
59+
return try {
60+
// "2025-07-15T21:27:03.54498" -> "2025/07/15"
61+
val datePart = dateString.split("T")[0] // "2025-07-15"
62+
datePart.replace("-", "/") // "2025/07/15"
63+
} catch (e: Exception) {
64+
dateString // 실패하면 원본 반환
65+
}
66+
}
67+
5668
Column(
5769
modifier = modifier
5870
.padding(8.dp)
5971
.fillMaxWidth()
60-
.size(width = 328.dp , height = 240.dp)
72+
.size(width = 328.dp, height = 240.dp)
6173
.background(Color.White, shape = RoundedCornerShape(20.dp))
6274
.noRippleClickable {
6375
onCLickItem()
@@ -70,32 +82,38 @@ fun CourseCard(
7082
.aspectRatio(343f / 172f)
7183
.clip(RoundedCornerShape(10.dp))
7284
) {
73-
// 지도 이미지 Todo : 테스트용
74-
Image(
75-
painter = painterResource(id = R.drawable.dummy_map),
76-
contentDescription = null,
77-
modifier = Modifier
78-
.fillMaxSize()
79-
.padding(start = 8.dp, end = 8.dp, top = 8.dp)
80-
.clip(RoundedCornerShape(8.dp)),
81-
contentScale = ContentScale.Crop
82-
)
83-
84-
/*AsyncImage(
85-
model = ImageRequest.Builder(LocalContext.current)
86-
.data("https://pawkey-server.com/image.jpg") // ← 서버에서 받은 이미지 URL 넣깅
87-
.crossfade(true)
88-
.build(),
89-
contentDescription = null,
90-
modifier = Modifier.fillMaxSize(),
91-
contentScale = ContentScale.Crop
92-
)*/
85+
// 서버 이미지 또는 기본 이미지
86+
if (representativeImageUrl != null) {
87+
AsyncImage(
88+
model = ImageRequest.Builder(LocalContext.current)
89+
.data(representativeImageUrl)
90+
.crossfade(true)
91+
.build(),
92+
contentDescription = null,
93+
modifier = Modifier
94+
.fillMaxSize()
95+
.padding(start = 8.dp, end = 8.dp, top = 8.dp)
96+
.clip(RoundedCornerShape(8.dp)),
97+
contentScale = ContentScale.Crop
98+
)
99+
} else {
100+
// 기본 이미지
101+
Image(
102+
painter = painterResource(id = R.drawable.dummy_map),
103+
contentDescription = null,
104+
modifier = Modifier
105+
.fillMaxSize()
106+
.padding(start = 8.dp, end = 8.dp, top = 8.dp)
107+
.clip(RoundedCornerShape(8.dp)),
108+
contentScale = ContentScale.Crop
109+
)
110+
}
93111

94112
// 하단 그라데이션 오버레이
95113
Box(
96114
modifier = Modifier
97115
.fillMaxWidth()
98-
.height(LocalConfiguration.current.screenHeightDp.dp * 0.6f) // 높이 조절 가능
116+
.height(LocalConfiguration.current.screenHeightDp.dp * 0.6f)
99117
.padding(start = 8.dp, end = 8.dp, top = 8.dp)
100118
.align(Alignment.BottomCenter)
101119
.background(
@@ -115,19 +133,39 @@ fun CourseCard(
115133
verticalAlignment = Alignment.CenterVertically,
116134
modifier = Modifier
117135
.align(Alignment.BottomStart)
118-
.padding(start = 16.dp, end = 16.dp,bottom = 16.dp)
136+
.padding(start = 16.dp, end = 16.dp, bottom = 16.dp)
119137
) {
120-
AsyncImage(
121-
model = ImageRequest.Builder(LocalContext.current)
122-
.data("https://pawkey-server.com/image.jpg") // ← 서버에서 받은 이미지 URL 넣깅
123-
.crossfade(true)
124-
.build(),
125-
contentDescription = null,
126-
modifier = Modifier
127-
.size(40.dp)
128-
.clip(CircleShape), // 원형 크롭
129-
contentScale = ContentScale.Crop
130-
)
138+
// 반려견 프로필 이미지
139+
if (petProfileImageUrl != null) {
140+
AsyncImage(
141+
model = ImageRequest.Builder(LocalContext.current)
142+
.data(petProfileImageUrl)
143+
.crossfade(true)
144+
.build(),
145+
contentDescription = null,
146+
modifier = Modifier
147+
.size(40.dp)
148+
.clip(CircleShape),
149+
contentScale = ContentScale.Crop
150+
)
151+
} else {
152+
// 기본 프로필 이미지
153+
Box(
154+
modifier = Modifier
155+
.size(40.dp)
156+
.clip(CircleShape)
157+
.background(PawKeyTheme.colors.gray200),
158+
contentAlignment = Alignment.Center
159+
) {
160+
Icon(
161+
imageVector = ImageVector.vectorResource(id = R.drawable.ic_heart_default),
162+
contentDescription = null,
163+
tint = PawKeyTheme.colors.gray400,
164+
modifier = Modifier.size(20.dp)
165+
)
166+
}
167+
}
168+
131169
Spacer(modifier = Modifier.width(10.dp))
132170
Column {
133171
Text(
@@ -144,7 +182,7 @@ fun CourseCard(
144182
)
145183
Spacer(modifier = Modifier.width(8.dp))
146184
Text(
147-
text = date,
185+
text = formatDate(date), // 포맷된 날짜 사용
148186
style = PawKeyTheme.typography.caption12R,
149187
color = PawKeyTheme.colors.gray100
150188
)
@@ -189,16 +227,9 @@ fun CourseCard(
189227

190228
Spacer(modifier = Modifier.height(12.dp))
191229

192-
230+
// 서버에서 받은 태그들 사용
193231
ChipRow(
194-
tags = listOf(
195-
"이륜차 거의 없음",
196-
"배변 쓰레기통",
197-
"쉼터",
198-
"CCTV 있음",
199-
"물그릇 비치","이륜차 거의 없음",
200-
"배변 쓰레기통",
201-
"쉼터",),
232+
tags = descriptionTags,
202233
modifier = Modifier
203234
.padding(start = 16.dp, end = 16.dp)
204235
)

app/src/main/java/com/paw/key/data/di/RepositoryModule.kt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import com.paw.key.data.repositoryimpl.WalkSharedResultRepositoryImpl
1111
import com.paw.key.data.repositoryimpl.filter.FilterOptionRepositoryImpl
1212
import com.paw.key.data.repositoryimpl.sharedwalk.SharedWalkRepositoryImpl
1313
import com.paw.key.data.repositoryimpl.home.HomeRegionRepositoryImpl
14+
import com.paw.key.data.repositoryimpl.list.PostsListRepositoryImpl
1415
import com.paw.key.data.repositoryimpl.walklist.WalkListDetailRepositoryImpl
1516
import com.paw.key.data.repositoryimpl.walkreview.WalkReviewRepositoryImpl
1617
import com.paw.key.domain.repository.DummyRepository
@@ -22,6 +23,7 @@ import com.paw.key.domain.repository.WalkSharedResultRepository
2223
import com.paw.key.domain.repository.filter.FilterOptionRepository
2324
import com.paw.key.domain.repository.sharedwalk.SharedWalkRepository
2425
import com.paw.key.domain.repository.home.HomeRegionRepository
26+
import com.paw.key.domain.repository.list.PostsListRepository
2527
import com.paw.key.domain.repository.petprofile.PetProfileRepository
2628
import com.paw.key.domain.repository.walkcourse.WalkCourseRepository
2729
import com.paw.key.domain.repository.walklist.WalkListRepository
@@ -97,7 +99,7 @@ interface RepositoryModule {
9799
fun bindPetProfileRepository(
98100
impl: PetProfileRepositoryImpl
99101
): PetProfileRepository
100-
102+
101103
@Binds
102104
@Singleton
103105
fun bindWalkReviewRepository(
@@ -117,4 +119,10 @@ interface RepositoryModule {
117119
impl: FilterOptionRepositoryImpl
118120
) : FilterOptionRepository
119121

122+
//게시물 리스트
123+
@Binds
124+
@Singleton
125+
fun bindPostsListRepository(
126+
impl: PostsListRepositoryImpl
127+
) : PostsListRepository
120128
}

app/src/main/java/com/paw/key/data/di/ServiceModule.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import com.paw.key.data.service.RegionService
99
import com.paw.key.data.service.filter.FilterOptionService
1010
import com.paw.key.data.service.sharedwalk.SharedWalkService
1111
import com.paw.key.data.service.home.HomeRegionService
12+
import com.paw.key.data.service.list.PostsListService
1213
import com.paw.key.data.service.walkcourse.WalkCourseService
1314
import com.paw.key.data.service.walklist.WalkListDetailService
1415
import com.paw.key.data.service.walkreview.WalkReviewService
@@ -85,4 +86,9 @@ object ServiceModule {
8586
@Singleton
8687
fun provideFilterOptionService(retrofit: Retrofit): FilterOptionService =
8788
retrofit.create()
89+
90+
@Provides
91+
@Singleton
92+
fun providePostsListService(retrofit: Retrofit): PostsListService =
93+
retrofit.create()
8894
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.paw.key.data.dto.request.list
2+
3+
import kotlinx.serialization.SerialName
4+
import kotlinx.serialization.Serializable
5+
6+
@Serializable
7+
data class PostsListRequestDto(
8+
@SerialName("durationStart")
9+
val durationStart: Int? = null,
10+
@SerialName("durationEnd")
11+
val durationEnd: Int? = null,
12+
@SerialName("selectedOptions")
13+
val selectedOptions: List<TraitList>? = null
14+
)
15+
16+
@Serializable
17+
data class TraitList(
18+
@SerialName("categoryId")
19+
val categoryId: Int? = null,
20+
@SerialName("optionsIds")
21+
val optionIds: List<Int>? = null
22+
)
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package com.paw.key.data.dto.response.list
2+
3+
import com.paw.key.domain.model.entity.list.ListEntity
4+
import com.paw.key.domain.model.entity.list.PostEntity
5+
import com.paw.key.domain.model.entity.list.WriterEntity
6+
import kotlinx.serialization.SerialName
7+
import kotlinx.serialization.Serializable
8+
9+
@Serializable
10+
data class PostsListResponseDto (
11+
@SerialName("posts")
12+
val posts : List<PostDto>
13+
)
14+
15+
@Serializable
16+
data class PostDto (
17+
@SerialName("postId")
18+
val postId : Int,
19+
@SerialName("createdAt")
20+
val createdAt : String,
21+
@SerialName("isLike")
22+
val isLike : Boolean,
23+
@SerialName("title")
24+
val title : String,
25+
@SerialName("representativeImageUrl")
26+
val representativeImageUrl : String,
27+
@SerialName("routeId")
28+
val routeId : Int,
29+
@SerialName("writer")
30+
val writer : WriterDto,
31+
@SerialName("descriptionTags")
32+
val descriptionTags : List<String>
33+
)
34+
35+
@Serializable
36+
data class WriterDto (
37+
@SerialName("userId")
38+
val userId : Int,
39+
@SerialName("petName")
40+
val petName : String,
41+
@SerialName("petProfileImageUrl")
42+
val petProfileImageUrl : String,
43+
)
44+
45+
fun PostsListResponseDto.toEntity(): ListEntity {
46+
return ListEntity(
47+
posts = posts.map { it.toEntity() }
48+
)
49+
}
50+
51+
fun PostDto.toEntity(): PostEntity {
52+
return PostEntity(
53+
postId = postId,
54+
createdAt = createdAt,
55+
isLike = isLike,
56+
title = title,
57+
representativeImageUrl = representativeImageUrl,
58+
routeId = routeId,
59+
writer = writer.toEntity(),
60+
descriptionTags = descriptionTags
61+
)
62+
}
63+
64+
fun WriterDto.toEntity(): WriterEntity {
65+
return WriterEntity(
66+
userId = userId,
67+
petName = petName,
68+
petProfileImageUrl = petProfileImageUrl
69+
)
70+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.paw.key.data.remote.datasource.list
2+
3+
import com.paw.key.data.dto.request.list.PostsListRequestDto
4+
import com.paw.key.data.service.list.PostsListService
5+
import javax.inject.Inject
6+
7+
class PostsListDataSource @Inject constructor(
8+
private val service: PostsListService
9+
) {
10+
suspend fun postList(userId: Int, request: PostsListRequestDto) =
11+
service.postList(userId, request)
12+
13+
suspend fun getAllPosts(userId: Int) =
14+
service.postList(userId, PostsListRequestDto(
15+
durationStart = 0,
16+
durationEnd = 0,
17+
selectedOptions = emptyList()
18+
))
19+
}

0 commit comments

Comments
 (0)