From cd660332ba698bddb5eef92d6e95d219aabc66e2 Mon Sep 17 00:00:00 2001 From: chrry03 Date: Sun, 31 May 2026 20:07:21 +0900 Subject: [PATCH 1/8] =?UTF-8?q?chore:=20Ma=C3=ABchore:=20=EB=94=94?= =?UTF-8?q?=EB=A0=89=ED=86=A0=EB=A6=AC=EA=B5=AC=EC=A1=B0=EC=83=9D=EC=84=B1?= =?UTF-8?q?=20MainActivity=EC=98=AE=EA=B9=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/src/main/AndroidManifest.xml | 2 +- .../{ => feature/main}/MainActivity.kt | 197 +++++++++--------- 2 files changed, 100 insertions(+), 99 deletions(-) rename Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/{ => feature/main}/MainActivity.kt (74%) diff --git a/Nyong/week7/nikecompose/app/src/main/AndroidManifest.xml b/Nyong/week7/nikecompose/app/src/main/AndroidManifest.xml index a4fe22dc..da7eeb20 100644 --- a/Nyong/week7/nikecompose/app/src/main/AndroidManifest.xml +++ b/Nyong/week7/nikecompose/app/src/main/AndroidManifest.xml @@ -12,7 +12,7 @@ android:supportsRtl="true" android:theme="@style/Theme.NikeCompose"> diff --git a/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/MainActivity.kt b/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/main/MainActivity.kt similarity index 74% rename from Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/MainActivity.kt rename to Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/main/MainActivity.kt index 215915a0..af096d56 100644 --- a/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/MainActivity.kt +++ b/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/main/MainActivity.kt @@ -1,4 +1,4 @@ -package com.example.nikecompose +package com.example.nikecompose.feature.main import android.os.Bundle import androidx.activity.ComponentActivity @@ -39,9 +39,9 @@ import androidx.compose.material3.Tab import androidx.compose.material3.TabRow import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.getValue import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -57,6 +57,7 @@ import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController +import com.example.nikecompose.R import com.example.nikecompose.ui.theme.NikeComposeTheme class MainActivity : ComponentActivity() { @@ -149,8 +150,8 @@ fun NikeComposeApp() { ) Scaffold( - modifier = Modifier.fillMaxSize(), - containerColor = Color.White, + modifier = Modifier.Companion.fillMaxSize(), + containerColor = Color.Companion.White, bottomBar = { NikeBottomBar( items = bottomItems, @@ -170,7 +171,7 @@ fun NikeComposeApp() { NavHost( navController = navController, startDestination = BottomRoute.Home.route, - modifier = Modifier + modifier = Modifier.Companion .fillMaxSize() .padding(innerPadding) ) { @@ -214,12 +215,12 @@ fun NikeBottomBar( onItemClick: (BottomRoute) -> Unit ) { NavigationBar( - containerColor = Color.White, + containerColor = Color.Companion.White, tonalElevation = 0.dp ) { items.forEach { item -> val selected = currentRoute == item.route - val iconColor = if (selected) Color.Black else Color.Gray + val iconColor = if (selected) Color.Companion.Black else Color.Companion.Gray NavigationBarItem( selected = selected, @@ -228,7 +229,7 @@ fun NikeBottomBar( Icon( painter = painterResource(id = item.iconResId), contentDescription = item.label, - modifier = Modifier.size(22.dp), + modifier = Modifier.Companion.size(22.dp), tint = iconColor ) }, @@ -239,11 +240,11 @@ fun NikeBottomBar( ) }, colors = NavigationBarItemDefaults.colors( - selectedIconColor = Color.Black, - selectedTextColor = Color.Black, - unselectedIconColor = Color.Gray, - unselectedTextColor = Color.Gray, - indicatorColor = Color.Transparent + selectedIconColor = Color.Companion.Black, + selectedTextColor = Color.Companion.Black, + unselectedIconColor = Color.Companion.Gray, + unselectedTextColor = Color.Companion.Gray, + indicatorColor = Color.Companion.Transparent ) ) } @@ -253,58 +254,58 @@ fun NikeBottomBar( @Composable fun HomeScreen() { LazyColumn( - modifier = Modifier + modifier = Modifier.Companion .fillMaxSize() - .background(Color.White), + .background(Color.Companion.White), contentPadding = PaddingValues(top = 32.dp, bottom = 24.dp) ) { item { Column( - modifier = Modifier.padding(horizontal = 24.dp) + modifier = Modifier.Companion.padding(horizontal = 24.dp) ) { Text( text = "Discover", fontSize = 26.sp, - fontWeight = FontWeight.Bold, - color = Color.Black + fontWeight = FontWeight.Companion.Bold, + color = Color.Companion.Black ) - Spacer(modifier = Modifier.height(4.dp)) + Spacer(modifier = Modifier.Companion.height(4.dp)) Text( text = "9월 4일 목요일", fontSize = 13.sp, - color = Color.Gray + color = Color.Companion.Gray ) - Spacer(modifier = Modifier.height(28.dp)) + Spacer(modifier = Modifier.Companion.height(28.dp)) Image( painter = painterResource(id = R.drawable.img_home_logo), contentDescription = "홈 배너 이미지", - modifier = Modifier.fillMaxWidth(), - contentScale = ContentScale.FillWidth + modifier = Modifier.Companion.fillMaxWidth(), + contentScale = ContentScale.Companion.FillWidth ) - Spacer(modifier = Modifier.height(24.dp)) + Spacer(modifier = Modifier.Companion.height(24.dp)) Text( text = "What's new", fontSize = 12.sp, - fontWeight = FontWeight.Bold, - color = Color.Gray + fontWeight = FontWeight.Companion.Bold, + color = Color.Companion.Gray ) - Spacer(modifier = Modifier.height(4.dp)) + Spacer(modifier = Modifier.Companion.height(4.dp)) Text( text = "나이키 최신 상품", fontSize = 20.sp, - fontWeight = FontWeight.Bold, - color = Color.Black + fontWeight = FontWeight.Companion.Bold, + color = Color.Companion.Black ) - Spacer(modifier = Modifier.height(16.dp)) + Spacer(modifier = Modifier.Companion.height(16.dp)) } } @@ -329,35 +330,35 @@ fun HomeProductCard( product: ProductItem ) { Column( - modifier = Modifier.width(230.dp) + modifier = Modifier.Companion.width(230.dp) ) { Image( painter = painterResource(id = product.imageResId), contentDescription = product.name, - modifier = Modifier + modifier = Modifier.Companion .fillMaxWidth() .height(230.dp) .background(Color(0xFFF5F5F5)), - contentScale = ContentScale.Crop + contentScale = ContentScale.Companion.Crop ) - Spacer(modifier = Modifier.height(12.dp)) + Spacer(modifier = Modifier.Companion.height(12.dp)) Text( text = product.name, fontSize = 11.sp, - fontWeight = FontWeight.Bold, - color = Color.Black, + fontWeight = FontWeight.Companion.Bold, + color = Color.Companion.Black, lineHeight = 14.sp ) - Spacer(modifier = Modifier.height(4.dp)) + Spacer(modifier = Modifier.Companion.height(4.dp)) Text( text = product.price, fontSize = 11.sp, - fontWeight = FontWeight.Bold, - color = Color.Black + fontWeight = FontWeight.Companion.Bold, + color = Color.Companion.Black ) } } @@ -368,16 +369,16 @@ fun ShopScreen() { val tabs = listOf("전체", "Tops & T-shirts", "sale") Column( - modifier = Modifier + modifier = Modifier.Companion .fillMaxSize() - .background(Color.White) + .background(Color.Companion.White) ) { - Spacer(modifier = Modifier.height(28.dp)) + Spacer(modifier = Modifier.Companion.height(28.dp)) TabRow( selectedTabIndex = selectedTabIndex, - containerColor = Color.White, - contentColor = Color.Black + containerColor = Color.Companion.White, + contentColor = Color.Companion.Black ) { tabs.forEachIndexed { index, title -> Tab( @@ -395,7 +396,7 @@ fun ShopScreen() { LazyVerticalGrid( columns = GridCells.Fixed(2), - modifier = Modifier.fillMaxSize(), + modifier = Modifier.Companion.fillMaxSize(), contentPadding = PaddingValues(horizontal = 24.dp, vertical = 24.dp), horizontalArrangement = Arrangement.spacedBy(16.dp), verticalArrangement = Arrangement.spacedBy(36.dp) @@ -415,19 +416,19 @@ fun ShopProductCard( product: ProductItem ) { Column( - modifier = Modifier.fillMaxWidth() + modifier = Modifier.Companion.fillMaxWidth() ) { Box( - modifier = Modifier.fillMaxWidth() + modifier = Modifier.Companion.fillMaxWidth() ) { Image( painter = painterResource(id = product.imageResId), contentDescription = product.name, - modifier = Modifier + modifier = Modifier.Companion .fillMaxWidth() .aspectRatio(1f) .background(Color(0xFFF5F5F5)), - contentScale = ContentScale.Crop + contentScale = ContentScale.Companion.Crop ) Icon( @@ -435,54 +436,54 @@ fun ShopProductCard( id = if (product.isLiked) R.drawable.ic_heart_on else R.drawable.ic_heart_off ), contentDescription = "좋아요", - modifier = Modifier - .align(Alignment.TopEnd) + modifier = Modifier.Companion + .align(Alignment.Companion.TopEnd) .padding(8.dp) .size(22.dp), - tint = Color.Unspecified + tint = Color.Companion.Unspecified ) } - Spacer(modifier = Modifier.height(8.dp)) + Spacer(modifier = Modifier.Companion.height(8.dp)) product.badge?.let { Text( text = it, fontSize = 11.sp, color = Color(0xFFFF6A00), - fontWeight = FontWeight.Bold + fontWeight = FontWeight.Companion.Bold ) } Text( text = product.name, fontSize = 11.sp, - fontWeight = FontWeight.Bold, - color = Color.Black, + fontWeight = FontWeight.Companion.Bold, + color = Color.Companion.Black, lineHeight = 14.sp ) Text( text = product.subTitle, fontSize = 10.sp, - color = Color.Gray, + color = Color.Companion.Gray, lineHeight = 13.sp ) Text( text = product.colors, fontSize = 10.sp, - color = Color.Gray, + color = Color.Companion.Gray, lineHeight = 13.sp ) - Spacer(modifier = Modifier.height(2.dp)) + Spacer(modifier = Modifier.Companion.height(2.dp)) Text( text = product.price, fontSize = 11.sp, - fontWeight = FontWeight.Bold, - color = Color.Black + fontWeight = FontWeight.Companion.Bold, + color = Color.Companion.Black ) } } @@ -490,21 +491,21 @@ fun ShopProductCard( @Composable fun WishlistScreen() { Column( - modifier = Modifier + modifier = Modifier.Companion .fillMaxSize() - .background(Color.White) + .background(Color.Companion.White) ) { Text( text = "위시리스트", fontSize = 24.sp, - fontWeight = FontWeight.Bold, - color = Color.Black, - modifier = Modifier.padding(start = 24.dp, top = 40.dp) + fontWeight = FontWeight.Companion.Bold, + color = Color.Companion.Black, + modifier = Modifier.Companion.padding(start = 24.dp, top = 40.dp) ) LazyVerticalGrid( columns = GridCells.Fixed(2), - modifier = Modifier.fillMaxSize(), + modifier = Modifier.Companion.fillMaxSize(), contentPadding = PaddingValues(horizontal = 24.dp, vertical = 28.dp), horizontalArrangement = Arrangement.spacedBy(16.dp), verticalArrangement = Arrangement.spacedBy(28.dp) @@ -524,55 +525,55 @@ fun WishlistProductCard( product: ProductItem ) { Column( - modifier = Modifier.fillMaxWidth() + modifier = Modifier.Companion.fillMaxWidth() ) { Image( painter = painterResource(id = product.imageResId), contentDescription = product.name, - modifier = Modifier + modifier = Modifier.Companion .fillMaxWidth() .aspectRatio(1f) .background(Color(0xFFF5F5F5)), - contentScale = ContentScale.Crop + contentScale = ContentScale.Companion.Crop ) - Spacer(modifier = Modifier.height(8.dp)) + Spacer(modifier = Modifier.Companion.height(8.dp)) Text( text = product.name, fontSize = 11.sp, - fontWeight = FontWeight.Bold, - color = Color.Black, + fontWeight = FontWeight.Companion.Bold, + color = Color.Companion.Black, lineHeight = 14.sp ) Text( text = product.price, fontSize = 11.sp, - fontWeight = FontWeight.Bold, - color = Color.Black + fontWeight = FontWeight.Companion.Bold, + color = Color.Companion.Black ) if (product.id == 2) { Text( text = product.subTitle, fontSize = 10.sp, - color = Color.Gray, + color = Color.Companion.Gray, lineHeight = 13.sp ) Text( text = product.colors, fontSize = 10.sp, - color = Color.Gray, + color = Color.Companion.Gray, lineHeight = 13.sp ) Text( text = "US$10", fontSize = 11.sp, - fontWeight = FontWeight.Bold, - color = Color.Black + fontWeight = FontWeight.Companion.Bold, + color = Color.Companion.Black ) } } @@ -583,33 +584,33 @@ fun CartScreen( onOrderClick: () -> Unit ) { Column( - modifier = Modifier + modifier = Modifier.Companion .fillMaxSize() - .background(Color.White) + .background(Color.Companion.White) .padding(horizontal = 24.dp) ) { Box( - modifier = Modifier + modifier = Modifier.Companion .fillMaxWidth() .weight(1f), - contentAlignment = Alignment.Center + contentAlignment = Alignment.Companion.Center ) { Column( - horizontalAlignment = Alignment.CenterHorizontally + horizontalAlignment = Alignment.Companion.CenterHorizontally ) { Image( painter = painterResource(id = R.drawable.ic_cartcircle), contentDescription = "장바구니 아이콘", - modifier = Modifier.size(56.dp) + modifier = Modifier.Companion.size(56.dp) ) - Spacer(modifier = Modifier.height(12.dp)) + Spacer(modifier = Modifier.Companion.height(12.dp)) Text( text = "장바구니가 비어 있습니다.\n제품을 추가하면 여기에 표시됩니다.", fontSize = 12.sp, - color = Color.Black, - textAlign = TextAlign.Center, + color = Color.Companion.Black, + textAlign = TextAlign.Companion.Center, lineHeight = 18.sp ) } @@ -617,39 +618,39 @@ fun CartScreen( Button( onClick = onOrderClick, - modifier = Modifier + modifier = Modifier.Companion .fillMaxWidth() .height(56.dp), colors = ButtonDefaults.buttonColors( - containerColor = Color.Black, - contentColor = Color.White + containerColor = Color.Companion.Black, + contentColor = Color.Companion.White ) ) { Text( text = "주문하기", fontSize = 15.sp, - fontWeight = FontWeight.Bold + fontWeight = FontWeight.Companion.Bold ) } - Spacer(modifier = Modifier.height(16.dp)) - Spacer(modifier = Modifier.windowInsetsBottomHeight(WindowInsets.navigationBars)) + Spacer(modifier = Modifier.Companion.height(16.dp)) + Spacer(modifier = Modifier.Companion.windowInsetsBottomHeight(WindowInsets.Companion.navigationBars)) } } @Composable fun ProfileScreen() { Column( - modifier = Modifier + modifier = Modifier.Companion .fillMaxSize() - .background(Color.White) + .background(Color.Companion.White) .padding(horizontal = 24.dp, vertical = 32.dp) ) { Text( text = "프로필", fontSize = 24.sp, - fontWeight = FontWeight.Bold, - color = Color.Black + fontWeight = FontWeight.Companion.Bold, + color = Color.Companion.Black ) } } \ No newline at end of file From 9dd57ab63b1e05462d88a25a5d6617191615a1bc Mon Sep 17 00:00:00 2001 From: chrry03 Date: Sun, 31 May 2026 20:13:36 +0900 Subject: [PATCH 2/8] chore: bottomroute --- .../nikecompose/feature/main/MainActivity.kt | 14 +------------- .../nikecompose/navigation/BottomRoute.kt | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 13 deletions(-) create mode 100644 Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/navigation/BottomRoute.kt diff --git a/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/main/MainActivity.kt b/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/main/MainActivity.kt index af096d56..26c2b77b 100644 --- a/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/main/MainActivity.kt +++ b/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/main/MainActivity.kt @@ -4,7 +4,6 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge -import androidx.annotation.DrawableRes import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement @@ -59,6 +58,7 @@ import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import com.example.nikecompose.R import com.example.nikecompose.ui.theme.NikeComposeTheme +import com.example.nikecompose.navigation.BottomRoute class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -73,18 +73,6 @@ class MainActivity : ComponentActivity() { } } -sealed class BottomRoute( - val route: String, - val label: String, - @DrawableRes val iconResId: Int -) { - data object Home : BottomRoute("home", "홈", R.drawable.ic_home) - data object Shop : BottomRoute("shop", "구매하기", R.drawable.ic_buy) - data object Wishlist : BottomRoute("wishlist", "위시리스트", R.drawable.ic_wishlist) - data object Cart : BottomRoute("cart", "장바구니", R.drawable.ic_cart) - data object Profile : BottomRoute("profile", "프로필", R.drawable.ic_profile) -} - data class ProductItem( val id: Int, @DrawableRes val imageResId: Int, diff --git a/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/navigation/BottomRoute.kt b/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/navigation/BottomRoute.kt new file mode 100644 index 00000000..c152efe7 --- /dev/null +++ b/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/navigation/BottomRoute.kt @@ -0,0 +1,16 @@ +package com.example.nikecompose.navigation + +import androidx.annotation.DrawableRes +import com.example.nikecompose.R + +sealed class BottomRoute( + val route: String, + val label: String, + @DrawableRes val iconResId: Int +) { + data object Home : BottomRoute("home", "홈", R.drawable.ic_home) + data object Shop : BottomRoute("shop", "구매하기", R.drawable.ic_buy) + data object Wishlist : BottomRoute("wishlist", "위시리스트", R.drawable.ic_wishlist) + data object Cart : BottomRoute("cart", "장바구니", R.drawable.ic_cart) + data object Profile : BottomRoute("profile", "프로필", R.drawable.ic_profile) +} \ No newline at end of file From 059a3dd7ce2e35f3e0dfecbb735c4e576a0f11a3 Mon Sep 17 00:00:00 2001 From: chrry03 Date: Sun, 31 May 2026 20:27:42 +0900 Subject: [PATCH 3/8] =?UTF-8?q?chore:=20=EB=94=94=EB=A0=89=C3=AB=ED=86=A0?= =?UTF-8?q?=EB=A6=AC=EA=B5=AC=EC=A1=B0=EC=84=A4=EC=A0=95=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...kotlin-compiler-4968883452369776417.salive | 0 .../core/component/NikeBottomBar.kt | 58 ++ .../data/dummy/ProductDummyData.kt | 45 ++ .../nikecompose/domain/model/ProductItem.kt | 14 + .../feature/cart/presentation/CartScreen.kt | 87 +++ .../feature/home/presentation/HomeScreen.kt | 101 ++++ .../presentation/component/HomeProductCard.kt | 57 ++ .../nikecompose/feature/main/MainActivity.kt | 555 +----------------- .../profile/presentation/ProfileScreen.kt | 30 + .../feature/shop/presentation/ShopScreen.kt | 74 +++ .../presentation/component/ShopProductCard.kt | 102 ++++ .../wishlist/presentation/WishlistScreen.kt | 52 ++ .../component/WishlistProductCard.kt | 78 +++ 13 files changed, 707 insertions(+), 546 deletions(-) create mode 100644 Nyong/week7/nikecompose/.kotlin/sessions/kotlin-compiler-4968883452369776417.salive create mode 100644 Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/core/component/NikeBottomBar.kt create mode 100644 Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/data/dummy/ProductDummyData.kt create mode 100644 Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/domain/model/ProductItem.kt create mode 100644 Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/cart/presentation/CartScreen.kt create mode 100644 Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/home/presentation/HomeScreen.kt create mode 100644 Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/home/presentation/component/HomeProductCard.kt create mode 100644 Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/profile/presentation/ProfileScreen.kt create mode 100644 Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/shop/presentation/ShopScreen.kt create mode 100644 Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/shop/presentation/component/ShopProductCard.kt create mode 100644 Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/wishlist/presentation/WishlistScreen.kt create mode 100644 Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/wishlist/presentation/component/WishlistProductCard.kt diff --git a/Nyong/week7/nikecompose/.kotlin/sessions/kotlin-compiler-4968883452369776417.salive b/Nyong/week7/nikecompose/.kotlin/sessions/kotlin-compiler-4968883452369776417.salive new file mode 100644 index 00000000..e69de29b diff --git a/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/core/component/NikeBottomBar.kt b/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/core/component/NikeBottomBar.kt new file mode 100644 index 00000000..ec69f724 --- /dev/null +++ b/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/core/component/NikeBottomBar.kt @@ -0,0 +1,58 @@ +package com.example.nikecompose.core.component + +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Icon +import androidx.compose.material3.NavigationBar +import androidx.compose.material3.NavigationBarItem +import androidx.compose.material3.NavigationBarItemDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.nikecompose.navigation.BottomRoute + +@Composable +fun NikeBottomBar( + items: List, + currentRoute: String?, + onItemClick: (BottomRoute) -> Unit +) { + NavigationBar( + containerColor = Color.White, + tonalElevation = 0.dp + ) { + items.forEach { item -> + val selected = currentRoute == item.route + val iconColor = if (selected) Color.Black else Color.Gray + + NavigationBarItem( + selected = selected, + onClick = { onItemClick(item) }, + icon = { + Icon( + painter = painterResource(id = item.iconResId), + contentDescription = item.label, + modifier = Modifier.size(22.dp), + tint = iconColor + ) + }, + label = { + Text( + text = item.label, + fontSize = 10.sp + ) + }, + colors = NavigationBarItemDefaults.colors( + selectedIconColor = Color.Black, + selectedTextColor = Color.Black, + unselectedIconColor = Color.Gray, + unselectedTextColor = Color.Gray, + indicatorColor = Color.Transparent + ) + ) + } + } +} \ No newline at end of file diff --git a/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/data/dummy/ProductDummyData.kt b/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/data/dummy/ProductDummyData.kt new file mode 100644 index 00000000..ab0b9cdf --- /dev/null +++ b/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/data/dummy/ProductDummyData.kt @@ -0,0 +1,45 @@ +package com.example.nikecompose.data.dummy + +import com.example.nikecompose.R +import com.example.nikecompose.domain.model.ProductItem + +val productList = listOf( + ProductItem( + id = 1, + imageResId = R.drawable.img_shoe_1, + name = "Nike Everyday Plus Cushioned", + subTitle = "Training Ankle Socks (6 Pairs)", + colors = "5 Colours", + price = "US$10", + isLiked = true + ), + ProductItem( + id = 2, + imageResId = R.drawable.img_shoe_2, + name = "Nike Elite Crew", + subTitle = "Basketball Socks", + colors = "7 Colours", + price = "US$16", + isLiked = false + ), + ProductItem( + id = 3, + imageResId = R.drawable.img_shoe_1, + name = "Nike Air Force 1 '07", + subTitle = "Women's Shoes", + colors = "5 Colours", + price = "US$115", + isLiked = false, + badge = "BestSeller" + ), + ProductItem( + id = 4, + imageResId = R.drawable.img_shoe_2, + name = "Jordan ENike Air Force 1 '07ssentials", + subTitle = "Men's Shoes", + colors = "2 Colours", + price = "US$115", + isLiked = false, + badge = "BestSeller" + ) +) \ No newline at end of file diff --git a/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/domain/model/ProductItem.kt b/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/domain/model/ProductItem.kt new file mode 100644 index 00000000..fe8dfcb5 --- /dev/null +++ b/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/domain/model/ProductItem.kt @@ -0,0 +1,14 @@ +package com.example.nikecompose.domain.model + +import androidx.annotation.DrawableRes + +data class ProductItem( + val id: Int, + @DrawableRes val imageResId: Int, + val name: String, + val subTitle: String, + val colors: String, + val price: String, + val isLiked: Boolean = false, + val badge: String? = null +) \ No newline at end of file diff --git a/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/cart/presentation/CartScreen.kt b/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/cart/presentation/CartScreen.kt new file mode 100644 index 00000000..4bd15193 --- /dev/null +++ b/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/cart/presentation/CartScreen.kt @@ -0,0 +1,87 @@ +package com.example.nikecompose.feature.cart.presentation + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBars +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.windowInsetsBottomHeight +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.nikecompose.R + +@Composable +fun CartScreen( + onOrderClick: () -> Unit +) { + Column( + modifier = Modifier + .fillMaxSize() + .background(Color.White) + .padding(horizontal = 24.dp) + ) { + Box( + modifier = Modifier + .fillMaxWidth() + .weight(1f), + contentAlignment = Alignment.Center + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { + Image( + painter = painterResource(id = R.drawable.ic_cartcircle), + contentDescription = "장바구니 아이콘", + modifier = Modifier.size(56.dp) + ) + + Spacer(modifier = Modifier.height(12.dp)) + + Text( + text = "장바구니가 비어 있습니다.\n제품을 추가하면 여기에 표시됩니다.", + fontSize = 12.sp, + color = Color.Black, + textAlign = TextAlign.Center, + lineHeight = 18.sp + ) + } + } + + Button( + onClick = onOrderClick, + modifier = Modifier + .fillMaxWidth() + .height(56.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Color.Black, + contentColor = Color.White + ) + ) { + Text( + text = "주문하기", + fontSize = 15.sp, + fontWeight = FontWeight.Bold + ) + } + + Spacer(modifier = Modifier.height(16.dp)) + Spacer(modifier = Modifier.windowInsetsBottomHeight(WindowInsets.navigationBars)) + } +} \ No newline at end of file diff --git a/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/home/presentation/HomeScreen.kt b/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/home/presentation/HomeScreen.kt new file mode 100644 index 00000000..1179d0fc --- /dev/null +++ b/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/home/presentation/HomeScreen.kt @@ -0,0 +1,101 @@ +package com.example.nikecompose.feature.home.presentation + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.nikecompose.R +import com.example.nikecompose.data.dummy.productList +import com.example.nikecompose.feature.home.presentation.component.HomeProductCard + +@Composable +fun HomeScreen() { + LazyColumn( + modifier = Modifier + .fillMaxSize() + .background(Color.White), + contentPadding = PaddingValues(top = 32.dp, bottom = 24.dp) + ) { + item { + Column( + modifier = Modifier.padding(horizontal = 24.dp) + ) { + Text( + text = "Discover", + fontSize = 26.sp, + fontWeight = FontWeight.Bold, + color = Color.Black + ) + + Spacer(modifier = Modifier.height(4.dp)) + + Text( + text = "9월 4일 목요일", + fontSize = 13.sp, + color = Color.Gray + ) + + Spacer(modifier = Modifier.height(28.dp)) + + Image( + painter = painterResource(id = R.drawable.img_home_logo), + contentDescription = "홈 배너 이미지", + modifier = Modifier.fillMaxWidth(), + contentScale = ContentScale.FillWidth + ) + + Spacer(modifier = Modifier.height(24.dp)) + + Text( + text = "What's new", + fontSize = 12.sp, + fontWeight = FontWeight.Bold, + color = Color.Gray + ) + + Spacer(modifier = Modifier.height(4.dp)) + + Text( + text = "나이키 최신 상품", + fontSize = 20.sp, + fontWeight = FontWeight.Bold, + color = Color.Black + ) + + Spacer(modifier = Modifier.height(16.dp)) + } + } + + item { + LazyRow( + contentPadding = PaddingValues(horizontal = 24.dp), + horizontalArrangement = Arrangement.spacedBy(12.dp) + ) { + items( + items = productList.take(2), + key = { product -> product.id } + ) { product -> + HomeProductCard(product = product) + } + } + } + } +} \ No newline at end of file diff --git a/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/home/presentation/component/HomeProductCard.kt b/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/home/presentation/component/HomeProductCard.kt new file mode 100644 index 00000000..d118827c --- /dev/null +++ b/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/home/presentation/component/HomeProductCard.kt @@ -0,0 +1,57 @@ +package com.example.nikecompose.feature.home.presentation.component + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.nikecompose.domain.model.ProductItem + +@Composable +fun HomeProductCard( + product: ProductItem +) { + Column( + modifier = Modifier.width(230.dp) + ) { + Image( + painter = painterResource(id = product.imageResId), + contentDescription = product.name, + modifier = Modifier + .fillMaxWidth() + .height(230.dp) + .background(Color(0xFFF5F5F5)), + contentScale = ContentScale.Crop + ) + + Spacer(modifier = Modifier.height(12.dp)) + + Text( + text = product.name, + fontSize = 11.sp, + fontWeight = FontWeight.Bold, + color = Color.Black, + lineHeight = 14.sp + ) + + Spacer(modifier = Modifier.height(4.dp)) + + Text( + text = product.price, + fontSize = 11.sp, + fontWeight = FontWeight.Bold, + color = Color.Black + ) + } +} \ No newline at end of file diff --git a/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/main/MainActivity.kt b/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/main/MainActivity.kt index 26c2b77b..0742c0f0 100644 --- a/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/main/MainActivity.kt +++ b/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/main/MainActivity.kt @@ -4,61 +4,24 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge -import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.layout.windowInsetsBottomHeight -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.LazyRow -import androidx.compose.foundation.lazy.grid.GridCells -import androidx.compose.foundation.lazy.grid.LazyVerticalGrid -import androidx.compose.foundation.lazy.grid.items -import androidx.compose.foundation.lazy.items -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.Icon -import androidx.compose.material3.NavigationBar -import androidx.compose.material3.NavigationBarItem -import androidx.compose.material3.NavigationBarItemDefaults import androidx.compose.material3.Scaffold -import androidx.compose.material3.Tab -import androidx.compose.material3.TabRow -import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.getValue -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import androidx.navigation.NavGraph.Companion.findStartDestination import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController -import com.example.nikecompose.R -import com.example.nikecompose.ui.theme.NikeComposeTheme +import com.example.nikecompose.core.component.NikeBottomBar +import com.example.nikecompose.feature.cart.presentation.CartScreen +import com.example.nikecompose.feature.home.presentation.HomeScreen +import com.example.nikecompose.feature.profile.presentation.ProfileScreen +import com.example.nikecompose.feature.shop.presentation.ShopScreen +import com.example.nikecompose.feature.wishlist.presentation.WishlistScreen import com.example.nikecompose.navigation.BottomRoute +import com.example.nikecompose.ui.theme.NikeComposeTheme class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -73,58 +36,6 @@ class MainActivity : ComponentActivity() { } } -data class ProductItem( - val id: Int, - @DrawableRes val imageResId: Int, - val name: String, - val subTitle: String, - val colors: String, - val price: String, - val isLiked: Boolean = false, - val badge: String? = null -) - -val productList = listOf( - ProductItem( - id = 1, - imageResId = R.drawable.img_shoe_1, - name = "Nike Everyday Plus Cushioned", - subTitle = "Training Ankle Socks (6 Pairs)", - colors = "5 Colours", - price = "US$10", - isLiked = true - ), - ProductItem( - id = 2, - imageResId = R.drawable.img_shoe_2, - name = "Nike Elite Crew", - subTitle = "Basketball Socks", - colors = "7 Colours", - price = "US$16", - isLiked = false - ), - ProductItem( - id = 3, - imageResId = R.drawable.img_shoe_1, - name = "Nike Air Force 1 '07", - subTitle = "Women's Shoes", - colors = "5 Colours", - price = "US$115", - isLiked = false, - badge = "BestSeller" - ), - ProductItem( - id = 4, - imageResId = R.drawable.img_shoe_2, - name = "Jordan ENike Air Force 1 '07ssentials", - subTitle = "Men's Shoes", - colors = "2 Colours", - price = "US$115", - isLiked = false, - badge = "BestSeller" - ) -) - @Composable fun NikeComposeApp() { val navController = rememberNavController() @@ -138,8 +49,7 @@ fun NikeComposeApp() { ) Scaffold( - modifier = Modifier.Companion.fillMaxSize(), - containerColor = Color.Companion.White, + modifier = Modifier.fillMaxSize(), bottomBar = { NikeBottomBar( items = bottomItems, @@ -159,7 +69,7 @@ fun NikeComposeApp() { NavHost( navController = navController, startDestination = BottomRoute.Home.route, - modifier = Modifier.Companion + modifier = Modifier .fillMaxSize() .padding(innerPadding) ) { @@ -194,451 +104,4 @@ fun NikeComposeApp() { } } } -} - -@Composable -fun NikeBottomBar( - items: List, - currentRoute: String?, - onItemClick: (BottomRoute) -> Unit -) { - NavigationBar( - containerColor = Color.Companion.White, - tonalElevation = 0.dp - ) { - items.forEach { item -> - val selected = currentRoute == item.route - val iconColor = if (selected) Color.Companion.Black else Color.Companion.Gray - - NavigationBarItem( - selected = selected, - onClick = { onItemClick(item) }, - icon = { - Icon( - painter = painterResource(id = item.iconResId), - contentDescription = item.label, - modifier = Modifier.Companion.size(22.dp), - tint = iconColor - ) - }, - label = { - Text( - text = item.label, - fontSize = 10.sp - ) - }, - colors = NavigationBarItemDefaults.colors( - selectedIconColor = Color.Companion.Black, - selectedTextColor = Color.Companion.Black, - unselectedIconColor = Color.Companion.Gray, - unselectedTextColor = Color.Companion.Gray, - indicatorColor = Color.Companion.Transparent - ) - ) - } - } -} - -@Composable -fun HomeScreen() { - LazyColumn( - modifier = Modifier.Companion - .fillMaxSize() - .background(Color.Companion.White), - contentPadding = PaddingValues(top = 32.dp, bottom = 24.dp) - ) { - item { - Column( - modifier = Modifier.Companion.padding(horizontal = 24.dp) - ) { - Text( - text = "Discover", - fontSize = 26.sp, - fontWeight = FontWeight.Companion.Bold, - color = Color.Companion.Black - ) - - Spacer(modifier = Modifier.Companion.height(4.dp)) - - Text( - text = "9월 4일 목요일", - fontSize = 13.sp, - color = Color.Companion.Gray - ) - - Spacer(modifier = Modifier.Companion.height(28.dp)) - - Image( - painter = painterResource(id = R.drawable.img_home_logo), - contentDescription = "홈 배너 이미지", - modifier = Modifier.Companion.fillMaxWidth(), - contentScale = ContentScale.Companion.FillWidth - ) - - Spacer(modifier = Modifier.Companion.height(24.dp)) - - Text( - text = "What's new", - fontSize = 12.sp, - fontWeight = FontWeight.Companion.Bold, - color = Color.Companion.Gray - ) - - Spacer(modifier = Modifier.Companion.height(4.dp)) - - Text( - text = "나이키 최신 상품", - fontSize = 20.sp, - fontWeight = FontWeight.Companion.Bold, - color = Color.Companion.Black - ) - - Spacer(modifier = Modifier.Companion.height(16.dp)) - } - } - - item { - LazyRow( - contentPadding = PaddingValues(horizontal = 24.dp), - horizontalArrangement = Arrangement.spacedBy(12.dp) - ) { - items( - items = productList.take(2), - key = { product -> product.id } - ) { product -> - HomeProductCard(product = product) - } - } - } - } -} - -@Composable -fun HomeProductCard( - product: ProductItem -) { - Column( - modifier = Modifier.Companion.width(230.dp) - ) { - Image( - painter = painterResource(id = product.imageResId), - contentDescription = product.name, - modifier = Modifier.Companion - .fillMaxWidth() - .height(230.dp) - .background(Color(0xFFF5F5F5)), - contentScale = ContentScale.Companion.Crop - ) - - Spacer(modifier = Modifier.Companion.height(12.dp)) - - Text( - text = product.name, - fontSize = 11.sp, - fontWeight = FontWeight.Companion.Bold, - color = Color.Companion.Black, - lineHeight = 14.sp - ) - - Spacer(modifier = Modifier.Companion.height(4.dp)) - - Text( - text = product.price, - fontSize = 11.sp, - fontWeight = FontWeight.Companion.Bold, - color = Color.Companion.Black - ) - } -} - -@Composable -fun ShopScreen() { - var selectedTabIndex by remember { mutableIntStateOf(0) } - val tabs = listOf("전체", "Tops & T-shirts", "sale") - - Column( - modifier = Modifier.Companion - .fillMaxSize() - .background(Color.Companion.White) - ) { - Spacer(modifier = Modifier.Companion.height(28.dp)) - - TabRow( - selectedTabIndex = selectedTabIndex, - containerColor = Color.Companion.White, - contentColor = Color.Companion.Black - ) { - tabs.forEachIndexed { index, title -> - Tab( - selected = selectedTabIndex == index, - onClick = { selectedTabIndex = index }, - text = { - Text( - text = title, - fontSize = 12.sp - ) - } - ) - } - } - - LazyVerticalGrid( - columns = GridCells.Fixed(2), - modifier = Modifier.Companion.fillMaxSize(), - contentPadding = PaddingValues(horizontal = 24.dp, vertical = 24.dp), - horizontalArrangement = Arrangement.spacedBy(16.dp), - verticalArrangement = Arrangement.spacedBy(36.dp) - ) { - items( - items = productList, - key = { product -> product.id } - ) { product -> - ShopProductCard(product = product) - } - } - } -} - -@Composable -fun ShopProductCard( - product: ProductItem -) { - Column( - modifier = Modifier.Companion.fillMaxWidth() - ) { - Box( - modifier = Modifier.Companion.fillMaxWidth() - ) { - Image( - painter = painterResource(id = product.imageResId), - contentDescription = product.name, - modifier = Modifier.Companion - .fillMaxWidth() - .aspectRatio(1f) - .background(Color(0xFFF5F5F5)), - contentScale = ContentScale.Companion.Crop - ) - - Icon( - painter = painterResource( - id = if (product.isLiked) R.drawable.ic_heart_on else R.drawable.ic_heart_off - ), - contentDescription = "좋아요", - modifier = Modifier.Companion - .align(Alignment.Companion.TopEnd) - .padding(8.dp) - .size(22.dp), - tint = Color.Companion.Unspecified - ) - } - - Spacer(modifier = Modifier.Companion.height(8.dp)) - - product.badge?.let { - Text( - text = it, - fontSize = 11.sp, - color = Color(0xFFFF6A00), - fontWeight = FontWeight.Companion.Bold - ) - } - - Text( - text = product.name, - fontSize = 11.sp, - fontWeight = FontWeight.Companion.Bold, - color = Color.Companion.Black, - lineHeight = 14.sp - ) - - Text( - text = product.subTitle, - fontSize = 10.sp, - color = Color.Companion.Gray, - lineHeight = 13.sp - ) - - Text( - text = product.colors, - fontSize = 10.sp, - color = Color.Companion.Gray, - lineHeight = 13.sp - ) - - Spacer(modifier = Modifier.Companion.height(2.dp)) - - Text( - text = product.price, - fontSize = 11.sp, - fontWeight = FontWeight.Companion.Bold, - color = Color.Companion.Black - ) - } -} - -@Composable -fun WishlistScreen() { - Column( - modifier = Modifier.Companion - .fillMaxSize() - .background(Color.Companion.White) - ) { - Text( - text = "위시리스트", - fontSize = 24.sp, - fontWeight = FontWeight.Companion.Bold, - color = Color.Companion.Black, - modifier = Modifier.Companion.padding(start = 24.dp, top = 40.dp) - ) - - LazyVerticalGrid( - columns = GridCells.Fixed(2), - modifier = Modifier.Companion.fillMaxSize(), - contentPadding = PaddingValues(horizontal = 24.dp, vertical = 28.dp), - horizontalArrangement = Arrangement.spacedBy(16.dp), - verticalArrangement = Arrangement.spacedBy(28.dp) - ) { - items( - items = productList, - key = { product -> product.id } - ) { product -> - WishlistProductCard(product = product) - } - } - } -} - -@Composable -fun WishlistProductCard( - product: ProductItem -) { - Column( - modifier = Modifier.Companion.fillMaxWidth() - ) { - Image( - painter = painterResource(id = product.imageResId), - contentDescription = product.name, - modifier = Modifier.Companion - .fillMaxWidth() - .aspectRatio(1f) - .background(Color(0xFFF5F5F5)), - contentScale = ContentScale.Companion.Crop - ) - - Spacer(modifier = Modifier.Companion.height(8.dp)) - - Text( - text = product.name, - fontSize = 11.sp, - fontWeight = FontWeight.Companion.Bold, - color = Color.Companion.Black, - lineHeight = 14.sp - ) - - Text( - text = product.price, - fontSize = 11.sp, - fontWeight = FontWeight.Companion.Bold, - color = Color.Companion.Black - ) - - if (product.id == 2) { - Text( - text = product.subTitle, - fontSize = 10.sp, - color = Color.Companion.Gray, - lineHeight = 13.sp - ) - - Text( - text = product.colors, - fontSize = 10.sp, - color = Color.Companion.Gray, - lineHeight = 13.sp - ) - - Text( - text = "US$10", - fontSize = 11.sp, - fontWeight = FontWeight.Companion.Bold, - color = Color.Companion.Black - ) - } - } -} - -@Composable -fun CartScreen( - onOrderClick: () -> Unit -) { - Column( - modifier = Modifier.Companion - .fillMaxSize() - .background(Color.Companion.White) - .padding(horizontal = 24.dp) - ) { - Box( - modifier = Modifier.Companion - .fillMaxWidth() - .weight(1f), - contentAlignment = Alignment.Companion.Center - ) { - Column( - horizontalAlignment = Alignment.Companion.CenterHorizontally - ) { - Image( - painter = painterResource(id = R.drawable.ic_cartcircle), - contentDescription = "장바구니 아이콘", - modifier = Modifier.Companion.size(56.dp) - ) - - Spacer(modifier = Modifier.Companion.height(12.dp)) - - Text( - text = "장바구니가 비어 있습니다.\n제품을 추가하면 여기에 표시됩니다.", - fontSize = 12.sp, - color = Color.Companion.Black, - textAlign = TextAlign.Companion.Center, - lineHeight = 18.sp - ) - } - } - - Button( - onClick = onOrderClick, - modifier = Modifier.Companion - .fillMaxWidth() - .height(56.dp), - colors = ButtonDefaults.buttonColors( - containerColor = Color.Companion.Black, - contentColor = Color.Companion.White - ) - ) { - Text( - text = "주문하기", - fontSize = 15.sp, - fontWeight = FontWeight.Companion.Bold - ) - } - - Spacer(modifier = Modifier.Companion.height(16.dp)) - Spacer(modifier = Modifier.Companion.windowInsetsBottomHeight(WindowInsets.Companion.navigationBars)) - } -} - -@Composable -fun ProfileScreen() { - Column( - modifier = Modifier.Companion - .fillMaxSize() - .background(Color.Companion.White) - .padding(horizontal = 24.dp, vertical = 32.dp) - ) { - Text( - text = "프로필", - fontSize = 24.sp, - fontWeight = FontWeight.Companion.Bold, - color = Color.Companion.Black - ) - } } \ No newline at end of file diff --git a/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/profile/presentation/ProfileScreen.kt b/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/profile/presentation/ProfileScreen.kt new file mode 100644 index 00000000..0e9c0d9b --- /dev/null +++ b/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/profile/presentation/ProfileScreen.kt @@ -0,0 +1,30 @@ +package com.example.nikecompose.feature.profile.presentation + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp + +@Composable +fun ProfileScreen() { + Column( + modifier = Modifier + .fillMaxSize() + .background(Color.White) + .padding(horizontal = 24.dp, vertical = 32.dp) + ) { + Text( + text = "프로필", + fontSize = 24.sp, + fontWeight = FontWeight.Bold, + color = Color.Black + ) + } +} \ No newline at end of file diff --git a/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/shop/presentation/ShopScreen.kt b/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/shop/presentation/ShopScreen.kt new file mode 100644 index 00000000..4f60226d --- /dev/null +++ b/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/shop/presentation/ShopScreen.kt @@ -0,0 +1,74 @@ +package com.example.nikecompose.feature.shop.presentation + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items +import androidx.compose.material3.Tab +import androidx.compose.material3.TabRow +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.nikecompose.data.dummy.productList +import com.example.nikecompose.feature.shop.presentation.component.ShopProductCard + +@Composable +fun ShopScreen() { + var selectedTabIndex by remember { mutableIntStateOf(0) } + val tabs = listOf("전체", "Tops & T-shirts", "sale") + + Column( + modifier = Modifier + .fillMaxSize() + .background(Color.White) + ) { + Spacer(modifier = Modifier.height(28.dp)) + + TabRow( + selectedTabIndex = selectedTabIndex, + containerColor = Color.White, + contentColor = Color.Black + ) { + tabs.forEachIndexed { index, title -> + Tab( + selected = selectedTabIndex == index, + onClick = { selectedTabIndex = index }, + text = { + Text( + text = title, + fontSize = 12.sp + ) + } + ) + } + } + + LazyVerticalGrid( + columns = GridCells.Fixed(2), + modifier = Modifier.fillMaxSize(), + contentPadding = PaddingValues(horizontal = 24.dp, vertical = 24.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp), + verticalArrangement = Arrangement.spacedBy(36.dp) + ) { + items( + items = productList, + key = { product -> product.id } + ) { product -> + ShopProductCard(product = product) + } + } + } +} \ No newline at end of file diff --git a/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/shop/presentation/component/ShopProductCard.kt b/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/shop/presentation/component/ShopProductCard.kt new file mode 100644 index 00000000..44026764 --- /dev/null +++ b/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/shop/presentation/component/ShopProductCard.kt @@ -0,0 +1,102 @@ +package com.example.nikecompose.feature.shop.presentation.component + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio +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.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.nikecompose.R +import com.example.nikecompose.domain.model.ProductItem + +@Composable +fun ShopProductCard( + product: ProductItem +) { + Column( + modifier = Modifier.fillMaxWidth() + ) { + Box( + modifier = Modifier.fillMaxWidth() + ) { + Image( + painter = painterResource(id = product.imageResId), + contentDescription = product.name, + modifier = Modifier + .fillMaxWidth() + .aspectRatio(1f) + .background(Color(0xFFF5F5F5)), + contentScale = ContentScale.Crop + ) + + Icon( + painter = painterResource( + id = if (product.isLiked) R.drawable.ic_heart_on else R.drawable.ic_heart_off + ), + contentDescription = "좋아요", + modifier = Modifier + .align(Alignment.TopEnd) + .padding(8.dp) + .size(22.dp), + tint = Color.Unspecified + ) + } + + Spacer(modifier = Modifier.height(8.dp)) + + product.badge?.let { + Text( + text = it, + fontSize = 11.sp, + color = Color(0xFFFF6A00), + fontWeight = FontWeight.Bold + ) + } + + Text( + text = product.name, + fontSize = 11.sp, + fontWeight = FontWeight.Bold, + color = Color.Black, + lineHeight = 14.sp + ) + + Text( + text = product.subTitle, + fontSize = 10.sp, + color = Color.Gray, + lineHeight = 13.sp + ) + + Text( + text = product.colors, + fontSize = 10.sp, + color = Color.Gray, + lineHeight = 13.sp + ) + + Spacer(modifier = Modifier.height(2.dp)) + + Text( + text = product.price, + fontSize = 11.sp, + fontWeight = FontWeight.Bold, + color = Color.Black + ) + } +} \ No newline at end of file diff --git a/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/wishlist/presentation/WishlistScreen.kt b/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/wishlist/presentation/WishlistScreen.kt new file mode 100644 index 00000000..51bfc9fa --- /dev/null +++ b/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/wishlist/presentation/WishlistScreen.kt @@ -0,0 +1,52 @@ +package com.example.nikecompose.feature.wishlist.presentation + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.nikecompose.data.dummy.productList +import com.example.nikecompose.feature.wishlist.presentation.component.WishlistProductCard + +@Composable +fun WishlistScreen() { + Column( + modifier = Modifier + .fillMaxSize() + .background(Color.White) + ) { + Text( + text = "위시리스트", + fontSize = 24.sp, + fontWeight = FontWeight.Bold, + color = Color.Black, + modifier = Modifier.padding(start = 24.dp, top = 40.dp) + ) + + LazyVerticalGrid( + columns = GridCells.Fixed(2), + modifier = Modifier.fillMaxSize(), + contentPadding = PaddingValues(horizontal = 24.dp, vertical = 28.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp), + verticalArrangement = Arrangement.spacedBy(28.dp) + ) { + items( + items = productList, + key = { product -> product.id } + ) { product -> + WishlistProductCard(product = product) + } + } + } +} \ No newline at end of file diff --git a/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/wishlist/presentation/component/WishlistProductCard.kt b/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/wishlist/presentation/component/WishlistProductCard.kt new file mode 100644 index 00000000..25d6790e --- /dev/null +++ b/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/wishlist/presentation/component/WishlistProductCard.kt @@ -0,0 +1,78 @@ +package com.example.nikecompose.feature.wishlist.presentation.component + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.nikecompose.domain.model.ProductItem + +@Composable +fun WishlistProductCard( + product: ProductItem +) { + Column( + modifier = Modifier.fillMaxWidth() + ) { + Image( + painter = painterResource(id = product.imageResId), + contentDescription = product.name, + modifier = Modifier + .fillMaxWidth() + .aspectRatio(1f) + .background(Color(0xFFF5F5F5)), + contentScale = ContentScale.Crop + ) + + Spacer(modifier = Modifier.height(8.dp)) + + Text( + text = product.name, + fontSize = 11.sp, + fontWeight = FontWeight.Bold, + color = Color.Black, + lineHeight = 14.sp + ) + + Text( + text = product.price, + fontSize = 11.sp, + fontWeight = FontWeight.Bold, + color = Color.Black + ) + + if (product.id == 2) { + Text( + text = product.subTitle, + fontSize = 10.sp, + color = Color.Gray, + lineHeight = 13.sp + ) + + Text( + text = product.colors, + fontSize = 10.sp, + color = Color.Gray, + lineHeight = 13.sp + ) + + Text( + text = "US$10", + fontSize = 11.sp, + fontWeight = FontWeight.Bold, + color = Color.Black + ) + } + } +} \ No newline at end of file From 7f7a874bc58e1b983917692a29aa17fcfafc003b Mon Sep 17 00:00:00 2001 From: chrry03 Date: Sun, 31 May 2026 20:54:20 +0900 Subject: [PATCH 4/8] =?UTF-8?q?chore:=20=EC=9D=98=EC=A1=B4=EC=84=B1=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=9D=B8=ED=84=B0=EB=84=B7?= =?UTF-8?q?=EA=B6=8C=ED=95=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...kotlin-compiler-4968883452369776417.salive | 0 Nyong/week7/nikecompose/app/build.gradle.kts | 25 +++++++++++++++++++ .../app/src/main/AndroidManifest.xml | 2 ++ 3 files changed, 27 insertions(+) delete mode 100644 Nyong/week7/nikecompose/.kotlin/sessions/kotlin-compiler-4968883452369776417.salive diff --git a/Nyong/week7/nikecompose/.kotlin/sessions/kotlin-compiler-4968883452369776417.salive b/Nyong/week7/nikecompose/.kotlin/sessions/kotlin-compiler-4968883452369776417.salive deleted file mode 100644 index e69de29b..00000000 diff --git a/Nyong/week7/nikecompose/app/build.gradle.kts b/Nyong/week7/nikecompose/app/build.gradle.kts index 4d13796c..9abeee9a 100644 --- a/Nyong/week7/nikecompose/app/build.gradle.kts +++ b/Nyong/week7/nikecompose/app/build.gradle.kts @@ -1,3 +1,5 @@ +import java.util.Properties + plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.compose) @@ -19,6 +21,15 @@ android { versionName = "1.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + + val properties = Properties() + properties.load(rootProject.file("local.properties").inputStream()) + + buildConfigField( + "String", + "REQRES_API_KEY", + "\"${properties.getProperty("REQRES_API_KEY")}\"" + ) } buildTypes { @@ -36,6 +47,7 @@ android { } buildFeatures { compose = true + buildConfig = true } } @@ -57,4 +69,17 @@ dependencies { androidTestImplementation(libs.androidx.compose.ui.test.junit4) debugImplementation(libs.androidx.compose.ui.tooling) debugImplementation(libs.androidx.compose.ui.test.manifest) + // ViewModel을 Compose에서 사용하기 위한 의존성 + implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.10.0") + + // Retrofit + implementation("com.squareup.retrofit2:retrofit:2.9.0") + implementation("com.squareup.retrofit2:converter-gson:2.9.0") + + // OkHttp 로그 확인용 + implementation("com.squareup.okhttp3:logging-interceptor:4.12.0") + + // Coil: Compose 이미지 로딩용 + implementation("io.coil-kt.coil3:coil-compose:3.0.4") + implementation("io.coil-kt.coil3:coil-network-okhttp:3.0.4") } \ No newline at end of file diff --git a/Nyong/week7/nikecompose/app/src/main/AndroidManifest.xml b/Nyong/week7/nikecompose/app/src/main/AndroidManifest.xml index da7eeb20..64c6697d 100644 --- a/Nyong/week7/nikecompose/app/src/main/AndroidManifest.xml +++ b/Nyong/week7/nikecompose/app/src/main/AndroidManifest.xml @@ -2,6 +2,8 @@ + + Date: Sun, 31 May 2026 20:59:58 +0900 Subject: [PATCH 5/8] =?UTF-8?q?feat:=20=EC=BB=A4=EB=B0=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nikecompose/data/api/ReqresApiService.kt | 24 ++++++++++++++++ .../nikecompose/data/api/ReqresClient.kt | 28 +++++++++++++++++++ .../data/dto/response/UserListResponse.kt | 9 ++++++ .../data/dto/response/UserResponse.kt | 17 +++++++++++ 4 files changed, 78 insertions(+) create mode 100644 Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/data/api/ReqresApiService.kt create mode 100644 Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/data/api/ReqresClient.kt create mode 100644 Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/data/dto/response/UserListResponse.kt create mode 100644 Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/data/dto/response/UserResponse.kt diff --git a/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/data/api/ReqresApiService.kt b/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/data/api/ReqresApiService.kt new file mode 100644 index 00000000..7b340f5d --- /dev/null +++ b/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/data/api/ReqresApiService.kt @@ -0,0 +1,24 @@ +package com.example.nikecompose.data.api + +import com.example.nikecompose.data.dto.response.UserListResponse +import com.example.nikecompose.data.dto.response.UserResponse +import retrofit2.Response +import retrofit2.http.GET +import retrofit2.http.Header +import retrofit2.http.Path +import retrofit2.http.Query + +interface ReqresApiService { + + @GET("api/users/{userId}") + suspend fun getUser( + @Header("x-api-key") apiKey: String, + @Path("userId") userId: Int + ): Response + + @GET("api/users") + suspend fun getUsers( + @Header("x-api-key") apiKey: String, + @Query("page") page: Int + ): Response +} \ No newline at end of file diff --git a/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/data/api/ReqresClient.kt b/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/data/api/ReqresClient.kt new file mode 100644 index 00000000..2bcd18cb --- /dev/null +++ b/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/data/api/ReqresClient.kt @@ -0,0 +1,28 @@ +package com.example.nikecompose.data.api + +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory + +object ReqresClient { + + private const val BASE_URL = "https://reqres.in/" + + private val loggingInterceptor = HttpLoggingInterceptor().apply { + level = HttpLoggingInterceptor.Level.BODY + } + + private val okHttpClient = OkHttpClient.Builder() + .addInterceptor(loggingInterceptor) + .build() + + val apiService: ReqresApiService by lazy { + Retrofit.Builder() + .baseUrl(BASE_URL) + .client(okHttpClient) + .addConverterFactory(GsonConverterFactory.create()) + .build() + .create(ReqresApiService::class.java) + } +} \ No newline at end of file diff --git a/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/data/dto/response/UserListResponse.kt b/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/data/dto/response/UserListResponse.kt new file mode 100644 index 00000000..904a06c7 --- /dev/null +++ b/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/data/dto/response/UserListResponse.kt @@ -0,0 +1,9 @@ +package com.example.nikecompose.data.dto.response + +data class UserListResponse( + val page: Int, + val per_page: Int, + val total: Int, + val total_pages: Int, + val data: List +) \ No newline at end of file diff --git a/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/data/dto/response/UserResponse.kt b/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/data/dto/response/UserResponse.kt new file mode 100644 index 00000000..b1e1fa61 --- /dev/null +++ b/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/data/dto/response/UserResponse.kt @@ -0,0 +1,17 @@ +package com.example.nikecompose.data.dto.response + +import com.google.gson.annotations.SerializedName + +data class UserResponse( + val data: ReqresUserDto +) + +data class ReqresUserDto( + val id: Int, + val email: String, + @SerializedName("first_name") + val firstName: String, + @SerializedName("last_name") + val lastName: String, + val avatar: String +) \ No newline at end of file From 5c8247c2f9c92a5993655b7bc87e78cc0bf9bab6 Mon Sep 17 00:00:00 2001 From: chrry03 Date: Sun, 31 May 2026 23:38:07 +0900 Subject: [PATCH 6/8] =?UTF-8?q?feat:=20=EC=BB=A4=EB=B0=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/repository/ProfileRepository.kt | 52 +++++++++++++++++++ .../profile/presentation/ProfileViewModel.kt | 50 ++++++++++++++++++ .../presentation/model/ProfileUiState.kt | 10 ++++ 3 files changed, 112 insertions(+) create mode 100644 Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/data/repository/ProfileRepository.kt create mode 100644 Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/profile/presentation/ProfileViewModel.kt create mode 100644 Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/profile/presentation/model/ProfileUiState.kt diff --git a/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/data/repository/ProfileRepository.kt b/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/data/repository/ProfileRepository.kt new file mode 100644 index 00000000..4be7b71e --- /dev/null +++ b/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/data/repository/ProfileRepository.kt @@ -0,0 +1,52 @@ +package com.example.nikecompose.data.repository + +import com.example.nikecompose.BuildConfig +import com.example.nikecompose.data.api.ReqresClient +import com.example.nikecompose.data.dto.response.ReqresUserDto + +class ProfileRepository { + + private val apiService = ReqresClient.apiService + private val apiKey = BuildConfig.REQRES_API_KEY + + suspend fun getProfileUser(): Result { + return try { + val response = apiService.getUser( + apiKey = apiKey, + userId = 1 + ) + + if (response.isSuccessful) { + val user = response.body()?.data + + if (user != null) { + Result.success(user) + } else { + Result.failure(Exception("유저 정보가 비어 있습니다.")) + } + } else { + Result.failure(Exception("프로필 요청 실패: ${response.code()}")) + } + } catch (e: Exception) { + Result.failure(e) + } + } + + suspend fun getFollowingUsers(): Result> { + return try { + val response = apiService.getUsers( + apiKey = apiKey, + page = 1 + ) + + if (response.isSuccessful) { + val users = response.body()?.data.orEmpty() + Result.success(users.take(6)) + } else { + Result.failure(Exception("팔로잉 목록 요청 실패: ${response.code()}")) + } + } catch (e: Exception) { + Result.failure(e) + } + } +} \ No newline at end of file diff --git a/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/profile/presentation/ProfileViewModel.kt b/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/profile/presentation/ProfileViewModel.kt new file mode 100644 index 00000000..5223d929 --- /dev/null +++ b/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/profile/presentation/ProfileViewModel.kt @@ -0,0 +1,50 @@ +package com.example.nikecompose.feature.profile.presentation + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.example.nikecompose.data.repository.ProfileRepository +import com.example.nikecompose.feature.profile.presentation.model.ProfileUiState +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch + +class ProfileViewModel : ViewModel() { + + private val repository = ProfileRepository() + + private val _uiState = MutableStateFlow(ProfileUiState()) + val uiState: StateFlow = _uiState.asStateFlow() + + init { + loadProfile() + } + + private fun loadProfile() { + viewModelScope.launch { + _uiState.value = _uiState.value.copy( + isLoading = true, + errorMessage = null + ) + + val profileResult = repository.getProfileUser() + val followingResult = repository.getFollowingUsers() + + val profileUser = profileResult.getOrNull() + val followingUsers = followingResult.getOrNull().orEmpty() + + val errorMessage = when { + profileResult.isFailure -> profileResult.exceptionOrNull()?.message + followingResult.isFailure -> followingResult.exceptionOrNull()?.message + else -> null + } + + _uiState.value = ProfileUiState( + isLoading = false, + profileUser = profileUser, + followingUsers = followingUsers, + errorMessage = errorMessage + ) + } + } +} \ No newline at end of file diff --git a/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/profile/presentation/model/ProfileUiState.kt b/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/profile/presentation/model/ProfileUiState.kt new file mode 100644 index 00000000..7a61d383 --- /dev/null +++ b/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/profile/presentation/model/ProfileUiState.kt @@ -0,0 +1,10 @@ +package com.example.nikecompose.feature.profile.presentation.model + +import com.example.nikecompose.data.dto.response.ReqresUserDto + +data class ProfileUiState( + val isLoading: Boolean = false, + val profileUser: ReqresUserDto? = null, + val followingUsers: List = emptyList(), + val errorMessage: String? = null +) \ No newline at end of file From 9fa42aac4d29019d8c3659a0ddeb092c03ebd4e8 Mon Sep 17 00:00:00 2001 From: chrry03 Date: Sun, 31 May 2026 23:42:13 +0900 Subject: [PATCH 7/8] =?UTF-8?q?feat:=20viewmodel=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../profile/presentation/ProfileScreen.kt | 163 +++++++++++++++++- 1 file changed, 160 insertions(+), 3 deletions(-) diff --git a/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/profile/presentation/ProfileScreen.kt b/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/profile/presentation/ProfileScreen.kt index 0e9c0d9b..8a55ebf9 100644 --- a/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/profile/presentation/ProfileScreen.kt +++ b/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/profile/presentation/ProfileScreen.kt @@ -1,30 +1,187 @@ package com.example.nikecompose.feature.profile.presentation import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.lifecycle.viewmodel.compose.viewModel +import coil3.compose.AsyncImage +import com.example.nikecompose.data.dto.response.ReqresUserDto @Composable -fun ProfileScreen() { - Column( +fun ProfileScreen( + viewModel: ProfileViewModel = viewModel() +) { + val uiState by viewModel.uiState.collectAsState() + + Box( modifier = Modifier .fillMaxSize() .background(Color.White) .padding(horizontal = 24.dp, vertical = 32.dp) ) { + when { + uiState.isLoading -> { + CircularProgressIndicator( + modifier = Modifier.align(Alignment.Center), + color = Color.Black + ) + } + + uiState.errorMessage != null -> { + Text( + text = uiState.errorMessage ?: "알 수 없는 오류가 발생했습니다.", + modifier = Modifier.align(Alignment.Center), + color = Color.Red, + textAlign = TextAlign.Center + ) + } + + else -> { + ProfileContent( + profileUser = uiState.profileUser, + followingUsers = uiState.followingUsers + ) + } + } + } +} + +@Composable +fun ProfileContent( + profileUser: ReqresUserDto?, + followingUsers: List +) { + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Spacer(modifier = Modifier.height(32.dp)) + + AsyncImage( + model = profileUser?.avatar, + contentDescription = "프로필 이미지", + modifier = Modifier + .size(96.dp) + .clip(CircleShape), + contentScale = ContentScale.Crop + ) + + Spacer(modifier = Modifier.height(16.dp)) + Text( - text = "프로필", + text = if (profileUser != null) { + "${profileUser.firstName} ${profileUser.lastName}" + } else { + "사용자 이름" + }, fontSize = 24.sp, fontWeight = FontWeight.Bold, color = Color.Black ) + + Spacer(modifier = Modifier.height(8.dp)) + + Text( + text = "팔로잉 ${followingUsers.size}", + fontSize = 14.sp, + color = Color.Gray + ) + + Spacer(modifier = Modifier.height(40.dp)) + + Text( + text = "팔로잉", + fontSize = 20.sp, + fontWeight = FontWeight.Bold, + color = Color.Black, + modifier = Modifier.align(Alignment.Start) + ) + + Spacer(modifier = Modifier.height(20.dp)) + + FollowingPager( + users = followingUsers + ) + } +} + +@Composable +fun FollowingPager( + users: List +) { + if (users.isEmpty()) { + Text( + text = "팔로잉 목록이 없습니다.", + fontSize = 14.sp, + color = Color.Gray + ) + return + } + + val pagerState = rememberPagerState( + pageCount = { users.size } + ) + + HorizontalPager( + state = pagerState, + contentPadding = PaddingValues(horizontal = 72.dp), + pageSpacing = 16.dp + ) { page -> + FollowingUserItem( + user = users[page] + ) + } +} + +@Composable +fun FollowingUserItem( + user: ReqresUserDto +) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + AsyncImage( + model = user.avatar, + contentDescription = "${user.firstName} ${user.lastName}", + modifier = Modifier + .size(72.dp) + .clip(CircleShape), + contentScale = ContentScale.Crop + ) + + Spacer(modifier = Modifier.height(8.dp)) + + Text( + text = user.firstName, + fontSize = 12.sp, + fontWeight = FontWeight.Bold, + color = Color.Black, + textAlign = TextAlign.Center + ) } } \ No newline at end of file From d0aae01e16373d5bfcf4ed5619e384f848274711 Mon Sep 17 00:00:00 2001 From: chrry03 Date: Sun, 31 May 2026 23:47:13 +0900 Subject: [PATCH 8/8] =?UTF-8?q?feat:=20=EC=BB=A4=EB=B0=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../profile/presentation/ProfileScreen.kt | 331 +++++++++++++++--- 1 file changed, 286 insertions(+), 45 deletions(-) diff --git a/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/profile/presentation/ProfileScreen.kt b/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/profile/presentation/ProfileScreen.kt index 8a55ebf9..2b85811e 100644 --- a/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/profile/presentation/ProfileScreen.kt +++ b/Nyong/week7/nikecompose/app/src/main/java/com/example/nikecompose/feature/profile/presentation/ProfileScreen.kt @@ -1,20 +1,38 @@ package com.example.nikecompose.feature.profile.presentation +import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize +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.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.CardTravel +import androidx.compose.material.icons.outlined.ChevronRight +import androidx.compose.material.icons.outlined.ConfirmationNumber +import androidx.compose.material.icons.outlined.Event +import androidx.compose.material.icons.outlined.Settings +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Divider +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue @@ -22,6 +40,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign @@ -41,7 +60,6 @@ fun ProfileScreen( modifier = Modifier .fillMaxSize() .background(Color.White) - .padding(horizontal = 24.dp, vertical = 32.dp) ) { when { uiState.isLoading -> { @@ -76,101 +94,307 @@ fun ProfileContent( followingUsers: List ) { Column( - modifier = Modifier.fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally + modifier = Modifier + .fillMaxSize() + .background(Color.White) ) { - Spacer(modifier = Modifier.height(32.dp)) + Spacer(modifier = Modifier.height(44.dp)) + ProfileHeader(profileUser = profileUser) + + Spacer(modifier = Modifier.height(28.dp)) + + ProfileMenuRow() + + Spacer(modifier = Modifier.height(20.dp)) + + HorizontalDivider( + thickness = 8.dp, + color = Color(0xFFF6F6F6) + ) + + MemberBenefitSection() + + HorizontalDivider( + thickness = 8.dp, + color = Color(0xFFF6F6F6) + ) + + FollowingSection( + followingUsers = followingUsers + ) + + Spacer(modifier = Modifier.weight(1f)) + + JoinDateSection() + } +} + +@Composable +fun ProfileHeader( + profileUser: ReqresUserDto? +) { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { AsyncImage( model = profileUser?.avatar, contentDescription = "프로필 이미지", modifier = Modifier - .size(96.dp) - .clip(CircleShape), + .size(92.dp) + .clip(CircleShape) + .background(Color(0xFFD9D9D9)), contentScale = ContentScale.Crop ) - Spacer(modifier = Modifier.height(16.dp)) + Spacer(modifier = Modifier.height(18.dp)) Text( text = if (profileUser != null) { "${profileUser.firstName} ${profileUser.lastName}" } else { - "사용자 이름" + "닉네임" }, - fontSize = 24.sp, + fontSize = 21.sp, fontWeight = FontWeight.Bold, color = Color.Black ) - Spacer(modifier = Modifier.height(8.dp)) + Spacer(modifier = Modifier.height(22.dp)) - Text( - text = "팔로잉 ${followingUsers.size}", - fontSize = 14.sp, - color = Color.Gray + Button( + onClick = { }, + modifier = Modifier + .width(160.dp) + .height(46.dp), + shape = RoundedCornerShape(24.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Color.White, + contentColor = Color.Black + ), + border = ButtonDefaults.outlinedButtonBorder + ) { + Text( + text = "프로필 수정", + fontSize = 14.sp, + fontWeight = FontWeight.Bold + ) + } + } +} + +@Composable +fun ProfileMenuRow() { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 28.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + ProfileMenuItem( + icon = Icons.Outlined.CardTravel, + label = "주문" ) - Spacer(modifier = Modifier.height(40.dp)) + VerticalMenuDivider() - Text( - text = "팔로잉", - fontSize = 20.sp, - fontWeight = FontWeight.Bold, - color = Color.Black, - modifier = Modifier.align(Alignment.Start) + ProfileMenuItem( + icon = Icons.Outlined.ConfirmationNumber, + label = "패스" ) - Spacer(modifier = Modifier.height(20.dp)) + VerticalMenuDivider() + + ProfileMenuItem( + icon = Icons.Outlined.Event, + label = "이벤트" + ) - FollowingPager( - users = followingUsers + VerticalMenuDivider() + + ProfileMenuItem( + icon = Icons.Outlined.Settings, + label = "설정" ) } } @Composable -fun FollowingPager( - users: List +fun ProfileMenuItem( + icon: ImageVector, + label: String ) { - if (users.isEmpty()) { + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { + Icon( + imageVector = icon, + contentDescription = label, + tint = Color(0xFFB5B5B5), + modifier = Modifier.size(24.dp) + ) + + Spacer(modifier = Modifier.height(6.dp)) + Text( - text = "팔로잉 목록이 없습니다.", - fontSize = 14.sp, - color = Color.Gray + text = label, + fontSize = 11.sp, + color = Color.Black ) - return } +} + +@Composable +fun VerticalMenuDivider() { + Box( + modifier = Modifier + .height(32.dp) + .width(1.dp) + .background(Color(0xFFE0E0E0)) + ) +} + +@Composable +fun MemberBenefitSection() { + Row( + modifier = Modifier + .fillMaxWidth() + .height(84.dp) + .padding(horizontal = 24.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Column( + modifier = Modifier.weight(1f) + ) { + Text( + text = "나이키 멤버 혜택", + fontSize = 15.sp, + fontWeight = FontWeight.Bold, + color = Color.Black + ) + + Spacer(modifier = Modifier.height(4.dp)) + + Text( + text = "0개 사용 가능", + fontSize = 12.sp, + color = Color.Gray + ) + } + + Icon( + imageVector = Icons.Outlined.ChevronRight, + contentDescription = "더보기", + tint = Color.Black, + modifier = Modifier.size(22.dp) + ) + } +} + +@Composable +fun FollowingSection( + followingUsers: List +) { + Column( + modifier = Modifier + .fillMaxWidth() + .height(220.dp) + .padding(top = 22.dp) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 24.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "팔로잉 (${followingUsers.size})", + fontSize = 15.sp, + fontWeight = FontWeight.Bold, + color = Color.Black, + modifier = Modifier.weight(1f) + ) + + Text( + text = "편집", + fontSize = 12.sp, + color = Color.Gray + ) + } + + Spacer(modifier = Modifier.height(16.dp)) + + if (followingUsers.isEmpty()) { + Box( + modifier = Modifier.fillMaxWidth(), + contentAlignment = Alignment.Center + ) { + Text( + text = "팔로잉 목록이 없습니다.", + fontSize = 13.sp, + color = Color.Gray + ) + } + } else { + FollowingPager(users = followingUsers) + } + } +} + +@Composable +fun FollowingPager( + users: List +) { + val pages = users.chunked(3) val pagerState = rememberPagerState( - pageCount = { users.size } + pageCount = { pages.size } ) HorizontalPager( state = pagerState, - contentPadding = PaddingValues(horizontal = 72.dp), - pageSpacing = 16.dp + modifier = Modifier + .fillMaxWidth() + .height(120.dp), + contentPadding = PaddingValues(horizontal = 24.dp), + pageSpacing = 12.dp ) { page -> - FollowingUserItem( - user = users[page] - ) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + pages[page].forEach { user -> + FollowingUserCard( + user = user, + modifier = Modifier.weight(1f) + ) + } + + repeat(3 - pages[page].size) { + Spacer(modifier = Modifier.weight(1f)) + } + } } } @Composable -fun FollowingUserItem( - user: ReqresUserDto +fun FollowingUserCard( + user: ReqresUserDto, + modifier: Modifier = Modifier ) { Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center + modifier = modifier, + horizontalAlignment = Alignment.Start ) { AsyncImage( model = user.avatar, contentDescription = "${user.firstName} ${user.lastName}", modifier = Modifier - .size(72.dp) - .clip(CircleShape), + .fillMaxWidth() + .height(84.dp) + .background(Color(0xFFD9D9D9)), contentScale = ContentScale.Crop ) @@ -178,10 +402,27 @@ fun FollowingUserItem( Text( text = user.firstName, - fontSize = 12.sp, + fontSize = 11.sp, fontWeight = FontWeight.Bold, color = Color.Black, - textAlign = TextAlign.Center + maxLines = 1 + ) + } +} + +@Composable +fun JoinDateSection() { + Box( + modifier = Modifier + .fillMaxWidth() + .height(42.dp) + .background(Color(0xFFF7F7F7)), + contentAlignment = Alignment.Center + ) { + Text( + text = "회원 가입일: 2025년 9월", + fontSize = 11.sp, + color = Color.Gray ) } } \ No newline at end of file