Skip to content

8주차 미션_종이#25

Open
jongheecode wants to merge 2 commits into
Week7from
Week8
Open

8주차 미션_종이#25
jongheecode wants to merge 2 commits into
Week7from
Week8

Conversation

@jongheecode

Copy link
Copy Markdown
Collaborator

📝 작업 내용

  • 8주차 미션 - Week3 RecyclerView + Adapter 코드를 Jetpack Compose LazyColumn으로 마이그레이션
    • HomeScreen: LazyColumn + 내부 LazyRow로 가로 스크롤 상품 목록 구현
    • BuyScreen / WishlistScreen: LazyColumn + Row 2열 쌍으로 그리드 레이아웃 구현
    • items() 함수 사용, Composable 아이템 레이아웃 작성, key 지정으로 상태 안정성 확보
    • Week3 drawable 리소스 적용)

📸 스크린샷

구현 결과를 확인할 수 있는 스크린샷을 첨부해주세요.


🙏 리뷰 요구사항 (선택)

리뷰어가 중점적으로 봐주었으면 하는 부분이 있다면 작성해주세요.

  • LazyColumn 내부에 LazyRow를 중첩하는 구조가 적절한지 궁금합니다.
  • BuyScreen / WishlistScreen에서 LazyColumn + Row 쌍으로 2열 그리드를 구현했는데, LazyVerticalGrid 대신 이 방식을 선택한 것이 미션 요구사항에 부합하는지 피드백 부탁드립니다.

jongheecode and others added 2 commits May 24, 2026 11:01
- Week3 RecyclerView + Adapter 코드를 Compose Lazy 레이아웃으로 교체
- HomeScreen: LazyRow로 수평 상품 목록 구현 (기존 horizontal RecyclerView 대체)
- BuyScreen: LazyVerticalGrid(GridCells.Fixed(2))로 그리드 목록 구현
- WishlistScreen: LazyVerticalGrid로 위시리스트 그리드 구현
- HomeProductItem, GridProductItem Composable로 아이템 레이아웃 작성
- 각 아이템에 key = { it.id } 지정으로 상태 안정성 확보

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- HeartButton 공통 컴포저블 추출 (HomeProductItem, GridProductItem 중복 제거)
- chunkedBuyProducts, chunkedWishItems 파일 레벨 val로 이동
- BagScreen 장바구니 아이콘 Material Icon → drawable 리소스 교체
- WishlistScreen, ProfileScreen 하드코딩 문자열 strings.xml 분리
- 네비게이션 아이콘 Week3 drawable 리소스 적용
- 앱 아이콘 기본 Android 템플릿으로 수정

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 24, 2026 02:49
@jongheecode jongheecode changed the title Week8 8주차 과제_종이 May 24, 2026
@jongheecode jongheecode changed the title 8주차 과제_종이 8주차 미션_종이 May 24, 2026

Copilot AI 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.

Pull request overview

Week 8 과제용으로 Week3 RecyclerView/Adapter 기반 UI를 Jetpack Compose(LazyColumn/LazyRow) 기반으로 마이그레이션한 독립 프로젝트(Week8)를 추가한 PR입니다. 홈/구매/위시리스트/장바구니/프로필 화면을 Compose로 구성하고, Compose Navigation 기반의 하단 탭 구조를 포함합니다.

Changes:

  • HomeScreen에 LazyColumn + 내부 LazyRow로 가로 상품 리스트 구성
  • Buy/Wishlist 화면에 LazyColumn + Row 조합으로 2열 그리드 형태 구성 및 공용 상품 아이템 컴포저블 추가
  • Week8 프로젝트 전반의 Gradle/Wrapper/리소스/테마/내비게이션 구성 추가

Reviewed changes

