Skip to content

[NDGL-140] 여행 템플릿 제안 관련 API 연동#2

Merged
mj010504 merged 4 commits into
developfrom
NDGL-140
May 15, 2026
Merged

[NDGL-140] 여행 템플릿 제안 관련 API 연동#2
mj010504 merged 4 commits into
developfrom
NDGL-140

Conversation

@mj010504

@mj010504 mj010504 commented May 15, 2026

Copy link
Copy Markdown

개요

  • 여행 템플릿 제안 관련 API 연동

연관 문서

디자인

변경사항

  • 콘텐츠 제안 API 연동
  • 게시 구독 알림 API 연동
  • 홈 화면에 알림 권한 요청 로직 추가
  • 콘텐츠 추천 화면에서 사용자가 콘텐츠 추천 보내기 시 알림 권한 요청 로직 추가

Summary by CodeRabbit

릴리스 노트

  • 새 기능

    • 여행 템플릿 추천 및 구독 기능 추가
    • 성공/중복 상태에 대한 모달 다이얼로그 추가
    • Android 13 이상 기기에서 알림 권한 요청
  • 개선 사항

    • 여행 테마 카테고리 업데이트 (카페, 힐링, 명소, 예산)
    • 사용자 피드백 메시지 개선

Review Change Stack

@mj010504 mj010504 self-assigned this May 15, 2026
@coderabbitai

coderabbitai Bot commented May 15, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 988178a3-4c6e-461e-ab60-a53024035659

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

워크스루

이 PR은 Android 13+ 알림 권한 요청 기반 구조와 여행 템플릿 추천/구독 API 연동을 통해 콘텐츠 추천 화면을 재구현합니다. 기존 스낵바 기반 피드백을 성공/중복 모달로 전환하고, 알림 구독 플로우를 추가합니다.

변경 사항

콘텐츠 추천 및 알림 권한 통합

Layer / File(s) 요약
알림 권한 요청 기반 구성
app/src/main/AndroidManifest.xml, app/src/main/java/com/yapp/ndgl/MainActivity.kt
Android 13(API 33) 이상에서 POST_NOTIFICATIONS 권한을 선언하고, ActivityResultContracts.RequestPermission을 사용해 런타임 권한 요청 런처와 requestNotificationPermission() 헬퍼 함수를 추가합니다.
API 요청 모델 및 예외 타입
data/travel/src/main/java/com/yapp/ndgl/data/travel/model/SuggestTemplateRequest.kt, data/travel/src/main/java/com/yapp/ndgl/data/travel/model/SubscribeTemplateRequest.kt, data/travel/src/main/java/com/yapp/ndgl/data/travel/exception/DuplicateSuggestedTemplateException.kt
템플릿 추천 요청(videoLink, recommendReason, category)과 구독 요청(videoLink) DTO를 추가하고, 중복 상태를 나타내는 DuplicateSuggestedTemplateException(ALREADY_PUBLISHED, OTHER_USER_PENDING, SELF_PENDING)을 정의합니다.
API 엔드포인트 및 리포지토리 통합
data/travel/src/main/java/com/yapp/ndgl/data/travel/api/TravelTemplateApi.kt, data/travel/src/main/java/com/yapp/ndgl/data/travel/repository/TravelTemplateRepository.kt
/api/v1/suggested-templates/api/v1/suggested-templates/subscribe 엔드포인트를 추가하고, suggestTemplate()subscribeTemplate() 메서드에서 HTTP 예외를 비즈니스 예외로 매핑합니다. 응답 코드별로 Reason을 분기해 예외를 재던집니다.
콘텐츠 추천 계약 갱신
feature/content-recommendation/src/main/java/com/yapp/ndgl/feature/contentrecommendation/ContentRecommendationContract.kt
Intent를 SubmitForm, SubscribeNotification, NavigateBack으로 변경하고, SideEffect를 ShowSuccessModal, ShowDuplicateModal, ShowSnackbar(message), NotifySubscribeSuccess로 갱신합니다. SNACKBAR_* 문자열 상수도 추가합니다.
ViewModel 제출/구독 로직 구현
feature/content-recommendation/src/main/java/com/yapp/ndgl/feature/contentrecommendation/ContentRecommendationViewModel.kt
TravelTemplateRepository 의존성을 추가하고, submitForm()에서 suggestTemplate() 호출 후 예외 Reason에 따라 성공 모달/중복 모달/에러 스낵바를 분기합니다. subscribeNotification()에서 subscribeTemplate() 호출 후 구독 성공 사이드이펙트 또는 에러 스낵바를 처리합니다.
화면 UI 및 권한 연결
feature/content-recommendation/src/main/java/com/yapp/ndgl/feature/contentrecommendation/ContentRecommendationScreen.kt
알림 권한 런처를 등록하고, ContentRecommendationRoute에서 성공/중복 모달을 상태로 관리하여 렌더링합니다. 모달의 버튼 액션은 권한 요청 또는 SubscribeNotification 인텐트를 디스패치합니다. 스낵바 하단 여백을 100dp에서 20dp로 감소하고, preview의 selectedThemes를 업데이트합니다.
모델 및 문자열 리소스 갱신
feature/content-recommendation/src/main/java/com/yapp/ndgl/feature/contentrecommendation/model/TravelTheme.kt, feature/content-recommendation/src/main/java/com/yapp/ndgl/feature/contentrecommendation/TravelThemeSection.kt, feature/content-recommendation/src/main/res/values/strings.xml
TravelTheme enum의 식별자를 CAFE_DESSERTCAFE, HEALING_SCENERYHEALING, LANDMARKATTRACTION, VALUE_FOR_MONEYBUDGET으로 변경합니다. 추천 성공/중복 모달 및 구독 실패 문자열을 추가합니다.

