Skip to content

Comments

feat : AlertScreen 구현#28

Merged
starshape7 merged 2 commits intodevelopfrom
feat/#26-alert-screen구현
Jan 16, 2026

Hidden character warning

The head ref may contain hidden characters: "feat/#26-alert-screen\uad6c\ud604"
Merged

feat : AlertScreen 구현#28
starshape7 merged 2 commits intodevelopfrom
feat/#26-alert-screen구현

Conversation

@starshape7
Copy link
Collaborator

@starshape7 starshape7 commented Jan 15, 2026

Related issue 🛠

Work Description 📝

  • AlertScreen API 연동 및 ui 구현

Screenshot 📸

image

Uncompleted Tasks 😅

  • 스터디 리스트 반환 내용 확인 필요

PR Point 📌

트러블 슈팅 💥

Summary by CodeRabbit

  • 새로운 기능

    • 알림 항목 클릭 시 해당 연구 상세 페이지로 이동
  • 개선사항

    • 이미지 로딩 개선 — URL/로컬/드로어블 등 다양한 소스 지원
    • 알림 화면 UI 단순화 및 목록 로딩/빈 상태 처리 개선
  • 변경

    • 기존의 참여/불참 적용 알림(관련 버튼 및 별도 목록) UI가 제거되어 알림 흐름이 간소화됨

✏️ Tip: You can customize this high-level summary in your review settings.

@starshape7 starshape7 linked an issue Jan 15, 2026 that may be closed by this pull request
2 tasks
@coderabbitai
Copy link

coderabbitai bot commented Jan 15, 2026

Walkthrough

Alert 관련 API와 데이터 모델을 단순화하고 AppliedAlert 관련 엔티티와 UI를 제거했으며, 이미지 처리 API를 Painter에서 ImageRef 기반으로 전환하고 Alert 화면·뷰모델·네비게이션 흐름을 리팩토링했습니다.

Changes

Cohort / File(s) 변경 요약
디자인시스템 — Enroll 항목 제거
core/designsystem/src/main/java/com/umcspot/spot/designsystem/component/study/EnrollStudyItem.kt
신청 알림 항목을 렌더링하던 EnrollStudyListItem 컴포저블 파일 전체 삭제
디자인시스템 — 이미지 API 전환
core/designsystem/src/main/java/com/umcspot/spot/designsystem/shapes/Shapes.kt
Painter 인자 → ImageRef로 변경; ShapeImageBox/ShapeImageWithBadge에 ImageRef 해석·로딩 로직 추가(로컬/URL/리소스/없음 처리)
디자인시스템 — 리소스 수정
core/designsystem/src/main/res/drawable/announce.xml
벡터 뷰포트 및 치수 48x48 → 16x16으로 축소 및 패스 데이터 조정
코어 모델 변경
core/model/src/main/java/com/umcspot/spot/model/Global.kt
AlertKind 열거형 제거
데이터 레이어 — 인터페이스/구현 정리
data/alert/src/main/java/com/umcspot/spot/alert/datasource/AlertDataSource.kt, data/alert/src/main/java/com/umcspot/spot/alert/datasourceimpl/AlertDataSourceImpl.kt
getAppliedAlerts() 메서드 및 관련 타입(AppliedAlertResponseDto) 제거
데이터 레이어 — DTO 구조 변경
data/alert/src/main/java/com/umcspot/spot/alert/dto/response/AlertResponseDto.kt
응답 컬렉션 alertsstudies; AlertItem 필드 리팩토링(id→applicationId, kind→studyId, profileImageRef 추가 등); AppliedAlert 관련 DTO 제거
데이터 레이어 — 매퍼 업데이트
data/alert/src/main/java/com/umcspot/spot/alert/mapper/AlertMapper.kt
AppliedAlert 매핑 제거; AlertItem.toDomain()이 applicationId/studyId 기반으로 매핑하도록 변경
저장소 구현 변경
data/alert/src/main/java/com/umcspot/spot/alert/repositoryimpl/AlertRepositoryImpl.kt
의존성: AlertServiceAlertDataSource로 교체; getAppliedAlerts() 및 더미 회복 로직 제거; 실패 시 로깅 추가
서비스 API 경로 변경
data/alert/src/main/java/com/umcspot/spot/alert/service/AlertService.kt
기존 엔드포인트 변경(예: "/api/v1/service" → "/api/studies/applied"); getAppliedAlerts() 제거
도메인 모델 단순화
domain/alert/src/main/java/com/umcspot/spot/alert/model/AlertResult.kt
AlertResult.alertsstudies; AlertInfo 필드 재정의(applicationId, studyId, title, studyImageRes); AppliedAlert 관련 도메인 타입 제거
저장소 인터페이스 정리
domain/alert/src/main/java/com/umcspot/spot/alert/repository/AlertRepository.kt
getAppliedAlerts() 제거
UI 리팩토링 — Alert 화면
feature/alert/src/main/java/com/umcspot/spot/alert/AlertScreen.kt
UI 흐름 단순화(단일 alerts UiState 기반), 여러 서브컴포저블 제거, AlertRow 클릭 시 studyId: Long 전달로 변경, 이미지 로딩에 ImageRef 사용
뷰모델 단순화
feature/alert/src/main/java/com/umcspot/spot/alert/AlertViewmodel.kt
AlertUiStateAlertState 단순화, 병렬 호출 제거 후 단일 getAlerts() 호출로 통합, onClick/onAccept/onReject 관련 메서드 제거, 실패 로깅 추가
네비게이션 — 클릭 전달 추가
feature/alert/src/main/java/com/umcspot/spot/alert/navigation/AlertNavigation.kt, feature/main/src/main/java/com/umcspot/spot/main/MainNavHost.kt
alertGraphonClickAlert: (Long) -> Unit 파라미터 추가 및 MainNavHost에서 스터디 상세 네비게이션으로 연결

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant Screen as AlertScreen
    participant VM as AlertViewModel
    participant Repo as AlertRepository
    participant DS as AlertDataSource
    participant Svc as AlertService
    participant API as API

    User->>Screen: 화면 진입
    Screen->>VM: load()
    VM->>VM: uiState = Loading
    VM->>Repo: getAlerts()
    Repo->>DS: getAlerts()
    DS->>Svc: getAlerts() 호출
    Svc->>API: GET /api/studies/applied
    API-->>Svc: AlertResponseDto(studies)
    Svc-->>DS: 응답 반환
    DS-->>Repo: 응답 반환
    Repo->>Repo: DTO → 도메인 매핑
    Repo-->>VM: Result<AlertResult>
    alt Success
        VM->>VM: studies 비었는지 검사
        alt 비었음
            VM->>VM: uiState = Empty
        else
            VM->>VM: uiState = Success(AlertResult)
        end
    else Failure
        VM->>VM: Log.e()
        VM->>VM: uiState = Failure
    end
    VM-->>Screen: uiState 업데이트
    Screen->>User: 목록 렌더링
    User->>Screen: 항목 클릭(studyId)
    Screen->>MainNav: onClickAlert(studyId)
    MainNav->>User: navigateToStudyDetail(studyId)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested labels