Copilot reviewed 44 out of 60 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
jongyee/Week8/settings.gradle.kts Week8 프로젝트 설정 및 저장소/플러그인 관리 추가
jongyee/Week8/gradlew.bat Windows Gradle wrapper 실행 스크립트 추가
jongyee/Week8/gradlew POSIX Gradle wrapper 실행 스크립트 추가
jongyee/Week8/gradle/wrapper/gradle-wrapper.properties Gradle wrapper 배포 설정 추가
jongyee/Week8/gradle/libs.versions.toml 버전 카탈로그(AGP/Kotlin/Compose/Navigation 등) 추가
jongyee/Week8/gradle/gradle-daemon-jvm.properties Gradle daemon JVM/toolchain 설정 파일 추가
jongyee/Week8/gradle.properties Gradle/AndroidX 기본 프로퍼티 추가
jongyee/Week8/build.gradle.kts 루트 빌드 플러그인 alias 구성 추가
jongyee/Week8/app/build.gradle.kts 앱 모듈 Android/Compose/의존성 설정 추가
jongyee/Week8/app/proguard-rules.pro ProGuard 규칙 파일 추가
jongyee/Week8/app/.gitignore 앱 모듈 빌드 산출물 ignore 추가
jongyee/Week8/.gitignore Week8 프로젝트 공통 ignore 규칙 추가
jongyee/Week8/app/src/main/AndroidManifest.xml 앱 매니페스트/런처 액티비티 등록
jongyee/Week8/app/src/main/java/com/example/week8/MainActivity.kt Compose 엔트리포인트(MainActivity) 추가
jongyee/Week8/app/src/main/java/com/example/week8/ui/MainScreen.kt 하단 탭 + Compose Navigation 기반 메인 화면 추가
jongyee/Week8/app/src/main/java/com/example/week8/navigation/AppDestination.kt typed destination 정의(Serializable object) 추가
jongyee/Week8/app/src/main/java/com/example/week8/Product.kt 상품 모델 데이터 클래스 추가
jongyee/Week8/app/src/main/java/com/example/week8/ui/home/HomeScreen.kt 홈 화면(LazyColumn + LazyRow) 추가
jongyee/Week8/app/src/main/java/com/example/week8/ui/buy/BuyScreen.kt 구매 화면(탭 + 2열 레이아웃) 추가
jongyee/Week8/app/src/main/java/com/example/week8/ui/wishlist/WishlistScreen.kt 위시리스트 화면(2열 레이아웃) 추가
jongyee/Week8/app/src/main/java/com/example/week8/ui/bag/BagScreen.kt 장바구니 빈 상태 UI 및 구매 화면 이동 버튼 추가
jongyee/Week8/app/src/main/java/com/example/week8/ui/profile/ProfileScreen.kt 프로필 화면 UI 추가
jongyee/Week8/app/src/main/java/com/example/week8/ui/product/ProductItem.kt 홈/그리드 상품 아이템 및 좋아요 토글 UI 추가
jongyee/Week8/app/src/main/java/com/example/week8/ui/theme/Color.kt Compose 컬러 팔레트 추가
jongyee/Week8/app/src/main/java/com/example/week8/ui/theme/Theme.kt Compose Material3 테마 래퍼 추가
jongyee/Week8/app/src/main/res/values/strings.xml 화면 문구 리소스 추가
jongyee/Week8/app/src/main/res/values/colors.xml 기본 색상 리소스 추가
jongyee/Week8/app/src/main/res/values/themes.xml 라이트 테마 리소스 추가
jongyee/Week8/app/src/main/res/values-night/themes.xml 나이트 테마 리소스 추가
jongyee/Week8/app/src/main/res/xml/backup_rules.xml 백업 규칙 샘플 파일 추가
jongyee/Week8/app/src/main/res/xml/data_extraction_rules.xml 데이터 추출 규칙 샘플 파일 추가
jongyee/Week8/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml 런처 아이콘 정의 추가
jongyee/Week8/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml 라운드 런처 아이콘 정의 추가
jongyee/Week8/app/src/main/res/drawable/ic_launcher_background.xml 런처 아이콘 배경 벡터 추가
jongyee/Week8/app/src/main/res/drawable/ic_launcher_foreground.xml 런처 아이콘 전경 벡터 추가
jongyee/Week8/app/src/main/res/drawable/ic_heart_filled.xml 좋아요(채움) 아이콘 추가
jongyee/Week8/app/src/main/res/drawable/ic_heart_empty.xml 좋아요(비움) 아이콘 추가
jongyee/Week8/app/src/main/res/drawable/home.xml 하단 탭 홈 아이콘 추가
jongyee/Week8/app/src/main/res/drawable/buy.xml 하단 탭 구매 아이콘 추가
jongyee/Week8/app/src/main/res/drawable/wishlist.xml 하단 탭 위시리스트 아이콘 추가
jongyee/Week8/app/src/main/res/drawable/bag.xml 하단 탭 장바구니 아이콘 추가
jongyee/Week8/app/src/main/res/drawable/profile.xml 하단 탭 프로필 아이콘 추가
jongyee/Week8/app/src/test/java/com/example/week8/ExampleUnitTest.kt 기본 유닛 테스트 템플릿 추가
jongyee/Week8/app/src/androidTest/java/com/example/week8/ExampleInstrumentedTest.kt 기본 계측 테스트 템플릿 추가

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread jongyee/Week8/gradlew
Comment on lines +115 to +118
esac

