Skip to content

[NDGL-137] 콘텐츠 추천 화면 UI/UX 제작#45

Closed
mj010504 wants to merge 5 commits into
YAPP-Github:developfrom
NDGL-official:NDGL-131
Closed

[NDGL-137] 콘텐츠 추천 화면 UI/UX 제작#45
mj010504 wants to merge 5 commits into
YAPP-Github:developfrom
NDGL-official:NDGL-131

Conversation

@mj010504
Copy link
Copy Markdown
Contributor

@mj010504 mj010504 commented May 13, 2026

개요

  • 콘텐츠 추천 화면 UI/UX 제작

연관 문서

디자인

스크린샷

bandicam.2026-05-13.23-57-29-100.mp4

변경사항

  • 기존 설정 화면에 콘텐츠 추천 링크 넣기 기능 변경
  • 콘텐츠 추천 화면 UI/UX 제작
  • Youtube Oembed API 연동

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 13, 2026

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

Run ID: aef18031-132c-4725-b497-a0f35b6f550f

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

Walkthrough

이 PR은 YouTube 콘텐츠의 메타데이터를 조회하고 여행 관련 추천을 제출할 수 있는 새로운 feature 모듈을 추가합니다. 데이터 계층의 API 통합부터 완전한 UI 화면까지, 설정 메뉴와의 네비게이션 통합을 포함합니다.

Changes

콘텐츠 추천 기능

Layer / File(s) Summary
YouTube oEmbed 데이터 계층
data/travel/src/main/java/com/yapp/ndgl/data/travel/api/YoutubeOembedApi.kt, data/travel/src/main/java/com/yapp/ndgl/data/travel/model/YoutubeOembedResponse.kt, data/travel/src/main/java/com/yapp/ndgl/data/travel/di/TravelNetworkModule.kt, data/travel/src/main/java/com/yapp/ndgl/data/travel/repository/ContentMetadataRepository.kt
YouTube oEmbed API 인터페이스, 응답 데이터 클래스, DI 설정을 통해 메타데이터 조회 기능을 구현합니다.
상태 계약 및 ViewModel
feature/content-recommendation/src/main/java/com/yapp/ndgl/feature/contentrecommendation/ContentRecommendationContract.kt, 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/ContentRecommendationViewModel.kt
UI 상태, 사용자 의도, 부수 효과를 정의하고, ViewModel에서 URL 검증, 메타데이터 로드, 테마 선택, 이유 입력을 처리합니다.
UI 컴포넌트 및 화면
feature/content-recommendation/src/main/java/com/yapp/ndgl/feature/contentrecommendation/HeaderSection.kt, ContentLinkSection.kt, TravelThemeSection.kt, ReasonSection.kt, ContentRecommendationScreen.kt
제목, 콘텐츠 링크 입력(상태별 미리보기 포함), 여행 테마 선택, 추천 이유 입력 섹션과 메인 화면을 구성합니다.
기능 모듈 설정 및 리소스
feature/content-recommendation/build.gradle.kts, feature/content-recommendation/src/main/AndroidManifest.xml, feature/content-recommendation/src/main/res/values/strings.xml, feature/content-recommendation/src/main/java/com/yapp/ndgl/feature/contentrecommendation/navigation/ContentRecommendationEntry.kt
빌드 설정, 매니페스트, 문자열 리소스, 네비게이션 진입점을 정의합니다.
앱 통합 및 네비게이션
navigation/src/main/java/com/yapp/ndgl/navigation/Route.kt, feature/home/src/main/java/com/yapp/ndgl/feature/home/settings/SettingsContract.kt, feature/home/src/main/java/com/yapp/ndgl/feature/home/settings/SettingsScreen.kt, feature/home/src/main/java/com/yapp/ndgl/feature/home/settings/SettingsViewModel.kt, feature/home/src/main/java/com/yapp/ndgl/feature/home/navigation/HomeEntry.kt, app/src/main/java/com/yapp/ndgl/ui/NDGLApp.kt, app/build.gradle.kts, settings.gradle.kts
Route 추가, 설정 메뉴에서 콘텐츠 추천으로의 네비게이션 통합, 앱 의존성 설정을 완료합니다.
시각 자산
core/ui/src/main/res/drawable/ic_24_alert.xml, ic_24_asterisk.xml, ic_24_youtube.xml, .gitignore, gradle/libs.versions.toml
경고, 별표, YouTube 아이콘 벡터 드로어블을 추가합니다.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 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
Title check ✅ Passed PR 제목은 콘텐츠 추천 화면 UI/UX 제작이라는 주요 변경 사항을 명확하게 설명하며, 실제 변경 내용과 일치합니다.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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
Copy Markdown