🐶 종훈, 🍒 [FEAT]

Suggested reviewers

  • fredleeJH

Poem

🐰 알림들이 줄지어 가는 길,
Applied는 안녕, studies가 인사하네.
ImageRef 들고 새 길을 가며,
뷰모델은 가볍게, 네비는 목적지를 가리키네.
당근 한 입, 변경 완료! 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed 제목은 주요 변경사항인 AlertScreen 구현을 명확하게 설명하고 있으며, 변경 내용과 관련이 있습니다.
Description check ✅ Passed PR 설명이 필수 섹션 대부분을 포함하고 있으나, PR Point와 트러블 슈팅 섹션이 비어있습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

🧹 Recent nitpick comments
feature/alert/src/main/java/com/umcspot/spot/alert/AlertScreen.kt (4)

50-59: LaunchedEffect(Unit) 두 개를 하나로 병합 가능

두 개의 LaunchedEffect(Unit) 블록이 연속으로 있습니다. 동일한 key를 사용하므로 하나로 합쳐 코드를 간결하게 할 수 있습니다.

♻️ 병합 제안
-    LaunchedEffect(Unit) {
-        onRegisterScrollToTop {
-            scope.launch { listState.animateScrollToItem(0) }
-        }
-    }
-
-    LaunchedEffect(Unit) { viewModel.load() }
+    LaunchedEffect(Unit) {
+        onRegisterScrollToTop {
+            scope.launch { listState.animateScrollToItem(0) }
+        }
+        viewModel.load()
+    }

164-175: 하드코딩된 문자열을 리소스로 추출 권장

UI에 직접 작성된 한국어 문자열이 있습니다. 다국어 지원(i18n)을 고려하여 strings.xml로 추출하면 유지보수성이 향상됩니다.

♻️ 문자열 리소스 추출 예시
<!-- res/values/strings.xml -->
<string name="alert_application_accepted">%s 신청이 수락되었어요!</string>
<string name="alert_greeting_prompt">게시판에서 가입 인사를 나눠보세요!</string>
Text(
    text = stringResource(R.string.alert_application_accepted, data.title),
    ...
)
Text(
    text = stringResource(R.string.alert_greeting_prompt),
    ...
)