예상 코드 리뷰 난이도

🎯 3 (Moderate) | ⏱️ ~25 분

관련 PR

  • NDGL-official/NDGL-android#1: 동일 PR #1에서 생성한 콘텐츠 추천 기능의 핵심 파일들(ContentRecommendationContract, ContentRecommendationScreen, ContentRecommendationViewModel, TravelTheme)을 확장하고 알림 구독 플로우를 추가합니다.

제안 라벨

UI/UX

🚥 Pre-merge checks | ✅ 4 | ❌ 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 (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목이 주요 변경사항인 여행 템플릿 제안 API 연동을 명확하게 요약하고 있습니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch NDGL-140

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@data/travel/src/main/java/com/yapp/ndgl/data/travel/repository/TravelTemplateRepository.kt`:
- Around line 105-106: The current catch maps CODE_ALREADY_SUBSCRIBED to
DuplicateSuggestedTemplateException.Reason.SELF_PENDING which conflates
subscription-duplicate with suggestion-duplicate; add a distinct enum value
(e.g., Reason.SUBSCRIPTION_DUPLICATE) to DuplicateSuggestedTemplateException and
change the throw in the block that checks CODE_ALREADY_SUBSCRIBED to throw a
DuplicateSuggestedTemplateException with that new reason and include the
original exception as the cause (pass the caught exception into the
DuplicateSuggestedTemplateException constructor) so callers can distinguish
subscription vs suggestion duplicates and inspect the root cause; update any
constructors/signatures of DuplicateSuggestedTemplateException to accept and
store the cause if needed.

In
`@feature/content-recommendation/src/main/java/com/yapp/ndgl/feature/contentrecommendation/ContentRecommendationScreen.kt`:
- Around line 69-79: The call sites in requestNotificationOrNavigate that
force-cast context to Activity (e.g.,
ActivityCompat.shouldShowRequestPermissionRationale(context as Activity, ... )
and context.startActivity(...)) can throw ClassCastException for non-Activity
Contexts; fix by safely obtaining an Activity at the start of
requestNotificationOrNavigate (e.g., val activity = context as? Activity) and
bail out or handle the no-Activity case (return or open settings via
context.applicationContext) if activity is null, then replace occurrences of
(context as Activity) and context.startActivity(...) with activity and
activity.startActivity(...), and use activity for permission rationale checks
and intent launching to avoid unsafe casts.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 939077ae-ab0a-4e93-8d73-319e0fed3857

📥 Commits

Reviewing files that changed from the base of the PR and between 11f32a2 and bfadfda.

📒 Files selected for processing (13)
  • app/src/main/AndroidManifest.xml
  • app/src/main/java/com/yapp/ndgl/MainActivity.kt
  • data/travel/src/main/java/com/yapp/ndgl/data/travel/api/TravelTemplateApi.kt
  • data/travel/src/main/java/com/yapp/ndgl/data/travel/exception/DuplicateSuggestedTemplateException.kt
  • data/travel/src/main/java/com/yapp/ndgl/data/travel/model/SubscribeTemplateRequest.kt
  • data/travel/src/main/java/com/yapp/ndgl/data/travel/model/SuggestTemplateRequest.kt
  • data/travel/src/main/java/com/yapp/ndgl/data/travel/repository/TravelTemplateRepository.kt
  • feature/content-recommendation/src/main/java/com/yapp/ndgl/feature/contentrecommendation/ContentRecommendationContract.kt
  • feature/content-recommendation/src/main/java/com/yapp/ndgl/feature/contentrecommendation/ContentRecommendationScreen.kt
  • feature/content-recommendation/src/main/java/com/yapp/ndgl/feature/contentrecommendation/ContentRecommendationViewModel.kt
  • feature/content-recommendation/src/main/java/com/yapp/ndgl/feature/contentrecommendation/TravelThemeSection.kt
  • feature/content-recommendation/src/main/java/com/yapp/ndgl/feature/contentrecommendation/model/TravelTheme.kt
  • feature/content-recommendation/src/main/res/values/strings.xml

Comment on lines +69 to +79
fun requestNotificationOrNavigate() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val hasPermission = ContextCompat.checkSelfPermission(
context,
Manifest.permission.POST_NOTIFICATIONS,
) == PackageManager.PERMISSION_GRANTED

val shouldShowRationale = ActivityCompat.shouldShowRequestPermissionRationale(
context as Activity,
Manifest.permission.POST_NOTIFICATIONS,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Line 77 강제 캐스팅 및 권한 rationale 호출 위치 확인
rg -n -C2 'context as Activity|shouldShowRequestPermissionRationale\(|LocalContext\.current' \
  feature/content-recommendation/src/main/java/com/yapp/ndgl/feature/contentrecommendation/ContentRecommendationScreen.kt

Repository: NDGL-official/NDGL-android

Length of output: 570


🏁 Script executed:

# ContentRecommendationScreen.kt 파일 전체 크기와 함수 시그니처 확인
wc -l feature/content-recommendation/src/main/java/com/yapp/ndgl/feature/contentrecommendation/ContentRecommendationScreen.kt

Repository: NDGL-official/NDGL-android

Length of output: 194


🏁 Script executed:

# 함수 시그니처와 주변 context 확인
head -70 feature/content-recommendation/src/main/java/com/yapp/ndgl/feature/contentrecommendation/ContentRecommendationScreen.kt | tail -20

Repository: NDGL-official/NDGL-android

Length of output: 812


🏁 Script executed:

# LocalActivity 사용 가능성 확인
rg -l 'LocalActivity|androidx.activity' feature/content-recommendation/

Repository: NDGL-official/NDGL-android

Length of output: 190


🏁 Script executed:

# 동일한 패턴(unsafe Activity cast) 확인
rg -n 'as Activity|as Context' feature/content-recommendation/src/main/java/com/yapp/ndgl/feature/contentrecommendation/ContentRecommendationScreen.kt

Repository: NDGL-official/NDGL-android

Length of output: 110


🏁 Script executed:

cat -n feature/content-recommendation/src/main/java/com/yapp/ndgl/feature/contentrecommendation/ContentRecommendationScreen.kt | head -100

Repository: NDGL-official/NDGL-android

Length of output: 4857


🏁 Script executed:

# 파일의 import 섹션 확인
head -50 feature/content-recommendation/src/main/java/com/yapp/ndgl/feature/contentrecommendation/ContentRecommendationScreen.kt

Repository: NDGL-official/NDGL-android

Length of output: 2383


🏁 Script executed:

# 권한 처리 관련 코드 전체 확인
sed -n '63,98p' feature/content-recommendation/src/main/java/com/yapp/ndgl/feature/contentrecommendation/ContentRecommendationScreen.kt

Repository: NDGL-official/NDGL-android

Length of output: 1346


LocalContext 강제 캐스팅으로 인한 크래시 위험

77번 줄의 context as ActivityLocalContext.currentContextThemeWrapper 등의 non-Activity Context를 반환할 때 ClassCastException을 유발할 수 있습니다. 또한 85번 줄의 context.startActivity() 호출도 동일한 이유로 위험합니다.

안전 캐스팅으로 변경하거나, 함수 진입 초기에 Activity 존재 여부를 검증해야 합니다:

🔧 제안 수정안
     fun requestNotificationOrNavigate() {
+        val activity = context as? Activity ?: return
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
             val hasPermission = ContextCompat.checkSelfPermission(
                 context,
                 Manifest.permission.POST_NOTIFICATIONS,
             ) == PackageManager.PERMISSION_GRANTED

             val shouldShowRationale = ActivityCompat.shouldShowRequestPermissionRationale(
-                context as Activity,
+                activity,
                 Manifest.permission.POST_NOTIFICATIONS,
             )

             when {
                 hasPermission -> navigateBack()

                 shouldShowRationale -> {
                     context.startActivity(
                         Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS).apply {
                             putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName)
                         },
                     )
                     navigateBack()
                 }

                 else -> notificationPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
             }
         } else {
             navigateBack()
         }
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@feature/content-recommendation/src/main/java/com/yapp/ndgl/feature/contentrecommendation/ContentRecommendationScreen.kt`
around lines 69 - 79, The call sites in requestNotificationOrNavigate that
force-cast context to Activity (e.g.,
ActivityCompat.shouldShowRequestPermissionRationale(context as Activity, ... )
and context.startActivity(...)) can throw ClassCastException for non-Activity
Contexts; fix by safely obtaining an Activity at the start of
requestNotificationOrNavigate (e.g., val activity = context as? Activity) and
bail out or handle the no-Activity case (return or open settings via
context.applicationContext) if activity is null, then replace occurrences of
(context as Activity) and context.startActivity(...) with activity and
activity.startActivity(...), and use activity for permission rationale checks
and intent launching to avoid unsafe casts.

@mj010504 mj010504 merged commit d208ede into develop May 15, 2026
4 checks passed
@mj010504 mj010504 deleted the NDGL-140 branch May 15, 2026 14:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant