diff --git a/.gitignore b/.gitignore index e5cbb641..33de84e3 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,6 @@ google-services.json # Android Profiling *.hprof + +# Claude Code (로컬 설정, 커밋하지 않음) +.claude/ diff --git a/Yoshi/NikeApp/.claude/settings.local.json b/Yoshi/NikeApp/.claude/settings.local.json deleted file mode 100644 index b6c5da46..00000000 --- a/Yoshi/NikeApp/.claude/settings.local.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "permissions": { - "allow": [ - "PowerShell(& \"C:\\\\Users\\\\doyeo\\\\AndroidStudioProjects\\\\NikeApp\\\\gradlew.bat\" assembleDebug 2>&1)" - ] - } -} diff --git a/Yoshi/NikeApp/.gitignore b/Yoshi/NikeApp/.gitignore index aa724b77..c158794b 100644 --- a/Yoshi/NikeApp/.gitignore +++ b/Yoshi/NikeApp/.gitignore @@ -13,3 +13,9 @@ .externalNativeBuild .cxx local.properties + +# 키스토어 / 앱 서명 (절대 커밋 금지) +*.jks +*.keystore +keystore.properties +signing.properties diff --git a/Yoshi/NikeApp/app/build.gradle.kts b/Yoshi/NikeApp/app/build.gradle.kts index 66b1f88d..4df68a5d 100644 --- a/Yoshi/NikeApp/app/build.gradle.kts +++ b/Yoshi/NikeApp/app/build.gradle.kts @@ -1,3 +1,5 @@ +import java.util.Properties + plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) @@ -5,6 +7,14 @@ plugins { alias(libs.plugins.kotlin.serialization) } +// local.properties 에서 API 키를 읽어옵니다. (local.properties 는 git 에 올라가지 않습니다) +val localProperties = Properties().apply { + val localFile = rootProject.file("local.properties") + if (localFile.exists()) { + localFile.inputStream().use { load(it) } + } +} + android { namespace = "com.example.NikeApp" compileSdk = 35 @@ -17,6 +27,10 @@ android { versionName = "1.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + + // local.properties 의 NIKE_API_KEY 값을 BuildConfig.REQRES_API_KEY 로 노출 + val reqresApiKey = localProperties.getProperty("NIKE_API_KEY") ?: "" + buildConfigField("String", "REQRES_API_KEY", "\"$reqresApiKey\"") } buildTypes { @@ -37,6 +51,7 @@ android { } buildFeatures { compose = true + buildConfig = true } } @@ -58,6 +73,15 @@ dependencies { implementation(libs.androidx.navigation.compose) implementation(libs.kotlinx.serialization.json) + // Coil — Compose 이미지 로딩 + implementation(libs.coil.compose) + implementation(libs.coil.network.okhttp) + + // Retrofit + OkHttp — ReqRes API 통신 + implementation(libs.retrofit) + implementation(libs.retrofit.kotlinx.serialization) + implementation(libs.okhttp.logging.interceptor) + testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) diff --git a/Yoshi/NikeApp/app/src/main/java/com/example/NikeApp/data/UserRepository.kt b/Yoshi/NikeApp/app/src/main/java/com/example/NikeApp/data/UserRepository.kt new file mode 100644 index 00000000..36ea2965 --- /dev/null +++ b/Yoshi/NikeApp/app/src/main/java/com/example/NikeApp/data/UserRepository.kt @@ -0,0 +1,28 @@ +package com.example.NikeApp.data + +import com.example.NikeApp.data.remote.ReqResApi +import com.example.NikeApp.data.remote.ReqResClient +import com.example.NikeApp.data.remote.dto.toUser +import com.example.NikeApp.model.User + +/** + * 사용자 데이터 저장소. + * 화면(Composable)은 이 Repository 의 suspend 함수만 호출하면 됩니다. + */ +class UserRepository( + private val api: ReqResApi = ReqResClient.api, +) { + /** 미션 요구사항: 내 정보(userId = 1) 가져오기 */ + suspend fun getMyProfile(): User = api.getUser(MY_USER_ID).data.toUser() + + /** 팔로잉 리스트 구성: 사용자 목록에서 나(1번)를 제외하고 가져오기 */ + suspend fun getFollowing(): List = + api.getUsers(page = 1).data + .filter { it.id != MY_USER_ID } + .map { it.toUser() } + + companion object { + /** 워크북 미션 기준 내 userId 는 1번 */ + const val MY_USER_ID = 1 + } +} diff --git a/Yoshi/NikeApp/app/src/main/java/com/example/NikeApp/data/WishlistRepository.kt b/Yoshi/NikeApp/app/src/main/java/com/example/NikeApp/data/WishlistRepository.kt new file mode 100644 index 00000000..a5b61d4b --- /dev/null +++ b/Yoshi/NikeApp/app/src/main/java/com/example/NikeApp/data/WishlistRepository.kt @@ -0,0 +1,46 @@ +package com.example.NikeApp.data + +import android.content.Context +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf + +/** + * 위시리스트 ID 집합 저장소 + * 앱이 종료/재실행되어도 데이터가 유지 + * Compose에서 즉시 반응할 수 있게 [State] 사용 + */ +class WishlistRepository(context: Context) { + + private val prefs = context.applicationContext + .getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) + + private val _wishlistIds = mutableStateOf>(loadFromPrefs()) + + /** 현재 위시리스트에 담긴 상품 ID 집합 */ + val wishlistIds: State> = _wishlistIds + + /** + * 하트 on/off + * 이미 들어있으면 제거 없으면 추가 + */ + fun toggle(productId: String) { + val current = _wishlistIds.value + val updated = if (productId in current) current - productId else current + productId + _wishlistIds.value = updated + saveToPrefs(updated) + } + + fun isWished(productId: String): Boolean = productId in _wishlistIds.value + + private fun loadFromPrefs(): Set = + prefs.getStringSet(KEY_IDS, emptySet())?.toSet() ?: emptySet() + + private fun saveToPrefs(ids: Set) { + prefs.edit().putStringSet(KEY_IDS, ids).apply() + } + + companion object { + private const val PREFS_NAME = "wishlist_prefs" + private const val KEY_IDS = "wishlist_ids" + } +} diff --git a/Yoshi/NikeApp/app/src/main/java/com/example/NikeApp/data/remote/ReqResApi.kt b/Yoshi/NikeApp/app/src/main/java/com/example/NikeApp/data/remote/ReqResApi.kt new file mode 100644 index 00000000..39a0d7f5 --- /dev/null +++ b/Yoshi/NikeApp/app/src/main/java/com/example/NikeApp/data/remote/ReqResApi.kt @@ -0,0 +1,21 @@ +package com.example.NikeApp.data.remote + +import com.example.NikeApp.data.remote.dto.SingleUserResponse +import com.example.NikeApp.data.remote.dto.UserListResponse +import retrofit2.http.GET +import retrofit2.http.Path +import retrofit2.http.Query + +/** + * ReqRes(https://reqres.in) 사용자 API + */ +interface ReqResApi { + + /** 단일 사용자 조회 — 미션 요구사항: userId = 1 */ + @GET("api/users/{id}") + suspend fun getUser(@Path("id") id: Int): SingleUserResponse + + /** 사용자 목록 조회 — 팔로잉 리스트 구성에 사용 */ + @GET("api/users") + suspend fun getUsers(@Query("page") page: Int = 1): UserListResponse +} diff --git a/Yoshi/NikeApp/app/src/main/java/com/example/NikeApp/data/remote/ReqResClient.kt b/Yoshi/NikeApp/app/src/main/java/com/example/NikeApp/data/remote/ReqResClient.kt new file mode 100644 index 00000000..22fae712 --- /dev/null +++ b/Yoshi/NikeApp/app/src/main/java/com/example/NikeApp/data/remote/ReqResClient.kt @@ -0,0 +1,46 @@ +package com.example.NikeApp.data.remote + +import com.example.NikeApp.BuildConfig +import kotlinx.serialization.json.Json +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import retrofit2.Retrofit +import retrofit2.converter.kotlinx.serialization.asConverterFactory + +/** + * Retrofit + OkHttp 설정을 한 곳에서 관리하는 싱글톤. + * - 모든 요청에 ReqRes 가 요구하는 `x-api-key` 헤더를 자동으로 붙입니다. + * - API 키는 .env -> BuildConfig.REQRES_API_KEY 를 통해 주입됩니다. + */ +object ReqResClient { + + private const val BASE_URL = "https://reqres.in/" + + private val json = Json { + ignoreUnknownKeys = true // 응답에 모르는 필드가 있어도 무시 + } + + private val okHttpClient: OkHttpClient = OkHttpClient.Builder() + // 1) API 키 헤더 자동 추가 + .addInterceptor { chain -> + val request = chain.request().newBuilder() + .addHeader("x-api-key", BuildConfig.REQRES_API_KEY) + .build() + chain.proceed(request) + } + // 2) 요청/응답 로깅 (디버깅용) + .addInterceptor( + HttpLoggingInterceptor().apply { + level = HttpLoggingInterceptor.Level.BODY + } + ) + .build() + + val api: ReqResApi = Retrofit.Builder() + .baseUrl(BASE_URL) + .client(okHttpClient) + .addConverterFactory(json.asConverterFactory("application/json".toMediaType())) + .build() + .create(ReqResApi::class.java) +} diff --git a/Yoshi/NikeApp/app/src/main/java/com/example/NikeApp/data/remote/dto/UserDto.kt b/Yoshi/NikeApp/app/src/main/java/com/example/NikeApp/data/remote/dto/UserDto.kt new file mode 100644 index 00000000..58a94c1c --- /dev/null +++ b/Yoshi/NikeApp/app/src/main/java/com/example/NikeApp/data/remote/dto/UserDto.kt @@ -0,0 +1,41 @@ +package com.example.NikeApp.data.remote.dto + +import com.example.NikeApp.model.User +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * ReqRes API 의 사용자 한 명을 표현하는 DTO + * 예) GET https://reqres.in/api/users/1 -> data 필드 + */ +@Serializable +data class UserDto( + val id: Int, + val email: String, + @SerialName("first_name") val firstName: String, + @SerialName("last_name") val lastName: String, + val avatar: String, +) + +/** GET /api/users/{id} 응답 */ +@Serializable +data class SingleUserResponse( + val data: UserDto, +) + +/** GET /api/users?page= 응답 */ +@Serializable +data class UserListResponse( + val page: Int, + @SerialName("per_page") val perPage: Int, + val total: Int, + @SerialName("total_pages") val totalPages: Int, + val data: List, +) + +/** DTO -> 도메인 모델 변환 (닉네임 = first_name + last_name) */ +fun UserDto.toUser(): User = User( + id = id, + nickname = "$firstName $lastName", + avatarUrl = avatar, +) diff --git a/Yoshi/NikeApp/app/src/main/java/com/example/NikeApp/model/Product.kt b/Yoshi/NikeApp/app/src/main/java/com/example/NikeApp/model/Product.kt new file mode 100644 index 00000000..1d8be1c3 --- /dev/null +++ b/Yoshi/NikeApp/app/src/main/java/com/example/NikeApp/model/Product.kt @@ -0,0 +1,13 @@ +package com.example.NikeApp.model + +import androidx.annotation.DrawableRes + +/** + * 상품 데이터 모델 + */ +data class Product( + val id: String, + val name: String, + val price: Int, + @DrawableRes val imageRes: Int? = null, +) diff --git a/Yoshi/NikeApp/app/src/main/java/com/example/NikeApp/model/SampleProducts.kt b/Yoshi/NikeApp/app/src/main/java/com/example/NikeApp/model/SampleProducts.kt new file mode 100644 index 00000000..d72a74f9 --- /dev/null +++ b/Yoshi/NikeApp/app/src/main/java/com/example/NikeApp/model/SampleProducts.kt @@ -0,0 +1,22 @@ +package com.example.NikeApp.model + +import com.example.NikeApp.R + +/** + * 홈/구매하기 화면에서 사용하는 더미 상품 목록 + * 홈 화면: 5개 + * 구매하기: 8개 + */ +val SampleProducts: List = listOf( + Product(id = "p1", name = "Air Force 1 '07", price = 150, imageRes = R.drawable.air_force_1_07), + Product(id = "p2", name = "Air Max 90", price = 200, imageRes = R.drawable.air_max_90), + Product(id = "p3", name = "Dunk Low Retro", price = 175, imageRes = R.drawable.dunk_low_retro), + Product(id = "p4", name = "Air Jordan 1 Mid", price = 220, imageRes = R.drawable.air_jordan_1_mid), + Product(id = "p5", name = "Cortez Basic", price = 100, imageRes = R.drawable.cortez_basic), + Product(id = "p6", name = "Pegasus 41", price = 130, imageRes = R.drawable.pegasus_41), + Product(id = "p7", name = "Blazer Mid '77", price = 90, imageRes = R.drawable.blazer_mid_77), + Product(id = "p8", name = "Sportswear Crew Socks", price = 15, imageRes = R.drawable.sportswear_crew_socks), +) + +/** 가격 형식 변환 */ +fun Product.formattedPrice(): String = "US$%,d".format(price) diff --git a/Yoshi/NikeApp/app/src/main/java/com/example/NikeApp/model/User.kt b/Yoshi/NikeApp/app/src/main/java/com/example/NikeApp/model/User.kt new file mode 100644 index 00000000..e07fbe92 --- /dev/null +++ b/Yoshi/NikeApp/app/src/main/java/com/example/NikeApp/model/User.kt @@ -0,0 +1,12 @@ +package com.example.NikeApp.model + +/** + * 화면에서 사용하는 사용자 도메인 모델 + * - [nickname] 은 ReqRes 의 first_name + last_name 을 합친 값 + * - [avatarUrl] 은 프로필/팔로잉 이미지로 사용 + */ +data class User( + val id: Int, + val nickname: String, + val avatarUrl: String, +) diff --git a/Yoshi/NikeApp/app/src/main/java/com/example/NikeApp/ui/NikeApp.kt b/Yoshi/NikeApp/app/src/main/java/com/example/NikeApp/ui/NikeApp.kt index 99384de0..a6bd6ded 100644 --- a/Yoshi/NikeApp/app/src/main/java/com/example/NikeApp/ui/NikeApp.kt +++ b/Yoshi/NikeApp/app/src/main/java/com/example/NikeApp/ui/NikeApp.kt @@ -4,13 +4,16 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController +import com.example.NikeApp.data.WishlistRepository import com.example.NikeApp.ui.component.NikeBottomBar import com.example.NikeApp.ui.navigation.AppDestination import com.example.NikeApp.ui.navigation.BottomTab @@ -26,11 +29,16 @@ import com.example.NikeApp.ui.screen.WishlistScreen * NavController는 화면 이동과 뒤로 가기를 담당 * Scaffold는 BottomBar와 본문을 분리하여 배치 * NavHost는 실제 화면이 교체되며 그려지는 컨테이너 + * WishlistRepository는 Activity 스코프로 1회만 생성하여 모든 화면이 동일한 위시리스트 상태를 공유 */ @Composable fun NikeApp() { val navController = rememberNavController() + // applicationContext 기반으로 1회만 생성 → 모든 화면이 동일 인스턴스 공유 + val context = LocalContext.current + val wishlistRepository = remember(context) { WishlistRepository(context) } + // 현재 어떤 화면이 보여지고 있는지 NavController에서 관찰 → BottomBar 선택 상태로 변환 val backStackEntry by navController.currentBackStackEntryAsState() val currentTab: BottomTab = backStackEntry?.destination.toBottomTab() ?: BottomTab.Home @@ -48,18 +56,36 @@ fun NikeApp() { startDestination = AppDestination.Home, modifier = Modifier.padding(innerPadding), ) { - mainGraph(onNavigateToPurchase = { navController.navigateToTab(BottomTab.Purchase) }) + mainGraph( + wishlistRepository = wishlistRepository, + onNavigateToPurchase = { navController.navigateToTab(BottomTab.Purchase) }, + ) } } } -/** NavGraph 정의를 확장 함수로 분리 */ +/** + * NavGraph 정의를 확장 함수로 분리함 + */ private fun NavGraphBuilder.mainGraph( + wishlistRepository: WishlistRepository, onNavigateToPurchase: () -> Unit, ) { composable { HomeScreen() } - composable { PurchaseScreen() } - composable { WishlistScreen() } + composable { + val wishlistIds by wishlistRepository.wishlistIds + PurchaseScreen( + wishlistIds = wishlistIds, + onToggleWishlist = wishlistRepository::toggle, + ) + } + composable { + val wishlistIds by wishlistRepository.wishlistIds + WishlistScreen( + wishlistIds = wishlistIds, + onToggleWishlist = wishlistRepository::toggle, + ) + } composable { // 장바구니 → 구매하기 CartScreen(onOrderClick = onNavigateToPurchase) diff --git a/Yoshi/NikeApp/app/src/main/java/com/example/NikeApp/ui/component/ProductCard.kt b/Yoshi/NikeApp/app/src/main/java/com/example/NikeApp/ui/component/ProductCard.kt new file mode 100644 index 00000000..ac845c3e --- /dev/null +++ b/Yoshi/NikeApp/app/src/main/java/com/example/NikeApp/ui/component/ProductCard.kt @@ -0,0 +1,130 @@ +package com.example.NikeApp.ui.component + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +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.aspectRatio +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.shape.RoundedCornerShape +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.draw.clip +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.NikeApp.R +import com.example.NikeApp.model.Product +import com.example.NikeApp.model.formattedPrice +import com.example.NikeApp.ui.theme.NikeBlack +import com.example.NikeApp.ui.theme.NikeGray +import com.example.NikeApp.ui.theme.NikeLightGray + +// 하트 ON 색상 +private val HeartOnColor = Color(0xFFE61E2B) + +/** + * 홈/구매하기/위시리스트 화면에서 공통으로 사용하는 상품 카드 + * [showWishButton]이 true이면 우상단에 하트 버튼 노출 + */ +@Composable +fun ProductCard( + product: Product, + modifier: Modifier = Modifier, + showWishButton: Boolean = false, + isWished: Boolean = false, + onWishClick: (() -> Unit)? = null, +) { + Column(modifier = modifier) { + Box( + modifier = Modifier + .fillMaxWidth() + .aspectRatio(1f) + .clip(RoundedCornerShape(8.dp)) + .background(NikeLightGray), + contentAlignment = Alignment.Center, + ) { + // 실제 사진이 등록된 상품은 실사진으로 아니면 작은 더미 아이콘으로 표시됨 + if (product.imageRes != null) { + Image( + painter = painterResource(id = product.imageRes), + contentDescription = product.name, + contentScale = ContentScale.Crop, + modifier = Modifier.fillMaxSize(), + ) + } else { + Image( + painter = painterResource(id = android.R.drawable.ic_menu_gallery), + contentDescription = product.name, + contentScale = ContentScale.Fit, + modifier = Modifier.size(80.dp), + ) + } + + if (showWishButton) { + WishHeartButton( + isWished = isWished, + onClick = { onWishClick?.invoke() }, + modifier = Modifier + .align(Alignment.TopEnd) + .padding(8.dp), + ) + } + } + + Spacer(modifier = Modifier.height(10.dp)) + Text( + text = product.name, + fontSize = 15.sp, + fontWeight = FontWeight.SemiBold, + color = NikeBlack, + ) + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = product.formattedPrice(), + fontSize = 13.sp, + color = NikeGray, + ) + } +} + +/** + * 상품 카드 우상단의 하트 토글 버튼. + */ +@Composable +private fun WishHeartButton( + isWished: Boolean, + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { + Box( + modifier = modifier + .size(32.dp) + .clip(RoundedCornerShape(50)) + .background(Color.White.copy(alpha = 0.85f)) + .clickable(onClick = onClick) + .padding(PaddingValues(6.dp)), + contentAlignment = Alignment.Center, + ) { + Icon( + painter = painterResource( + id = if (isWished) R.drawable.ic_heart_on else R.drawable.ic_heart_off, + ), + contentDescription = if (isWished) "위시리스트에서 제거" else "위시리스트에 추가", + tint = if (isWished) HeartOnColor else NikeGray, + ) + } +} diff --git a/Yoshi/NikeApp/app/src/main/java/com/example/NikeApp/ui/navigation/AppDestination.kt b/Yoshi/NikeApp/app/src/main/java/com/example/NikeApp/ui/navigation/AppDestination.kt index 7a75127a..da0bf026 100644 --- a/Yoshi/NikeApp/app/src/main/java/com/example/NikeApp/ui/navigation/AppDestination.kt +++ b/Yoshi/NikeApp/app/src/main/java/com/example/NikeApp/ui/navigation/AppDestination.kt @@ -41,7 +41,7 @@ enum class BottomTab( Profile(AppDestination.Profile, R.drawable.profile_menu), } -/** 현재 보여지는 NavDestination이 어떤 탭에 해당하는지 매칭. */ +/** 현재 보여지는 NavDestination이 어떤 탭에 해당하는지 매칭 */ fun NavDestination?.toBottomTab(): BottomTab? = BottomTab.entries.firstOrNull { tab -> this?.hasRoute(tab.route::class) == true diff --git a/Yoshi/NikeApp/app/src/main/java/com/example/NikeApp/ui/screen/HomeScreen.kt b/Yoshi/NikeApp/app/src/main/java/com/example/NikeApp/ui/screen/HomeScreen.kt index 856873b2..7d56378a 100644 --- a/Yoshi/NikeApp/app/src/main/java/com/example/NikeApp/ui/screen/HomeScreen.kt +++ b/Yoshi/NikeApp/app/src/main/java/com/example/NikeApp/ui/screen/HomeScreen.kt @@ -1,15 +1,21 @@ package com.example.NikeApp.ui.screen import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Arrangement +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.rememberScrollState -import androidx.compose.foundation.verticalScroll +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource @@ -18,39 +24,86 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.example.NikeApp.R +import com.example.NikeApp.model.SampleProducts +import com.example.NikeApp.ui.component.ProductCard import com.example.NikeApp.ui.theme.NikeAppTheme import com.example.NikeApp.ui.theme.NikeBlack import com.example.NikeApp.ui.theme.NikeGray +/** + * 홈 화면 + */ @Composable fun HomeScreen(modifier: Modifier = Modifier) { - Column( - modifier = modifier - .fillMaxSize() - .verticalScroll(rememberScrollState()) - .padding(horizontal = 20.dp, vertical = 16.dp), + // 홈 화면에 5개의 상품만 보여주기 + val homeProducts = remember { SampleProducts.take(5) } + + LazyColumn( + modifier = modifier.fillMaxSize(), + contentPadding = PaddingValues(vertical = 16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), ) { - Text( - text = "Discover", - fontSize = 32.sp, - fontWeight = FontWeight.Bold, - color = NikeBlack, - ) - Text( - text = "9월 4일 목요일", - style = MaterialTheme.typography.bodyMedium, - color = NikeGray, - modifier = Modifier.padding(top = 4.dp), - ) + item { + Text( + text = "Discover", + fontSize = 32.sp, + fontWeight = FontWeight.Bold, + color = NikeBlack, + modifier = Modifier.padding(horizontal = 20.dp), + ) + } + item { + Text( + text = "9월 4일 목요일", + style = MaterialTheme.typography.bodyMedium, + color = NikeGray, + modifier = Modifier.padding(horizontal = 20.dp), + ) + } + item { + Image( + painter = painterResource(id = R.drawable.home_img), + contentDescription = "홈 메인 이미지", + contentScale = ContentScale.FillWidth, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp), + ) + } + item { + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = "What's new", + fontSize = 13.sp, + color = NikeGray, + modifier = Modifier.padding(horizontal = 20.dp), + ) + Text( + text = "나이키 최신 상품", + fontSize = 22.sp, + fontWeight = FontWeight.Bold, + color = NikeBlack, + modifier = Modifier.padding(horizontal = 20.dp, vertical = 2.dp), + ) + } - Image( - painter = painterResource(id = R.drawable.home_img), - contentDescription = "홈 메인 이미지", - contentScale = ContentScale.FillWidth, - modifier = Modifier - .padding(top = 20.dp) - .fillMaxWidth(), - ) + // LazyRow를 사용해 수평으로 스크롤 + item { + LazyRow( + contentPadding = PaddingValues(horizontal = 20.dp), + horizontalArrangement = Arrangement.spacedBy(12.dp), + ) { + items( + items = homeProducts, + key = { product -> product.id }, + ) { product -> + ProductCard( + product = product, + modifier = Modifier.width(280.dp), + ) + } + } + } } } diff --git a/Yoshi/NikeApp/app/src/main/java/com/example/NikeApp/ui/screen/ProfileScreen.kt b/Yoshi/NikeApp/app/src/main/java/com/example/NikeApp/ui/screen/ProfileScreen.kt index dbf00661..8aa7500f 100644 --- a/Yoshi/NikeApp/app/src/main/java/com/example/NikeApp/ui/screen/ProfileScreen.kt +++ b/Yoshi/NikeApp/app/src/main/java/com/example/NikeApp/ui/screen/ProfileScreen.kt @@ -1,36 +1,343 @@ package com.example.NikeApp.ui.screen +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.aspectRatio 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.pager.HorizontalPager +import androidx.compose.foundation.pager.PageSize +import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Button +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Text +import androidx.compose.material3.VerticalDivider import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import coil3.compose.AsyncImage +import com.example.NikeApp.R +import com.example.NikeApp.data.UserRepository +import com.example.NikeApp.model.User import com.example.NikeApp.ui.theme.NikeAppTheme import com.example.NikeApp.ui.theme.NikeBlack +import com.example.NikeApp.ui.theme.NikeGray +import com.example.NikeApp.ui.theme.NikeLightGray +import kotlin.coroutines.cancellation.CancellationException +/** + * 프로필(마이페이지) 화면의 데이터 상태 + * - 워크북의 LaunchedEffect 비동기 패턴으로 ReqRes 에서 데이터를 받아옵니다. + */ +private sealed interface ProfileUiState { + data object Loading : ProfileUiState + data class Success(val me: User, val following: List) : ProfileUiState + data class Error(val message: String) : ProfileUiState +} + +/** + * 마이페이지 화면 + * - userId 1번 사용자의 이미지/닉네임을 ReqRes 에서 받아와 표시 + * - 팔로잉 리스트는 HorizontalPager 로 구현 + */ @Composable fun ProfileScreen(modifier: Modifier = Modifier) { + val repository = remember { UserRepository() } + + var uiState by remember { mutableStateOf(ProfileUiState.Loading) } + // 에러 시 "다시 시도"를 누르면 key 가 바뀌어 LaunchedEffect 가 재실행됩니다. + var retryKey by remember { mutableIntStateOf(0) } + + LaunchedEffect(retryKey) { + uiState = ProfileUiState.Loading + uiState = try { + val me = repository.getMyProfile() + val following = repository.getFollowing() + ProfileUiState.Success(me = me, following = following) + } catch (e: CancellationException) { + throw e + } catch (e: Exception) { + ProfileUiState.Error(e.message ?: "데이터를 불러오지 못했습니다.") + } + } + + when (val state = uiState) { + ProfileUiState.Loading -> LoadingView(modifier) + is ProfileUiState.Error -> ErrorView( + message = state.message, + onRetry = { retryKey++ }, + modifier = modifier, + ) + is ProfileUiState.Success -> ProfileContent( + me = state.me, + following = state.following, + modifier = modifier, + ) + } +} + +@Composable +private fun LoadingView(modifier: Modifier = Modifier) { + Box(modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + CircularProgressIndicator(color = NikeBlack) + } +} + +@Composable +private fun ErrorView( + message: String, + onRetry: () -> Unit, + modifier: Modifier = Modifier, +) { Column( modifier = modifier .fillMaxSize() - .padding(horizontal = 20.dp, vertical = 16.dp), + .padding(24.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, ) { + Text(text = "오류가 발생했어요", fontWeight = FontWeight.Bold, fontSize = 18.sp) + Spacer(Modifier.height(8.dp)) + Text(text = message, color = NikeGray, fontSize = 14.sp) + Spacer(Modifier.height(16.dp)) + Button(onClick = onRetry) { Text("다시 시도") } + } +} + +@Composable +private fun ProfileContent( + me: User, + following: List, + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Spacer(Modifier.height(24.dp)) + + // 프로필 이미지 (ReqRes avatar) — 원형 + AsyncImage( + model = me.avatarUrl, + contentDescription = "${me.nickname} 프로필 이미지", + contentScale = ContentScale.Crop, + modifier = Modifier + .size(120.dp) + .clip(CircleShape) + .background(NikeLightGray), + ) + + Spacer(Modifier.height(16.dp)) + + // 닉네임 (first_name + last_name) Text( - text = "프로필", - fontSize = 28.sp, + text = me.nickname, + fontSize = 24.sp, fontWeight = FontWeight.Bold, color = NikeBlack, ) + + Spacer(Modifier.height(16.dp)) + + OutlinedButton( + onClick = { /* 프로필 수정 (미구현) */ }, + shape = RoundedCornerShape(50), + modifier = Modifier.padding(horizontal = 80.dp), + ) { + Text( + text = "프로필 수정", + color = NikeBlack, + fontWeight = FontWeight.Bold, + modifier = Modifier.padding(vertical = 4.dp, horizontal = 24.dp), + ) + } + + Spacer(Modifier.height(24.dp)) + + // 빠른 메뉴: 주문 / 패스 / 이벤트 / 설정 + QuickActionRow() + + Spacer(Modifier.height(16.dp)) + HorizontalDivider(thickness = 8.dp, color = NikeLightGray) + + // 나이키 멤버 혜택 + MemberBenefitRow() + + HorizontalDivider(thickness = 8.dp, color = NikeLightGray) + + Spacer(Modifier.height(24.dp)) + + // 팔로잉 리스트 (HorizontalPager) + FollowingSection(following = following) + + Spacer(Modifier.height(40.dp)) + + Text( + text = "회원 가입일: 2025년 9월", + color = NikeGray, + fontSize = 13.sp, + ) + + Spacer(Modifier.height(24.dp)) + } +} + +/** 주문 / 패스 / 이벤트 / 설정 — 제공된 drawable(아이콘+라벨 일체형)을 그대로 사용 */ +@Composable +private fun QuickActionRow(modifier: Modifier = Modifier) { + val items = listOf( + R.drawable.menu_order, + R.drawable.menu_pass, + R.drawable.menu_event, + R.drawable.menu_settings, + ) + Row( + modifier = modifier + .fillMaxWidth() + .padding(horizontal = 20.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + items.forEachIndexed { index, drawable -> + Icon( + painter = painterResource(id = drawable), + contentDescription = null, + tint = NikeBlack, + modifier = Modifier + .weight(1f) + .height(46.dp), + ) + if (index != items.lastIndex) { + VerticalDivider( + modifier = Modifier.height(36.dp), + color = NikeLightGray, + ) + } + } + } +} + +/** 나이키 멤버 혜택 — 클릭 가능한 행 */ +@Composable +private fun MemberBenefitRow(modifier: Modifier = Modifier) { + Row( + modifier = modifier + .fillMaxWidth() + .padding(horizontal = 20.dp, vertical = 20.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Column(modifier = Modifier.weight(1f)) { + Text( + text = "나이키 멤버 혜택", + fontSize = 18.sp, + fontWeight = FontWeight.Bold, + color = NikeBlack, + ) + Spacer(Modifier.height(4.dp)) + Text(text = "0개 사용 가능", fontSize = 13.sp, color = NikeGray) + } + Icon( + painter = painterResource(id = R.drawable.ic_arrow_right), + contentDescription = null, + tint = NikeBlack, + ) + } +} + +/** 팔로잉 리스트 — HorizontalPager 로 구현 */ +@Composable +private fun FollowingSection( + following: List, + modifier: Modifier = Modifier, +) { + Column(modifier = modifier.fillMaxWidth()) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Text( + text = "팔로잉 (${following.size})", + fontSize = 20.sp, + fontWeight = FontWeight.Bold, + color = NikeBlack, + ) + Text(text = "편집", fontSize = 14.sp, color = NikeGray) + } + + Spacer(Modifier.height(16.dp)) + + if (following.isEmpty()) { + Text( + text = "팔로잉한 사용자가 없습니다.", + color = NikeGray, + modifier = Modifier.padding(horizontal = 20.dp), + ) + return@Column + } + + val pagerState = rememberPagerState(pageCount = { following.size }) + HorizontalPager( + state = pagerState, + pageSize = PageSize.Fixed(150.dp), + pageSpacing = 12.dp, + contentPadding = PaddingValues(horizontal = 20.dp), + ) { page -> + AsyncImage( + model = following[page].avatarUrl, + contentDescription = "${following[page].nickname} 이미지", + contentScale = ContentScale.Crop, + modifier = Modifier + .fillMaxWidth() + .aspectRatio(1f) + .clip(RoundedCornerShape(8.dp)) + .background(NikeLightGray), + ) + } } } @Preview(showBackground = true) @Composable -private fun ProfileScreenPreview() { - NikeAppTheme { ProfileScreen() } +private fun ProfileContentPreview() { + val sampleMe = User(1, "George Bluth", "") + val sampleFollowing = listOf( + User(2, "Janet Weaver", ""), + User(3, "Emma Wong", ""), + User(4, "Eve Holt", ""), + ) + NikeAppTheme { + ProfileContent(me = sampleMe, following = sampleFollowing) + } } diff --git a/Yoshi/NikeApp/app/src/main/java/com/example/NikeApp/ui/screen/PurchaseScreen.kt b/Yoshi/NikeApp/app/src/main/java/com/example/NikeApp/ui/screen/PurchaseScreen.kt index 25f0d418..ff9c5a8f 100644 --- a/Yoshi/NikeApp/app/src/main/java/com/example/NikeApp/ui/screen/PurchaseScreen.kt +++ b/Yoshi/NikeApp/app/src/main/java/com/example/NikeApp/ui/screen/PurchaseScreen.kt @@ -2,13 +2,17 @@ package com.example.NikeApp.ui.screen import androidx.compose.foundation.background import androidx.compose.foundation.clickable +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.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -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.runtime.getValue @@ -22,22 +26,29 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import com.example.NikeApp.model.SampleProducts +import com.example.NikeApp.ui.component.ProductCard import com.example.NikeApp.ui.theme.NikeAppTheme import com.example.NikeApp.ui.theme.NikeBlack import com.example.NikeApp.ui.theme.NikeGray import com.example.NikeApp.ui.theme.NikeLightGray /** - * 구매하기 화면. + * 구매하기 화면 + * - 전체 / Tops & T-shirts / Sale 탭 구성 */ private enum class PurchaseTab(val label: String) { All("전체"), Tops("Tops & T-shirts"), - Shoes("Shoes"), + Sale("Sale"), } @Composable -fun PurchaseScreen(modifier: Modifier = Modifier) { +fun PurchaseScreen( + wishlistIds: Set, + onToggleWishlist: (String) -> Unit, + modifier: Modifier = Modifier, +) { var selectedTab by remember { mutableStateOf(PurchaseTab.All) } Column(modifier = modifier.fillMaxSize()) { @@ -46,8 +57,60 @@ fun PurchaseScreen(modifier: Modifier = Modifier) { onTabSelected = { selectedTab = it }, ) - // 본문은 빈 화면 - Box(modifier = Modifier.fillMaxSize()) + when (selectedTab) { + PurchaseTab.All -> AllProductsGrid( + wishlistIds = wishlistIds, + onToggleWishlist = onToggleWishlist, + ) + PurchaseTab.Tops, PurchaseTab.Sale -> EmptyTabPlaceholder() + } + } +} + +/** + * '전체' 탭 — LazyVerticalGrid 로 2열 × 4행 구성 + * items( ) 로 안정적인 키 부여 + * 각 상품 카드 우상단의 하트 버튼으로 위시리스트에 추가 및 제거 + */ +@Composable +private fun AllProductsGrid( + wishlistIds: Set, + onToggleWishlist: (String) -> Unit, + modifier: Modifier = Modifier, +) { + LazyVerticalGrid( + columns = GridCells.Fixed(2), + modifier = modifier.fillMaxSize(), + contentPadding = PaddingValues(horizontal = 20.dp, vertical = 16.dp), + verticalArrangement = Arrangement.spacedBy(20.dp), + horizontalArrangement = Arrangement.spacedBy(12.dp), + ) { + items( + items = SampleProducts, + key = { product -> product.id }, + ) { product -> + ProductCard( + product = product, + modifier = Modifier.fillMaxWidth(), + showWishButton = true, + isWished = product.id in wishlistIds, + onWishClick = { onToggleWishlist(product.id) }, + ) + } + } +} + +@Composable +private fun EmptyTabPlaceholder(modifier: Modifier = Modifier) { + Box( + modifier = modifier.fillMaxSize(), + contentAlignment = Alignment.Center, + ) { + Text( + text = " ", + fontSize = 14.sp, + color = NikeGray, + ) } } @@ -113,5 +176,10 @@ private fun PurchaseTabItem( @Preview(showBackground = true) @Composable private fun PurchaseScreenPreview() { - NikeAppTheme { PurchaseScreen() } + NikeAppTheme { + PurchaseScreen( + wishlistIds = emptySet(), + onToggleWishlist = {}, + ) + } } diff --git a/Yoshi/NikeApp/app/src/main/java/com/example/NikeApp/ui/screen/WishlistScreen.kt b/Yoshi/NikeApp/app/src/main/java/com/example/NikeApp/ui/screen/WishlistScreen.kt index 71f83e83..b0eb9d61 100644 --- a/Yoshi/NikeApp/app/src/main/java/com/example/NikeApp/ui/screen/WishlistScreen.kt +++ b/Yoshi/NikeApp/app/src/main/java/com/example/NikeApp/ui/screen/WishlistScreen.kt @@ -1,30 +1,93 @@ package com.example.NikeApp.ui.screen -import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.GridItemSpan +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.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import com.example.NikeApp.model.SampleProducts +import com.example.NikeApp.ui.component.ProductCard import com.example.NikeApp.ui.theme.NikeAppTheme import com.example.NikeApp.ui.theme.NikeBlack +import com.example.NikeApp.ui.theme.NikeGray +/** + * 위시리스트 화면 + * 전체 상품 중 [wishlistIds] 에 포함된 것만 LazyVerticalGrid 로 2 x N 형태로 표시 + * 카드의 하트를 다시 누르면 위시리스트에서 제거 + */ @Composable -fun WishlistScreen(modifier: Modifier = Modifier) { - Column( +fun WishlistScreen( + wishlistIds: Set, + onToggleWishlist: (String) -> Unit, + modifier: Modifier = Modifier, +) { + val wishedProducts = SampleProducts.filter { it.id in wishlistIds } + + LazyVerticalGrid( + columns = GridCells.Fixed(2), + modifier = modifier.fillMaxSize(), + contentPadding = PaddingValues(horizontal = 20.dp, vertical = 16.dp), + verticalArrangement = Arrangement.spacedBy(20.dp), + horizontalArrangement = Arrangement.spacedBy(12.dp), + ) { + // 제목은 전체 열을 차지하도록 span 지정 + item(span = { GridItemSpan(maxLineSpan) }) { + Text( + text = "위시리스트", + fontSize = 28.sp, + fontWeight = FontWeight.Bold, + color = NikeBlack, + ) + } + + if (wishedProducts.isEmpty()) { + // 빈 상태 메시지도 전체 행 차지 + item(span = { GridItemSpan(maxLineSpan) }) { + EmptyWishlistMessage() + } + } else { + items( + items = wishedProducts, + key = { product -> product.id }, + ) { product -> + ProductCard( + product = product, + modifier = Modifier.fillMaxWidth(), + showWishButton = true, + isWished = true, + onWishClick = { onToggleWishlist(product.id) }, + ) + } + } + } +} + +@Composable +private fun EmptyWishlistMessage(modifier: Modifier = Modifier) { + Box( modifier = modifier - .fillMaxSize() - .padding(horizontal = 20.dp, vertical = 16.dp), + .fillMaxWidth() + .height(240.dp), + contentAlignment = Alignment.Center, ) { Text( - text = "위시리스트", - fontSize = 28.sp, - fontWeight = FontWeight.Bold, - color = NikeBlack, + text = "아직 위시한 상품이 없습니다", + fontSize = 14.sp, + color = NikeGray, ) } } @@ -32,5 +95,10 @@ fun WishlistScreen(modifier: Modifier = Modifier) { @Preview(showBackground = true) @Composable private fun WishlistScreenPreview() { - NikeAppTheme { WishlistScreen() } + NikeAppTheme { + WishlistScreen( + wishlistIds = setOf("p1", "p3"), + onToggleWishlist = {}, + ) + } } diff --git a/Yoshi/NikeApp/app/src/main/res/drawable/air_force_1_07.png b/Yoshi/NikeApp/app/src/main/res/drawable/air_force_1_07.png new file mode 100644 index 00000000..b51811cc Binary files /dev/null and b/Yoshi/NikeApp/app/src/main/res/drawable/air_force_1_07.png differ diff --git a/Yoshi/NikeApp/app/src/main/res/drawable/air_jordan_1_mid.png b/Yoshi/NikeApp/app/src/main/res/drawable/air_jordan_1_mid.png new file mode 100644 index 00000000..5d2f1910 Binary files /dev/null and b/Yoshi/NikeApp/app/src/main/res/drawable/air_jordan_1_mid.png differ diff --git a/Yoshi/NikeApp/app/src/main/res/drawable/air_max_90.png b/Yoshi/NikeApp/app/src/main/res/drawable/air_max_90.png new file mode 100644 index 00000000..2dac1e4b Binary files /dev/null and b/Yoshi/NikeApp/app/src/main/res/drawable/air_max_90.png differ diff --git a/Yoshi/NikeApp/app/src/main/res/drawable/blazer_mid_77.png b/Yoshi/NikeApp/app/src/main/res/drawable/blazer_mid_77.png new file mode 100644 index 00000000..53b18739 Binary files /dev/null and b/Yoshi/NikeApp/app/src/main/res/drawable/blazer_mid_77.png differ diff --git a/Yoshi/NikeApp/app/src/main/res/drawable/cortez_basic.png b/Yoshi/NikeApp/app/src/main/res/drawable/cortez_basic.png new file mode 100644 index 00000000..dca7664a Binary files /dev/null and b/Yoshi/NikeApp/app/src/main/res/drawable/cortez_basic.png differ diff --git a/Yoshi/NikeApp/app/src/main/res/drawable/dunk_low_retro.png b/Yoshi/NikeApp/app/src/main/res/drawable/dunk_low_retro.png new file mode 100644 index 00000000..4d362174 Binary files /dev/null and b/Yoshi/NikeApp/app/src/main/res/drawable/dunk_low_retro.png differ diff --git a/Yoshi/NikeApp/app/src/main/res/drawable/pegasus_41.png b/Yoshi/NikeApp/app/src/main/res/drawable/pegasus_41.png new file mode 100644 index 00000000..991f2db3 Binary files /dev/null and b/Yoshi/NikeApp/app/src/main/res/drawable/pegasus_41.png differ diff --git a/Yoshi/NikeApp/app/src/main/res/drawable/sportswear_crew_socks.png b/Yoshi/NikeApp/app/src/main/res/drawable/sportswear_crew_socks.png new file mode 100644 index 00000000..75d25114 Binary files /dev/null and b/Yoshi/NikeApp/app/src/main/res/drawable/sportswear_crew_socks.png differ diff --git a/Yoshi/NikeApp/gradle/libs.versions.toml b/Yoshi/NikeApp/gradle/libs.versions.toml index ba499b54..f1fe1622 100644 --- a/Yoshi/NikeApp/gradle/libs.versions.toml +++ b/Yoshi/NikeApp/gradle/libs.versions.toml @@ -10,6 +10,9 @@ activityCompose = "1.9.3" composeBom = "2024.12.01" navigationCompose = "2.8.5" kotlinxSerializationJson = "1.7.3" +coil = "3.1.0" +retrofit = "2.11.0" +okhttp = "4.12.0" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -24,6 +27,16 @@ androidx-material3 = { group = "androidx.compose.material3", name = "material3" androidx-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended" } androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" } kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } + +# Coil — Compose 이미지 로딩 (워크북 9주차) +coil-compose = { group = "io.coil-kt.coil3", name = "coil-compose", version.ref = "coil" } +coil-network-okhttp = { group = "io.coil-kt.coil3", name = "coil-network-okhttp", version.ref = "coil" } + +# Retrofit + OkHttp — ReqRes API 통신 +retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" } +retrofit-kotlinx-serialization = { group = "com.squareup.retrofit2", name = "converter-kotlinx-serialization", version.ref = "retrofit" } +okhttp-logging-interceptor = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp" } + junit = { group = "junit", name = "junit", version.ref = "junit" } androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } diff --git "a/\354\232\224\354\213\234/UMC10th_Android_week1/.gitignore" "b/\354\232\224\354\213\234/UMC10th_Android_week1/.gitignore" deleted file mode 100644 index aa724b77..00000000 --- "a/\354\232\224\354\213\234/UMC10th_Android_week1/.gitignore" +++ /dev/null @@ -1,15 +0,0 @@ -*.iml -.gradle -/local.properties -/.idea/caches -/.idea/libraries -/.idea/modules.xml -/.idea/workspace.xml -/.idea/navEditor.xml -/.idea/assetWizardSettings.xml -.DS_Store -/build -/captures -.externalNativeBuild -.cxx -local.properties diff --git "a/\354\232\224\354\213\234/UMC10th_Android_week1/Group 462.svg" "b/\354\232\224\354\213\234/UMC10th_Android_week1/Group 462.svg" deleted file mode 100644 index 45ef4e50..00000000 --- "a/\354\232\224\354\213\234/UMC10th_Android_week1/Group 462.svg" +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git "a/\354\232\224\354\213\234/UMC10th_Android_week1/Group 573.svg" "b/\354\232\224\354\213\234/UMC10th_Android_week1/Group 573.svg" deleted file mode 100644 index b987bad8..00000000 --- "a/\354\232\224\354\213\234/UMC10th_Android_week1/Group 573.svg" +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git "a/\354\232\224\354\213\234/UMC10th_Android_week1/Group 574.svg" "b/\354\232\224\354\213\234/UMC10th_Android_week1/Group 574.svg" deleted file mode 100644 index 915d24f2..00000000 --- "a/\354\232\224\354\213\234/UMC10th_Android_week1/Group 574.svg" +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git "a/\354\232\224\354\213\234/UMC10th_Android_week1/Group 575.svg" "b/\354\232\224\354\213\234/UMC10th_Android_week1/Group 575.svg" deleted file mode 100644 index 84ee0c6f..00000000 --- "a/\354\232\224\354\213\234/UMC10th_Android_week1/Group 575.svg" +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git "a/\354\232\224\354\213\234/UMC10th_Android_week1/Group 576.svg" "b/\354\232\224\354\213\234/UMC10th_Android_week1/Group 576.svg" deleted file mode 100644 index b65626a4..00000000 --- "a/\354\232\224\354\213\234/UMC10th_Android_week1/Group 576.svg" +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git "a/\354\232\224\354\213\234/UMC10th_Android_week1/Vector.svg" "b/\354\232\224\354\213\234/UMC10th_Android_week1/Vector.svg" deleted file mode 100644 index dab8b024..00000000 --- "a/\354\232\224\354\213\234/UMC10th_Android_week1/Vector.svg" +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/.gitignore" "b/\354\232\224\354\213\234/UMC10th_Android_week1/app/.gitignore" deleted file mode 100644 index 42afabfd..00000000 --- "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/.gitignore" +++ /dev/null @@ -1 +0,0 @@ -/build \ No newline at end of file diff --git "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/build.gradle.kts" "b/\354\232\224\354\213\234/UMC10th_Android_week1/app/build.gradle.kts" deleted file mode 100644 index 4760d90d..00000000 --- "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/build.gradle.kts" +++ /dev/null @@ -1,47 +0,0 @@ -plugins { - alias(libs.plugins.android.application) -} - -android { - namespace = "com.example.umc10th_android_week1" - compileSdk { - version = release(36) { - minorApiLevel = 1 - } - } - - defaultConfig { - applicationId = "com.example.umc10th_android_week1" - minSdk = 24 - targetSdk = 36 - versionCode = 1 - versionName = "1.0" - - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - } - - buildTypes { - release { - isMinifyEnabled = false - proguardFiles( - getDefaultProguardFile("proguard-android-optimize.txt"), - "proguard-rules.pro" - ) - } - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 - } -} - -dependencies { - implementation(libs.androidx.core.ktx) - implementation(libs.androidx.appcompat) - implementation(libs.material) - implementation(libs.androidx.activity) - implementation(libs.androidx.constraintlayout) - testImplementation(libs.junit) - androidTestImplementation(libs.androidx.junit) - androidTestImplementation(libs.androidx.espresso.core) -} \ No newline at end of file diff --git "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/proguard-rules.pro" "b/\354\232\224\354\213\234/UMC10th_Android_week1/app/proguard-rules.pro" deleted file mode 100644 index 481bb434..00000000 --- "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/proguard-rules.pro" +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/androidTest/java/com/example/umc10th_android_week1/ExampleInstrumentedTest.kt" "b/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/androidTest/java/com/example/umc10th_android_week1/ExampleInstrumentedTest.kt" deleted file mode 100644 index a77f1fd0..00000000 --- "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/androidTest/java/com/example/umc10th_android_week1/ExampleInstrumentedTest.kt" +++ /dev/null @@ -1,24 +0,0 @@ -package com.example.umc10th_android_week1 - -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.ext.junit.runners.AndroidJUnit4 - -import org.junit.Test -import org.junit.runner.RunWith - -import org.junit.Assert.* - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("com.example.umc10th_android_week1", appContext.packageName) - } -} \ No newline at end of file diff --git "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/AndroidManifest.xml" "b/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/AndroidManifest.xml" deleted file mode 100644 index 0e1b0236..00000000 --- "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/AndroidManifest.xml" +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/java/com/example/umc10th_android_week1/MainActivity.kt" "b/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/java/com/example/umc10th_android_week1/MainActivity.kt" deleted file mode 100644 index e0419abf..00000000 --- "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/java/com/example/umc10th_android_week1/MainActivity.kt" +++ /dev/null @@ -1,65 +0,0 @@ -package com.example.umc10th_android_week1 - -import android.os.Bundle -import androidx.activity.enableEdgeToEdge -import androidx.appcompat.app.AppCompatActivity -import androidx.core.view.ViewCompat -import androidx.core.view.WindowInsetsCompat -import android.graphics.Color -import android.widget.ImageView -import android.widget.TextView - -class MainActivity : AppCompatActivity() { - private var selectedTextView: TextView? = null - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - enableEdgeToEdge() - setContentView(R.layout.activity_main) - ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets -> - val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) - v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom) - insets - } - - setupEmotion( - imageId = R.id.imgHappy, - textId = R.id.txtHappy, - color = Color.parseColor("#FFB300") // 노란색 (행복) - ) - setupEmotion( - imageId = R.id.imgExcited, - textId = R.id.txtExcited, - color = Color.parseColor("#B8F8FB") // 초록색 (흥분) - ) - setupEmotion( - imageId = R.id.imgNormal, - textId = R.id.txtNormal, - color = Color.parseColor("#9C27B0") // 파란색 (평범) - ) - setupEmotion( - imageId = R.id.imgAnxious, - textId = R.id.txtAnxious, - color = Color.parseColor("#4CAF50") // 보라색 (불안) - ) - setupEmotion( - imageId = R.id.imgAngry, - textId = R.id.txtAngry, - color = Color.parseColor("#F44336") // 빨간색 (화남) - ) - } - private fun setupEmotion(imageId: Int, textId: Int, color: Int) { - val imageView = findViewById(imageId) - val textView = findViewById(textId) - - imageView.setOnClickListener { - // 이전에 선택된 텍스트 원래 색으로 복구 - selectedTextView?.setTextColor(Color.BLACK) - - // 현재 클릭된 텍스트 색상 변경 - textView.setTextColor(color) - - // 현재 선택 저장 - selectedTextView = textView - } - } -} \ No newline at end of file diff --git "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/drawable/group_462.xml" "b/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/drawable/group_462.xml" deleted file mode 100644 index c6b43a21..00000000 --- "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/drawable/group_462.xml" +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - diff --git "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/drawable/group_573.xml" "b/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/drawable/group_573.xml" deleted file mode 100644 index 72176dae..00000000 --- "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/drawable/group_573.xml" +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - diff --git "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/drawable/group_574.xml" "b/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/drawable/group_574.xml" deleted file mode 100644 index 5c4b19d6..00000000 --- "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/drawable/group_574.xml" +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - diff --git "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/drawable/group_575.xml" "b/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/drawable/group_575.xml" deleted file mode 100644 index 9b02528e..00000000 --- "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/drawable/group_575.xml" +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - diff --git "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/drawable/group_576.xml" "b/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/drawable/group_576.xml" deleted file mode 100644 index fe91f331..00000000 --- "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/drawable/group_576.xml" +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - diff --git "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/drawable/ic_launcher_background.xml" "b/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/drawable/ic_launcher_background.xml" deleted file mode 100644 index 07d5da9c..00000000 --- "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/drawable/ic_launcher_background.xml" +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/drawable/ic_launcher_foreground.xml" "b/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/drawable/ic_launcher_foreground.xml" deleted file mode 100644 index 2b068d11..00000000 --- "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/drawable/ic_launcher_foreground.xml" +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/drawable/vector.xml" "b/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/drawable/vector.xml" deleted file mode 100644 index af620265..00000000 --- "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/drawable/vector.xml" +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/layout/activity_main.xml" "b/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/layout/activity_main.xml" deleted file mode 100644 index a1639f0f..00000000 --- "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/layout/activity_main.xml" +++ /dev/null @@ -1,167 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml" "b/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml" deleted file mode 100644 index 6f3b755b..00000000 --- "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml" +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml" "b/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml" deleted file mode 100644 index 6f3b755b..00000000 --- "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml" +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/mipmap-hdpi/ic_launcher.webp" "b/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/mipmap-hdpi/ic_launcher.webp" deleted file mode 100644 index c209e78e..00000000 Binary files "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/mipmap-hdpi/ic_launcher.webp" and /dev/null differ diff --git "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp" "b/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp" deleted file mode 100644 index b2dfe3d1..00000000 Binary files "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp" and /dev/null differ diff --git "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/mipmap-mdpi/ic_launcher.webp" "b/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/mipmap-mdpi/ic_launcher.webp" deleted file mode 100644 index 4f0f1d64..00000000 Binary files "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/mipmap-mdpi/ic_launcher.webp" and /dev/null differ diff --git "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp" "b/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp" deleted file mode 100644 index 62b611da..00000000 Binary files "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp" and /dev/null differ diff --git "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/mipmap-xhdpi/ic_launcher.webp" "b/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/mipmap-xhdpi/ic_launcher.webp" deleted file mode 100644 index 948a3070..00000000 Binary files "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/mipmap-xhdpi/ic_launcher.webp" and /dev/null differ diff --git "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp" "b/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp" deleted file mode 100644 index 1b9a6956..00000000 Binary files "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp" and /dev/null differ diff --git "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp" "b/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp" deleted file mode 100644 index 28d4b77f..00000000 Binary files "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp" and /dev/null differ diff --git "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp" "b/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp" deleted file mode 100644 index 9287f508..00000000 Binary files "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp" and /dev/null differ diff --git "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp" "b/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp" deleted file mode 100644 index aa7d6427..00000000 Binary files "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp" and /dev/null differ diff --git "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp" "b/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp" deleted file mode 100644 index 9126ae37..00000000 Binary files "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp" and /dev/null differ diff --git "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/values-night/themes.xml" "b/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/values-night/themes.xml" deleted file mode 100644 index 87b18131..00000000 --- "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/values-night/themes.xml" +++ /dev/null @@ -1,7 +0,0 @@ - - - - \ No newline at end of file diff --git "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/values/colors.xml" "b/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/values/colors.xml" deleted file mode 100644 index c8524cd9..00000000 --- "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/values/colors.xml" +++ /dev/null @@ -1,5 +0,0 @@ - - - #FF000000 - #FFFFFFFF - \ No newline at end of file diff --git "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/values/strings.xml" "b/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/values/strings.xml" deleted file mode 100644 index d87c7d00..00000000 --- "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/values/strings.xml" +++ /dev/null @@ -1,3 +0,0 @@ - - UMC10th_Android_week1 - \ No newline at end of file diff --git "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/values/themes.xml" "b/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/values/themes.xml" deleted file mode 100644 index 8f0d38e7..00000000 --- "a/\354\232\224\354\213\234/UMC10th_Android_week1/app/src/main/res/values/themes.xml" +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - \ No newline at end of file diff --git "a/\354\232\224\354\213\234/UMC10th_Android_week2/app/src/main/res/values/colors.xml" "b/\354\232\224\354\213\234/UMC10th_Android_week2/app/src/main/res/values/colors.xml" deleted file mode 100644 index c8524cd9..00000000 --- "a/\354\232\224\354\213\234/UMC10th_Android_week2/app/src/main/res/values/colors.xml" +++ /dev/null @@ -1,5 +0,0 @@ - - - #FF000000 - #FFFFFFFF - \ No newline at end of file diff --git "a/\354\232\224\354\213\234/UMC10th_Android_week2/app/src/main/res/values/strings.xml" "b/\354\232\224\354\213\234/UMC10th_Android_week2/app/src/main/res/values/strings.xml" deleted file mode 100644 index dfe7f25e..00000000 --- "a/\354\232\224\354\213\234/UMC10th_Android_week2/app/src/main/res/values/strings.xml" +++ /dev/null @@ -1,5 +0,0 @@ - - UMC10th_Android_week2 - - Hello blank fragment - \ No newline at end of file diff --git "a/\354\232\224\354\213\234/UMC10th_Android_week2/app/src/main/res/values/themes.xml" "b/\354\232\224\354\213\234/UMC10th_Android_week2/app/src/main/res/values/themes.xml" deleted file mode 100644 index f7f76f0e..00000000 --- "a/\354\232\224\354\213\234/UMC10th_Android_week2/app/src/main/res/values/themes.xml" +++ /dev/null @@ -1,9 +0,0 @@ - - - - -