144-148: 고정 너비 대신 fillMaxWidth() 사용 고려

screenWidthDp(326.dp) 고정 너비는 figma 기준이지만, 부모 패딩이 이미 적용된 상태에서 fillMaxWidth()를 사용하면 다양한 화면 크기에 더 유연하게 대응할 수 있습니다.

♻️ 수정 제안
     BlankButton(
         modifier = Modifier
-            .width(screenWidthDp(326.dp))
+            .fillMaxWidth()
             .height(screenHeightDp(65.dp)),
         onClick = { onClick(data.studyId) },
     ) {

86-98: Failure 상태에서 오류 정보 표시 고려

현재 FailureEmpty 상태를 동일하게 처리하고 있습니다. 사용자에게 오류 원인을 알려주거나 재시도 버튼을 제공하면 UX가 개선될 수 있습니다.

♻️ Failure 상태 분리 예시
-            is UiState.Failure, is UiState.Empty -> {
+            is UiState.Empty -> {
                 Box(
                     modifier = Modifier.fillMaxSize(),
                     contentAlignment = Alignment.Center
                 ) {
                     EmptyAlert(
                         modifier = Modifier.fillMaxSize(),
                         painter = painterResource(R.drawable.alert),
                         alertTitle = "아직 알림이 없어요.",
                         alertDes = "스팟에서 내 목표를 이뤄봐요."
                     )
                 }
             }
+
+            is UiState.Failure -> {
+                Box(
+                    modifier = Modifier.fillMaxSize(),
+                    contentAlignment = Alignment.Center
+                ) {
+                    EmptyAlert(
+                        modifier = Modifier.fillMaxSize(),
+                        painter = painterResource(R.drawable.alert),
+                        alertTitle = "알림을 불러올 수 없어요.",
+                        alertDes = "네트워크 상태를 확인해 주세요."
+                    ) {
+                        // 재시도 버튼 추가 가능
+                    }
+                }
+            }

📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ffb7d34 and c3a5d1a.

📒 Files selected for processing (1)
  • feature/alert/src/main/java/com/umcspot/spot/alert/AlertScreen.kt
🧰 Additional context used
🧬 Code graph analysis (1)
feature/alert/src/main/java/com/umcspot/spot/alert/AlertScreen.kt (5)
core/ui/src/main/java/com/umcspot/spot/ui/extension/DimensionExt.kt (2)
  • screenWidthDp (18-23)
  • screenHeightDp (11-16)
core/designsystem/src/main/java/com/umcspot/spot/designsystem/component/Spinner.kt (1)
  • SpotSpinner (23-61)
core/designsystem/src/main/java/com/umcspot/spot/designsystem/component/empty/EmptyAlert.kt (1)
  • EmptyAlert (34-71)
core/designsystem/src/main/java/com/umcspot/spot/designsystem/component/button/ImageButton.kt (1)
  • BlankButton (112-151)
core/designsystem/src/main/java/com/umcspot/spot/designsystem/shapes/Shapes.kt (1)
  • ShapeImageBox (113-172)
🔇 Additional comments (1)
feature/alert/src/main/java/com/umcspot/spot/alert/AlertScreen.kt (1)

38-43: AlertScreen 파라미터 통합 확인됨

onClickAlert: (Long) -> Unit 파라미터가 모든 호출부에서 올바르게 전달되고 있습니다. AlertScreenContent → AlertRow를 거쳐 최종적으로 BlankButton의 onClick 핸들러에서 onClick(data.studyId)로 사용되어 네비게이션 연동이 완전히 구현되었습니다.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@data/alert/src/main/java/com/umcspot/spot/alert/mapper/AlertMapper.kt`:
- Around line 9-15: AlertItem.toDomain currently calls
this.applicationId.toLong() and this.studyId.toLong() which can throw
NumberFormatException for empty/invalid/null strings; change the mapper to
safely parse these values (use toLongOrNull on applicationId and studyId),
handle null results (e.g., return a default/fallback, throw a clear
domain-specific exception, or propagate a validation error) and update AlertInfo
construction to use the validated Longs; alternatively, if API spec guarantees
numeric IDs, update the DTO types so applicationId and studyId are Long and
remove string-to-long conversion in AlertItem.toDomain.

In `@domain/alert/src/main/java/com/umcspot/spot/alert/model/AlertResult.kt`:
- Around line 10-15: The AlertInfo data class contains an unused applicationId
field; remove applicationId from AlertInfo and update any mappers and DTOs that
populate it (e.g., AlertMapper) so they no longer map or expect that property,
and update any tests or serialization logic accordingly; if you intend to keep
it for future use instead, add a clear comment above AlertInfo explaining the
planned use and why it is kept, and ensure callers (e.g., places building
AlertInfo and the AlertRow onClick flow) are consistent with the chosen
approach.

In `@feature/alert/src/main/java/com/umcspot/spot/alert/AlertScreen.kt`:
- Around line 115-118: The current LazyColumn items call uses key = { it.studyId
} which can collide when a study has multiple alerts; change the key selector to
use the alert's unique identifier (applicationId) instead (i.e., replace key = {
it.studyId } with key = { it.applicationId }) and ensure applicationId is
stable/non-null (fall back to a stable alternative like toString()/hash if
nullable) so each alert item has a unique, stable key.
🧹 Nitpick comments (9)
core/designsystem/src/main/java/com/umcspot/spot/designsystem/shapes/Shapes.kt (4)

128-147: painter 타입이 nullable일 필요가 없습니다.

when 표현식의 모든 분기에서 non-null Painter를 반환하므로, Painter? 타입과 line 160의 null 체크가 불필요합니다. 이는 dead code로 남게 됩니다.

♻️ 제안하는 수정
-    val painter: Painter? = when (imageRef) {
+    val painter: Painter = when (imageRef) {
         ImageRef.None -> painterResource(fallback)

         is ImageRef.Name -> {
             val resId = remember(imageRef.name) {
                 context.resources.getIdentifier(
                     imageRef.name,
                     "drawable",
                     context.packageName
                 )
             }
             if (resId != 0) painterResource(resId) else painterResource(fallback)
         }

         is ImageRef.Url ->
             rememberAsyncImagePainter(model = imageRef.url)

         is ImageRef.LocalUri ->
             rememberAsyncImagePainter(model = imageRef.uri)
     }
-        if (painter != null) {
-            Image(
-                painter = painter,
-                contentDescription = null,
-                contentScale = contentScale,
-                modifier = Modifier
-                    .wrapContentSize()
-                    .padding(padding)
-            )
-        }
+        Image(
+            painter = painter,
+            contentDescription = null,
+            contentScale = contentScale,
+            modifier = Modifier
+                .wrapContentSize()
+                .padding(padding)
+        )

Also applies to: 160-169


88-88: 중복된 screenWidthDp(size) 적용

외부 Box(line 88)와 내부 ShapeImageBox(line 92) 모두에 동일한 screenWidthDp(size)가 적용되어 있습니다. 기능적으로는 문제없지만, 내부 컴포넌트에서 fillMaxSize()를 사용하면 코드가 더 명확해질 수 있습니다.

Also applies to: 92-92


107-107: contentDescription이 null로 하드코딩됨 - 접근성 고려 필요

contentDescriptionnull로 설정되어 있어 스크린 리더 사용자에게 이미지 정보가 전달되지 않습니다. 접근성(Accessibility)을 위해 이미지 설명을 매개변수로 받는 것을 고려해 주세요.

단, 순수 장식용 이미지라면 null이 적절합니다.

Also applies to: 163-163


142-146: URL/LocalUri 이미지 로딩 시 placeholder 및 error 처리 권장

rememberAsyncImagePainter를 사용할 때 네트워크 지연이나 로딩 실패 시 사용자 경험을 위해 placeholdererror 파라미터를 추가하는 것이 좋습니다.

♻️ 제안하는 수정
         is ImageRef.Url ->
-            rememberAsyncImagePainter(model = imageRef.url)
+            rememberAsyncImagePainter(
+                model = imageRef.url,
+                placeholder = painterResource(fallback),
+                error = painterResource(fallback)
+            )

         is ImageRef.LocalUri ->
-            rememberAsyncImagePainter(model = imageRef.uri)
+            rememberAsyncImagePainter(
+                model = imageRef.uri,
+                placeholder = painterResource(fallback),
+                error = painterResource(fallback)
+            )
data/alert/src/main/java/com/umcspot/spot/alert/repositoryimpl/AlertRepositoryImpl.kt (1)

13-19: 구현이 적절합니다.

DataSource 추상화를 통해 Repository가 Service에 직접 의존하지 않도록 개선되었습니다. runCatchingonFailure를 사용한 에러 처리 패턴도 적절합니다.

다만, android.util.Log 대신 프로젝트 전반에서 사용하는 로깅 추상화(Timber 등)가 있다면 일관성을 위해 해당 라이브러리 사용을 고려해 보세요.

data/alert/src/main/java/com/umcspot/spot/alert/dto/response/AlertResponseDto.kt (1)

17-29: ID 필드 타입을 API 스펙에 맞게 조정 권장

applicationIdstudyId가 String으로 선언되어 있지만, mapper에서 toLong()으로 변환하고 있습니다. API 응답이 숫자 형태라면 DTO에서 직접 Long 타입으로 선언하는 것이 직렬화 오류를 조기에 감지할 수 있고 mapper에서의 변환 로직을 제거할 수 있습니다.

♻️ Long 타입 사용 제안
 data class AlertItem(
     `@SerialName`("applicationId")
-    val applicationId: String,
+    val applicationId: Long,

     `@SerialName`("studyId")
-    val studyId: String,
+    val studyId: Long,

     `@SerialName`("title")
     val title: String,

     `@SerialName`("profileImageRef")
     val profileImageRef: ImageRef,
 )
feature/alert/src/main/java/com/umcspot/spot/alert/AlertViewmodel.kt (1)

23-25: AlertState를 ViewModel 외부로 분리 고려

AlertState data class가 AlertViewModel 내부에 정의되어 있어, 다른 곳에서 참조할 때 AlertViewModel.AlertState로 접근해야 합니다. 재사용성과 테스트 용이성을 위해 별도 파일이나 클래스 외부로 분리하는 것을 권장합니다.

feature/alert/src/main/java/com/umcspot/spot/alert/AlertScreen.kt (2)

86-98: Failure와 Empty 상태의 UI 동일 처리

UiState.FailureUiState.Empty가 동일한 UI로 표시되어 사용자가 네트워크 오류인지 실제로 알림이 없는 것인지 구분할 수 없습니다. 에러 상태에 대해 별도의 UI나 재시도 버튼을 제공하는 것을 고려해 주세요.


144-148: 고정 크기 사용에 대한 검토 필요

BlankButtonscreenWidthDp(326.dp)screenHeightDp(65.dp)로 고정 크기가 설정되어 있습니다. 부모 컨테이너의 padding과 함께 사용될 때 일부 화면에서 잘림 현상이 발생할 수 있습니다. fillMaxWidth()wrapContentHeight() 또는 IntrinsicSize 사용을 검토해 주세요.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e66ab6e and ffb7d34.

📒 Files selected for processing (16)
  • core/designsystem/src/main/java/com/umcspot/spot/designsystem/component/study/EnrollStudyItem.kt
  • core/designsystem/src/main/java/com/umcspot/spot/designsystem/shapes/Shapes.kt
  • core/designsystem/src/main/res/drawable/announce.xml
  • core/model/src/main/java/com/umcspot/spot/model/Global.kt
  • data/alert/src/main/java/com/umcspot/spot/alert/datasource/AlertDataSource.kt
  • data/alert/src/main/java/com/umcspot/spot/alert/datasourceimpl/AlertDataSourceImpl.kt
  • data/alert/src/main/java/com/umcspot/spot/alert/dto/response/AlertResponseDto.kt
  • data/alert/src/main/java/com/umcspot/spot/alert/mapper/AlertMapper.kt
  • data/alert/src/main/java/com/umcspot/spot/alert/repositoryimpl/AlertRepositoryImpl.kt
  • data/alert/src/main/java/com/umcspot/spot/alert/service/AlertService.kt
  • domain/alert/src/main/java/com/umcspot/spot/alert/model/AlertResult.kt
  • domain/alert/src/main/java/com/umcspot/spot/alert/repository/AlertRepository.kt
  • feature/alert/src/main/java/com/umcspot/spot/alert/AlertScreen.kt
  • feature/alert/src/main/java/com/umcspot/spot/alert/AlertViewmodel.kt
  • feature/alert/src/main/java/com/umcspot/spot/alert/navigation/AlertNavigation.kt
  • feature/main/src/main/java/com/umcspot/spot/main/MainNavHost.kt
💤 Files with no reviewable changes (5)
  • data/alert/src/main/java/com/umcspot/spot/alert/datasource/AlertDataSource.kt
  • core/model/src/main/java/com/umcspot/spot/model/Global.kt
  • core/designsystem/src/main/java/com/umcspot/spot/designsystem/component/study/EnrollStudyItem.kt
  • data/alert/src/main/java/com/umcspot/spot/alert/datasourceimpl/AlertDataSourceImpl.kt
  • domain/alert/src/main/java/com/umcspot/spot/alert/repository/AlertRepository.kt
🧰 Additional context used
🧬 Code graph analysis (3)
feature/alert/src/main/java/com/umcspot/spot/alert/navigation/AlertNavigation.kt (1)
feature/alert/src/main/java/com/umcspot/spot/alert/AlertScreen.kt (1)
  • AlertScreen (38-101)
core/designsystem/src/main/java/com/umcspot/spot/designsystem/shapes/Shapes.kt (1)
core/ui/src/main/java/com/umcspot/spot/ui/extension/DimensionExt.kt (1)
  • screenWidthDp (18-23)
feature/alert/src/main/java/com/umcspot/spot/alert/AlertScreen.kt (4)
core/ui/src/main/java/com/umcspot/spot/ui/extension/DimensionExt.kt (2)
  • screenWidthDp (18-23)
  • screenHeightDp (11-16)
core/designsystem/src/main/java/com/umcspot/spot/designsystem/component/empty/EmptyAlert.kt (1)
  • EmptyAlert (34-71)
core/designsystem/src/main/java/com/umcspot/spot/designsystem/component/button/ImageButton.kt (1)
  • BlankButton (112-151)
core/designsystem/src/main/java/com/umcspot/spot/designsystem/shapes/Shapes.kt (1)
  • ShapeImageBox (113-172)
🔇 Additional comments (7)
core/designsystem/src/main/res/drawable/announce.xml (1)

1-12: LGTM!

벡터 드로어블이 48dp에서 16dp로 적절하게 리사이징되었습니다. viewport 크기와 pathData가 새로운 좌표계에 맞게 올바르게 재계산되었으며, fillColor도 그대로 유지되어 있습니다.

core/designsystem/src/main/java/com/umcspot/spot/designsystem/shapes/Shapes.kt (2)

16-16: LGTM!

ImageRef 기반 이미지 처리를 위한 필요한 import들이 적절하게 추가되었습니다. Coil의 rememberAsyncImagePainterscreenWidthDp 확장 함수 사용이 적절합니다.

Also applies to: 24-24, 28-28, 31-32


75-110: ImageRef 패턴 도입 승인

ShapeImageWithBadge에서 ImageRef 기반 이미지 처리로 전환한 것은 좋은 설계입니다. 다양한 이미지 소스(None, Name, Url, LocalUri)를 유연하게 처리할 수 있게 되었습니다.

data/alert/src/main/java/com/umcspot/spot/alert/service/AlertService.kt (1)

7-11: LGTM!

API 엔드포인트가 /api/studies/applied로 업데이트되었고, 인터페이스가 깔끔하게 정의되어 있습니다. AppliedAlert 관련 메서드 제거와 함께 단순화된 구조가 적절합니다.

feature/alert/src/main/java/com/umcspot/spot/alert/navigation/AlertNavigation.kt (1)

17-29: LGTM!

onClickAlert: (Long) -> Unit 콜백이 적절하게 추가되었고, AlertScreen으로 올바르게 전달됩니다. 기존 onRegisterScrollToTop 패턴과 일관된 구조입니다.

feature/main/src/main/java/com/umcspot/spot/main/MainNavHost.kt (1)

143-147: LGTM!

알림 클릭 시 스터디 상세 화면으로 이동하는 네비게이션 연결이 적절합니다. registerStudyGraphnavigateToStudyDetail 호출과 동일한 패턴을 따르고 있습니다.

feature/alert/src/main/java/com/umcspot/spot/alert/AlertScreen.kt (1)

38-44: LGTM!

contentPaddingonClickAlert 콜백을 통한 외부 연동이 잘 설계되어 있습니다. rememberSaveable을 사용한 리스트 상태 보존도 적절합니다.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines 9 to 15
fun AlertItem.toDomain() : AlertInfo =
AlertInfo (
id = this.id,
kind = this.kind,
applicationId = this.applicationId.toLong(),
studyId = this.studyId.toLong(),
title = this.title,
studyImageRes = this.studyImageRes,
subtitle = this.subtitle,
isRead = this.isRead
studyImageRes = this.profileImageRef,
)
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

toLong() 호출 시 NumberFormatException 발생 가능

applicationIdstudyId가 String 타입인데 toLong()을 직접 호출하면, API에서 유효하지 않은 값(빈 문자열, null, 숫자가 아닌 문자열 등)이 반환될 경우 런타임 크래시가 발생합니다.

🛠️ 안전한 변환을 위한 수정 제안
 fun AlertItem.toDomain() : AlertInfo =
     AlertInfo (
-        applicationId = this.applicationId.toLong(),
-        studyId = this.studyId.toLong(),
+        applicationId = this.applicationId.toLongOrNull() ?: 0L,
+        studyId = this.studyId.toLongOrNull() ?: 0L,
         title = this.title,
         studyImageRes = this.profileImageRef,
     )

또는 DTO에서 applicationIdstudyId를 처음부터 Long 타입으로 선언하는 것이 더 적절할 수 있습니다. API 응답 스펙을 확인해 주세요.

📝 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
fun AlertItem.toDomain() : AlertInfo =
AlertInfo (
id = this.id,
kind = this.kind,
applicationId = this.applicationId.toLong(),
studyId = this.studyId.toLong(),
title = this.title,
studyImageRes = this.studyImageRes,
subtitle = this.subtitle,
isRead = this.isRead
studyImageRes = this.profileImageRef,
)
fun AlertItem.toDomain() : AlertInfo =
AlertInfo (
applicationId = this.applicationId.toLongOrNull() ?: 0L,
studyId = this.studyId.toLongOrNull() ?: 0L,
title = this.title,
studyImageRes = this.profileImageRef,
)
🤖 Prompt for AI Agents
In `@data/alert/src/main/java/com/umcspot/spot/alert/mapper/AlertMapper.kt` around
lines 9 - 15, AlertItem.toDomain currently calls this.applicationId.toLong() and
this.studyId.toLong() which can throw NumberFormatException for
empty/invalid/null strings; change the mapper to safely parse these values (use
toLongOrNull on applicationId and studyId), handle null results (e.g., return a
default/fallback, throw a clear domain-specific exception, or propagate a
validation error) and update AlertInfo construction to use the validated Longs;
alternatively, if API spec guarantees numeric IDs, update the DTO types so
applicationId and studyId are Long and remove string-to-long conversion in
AlertItem.toDomain.

Comment on lines 10 to +15
data class AlertInfo(
val id: Int,
val kind: AlertKind,
val applicationId: Long,
val studyId: Long,
val title: String,
val subtitle: String = "",
val studyImageRes: ImageRef = ImageRef.None,
val isRead: Boolean = false
) {
companion object {
fun dummyAlert(id: Int, total: Int): AlertInfo {
val kind = AlertKind.values()[(id - 1) % AlertKind.values().size]

return when (kind) {
AlertKind.POPULAR_POST ->
AlertInfo(
id = id,
kind = kind,
title = "실시간 인기 글",
subtitle = "Sample Post Title",
studyImageRes = ImageRef.Name("ic_fire"),
isRead = false
)

AlertKind.STUDY_NOTICE ->
AlertInfo(
id = id,
kind = kind,
title = "내 스터디 '공지' 업데이트",
subtitle = "\"Sample Study\"의 새로운 공지",
studyImageRes = ImageRef.Name("sample"),
isRead = false
)

AlertKind.STUDY_SCHEDULE ->
AlertInfo(
id = id,
kind = kind,
title = "내 스터디 '새 일정' 등록",
subtitle = "\"Sample Study\"의 새로운 일정",
studyImageRes = ImageRef.Name("ic_study_default"),
isRead = false
)

AlertKind.TODO_DONE ->
AlertInfo(
id = id,
kind = kind,
title = "‘사용자님’의 “Sample Todolist” 할 일 완료!",
subtitle = "\"Sample Study\"의 ‘사용자님’",
studyImageRes = ImageRef.Name("ic_check_filled"),
isRead = false
)
}
}
}
}
val studyImageRes: ImageRef,
) No newline at end of file
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for applicationId usage across the codebase
echo "=== Searching for applicationId references ==="
rg "applicationId" --type=kt -C2

Repository: SPOTeam/Android-V2

Length of output: 152


🏁 Script executed:

#!/bin/bash
# Also check how AlertInfo is instantiated
echo "=== Searching for AlertInfo instantiation ==="
rg "AlertInfo\(" --type=kt -C3

Repository: SPOTeam/Android-V2

Length of output: 151


🏁 Script executed:

#!/bin/bash
# Check the AlertResult.kt file to see the full context
echo "=== Full content of AlertResult.kt ==="
find . -name "AlertResult.kt" -type f | head -5

Repository: SPOTeam/Android-V2

Length of output: 173


🏁 Script executed:

#!/bin/bash
# Read the AlertResult.kt file
cat -n ./domain/alert/src/main/java/com/umcspot/spot/alert/model/AlertResult.kt

Repository: SPOTeam/Android-V2

Length of output: 464


🏁 Script executed:

#!/bin/bash
# Search for applicationId without file type restriction
rg "applicationId" -C2

Repository: SPOTeam/Android-V2

Length of output: 1661


🏁 Script executed:

#!/bin/bash
# Search for AlertInfo instantiation/usage
rg "AlertInfo" -C2

Repository: SPOTeam/Android-V2

Length of output: 3858


🏁 Script executed:

#!/bin/bash
# Check where applicationId is actually used (not just declared/imported)
# Look in AlertScreen and AlertViewmodel for actual usage
echo "=== AlertScreen.kt content ==="
rg -A 30 "fun AlertRow" feature/alert/src/main/java/com/umcspot/spot/alert/AlertScreen.kt

Repository: SPOTeam/Android-V2

Length of output: 1115


🏁 Script executed:

#!/bin/bash
# Check AlertViewmodel for applicationId usage
echo "=== AlertViewmodel.kt content ==="
cat feature/alert/src/main/java/com/umcspot/spot/alert/AlertViewmodel.kt

Repository: SPOTeam/Android-V2

Length of output: 1761


🏁 Script executed:

#!/bin/bash
# Search for where onClickAlert is called and what parameter is passed
echo "=== Search for onClickAlert usage ==="
rg "onClickAlert\|onClick\(" feature/alert/src/main/java/com/umcspot/spot/alert/AlertScreen.kt -C3

Repository: SPOTeam/Android-V2

Length of output: 99


🏁 Script executed:

#!/bin/bash
# Check the full AlertScreen.kt to see navigation parameters
echo "=== Full AlertScreen.kt ==="
cat feature/alert/src/main/java/com/umcspot/spot/alert/AlertScreen.kt

Repository: SPOTeam/Android-V2

Length of output: 5950


🏁 Script executed:

#!/bin/bash
# Search for where AlertScreen is composed and how onClickAlert is handled
echo "=== Search for AlertScreen composition ==="
rg "AlertScreen\(" -C5

Repository: SPOTeam/Android-V2

Length of output: 2381


🏁 Script executed:

#!/bin/bash
# Search for any other usage of applicationId in the navigation or elsewhere
echo "=== Search for applicationId usage beyond definitions ==="
rg "\.applicationId" -C3

Repository: SPOTeam/Android-V2

Length of output: 833


applicationId 필드는 사용되지 않고 있습니다.

코드를 검토한 결과, applicationIdAlertMapper에서 API 응답값으로 매핑되지만 실제 애플리케이션 로직 어디에서도 사용되지 않습니다. AlertRow의 네비게이션에서는 오직 studyId만 전달되며(onClick(data.studyId)), 다른 곳에서도 applicationId를 참조하지 않습니다.

불필요한 필드는 제거하거나, 향후 사용 계획이 있다면 명확하게 주석으로 문서화하는 것을 권장합니다.

🤖 Prompt for AI Agents
In `@domain/alert/src/main/java/com/umcspot/spot/alert/model/AlertResult.kt`
around lines 10 - 15, The AlertInfo data class contains an unused applicationId
field; remove applicationId from AlertInfo and update any mappers and DTOs that
populate it (e.g., AlertMapper) so they no longer map or expect that property,
and update any tests or serialization logic accordingly; if you intend to keep
it for future use instead, add a clear comment above AlertInfo explaining the
planned use and why it is kept, and ensure callers (e.g., places building
AlertInfo and the AlertRow onClick flow) are consistent with the chosen
approach.

Comment on lines +41 to +44
.onFailure { e ->
Log.e("AlertViewModel", "loadAlert error", e)
// _uiState.update { state -> state.copy(alerts = UiState.Failure(e.message ?: e.toString())) }
}
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

실패 시 UI 상태 업데이트 누락

onFailure 블록에서 로그만 출력하고 UI 상태를 업데이트하지 않아, API 호출 실패 시 사용자가 무한 로딩 상태에 머물게 됩니다.

🛠️ 실패 상태 처리 활성화
                 .onFailure { e ->
                     Log.e("AlertViewModel", "loadAlert error", e)
-//                  _uiState.update { state -> state.copy(alerts = UiState.Failure(e.message ?: e.toString())) }
+                    _uiState.update { state -> state.copy(alerts = UiState.Failure(e.message ?: e.toString())) }
                 }
📝 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("AlertViewModel", "loadAlert error", e)
// _uiState.update { state -> state.copy(alerts = UiState.Failure(e.message ?: e.toString())) }
}
.onFailure { e ->
Log.e("AlertViewModel", "loadAlert error", e)
_uiState.update { state -> state.copy(alerts = UiState.Failure(e.message ?: e.toString())) }
}

@starshape7 starshape7 merged commit 9088e77 into develop Jan 16, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT/#26] AlertScreen 구현

1 participant