CLASSPATH="\\\"\\\""

Comment thread jongyee/Week8/gradlew
Comment on lines +213 to +217
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
Comment thread jongyee/Week8/gradlew.bat
Comment on lines +70 to +78
:execute
@rem Setup the command line

set CLASSPATH=


@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*

@@ -0,0 +1,2 @@
# Add project specific ProGuard rules here.
-keep class com.example.week7.navigation.** { *; }
Comment on lines +31 to +41
@Composable
private fun HeartButton(isFavorite: Boolean, onToggle: () -> Unit) {
Image(
painter = painterResource(
id = if (isFavorite) R.drawable.ic_heart_filled else R.drawable.ic_heart_empty
),
contentDescription = null,
modifier = Modifier
.size(24.dp)
.clickable { onToggle() }
)
Comment on lines +37 to +43
private val bottomNavItems = listOf(
BottomNavItem("홈", R.drawable.home, AppDestination.Home),
BottomNavItem("구매하기", R.drawable.buy, AppDestination.Buy),
BottomNavItem("위시리스트", R.drawable.wishlist, AppDestination.Wishlist),
BottomNavItem("장바구니", R.drawable.bag, AppDestination.Bag),
BottomNavItem("프로필", R.drawable.profile, AppDestination.Profile),
)
Comment on lines +85 to +95
items(
items = chunkedBuyProducts,
key = { chunk -> chunk.first().id }
) { chunk ->
Row(modifier = Modifier.fillMaxWidth()) {
chunk.forEach { product ->
GridProductItem(
product = product,
modifier = Modifier.weight(1f)
)
}
Comment on lines +47 to +57
items(
items = chunkedWishItems,
key = { chunk -> chunk.first().id }
) { chunk ->
Row(modifier = Modifier.fillMaxWidth()) {
chunk.forEach { product ->
GridProductItem(
product = product,
modifier = Modifier.weight(1f)
)
}
Comment on lines +1 to +4
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.Week8" parent="android:Theme.Material.Light.NoActionBar" />
</resources>

@kimdoyeon1234 kimdoyeon1234 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

수고하셨습니다!

@serializable + data object 기반 Type-safe Navigation을 완벽하게 적용하신 점, composable<AppDestination.Home> 형태로 올바르게 사용하신 점 정말 좋았습니다!
BagScreen에서 onNavigateToBuy를 람다로 받아 Events Flow Up 패턴을 잘 적용하신 점도 깔끔합니다

다만 BuyScreen과 WishlistScreen에서 chunked() + LazyColumn + Row 조합으로 2열 그리드를 구현하셨는데, LazyVerticalGrid를 사용하시면 훨씬 간결하게 구현할 수 있고 key도 아이템별로 안정적으로 잡을 수 있습니다!
현재 key가 chunk.first().id로만 잡혀있어서 chunk 내부 두 번째 아이템은 key가 없는 상태입니다! 리스트가 변경될 경우 rememberSaveable로 관리 중인 하트 상태가 엉뚱한 아이템에 붙을 수 있으니 꼭 수정해주세요!

그리고 하트 상태를 GridProductItem, HomeProductItem 내부에서 rememberSaveable로 관리하고 계신데, 상태를 상위로 끌어올려서(State Hoisting) 뷰모델이나 상위 컴포저블에서 관리하는 게 Compose 권장 패턴입니다! 현재 구조에서는 화면을 이탈했다가 돌아오면 하트 상태가 초기화될 수 있습니다!

전반적으로 구조도 깔끔하고 완성도가 높습니다! 수고하셨습니다

Comment on lines +82 to +93
LazyRow(
contentPadding = PaddingValues(horizontal = 24.dp)
) {
items(
items = homeProducts,
key = { product -> product.id }
) { product ->
HomeProductItem(product = product)
}
}
Spacer(Modifier.height(16.dp))
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LazyColumn 안에 LazyRow를 중첩하는 건 스크롤 방향이 다르기 때문에 허용됩니다! 같은 방향(세로 안에 세로, 가로 안에 가로)일 때만 크래시가 발생하니 현재 구조는 문제없습니다

Comment on lines +33 to +59
LazyColumn(
modifier = Modifier
.fillMaxSize()
.background(NikeWhite)
) {
item {
Text(
text = stringResource(R.string.wishlist_title),
color = NikeBlack,
fontSize = 24.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(start = 24.dp, top = 78.dp, bottom = 16.dp)
)
}
items(
items = chunkedWishItems,
key = { chunk -> chunk.first().id }
) { chunk ->
Row(modifier = Modifier.fillMaxWidth()) {
chunk.forEach { product ->
GridProductItem(
product = product,
modifier = Modifier.weight(1f)
)
}
if (chunk.size == 1) {
Spacer(modifier = Modifier.weight(1f))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

동작은 하지만 LazyVerticalGrid를 사용하시면 chunked() 없이도 2열 그리드를 구현할 수 있고, 홀수 아이템 처리도 자동으로 됩니다! 아래처럼 변경해보세요!

kotlinLazyVerticalGrid(
    columns = GridCells.Fixed(2)
) {
    items(
        items = buyProducts,
        key = { it.id }
    ) { product ->
        GridProductItem(product = product)
    }
}

현재 chunked 방식은 key도 chunk.first().id로만 잡혀있어서 내부 아이템들이 개별적으로 키를 가지지 못한다는 문제도 있습니다! LazyVerticalGrid로 바꾸시면 이 문제도 함께 해결됩니다!

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

isFavorite은 UI 상태이기 때문에 데이터 모델인 Product에 포함시키기보다는 별도의 UI State 클래스로 분리하는 게 더 좋습니다! 예를 들어 data class ProductUiState(val product: Product, val isFavorite: Boolean)처럼 분리해보세요!

Comment on lines +44 to +51
@Composable
fun HomeProductItem(product: Product) {
var isFavorite by rememberSaveable { mutableStateOf(product.isFavorite) }

Column(
modifier = Modifier
.width(180.dp)
.padding(end = 12.dp)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

현재 하트 상태를 GridProductItem, HomeProductItem 내부에서 rememberSaveable로 관리하고 계신데, 상태를 상위로 끌어올려서(State Hoisting) 뷰모델이나 상위 컴포저블에서 관리하는 게 Compose 권장 패턴입니다! 현재 구조에서는 화면을 이탈했다가 돌아오면 하트 상태가 초기화될 수 있습니다!

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

GridProductItem 내부에서 rememberSaveable { mutableStateOf(product.isFavorite) }로 하트 상태를 관리하고 계신데, LazyColumn의 key가 chunk.first().id로만 잡혀있어서 chunk 내부 두 번째 아이템은 key가 없는 상태입니다! 리스트가 변경될 경우 하트 상태가 엉뚱한 아이템에 붙을 수 있습니다! LazyVerticalGrid로 변경하시면 아이템별로 key가 안정적으로 잡혀서 이 문제도 함께 해결됩니다!

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.

3 participants