Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
b548355
docs : fix file name
starshape7 Jan 21, 2026
3c920ce
feat : add route to ParticipatingStudy
starshape7 Jan 21, 2026
9f1666e
chore : remove comment
starshape7 Jan 21, 2026
b422e57
feat : add meetball
starshape7 Jan 21, 2026
5fe71c7
feat : add studies/me
starshape7 Jan 21, 2026
1ba0317
fix : meetball 외부 구현으로 변경
starshape7 Jan 21, 2026
4c48297
feat : participatingStudy 구현
starshape7 Jan 21, 2026
6baf46a
fix : route 수정
starshape7 Jan 21, 2026
195c8b8
Merge branch 'refs/heads/develop' into feat/#5-mypage-screen구현
starshape7 Jan 21, 2026
389f96d
feat : 신청확인 버튼 외부 접근 구현
starshape7 Jan 21, 2026
51e455a
feat : empty state 추가
starshape7 Jan 21, 2026
1e14e81
feat : isOwner 필드 추가 반영
starshape7 Jan 21, 2026
6d73778
feat : route 추가
starshape7 Jan 21, 2026
911429e
feat : weather no_data 반환 케이스 추가
starshape7 Jan 21, 2026
642c5bb
fix : checkApplied button 위치 변경
starshape7 Jan 21, 2026
c7a856b
fix : myRecruitingStudy route 추가
starshape7 Jan 21, 2026
48942a9
feat : myRecruitingStudy screen 구현
starshape7 Jan 21, 2026
14ae354
feat : myRecruitingStudy api 연동
starshape7 Jan 21, 2026
87eddce
feat : waitingStudy route 추가
starshape7 Jan 21, 2026
24ea079
feat : navigation to waitingStudy 추가
starshape7 Jan 21, 2026
e9b3f9f
feat : emptyState 추가
starshape7 Jan 21, 2026
00ad875
fix : background color 수정
starshape7 Jan 21, 2026
81db329
feat : waitingStudy Screen 구현
starshape7 Jan 21, 2026
634b84c
feat : waitingStudy api 연결
starshape7 Jan 21, 2026
694bdd4
fix : navigation 수정
starshape7 Jan 21, 2026
7cfa612
chore : 이름 불일치 수정
starshape7 Jan 21, 2026
e2c63bf
chore : log 수정
starshape7 Jan 21, 2026
2a967df
fix : 간격 수정
starshape7 Jan 21, 2026
95b71b1
fix : spinner 사이즈 반영 수정
starshape7 Jan 21, 2026
a461e2a
fix : spinner 기본 사이즈 수정
starshape7 Jan 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import com.umcspot.spot.ui.extension.screenWidthDp
@Composable
fun SpotSpinner(
modifier: Modifier = Modifier,
size: Dp = 24.dp,
size: Dp = screenWidthDp(24.dp),
speed: Float = 1f,
isPlaying: Boolean = true,
iterations: Int = LottieConstants.IterateForever,
Expand Down Expand Up @@ -53,7 +53,7 @@ fun SpotSpinner(
speed = speed,
dynamicProperties = dynamicProps,
modifier = modifier
.size(screenWidthDp(size))
.size(size)
.semantics {
if (contentDescription != null) this.contentDescription = contentDescription
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,15 @@ package com.umcspot.spot.designsystem.component.study

import androidx.annotation.DrawableRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material3.HorizontalDivider
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
Expand All @@ -25,12 +23,10 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil.compose.AsyncImage
import com.umcspot.spot.designsystem.R
import com.umcspot.spot.designsystem.component.button.ClickSurface
import com.umcspot.spot.designsystem.shapes.SpotShapes
import com.umcspot.spot.designsystem.theme.G300
import com.umcspot.spot.designsystem.theme.SpotTheme
import com.umcspot.spot.model.ImageRef
import com.umcspot.spot.study.model.StudyResult
Expand All @@ -42,15 +38,17 @@ fun StudyListItem(
item: StudyResult,
modifier: Modifier = Modifier,
onClick: (StudyResult) -> Unit = {},
meetballSlot: (@Composable () -> Unit)? = null,
checkAppliedSlot: (@Composable () -> Unit)? = null
) {
ClickSurface(
onClick = { onClick(item)},
onClick = { onClick(item) },
modifier = modifier
) {
Row(
modifier = Modifier.padding(screenWidthDp(7.dp)),
horizontalArrangement = Arrangement.spacedBy(13.dp),
verticalAlignment = Alignment.CenterVertically
verticalAlignment = Alignment.Top
) {
StudyThumbnail(
imageRef = item.profileImageUrl,
Expand All @@ -62,22 +60,31 @@ fun StudyListItem(
// 텍스트 + 통계
Column(
modifier = Modifier
.fillMaxWidth()
.wrapContentWidth()
.height(screenHeightDp(73.dp))
.padding(screenHeightDp(4.dp))
){
Text(
text = item.name,
style = SpotTheme.typography.h5,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Text(
text = item.description,
style = SpotTheme.typography.regular_400,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
) {
Row{
Column{
Text(
text = item.name,
style = SpotTheme.typography.h5,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Text(
text = item.description,
style = SpotTheme.typography.regular_400,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
if (checkAppliedSlot != null) {
Spacer(modifier = Modifier.weight(1f))

checkAppliedSlot()
}
}

Spacer(modifier = Modifier.weight(1f))

Expand All @@ -95,6 +102,11 @@ fun StudyListItem(
)
}
}
Spacer(modifier = Modifier.weight(1f))

if (meetballSlot != null) {
meetballSlot()
}
}
}
}
Expand All @@ -109,6 +121,9 @@ private fun Stat(
val display = if (count1 != 0) "${cap(count1)} / ${cap(count2)}" else cap(count2)

Row(
modifier = Modifier
.width(screenWidthDp(56.dp))
.height(screenHeightDp(17.dp)),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(screenWidthDp(4.dp))
) {
Expand Down Expand Up @@ -142,6 +157,7 @@ fun StudyThumbnail(
modifier = modifier
)
}

is ImageRef.Url -> {
AsyncImage(
model = img.url,
Expand All @@ -151,6 +167,7 @@ fun StudyThumbnail(
modifier = modifier
)
}

ImageRef.None, null -> {
Image(
painter = painterResource(placeholder),
Expand All @@ -174,10 +191,10 @@ fun StudyThumbnail(

/* ============== Preview ============== */

@Preview(showBackground = true, widthDp = 300)
@Preview(showBackground = true, widthDp = 326)
@Composable
private fun StudyListItemPreview() {
SpotTheme{
SpotTheme {
StudyListItem(
item = StudyResult(
id = 1,
Expand All @@ -189,9 +206,10 @@ private fun StudyListItemPreview() {
isLiked = false,
hitCount = 1200,
profileImageUrl = ImageRef.Name("spot_logo"),
isOwner = false
),
modifier = Modifier.padding(10.dp),
onClick = {}
onClick = {},
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,10 @@ interface StudyDataSource {
cursor: Long?,
size: Int
): BaseResponse<StudyResponseDto>

suspend fun getMyPageStudy(
statuses : List<String>,
cursor: Long?,
size: Int
): BaseResponse<StudyResponseDto>
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,15 @@ class StudyDataSourceImpl @Inject constructor(
cursor = cursor,
size = size
)

override suspend fun getMyPageStudy(
statuses: List<String>,
cursor: Long?,
size: Int
): BaseResponse<StudyResponseDto> =
studyService.getMyPageStudy(
statuses = statuses,
cursor = cursor,
size = size
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ data class Study (
@SerialName("isLiked")
val isLiked: Boolean,

@SerialName("isOwner")
val isOwner: Boolean,

@SerialName("hitCount")
val hitCount: Int = 0,

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ fun Study.toDomain() : StudyResult =
currentMembers = this.currentMembers,
likeCount = this.likeCount,
isLiked = this.isLiked,
isOwner = this.isOwner,
hitCount = this.hitCount,
profileImageUrl = this.profileImageUrl.toImageRef()
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,4 +129,49 @@ class StudyRepositoryImpl @Inject constructor(
}.onFailure { e ->
Log.e("StudyRepository", "getLikedStudies failed", e)
}

override suspend fun getParticipatingStudy(
cursor: Long?,
size: Int
): Result<StudyResultList> =
runCatching {
val response = studyDataSource.getMyPageStudy(
statuses = listOf("OWNER","APPROVED"),
cursor = cursor,
size = size
)
response.result.toDomainList()
}.onFailure { e ->
Log.e("StudyRepository", "getParticipatingStudy failed", e)
}

override suspend fun getRecruitingStudy(
cursor: Long?,
size: Int
): Result<StudyResultList> =
runCatching {
val response = studyDataSource.getMyPageStudy(
statuses = listOf("OWNER"),
cursor = cursor,
size = size
)
response.result.toDomainList()
}.onFailure { e ->
Log.e("StudyRepository", "getRecruitingStudy failed", e)
}

override suspend fun getWaitingStudy(
cursor: Long?,
size: Int
): Result<StudyResultList> =
runCatching {
val response = studyDataSource.getMyPageStudy(
statuses = listOf("APPLIED"),
cursor = cursor,
size = size
)
response.result.toDomainList()
}.onFailure { e ->
Log.e("StudyRepository", "getRecruitingStudy failed", e)
Comment on lines +174 to +175
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

복사-붙여넣기 오류: 잘못된 로그 메시지.

getWaitingStudy 메서드에서 로그 메시지가 "getRecruitingStudy failed"로 잘못 표시되어 있습니다. 디버깅 시 혼란을 줄 수 있습니다.

🐛 제안하는 수정
         }.onFailure { e ->
-            Log.e("StudyRepository", "getRecruitingStudy failed", e)
+            Log.e("StudyRepository", "getWaitingStudy failed", e)
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
}.onFailure { e ->
Log.e("StudyRepository", "getRecruitingStudy failed", e)
}.onFailure { e ->
Log.e("StudyRepository", "getWaitingStudy failed", e)
🤖 Prompt for AI Agents
In
`@data/study/src/main/java/com/umcspot/spot/study/repositoryimpl/StudyRepositoryImpl.kt`
around lines 174 - 175, getWaitingStudy의 onFailure 블록에서 복사·붙여넣기로 인해 로그 메시지가 잘못
`"getRecruitingStudy failed"`로 기록되어 있으니, StudyRepositoryImpl의 getWaitingStudy
메서드 내부 onFailure 핸들러(Log.e 호출)를 찾아 로그 메시지를 올바르게 `"getWaitingStudy failed"`로 수정해
주세요; 필요하면 메시지에 예외 정보(e)를 그대로 포함시켜 디버깅 정보를 보존하세요.

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,11 @@ interface StudyService {
@Part request: MultipartBody.Part,
@Part imageFile: MultipartBody.Part?
): BaseResponse<CreateStudyResponseDto>

@GET("/api/studies/me")
suspend fun getMyPageStudy(
@Query("statuses") statuses: List<String>,
@Query("cursor") cursor: Long?,
@Query("size") size: Int
): BaseResponse<StudyResponseDto>
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package com.umcspot.spot.weather.dto.response

import android.annotation.SuppressLint
import com.umcspot.spot.model.WeatherType
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import java.time.LocalTime

@SuppressLint("UnsafeOptInUsageError")
@Serializable
Expand All @@ -20,7 +18,7 @@ data class WeatherResponse(
val header: WeatherHeader,

@SerialName("body")
val body: WeatherBody
val body: WeatherBody? = null
Comment on lines 20 to +21
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for all usages of response.body to check null handling
rg -n --type=kotlin 'response\.body' -C3

Repository: SPOTeam/Android-V2

Length of output: 814


🏁 Script executed:

#!/bin/bash
# Search for all usages of toDomain() to see error handling context
rg -n --type=kotlin 'toDomain\(\)' -C3

# Also search for WeatherResponseDto usages
rg -n --type=kotlin 'WeatherResponseDto' -C2

# Check if body is accessed in other ways
rg -n --type=kotlin '\.body' data/weather -C2

Repository: SPOTeam/Android-V2

Length of output: 24024


🏁 Script executed:

#!/bin/bash
# Check the full context around the toDomain() call in WeatherRepositoryImpl
sed -n '60,85p' data/weather/src/main/java/com/umcspot/spot/weather/repositoryimpl/WeatherRepositoryImpl.kt

Repository: SPOTeam/Android-V2

Length of output: 854


response.body!! 사용으로 null 처리 오류 발생 위험

body 필드를 nullable로 변경한 것은 API 응답에서 body가 누락될 수 있는 상황을 처리하기 위한 것으로 보입니다. 그러나 WeatherMapper.kt 라인 10에서 response.body!!.items.item으로 non-null assertion을 사용하고 있어, body가 실제로 null인 경우 NullPointerException이 발생합니다.

현재는 WeatherRepositoryImpl.kt에서 runCatching { }으로 감싸져 있어 예외가 처리되지만, 이는 임시 방편입니다. 매퍼 함수 내에서 null 케이스를 명시적으로 처리하도록 수정하세요.

WeatherMapper.kt 라인 10
val items = response.body!!.items.item
🤖 Prompt for AI Agents
In
`@data/weather/src/main/java/com/umcspot/spot/weather/dto/response/WeatherResponseDto.kt`
around lines 20 - 21, WeatherMapper currently uses response.body!! (val items =
response.body!!.items.item) which will NPE if body is null; update the mapper to
handle the nullable body field explicitly by replacing the non-null assertion
with a safe-check: detect when response.body is null and either return an empty
items list or a default Weather DTO (or throw a controlled exception type), then
map items.item only when body is non-null; ensure the mapper function name where
this occurs (the WeatherMapper mapping function that computes items) is updated
accordingly and adjust callers (e.g., WeatherRepositoryImpl) to rely on the
mapper's explicit null-handling instead of relying on runCatching to absorb
NPEs.

)

@SuppressLint("UnsafeOptInUsageError")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import java.time.LocalTime
import java.time.format.DateTimeFormatter

fun WeatherResponseDto.toDomain(): WeatherResult {
val items = response.body.items.item
val items = response.body!!.items.item

// 기준 시간 (HHmm)
val time = items.firstOrNull()?.baseTime
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ data class StudyResult(
val currentMembers: Int = 0,
val likeCount: Int = 0,
val isLiked : Boolean,
val isOwner : Boolean,
val hitCount: Int = 0,
val profileImageUrl: ImageRef
) {
Expand All @@ -51,6 +52,7 @@ data class StudyResult(
currentMembers = mem,
likeCount = 10 + index * 2,
isLiked = index%2 == 0,
isOwner = index%2 == 1,
hitCount = 150 + index * 20,
profileImageUrl = ImageRef.Name("ic_study_default")
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,19 @@ interface StudyRepository {
cursor: Long?,
size: Int,
): Result<StudyResultList>

suspend fun getParticipatingStudy(
cursor: Long?,
size: Int,
): Result<StudyResultList>

suspend fun getRecruitingStudy(
cursor: Long?,
size: Int,
): Result<StudyResultList>

suspend fun getWaitingStudy(
cursor: Long?,
size: Int,
): Result<StudyResultList>
}
Loading