@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: 5

🧹 Nitpick comments (2)
feature/content-recommendation/src/main/java/com/yapp/ndgl/feature/contentrecommendation/ReasonSection.kt (1)

32-51: ⚡ Quick win

추천 이유 입력 필드에 최대 길이 제한을 고려하세요.

서버 API에 추천 이유 길이 제한이 있다면, BasicTextFieldmaxLength 제약을 추가하여 사용자가 입력 중에 한도를 초과하지 않도록 해야 합니다. 현재는 문자 수만 표시하고 있어, 사용자가 제한을 초과한 후 제출 시 오류가 발생할 수 있습니다.

제안하는 수정 사항
+private const val MAX_REASON_LENGTH = 500  // 서버 제한에 맞게 조정
+
 `@Composable`
 internal fun ReasonSection(
     reason: String,
     onReasonChange: (String) -> Unit,
 ) {
     Column(modifier = Modifier.imePadding(), verticalArrangement = Arrangement.spacedBy(8.dp)) {
         Text(
             text = stringResource(R.string.content_recommendation_reason_label),
             style = NDGLTheme.typography.bodyMdSemiBold,
             color = NDGLTheme.colors.black700,
         )
         BasicTextField(
             value = reason,
-            onValueChange = onReasonChange,
+            onValueChange = { if (it.length <= MAX_REASON_LENGTH) onReasonChange(it) },
🤖 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/ReasonSection.kt`
around lines 32 - 51, The reason input lacks an enforced max-length, so update
ReasonSection (the BasicTextField instance using value = reason and
onValueChange = onReasonChange) to enforce the server-side length limit: define
a constant MAX_REASON_LENGTH, clamp or ignore additional characters inside the
onValueChange handler (only pass up to MAX_REASON_LENGTH), and update any
character counter logic to reflect MAX_REASON_LENGTH so users cannot type beyond
the allowed length and the displayed remaining/used count stays consistent with
the enforced limit.
feature/content-recommendation/src/main/res/values/strings.xml (1)

18-18: 💤 Low value

규칙적 공백 사용으로 단순화 검토 필요

Line 18에서 \u0020을 사용하고 있으나, 이는 프로젝트 전체 strings.xml 파일에서 유일한 유니코드 이스케이프입니다. XML 공백 처리 방지를 위해 의도적으로 사용된 것이 아니라면, 일반 공백으로 단순화하는 것이 좋습니다.

<string name="content_recommendation_no_link_prefix">링크가 없나요? </string>

특별한 이유로 \u0020이 필요하다면 코드에 주석을 추가하여 명시하세요.

🤖 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/res/values/strings.xml` at line 18,
The string resource content_recommendation_no_link_prefix currently uses a
Unicode escape (\u0020) for a trailing space; replace the escape with a normal
ASCII space in the string value (i.e., change the value for string name
content_recommendation_no_link_prefix to use a regular space) unless the escape
is intentionally required—if so, add a clarifying comment near the string
explaining why \u0020 must be used to prevent future confusion.
🤖 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/di/TravelNetworkModule.kt`:
- Around line 181-183: provideYoutubeOembedRetrofit currently injects an
OkHttpClient qualified as `@ExchangeRateClient` which breaks the
qualifier-consistency pattern; change the parameter to use the
`@YoutubeOembedClient` qualifier (or create a dedicated provider that returns an
OkHttpClient annotated with `@YoutubeOembedClient`) and update its
provider/binding so YouTube oEmbed uses its own qualified client instead of
`@ExchangeRateClient`, keeping the rest of the method signature
(provideYoutubeOembedRetrofit) and existing Json parameter unchanged.

In `@feature/content-recommendation/build.gradle.kts`:
- Around line 10-12: local.properties 파일이 없을 경우 FileNotFoundException으로 빌드가 실패하니
load 호출 전에 파일 존재 여부를 검사하거나 예외를 잡아 무시하도록 변경하세요: locate the localProperties
initialization (variable localProperties) and replace the direct
load(rootProject.file("local.properties").bufferedReader()) with logic that
checks rootProject.file("local.properties").exists() (or wraps the load in
try/catch) and only calls load when present, otherwise leave localProperties
empty or log a warning so cloning 환경에서도 빌드가 실패하지 않도록 만드세요.

In
`@feature/content-recommendation/src/main/java/com/yapp/ndgl/feature/contentrecommendation/ContentRecommendationViewModel.kt`:
- Around line 41-43: The metadata fetch races when multiple URLs are entered
because fetchMetadata(url) runs without cancellation or a response guard; modify
ContentRecommendationViewModel to track and cancel the previous metadata job
(e.g., a metadataJob Coroutine Job) before launching a new one in the same
handler that calls reduce { copy(contentUrl = url, metadataState =
MetadataState.Loading) }, or alternatively include a requestId/url guard inside
fetchMetadata's response handling so you only reduce state if the current
contentUrl still equals the requested url; ensure you reference and update the
same fetchMetadata invocation and the reduce call so stale responses no longer
overwrite newer state.
- Around line 84-85: The isYoutubeUrl method is too loose (uses String.contains)
and can false-positive on query values; replace its implementation to parse the
input as a URL/URI in ContentRecommendationViewModel.kt (isYoutubeUrl) and
validate the host component: normalize to lowercase and accept hosts that are
exactly "youtube.com" or "youtu.be" or end with ".youtube.com" (to allow
subdomains like "www.youtube.com"), and exact "youtu.be"; gracefully handle
invalid URLs (return false) and avoid matching when host is missing or part of a
query/path.
- Around line 93-98: The submit() function currently no-ops when isSubmitEnabled
is true; implement a temporary UI feedback flow before API integration by
toggling a loading flag on state (use state.value and update the view model
state), emit side-effects for progress and outcome (e.g., send a "ShowLoading"
side effect, then a "ShowSuccess" or "ShowError" side effect), and clear/reset
loading when finished; keep this in submit() as the placeholder for the eventual
API call so the UI receives immediate success/failure feedback even without
backend integration.

---

Nitpick comments:
In
`@feature/content-recommendation/src/main/java/com/yapp/ndgl/feature/contentrecommendation/ReasonSection.kt`:
- Around line 32-51: The reason input lacks an enforced max-length, so update
ReasonSection (the BasicTextField instance using value = reason and
onValueChange = onReasonChange) to enforce the server-side length limit: define
a constant MAX_REASON_LENGTH, clamp or ignore additional characters inside the
onValueChange handler (only pass up to MAX_REASON_LENGTH), and update any
character counter logic to reflect MAX_REASON_LENGTH so users cannot type beyond
the allowed length and the displayed remaining/used count stays consistent with
the enforced limit.

In `@feature/content-recommendation/src/main/res/values/strings.xml`:
- Line 18: The string resource content_recommendation_no_link_prefix currently
uses a Unicode escape (\u0020) for a trailing space; replace the escape with a
normal ASCII space in the string value (i.e., change the value for string name
content_recommendation_no_link_prefix to use a regular space) unless the escape
is intentionally required—if so, add a clarifying comment near the string
explaining why \u0020 must be used to prevent future confusion.
🪄 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

Run ID: e0672360-add7-4ae3-8f30-e01814988ac5

📥 Commits

Reviewing files that changed from the base of the PR and between c2428ac and 2b1ef95.

📒 Files selected for processing (32)
  • .gitignore
  • app/build.gradle.kts
  • app/src/main/java/com/yapp/ndgl/ui/NDGLApp.kt
  • core/ui/src/main/res/drawable/ic_24_alert.xml
  • core/ui/src/main/res/drawable/ic_24_asterisk.xml
  • core/ui/src/main/res/drawable/ic_24_youtube.xml
  • data/travel/src/main/java/com/yapp/ndgl/data/travel/api/YoutubeOembedApi.kt
  • data/travel/src/main/java/com/yapp/ndgl/data/travel/di/TravelNetworkModule.kt
  • data/travel/src/main/java/com/yapp/ndgl/data/travel/model/YoutubeOembedResponse.kt
  • data/travel/src/main/java/com/yapp/ndgl/data/travel/repository/ContentMetadataRepository.kt
  • feature/content-recommendation/.gitignore
  • feature/content-recommendation/build.gradle.kts
  • feature/content-recommendation/consumer-rules.pro
  • feature/content-recommendation/proguard-rules.pro
  • feature/content-recommendation/src/main/AndroidManifest.xml
  • feature/content-recommendation/src/main/java/com/yapp/ndgl/feature/contentrecommendation/ContentLinkSection.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/HeaderSection.kt
  • feature/content-recommendation/src/main/java/com/yapp/ndgl/feature/contentrecommendation/ReasonSection.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/java/com/yapp/ndgl/feature/contentrecommendation/navigation/ContentRecommendationEntry.kt
  • feature/content-recommendation/src/main/res/values/strings.xml
  • feature/home/src/main/java/com/yapp/ndgl/feature/home/navigation/HomeEntry.kt
  • feature/home/src/main/java/com/yapp/ndgl/feature/home/settings/SettingsContract.kt
  • feature/home/src/main/java/com/yapp/ndgl/feature/home/settings/SettingsScreen.kt
  • feature/home/src/main/java/com/yapp/ndgl/feature/home/settings/SettingsViewModel.kt
  • gradle/libs.versions.toml
  • navigation/src/main/java/com/yapp/ndgl/navigation/Route.kt
  • settings.gradle.kts

Comment thread feature/content-recommendation/build.gradle.kts
Comment on lines +41 to +43
reduce { copy(contentUrl = url, metadataState = MetadataState.Loading) }
fetchMetadata(url)
}
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot May 13, 2026

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

URL 연속 입력 시 이전 요청 응답이 최신 상태를 덮어쓰는 레이스가 발생합니다.

이전 fetchMetadata 작업 취소/가드가 없어서 느린 이전 응답이 나중 URL의 상태를 덮을 수 있습니다.

🔧 제안 수정안
+import kotlinx.coroutines.Job
...
 class ContentRecommendationViewModel `@Inject` constructor(
@@
 ) {
+    private var metadataJob: Job? = null
@@
             else -> {
+                metadataJob?.cancel()
                 reduce { copy(contentUrl = url, metadataState = MetadataState.Loading) }
-                fetchMetadata(url)
+                metadataJob = fetchMetadata(url)
             }
         }
     }

-    private fun fetchMetadata(url: String) = viewModelScope.launch {
+    private fun fetchMetadata(url: String): Job = viewModelScope.launch {
         suspendRunCatching {
             contentMetadataRepository.getMetadata(url)
         }
             .onSuccess { response ->
+                if (state.value.contentUrl != url) return@onSuccess
                 reduce {
                     copy(
@@
             .onFailure {
+                if (state.value.contentUrl != url) return@onFailure
                 reduce {
                     copy(

Also applies to: 47-71

🤖 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/ContentRecommendationViewModel.kt`
around lines 41 - 43, The metadata fetch races when multiple URLs are entered
because fetchMetadata(url) runs without cancellation or a response guard; modify
ContentRecommendationViewModel to track and cancel the previous metadata job
(e.g., a metadataJob Coroutine Job) before launching a new one in the same
handler that calls reduce { copy(contentUrl = url, metadataState =
MetadataState.Loading) }, or alternatively include a requestId/url guard inside
fetchMetadata's response handling so you only reduce state if the current
contentUrl still equals the requested url; ensure you reference and update the
same fetchMetadata invocation and the reduce call so stale responses no longer
overwrite newer state.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

너무 엣지 케이스라 판단하여 해당 로직은 작성하지 않았습니다.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@mj010504, 이해했습니다. 현재 UX 패턴(URL 직접 붙여넣기 위주)에서는 빈번히 발생할 가능성이 낮은 엣지 케이스로 판단하신 점 수용합니다. 향후 요청 빈도가 높아지거나 UX가 변경되는 시점에 재검토해 주시면 될 것 같습니다.


🧠 Learnings used
Learnt from: jihee-dev
Repo: YAPP-Github/27th-App-Team-1-Android PR: 25
File: feature/travel/src/main/java/com/yapp/ndgl/feature/travel/mytravel/MyTravelContract.kt:52-54
Timestamp: 2026-02-17T22:21:31.141Z
Learning: Use the naming convention estimatedDuration for duration-related fields across Kotlin files to align with server API response parameter names and maintain consistency throughout the codebase. When introducing or renaming duration fields, replace generic names (e.g., duration, totalDuration) with estimatedDuration and update all references (data classes, mappings, JSON parsing) accordingly to avoid mismatch with server payloads.

Learnt from: jihee-dev
Repo: YAPP-Github/27th-App-Team-1-Android PR: 27
File: feature/home/src/main/java/com/yapp/ndgl/feature/home/search/TemplateSearchScreen.kt:0-0
Timestamp: 2026-02-18T18:16:40.700Z
Learning: Use the correct Hilt Compose ViewModel import across Kotlin Android files: import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel (from androidx.hilt:hilt-lifecycle-viewmodel-compose:1.3.0). The old import androidx.hilt.navigation.compose.hiltViewModel is deprecated in Hilt 1.3.0. Ensure the hilt-lifecycle-viewmodel-compose dependency is added to Gradle (both Gradle catalog and module dependencies) and update imports accordingly to avoid runtime issues or build failures.

Learnt from: mj010504
Repo: YAPP-Github/NDGL-android PR: 45
File: feature/content-recommendation/src/main/java/com/yapp/ndgl/feature/contentrecommendation/ContentRecommendationViewModel.kt:93-98
Timestamp: 2026-05-13T15:20:09.098Z
Learning: When performing code reviews in this repo, do not add review comments on any line(s) or code blocks that contain placeholder markers `//TODO` or `//FIXME`. Treat these as intentional placeholders and avoid automated feedback on them (unless the reported issue is clearly unrelated to the TODO/FIXME content itself).

Comment on lines +84 to +85
private fun isYoutubeUrl(url: String): Boolean =
url.contains("youtube.com", ignoreCase = true) || url.contains("youtu.be", ignoreCase = true)
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot May 13, 2026

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

YouTube URL 검증이 너무 느슨해서 오탐 URL을 허용합니다.

contains("youtube.com") 방식은 https://evil.com/?next=youtube.com 같은 값도 통과시킵니다. host 기반 검증으로 바꿔주세요.

🔧 제안 수정안
+import android.net.Uri
...
-    private fun isYoutubeUrl(url: String): Boolean =
-        url.contains("youtube.com", ignoreCase = true) || url.contains("youtu.be", ignoreCase = true)
+    private fun isYoutubeUrl(url: String): Boolean {
+        val host = runCatching { Uri.parse(url).host?.lowercase() }.getOrNull() ?: return false
+        return host == "youtu.be" || host == "youtube.com" || host.endsWith(".youtube.com")
+    }
📝 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
private fun isYoutubeUrl(url: String): Boolean =
url.contains("youtube.com", ignoreCase = true) || url.contains("youtu.be", ignoreCase = true)
private fun isYoutubeUrl(url: String): Boolean {
val host = runCatching { Uri.parse(url).host?.lowercase() }.getOrNull() ?: return false
return host == "youtu.be" || host == "youtube.com" || host.endsWith(".youtube.com")
}
🤖 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/ContentRecommendationViewModel.kt`
around lines 84 - 85, The isYoutubeUrl method is too loose (uses
String.contains) and can false-positive on query values; replace its
implementation to parse the input as a URL/URI in
ContentRecommendationViewModel.kt (isYoutubeUrl) and validate the host
component: normalize to lowercase and accept hosts that are exactly
"youtube.com" or "youtu.be" or end with ".youtube.com" (to allow subdomains like
"www.youtube.com"), and exact "youtu.be"; gracefully handle invalid URLs (return
false) and avoid matching when host is missing or part of a query/path.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

가장 단순한 방법으로 해당 방법 선택했습니다.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@mj010504, 네, 이해했습니다! 단순함을 우선시하는 선택이시군요. 다만 참고로, contains 방식은 https://evil.com/?redirect=youtube.com 같은 URL도 YouTube URL로 인식할 수 있어서, 향후 보안이 중요해지는 시점에 호스트 기반 검증으로 개선을 고려해 보시면 좋을 것 같습니다. 현재 결정을 존중합니다! 🙂


🧠 Learnings used
Learnt from: jihee-dev
Repo: YAPP-Github/27th-App-Team-1-Android PR: 25
File: feature/travel/src/main/java/com/yapp/ndgl/feature/travel/mytravel/MyTravelContract.kt:52-54
Timestamp: 2026-02-17T22:21:31.141Z
Learning: Use the naming convention estimatedDuration for duration-related fields across Kotlin files to align with server API response parameter names and maintain consistency throughout the codebase. When introducing or renaming duration fields, replace generic names (e.g., duration, totalDuration) with estimatedDuration and update all references (data classes, mappings, JSON parsing) accordingly to avoid mismatch with server payloads.

Learnt from: jihee-dev
Repo: YAPP-Github/27th-App-Team-1-Android PR: 27
File: feature/home/src/main/java/com/yapp/ndgl/feature/home/search/TemplateSearchScreen.kt:0-0
Timestamp: 2026-02-18T18:16:40.700Z
Learning: Use the correct Hilt Compose ViewModel import across Kotlin Android files: import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel (from androidx.hilt:hilt-lifecycle-viewmodel-compose:1.3.0). The old import androidx.hilt.navigation.compose.hiltViewModel is deprecated in Hilt 1.3.0. Ensure the hilt-lifecycle-viewmodel-compose dependency is added to Gradle (both Gradle catalog and module dependencies) and update imports accordingly to avoid runtime issues or build failures.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant