diff --git a/Week08/Yido/.idea/gradle.xml b/Week08/Yido/.idea/gradle.xml new file mode 100644 index 0000000..3e3960b --- /dev/null +++ b/Week08/Yido/.idea/gradle.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Week09/Yido/.gitignore b/Week09/Yido/.gitignore new file mode 100644 index 0000000..83c063c --- /dev/null +++ b/Week09/Yido/.gitignore @@ -0,0 +1,16 @@ +*.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 +.claude/ diff --git a/Week09/Yido/.idea/.gitignore b/Week09/Yido/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/Week09/Yido/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/Week09/Yido/.idea/AndroidProjectSystem.xml b/Week09/Yido/.idea/AndroidProjectSystem.xml new file mode 100644 index 0000000..4a53bee --- /dev/null +++ b/Week09/Yido/.idea/AndroidProjectSystem.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/Week09/Yido/.idea/compiler.xml b/Week09/Yido/.idea/compiler.xml new file mode 100644 index 0000000..b86273d --- /dev/null +++ b/Week09/Yido/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Week09/Yido/.idea/deploymentTargetSelector.xml b/Week09/Yido/.idea/deploymentTargetSelector.xml new file mode 100644 index 0000000..c130a78 --- /dev/null +++ b/Week09/Yido/.idea/deploymentTargetSelector.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/Week09/Yido/.idea/deviceManager.xml b/Week09/Yido/.idea/deviceManager.xml new file mode 100644 index 0000000..91f9558 --- /dev/null +++ b/Week09/Yido/.idea/deviceManager.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/Week09/Yido/.idea/gradle.xml b/Week09/Yido/.idea/gradle.xml new file mode 100644 index 0000000..02c4aa5 --- /dev/null +++ b/Week09/Yido/.idea/gradle.xml @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/Week09/Yido/.idea/markdown.xml b/Week09/Yido/.idea/markdown.xml new file mode 100644 index 0000000..c61ea33 --- /dev/null +++ b/Week09/Yido/.idea/markdown.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/Week09/Yido/.idea/misc.xml b/Week09/Yido/.idea/misc.xml new file mode 100644 index 0000000..f7608ed --- /dev/null +++ b/Week09/Yido/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Week09/Yido/.idea/runConfigurations.xml b/Week09/Yido/.idea/runConfigurations.xml new file mode 100644 index 0000000..16660f1 --- /dev/null +++ b/Week09/Yido/.idea/runConfigurations.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/Week09/Yido/.idea/studiobot.xml b/Week09/Yido/.idea/studiobot.xml new file mode 100644 index 0000000..539e3b8 --- /dev/null +++ b/Week09/Yido/.idea/studiobot.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/Week09/Yido/.idea/vcs.xml b/Week09/Yido/.idea/vcs.xml new file mode 100644 index 0000000..b2bdec2 --- /dev/null +++ b/Week09/Yido/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Week09/Yido/README.md b/Week09/Yido/README.md new file mode 100644 index 0000000..16ba548 --- /dev/null +++ b/Week09/Yido/README.md @@ -0,0 +1,29 @@ +# ๐Ÿ“Œ ๋ฏธ์…˜ ๋ฒˆํ˜ธ + +9์ฃผ์ฐจ ์‹ค์ „ ๋ฏธ์…˜ + +--- + +# ๐Ÿ“‹ ๊ตฌํ˜„ ์‚ฌํ•ญ + +- ๋งˆ์ดํŽ˜์ด์ง€ UI ์ „์ฒด ๊ตฌํ˜„ +- ReqRes API๋ฅผ ํ™œ์šฉํ•˜์—ฌ 1๋ฒˆ ์œ ์ € ํ”„๋กœํ•„ ์ •๋ณด ์„œ๋ฒ„ ์—ฐ๋™ +- ํŒ”๋กœ์ž‰ ๋ฆฌ์ŠคํŠธ๋ฅผ HorizontalPager๋กœ ๊ตฌํ˜„ + +--- + +# ๐Ÿ“ท ์Šคํฌ๋ฆฐ์ƒท + + + +--- + +# โœ… ์ฒดํฌ๋ฆฌ์ŠคํŠธ + +- [ ] Merge ํ•˜๋ ค๋Š” ๋ธŒ๋žœ์น˜๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์„ค์ •๋˜์–ด ์žˆ๋‚˜์š”? +- [ ] ์—๋ฎฌ๋ ˆ์ดํ„ฐ ๋˜๋Š” ์‹ค์ œ ๊ธฐ๊ธฐ์—์„œ ์ •์ƒ ๋™์ž‘ํ•˜๋‚˜์š”? +- [ ] ๋ถˆํ•„์š”ํ•œ ์ฃผ์„ ๋ฐ Log๊ฐ€ ์ œ๊ฑฐ๋˜์—ˆ๋‚˜์š”? + +--- + +# ๐Ÿ”ฅ ์งˆ๋ฌธ ์‚ฌํ•ญ diff --git a/Week09/Yido/app/.gitignore b/Week09/Yido/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/Week09/Yido/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/Week09/Yido/app/build.gradle.kts b/Week09/Yido/app/build.gradle.kts new file mode 100644 index 0000000..e468e13 --- /dev/null +++ b/Week09/Yido/app/build.gradle.kts @@ -0,0 +1,105 @@ +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.ksp) + alias(libs.plugins.hilt.android) + alias(libs.plugins.kotlin.compose) + alias(libs.plugins.kotlin.serialization) +} + +android { + namespace = "com.example.yido" + compileSdk { + version = release(36) { + minorApiLevel = 1 + } + } + + defaultConfig { + applicationId = "com.example.yido" + 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 + } + buildFeatures { + compose = true + } +} + +dependencies { + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.appcompat) + implementation(libs.material) + implementation(libs.androidx.activity) + implementation(libs.androidx.constraintlayout) + + // Splash Screen + implementation("androidx.core:core-splashscreen:1.0.1") + + // DataStore + implementation("androidx.datastore:datastore-preferences:1.1.1") + + // Gson + implementation("com.google.code.gson:gson:2.10.1") + + // Lifecycle (ViewModel + Runtime) + implementation(libs.androidx.lifecycle.runtime.ktx) + implementation(libs.androidx.lifecycle.viewmodel.ktx) + + // Hilt + implementation(libs.hilt.android) + ksp(libs.hilt.android.compiler) + + // Retrofit & Network + implementation("com.squareup.retrofit2:retrofit:2.9.0") + implementation("com.squareup.retrofit2:converter-gson:2.9.0") + implementation("com.squareup.okhttp3:logging-interceptor:4.12.0") + + // Coroutines + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0") + + // Compose BOM + val composeBom = platform(libs.androidx.compose.bom) + implementation(composeBom) + implementation(libs.androidx.compose.ui) + implementation(libs.androidx.compose.ui.graphics) + implementation(libs.androidx.compose.ui.tooling.preview) + implementation(libs.androidx.compose.material3) + debugImplementation(libs.androidx.compose.ui.tooling) + + // Activity Compose + implementation(libs.androidx.activity.compose) + + // Navigation Compose + implementation(libs.androidx.navigation.compose) + + // Kotlinx Serialization + implementation(libs.kotlinx.serialization.json) + + // Coil (image loading for Compose) + implementation(libs.coil.compose) + + // Hilt Navigation Compose + implementation(libs.hilt.navigation.compose) + + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) +} diff --git a/Week09/Yido/app/proguard-rules.pro b/Week09/Yido/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/Week09/Yido/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# 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/Week09/Yido/app/src/androidTest/java/com/example/yido/ExampleInstrumentedTest.kt b/Week09/Yido/app/src/androidTest/java/com/example/yido/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..254c793 --- /dev/null +++ b/Week09/Yido/app/src/androidTest/java/com/example/yido/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.example.yido + +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.yido", appContext.packageName) + } +} \ No newline at end of file diff --git a/Week09/Yido/app/src/main/AndroidManifest.xml b/Week09/Yido/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..47cdb30 --- /dev/null +++ b/Week09/Yido/app/src/main/AndroidManifest.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + diff --git a/Week09/Yido/app/src/main/java/com/example/yido/MyApplication.kt b/Week09/Yido/app/src/main/java/com/example/yido/MyApplication.kt new file mode 100644 index 0000000..556a9cb --- /dev/null +++ b/Week09/Yido/app/src/main/java/com/example/yido/MyApplication.kt @@ -0,0 +1,7 @@ +package com.example.yido + +import android.app.Application +import dagger.hilt.android.HiltAndroidApp + +@HiltAndroidApp +class MyApplication : Application() diff --git a/Week09/Yido/app/src/main/java/com/example/yido/data/local/ProductDataStore.kt b/Week09/Yido/app/src/main/java/com/example/yido/data/local/ProductDataStore.kt new file mode 100644 index 0000000..0e5448e --- /dev/null +++ b/Week09/Yido/app/src/main/java/com/example/yido/data/local/ProductDataStore.kt @@ -0,0 +1,81 @@ +package com.example.yido.data.local + +import android.content.Context +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.stringPreferencesKey +import androidx.datastore.preferences.preferencesDataStore +import com.example.yido.R +import com.example.yido.data.model.Product +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +val Context.dataStore: DataStore by preferencesDataStore(name = "product_store") + +object ProductDataStore { + + private val gson = Gson() + private val PRODUCTS_KEY = stringPreferencesKey("products_v2") + + val defaultProducts = listOf( + Product( + imageResId = R.drawable.img_shoe_1, + name = "Nike Everyday Plus Cushioned", + subtitle = "Training Ankle Socks (6 Pairs)", + colours = "5 Colours", + price = "US\$10" + ), + Product( + imageResId = R.drawable.img_shoe_3, + name = "Nike Elite Crew", + subtitle = "Basketball Socks", + colours = "7 Colours", + price = "US\$16" + ), + Product( + imageResId = R.drawable.img_shoe_5, + name = "Nike Air Force 1 '07", + subtitle = "Women's Shoes", + colours = "5 Colours", + price = "US\$115", + isWished = true, + isBestSeller = true + ), + Product( + imageResId = R.drawable.img_shoe_2, + name = "Nike Air Force 1 '07 Essentials", + subtitle = "Men's Shoes", + colours = "2 Colours", + price = "US\$115", + isWished = true, + isBestSeller = true + ), + Product( + imageResId = R.drawable.img_shoe_4, + name = "Air Jordan 1 Mid", + subtitle = "", + colours = "", + price = "US\$125" + ) + ) + + fun getProducts(context: Context): Flow> { + return context.dataStore.data.map { prefs -> + val json = prefs[PRODUCTS_KEY] + if (json.isNullOrEmpty()) { + emptyList() + } else { + gson.fromJson(json, object : TypeToken>() {}.type) + } + } + } + + suspend fun saveProducts(context: Context, products: List) { + context.dataStore.edit { prefs -> + prefs[PRODUCTS_KEY] = gson.toJson(products) + } + } +} diff --git a/Week09/Yido/app/src/main/java/com/example/yido/data/model/Product.kt b/Week09/Yido/app/src/main/java/com/example/yido/data/model/Product.kt new file mode 100644 index 0000000..a19c31c --- /dev/null +++ b/Week09/Yido/app/src/main/java/com/example/yido/data/model/Product.kt @@ -0,0 +1,12 @@ +package com.example.yido.data.model + +data class Product( + val imageResId: Int, + val name: String, + val subtitle: String, + val colours: String, + val price: String, + val isWished: Boolean = false, + val isBestSeller: Boolean = false, + val category: String = "์ „์ฒด" +) diff --git a/Week09/Yido/app/src/main/java/com/example/yido/data/model/UserData.kt b/Week09/Yido/app/src/main/java/com/example/yido/data/model/UserData.kt new file mode 100644 index 0000000..ae195a5 --- /dev/null +++ b/Week09/Yido/app/src/main/java/com/example/yido/data/model/UserData.kt @@ -0,0 +1,27 @@ +package com.example.yido.data.model + +import com.google.gson.annotations.SerializedName + +data class UserData( + val id: Int, + val email: String, + @SerializedName("first_name") + val firstName: String, + @SerializedName("last_name") + val lastName: String, + val avatar: String +) + +data class UserResponse( + val data: UserData +) + +data class UserListResponse( + val page: Int, + @SerializedName("per_page") + val perPage: Int, + val total: Int, + @SerializedName("total_pages") + val totalPages: Int, + val data: List +) diff --git a/Week09/Yido/app/src/main/java/com/example/yido/data/remote/ReqResService.kt b/Week09/Yido/app/src/main/java/com/example/yido/data/remote/ReqResService.kt new file mode 100644 index 0000000..7b32c66 --- /dev/null +++ b/Week09/Yido/app/src/main/java/com/example/yido/data/remote/ReqResService.kt @@ -0,0 +1,21 @@ +package com.example.yido.data.remote + +import com.example.yido.data.model.UserListResponse +import com.example.yido.data.model.UserResponse +import retrofit2.Response +import retrofit2.http.GET +import retrofit2.http.Path +import retrofit2.http.Query + +interface ReqResService { + + @GET("api/users/{id}") + suspend fun getUserProfile( + @Path("id") userId: Int + ): Response + + @GET("api/users") + suspend fun getUserList( + @Query("page") page: Int = 1 + ): Response +} diff --git a/Week09/Yido/app/src/main/java/com/example/yido/data/repository/ProductRepositoryImpl.kt b/Week09/Yido/app/src/main/java/com/example/yido/data/repository/ProductRepositoryImpl.kt new file mode 100644 index 0000000..ad51357 --- /dev/null +++ b/Week09/Yido/app/src/main/java/com/example/yido/data/repository/ProductRepositoryImpl.kt @@ -0,0 +1,28 @@ +package com.example.yido.data.repository + +import android.content.Context +import com.example.yido.data.local.ProductDataStore +import com.example.yido.data.model.Product +import com.example.yido.domain.repository.ProductRepository +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class ProductRepositoryImpl @Inject constructor( + @ApplicationContext private val context: Context +) : ProductRepository { + + override fun getProductsStream(): Flow> { + return ProductDataStore.getProducts(context) + } + + override suspend fun saveProducts(products: List) { + ProductDataStore.saveProducts(context, products) + } + + override fun getDefaultProducts(): List { + return ProductDataStore.defaultProducts + } +} diff --git a/Week09/Yido/app/src/main/java/com/example/yido/data/repository/UserRepositoryImpl.kt b/Week09/Yido/app/src/main/java/com/example/yido/data/repository/UserRepositoryImpl.kt new file mode 100644 index 0000000..0e8fdaa --- /dev/null +++ b/Week09/Yido/app/src/main/java/com/example/yido/data/repository/UserRepositoryImpl.kt @@ -0,0 +1,29 @@ +package com.example.yido.data.repository + +import com.example.yido.data.model.UserListResponse +import com.example.yido.data.model.UserResponse +import com.example.yido.data.remote.ReqResService +import com.example.yido.domain.repository.UserRepository +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import retrofit2.Response +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class UserRepositoryImpl @Inject constructor( + private val reqResService: ReqResService +) : UserRepository { + + override suspend fun getUserProfile(userId: Int): Response { + return withContext(Dispatchers.IO) { + reqResService.getUserProfile(userId) + } + } + + override suspend fun getUserList(page: Int): Response { + return withContext(Dispatchers.IO) { + reqResService.getUserList(page) + } + } +} diff --git a/Week09/Yido/app/src/main/java/com/example/yido/di/NetworkModule.kt b/Week09/Yido/app/src/main/java/com/example/yido/di/NetworkModule.kt new file mode 100644 index 0000000..c7d0918 --- /dev/null +++ b/Week09/Yido/app/src/main/java/com/example/yido/di/NetworkModule.kt @@ -0,0 +1,60 @@ +package com.example.yido.di + +import com.example.yido.data.remote.ReqResService +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import okhttp3.Interceptor +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import java.util.concurrent.TimeUnit +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object NetworkModule { + + private const val BASE_URL = "https://reqres.in/" + + @Provides + @Singleton + fun provideOkHttpClient(): OkHttpClient { + val logging = HttpLoggingInterceptor().apply { + level = HttpLoggingInterceptor.Level.BODY + } + + val authInterceptor = Interceptor { chain -> + val newRequest = chain.request().newBuilder() + .addHeader("x-api-key", "reqres_3cc917778886402cb49885a9215fb6ff") + .build() + chain.proceed(newRequest) + } + + return OkHttpClient.Builder() + .addInterceptor(logging) + .addInterceptor(authInterceptor) + .connectTimeout(30, TimeUnit.SECONDS) + .readTimeout(30, TimeUnit.SECONDS) + .writeTimeout(30, TimeUnit.SECONDS) + .build() + } + + @Provides + @Singleton + fun provideRetrofit(client: OkHttpClient): Retrofit { + return Retrofit.Builder() + .baseUrl(BASE_URL) + .client(client) + .addConverterFactory(GsonConverterFactory.create()) + .build() + } + + @Provides + @Singleton + fun provideReqResService(retrofit: Retrofit): ReqResService { + return retrofit.create(ReqResService::class.java) + } +} diff --git a/Week09/Yido/app/src/main/java/com/example/yido/di/RepositoryModule.kt b/Week09/Yido/app/src/main/java/com/example/yido/di/RepositoryModule.kt new file mode 100644 index 0000000..fcde2b9 --- /dev/null +++ b/Week09/Yido/app/src/main/java/com/example/yido/di/RepositoryModule.kt @@ -0,0 +1,24 @@ +package com.example.yido.di + +import com.example.yido.data.repository.ProductRepositoryImpl +import com.example.yido.data.repository.UserRepositoryImpl +import com.example.yido.domain.repository.ProductRepository +import com.example.yido.domain.repository.UserRepository +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +abstract class RepositoryModule { + + @Binds + @Singleton + abstract fun bindProductRepository(impl: ProductRepositoryImpl): ProductRepository + + @Binds + @Singleton + abstract fun bindUserRepository(impl: UserRepositoryImpl): UserRepository +} diff --git a/Week09/Yido/app/src/main/java/com/example/yido/domain/repository/ProductRepository.kt b/Week09/Yido/app/src/main/java/com/example/yido/domain/repository/ProductRepository.kt new file mode 100644 index 0000000..f6dae1e --- /dev/null +++ b/Week09/Yido/app/src/main/java/com/example/yido/domain/repository/ProductRepository.kt @@ -0,0 +1,10 @@ +package com.example.yido.domain.repository + +import com.example.yido.data.model.Product +import kotlinx.coroutines.flow.Flow + +interface ProductRepository { + fun getProductsStream(): Flow> + suspend fun saveProducts(products: List) + fun getDefaultProducts(): List +} diff --git a/Week09/Yido/app/src/main/java/com/example/yido/domain/repository/UserRepository.kt b/Week09/Yido/app/src/main/java/com/example/yido/domain/repository/UserRepository.kt new file mode 100644 index 0000000..c6f0f56 --- /dev/null +++ b/Week09/Yido/app/src/main/java/com/example/yido/domain/repository/UserRepository.kt @@ -0,0 +1,10 @@ +package com.example.yido.domain.repository + +import com.example.yido.data.model.UserListResponse +import com.example.yido.data.model.UserResponse +import retrofit2.Response + +interface UserRepository { + suspend fun getUserProfile(userId: Int): Response + suspend fun getUserList(page: Int = 1): Response +} diff --git a/Week09/Yido/app/src/main/java/com/example/yido/ui/cart/CartScreen.kt b/Week09/Yido/app/src/main/java/com/example/yido/ui/cart/CartScreen.kt new file mode 100644 index 0000000..d92df95 --- /dev/null +++ b/Week09/Yido/app/src/main/java/com/example/yido/ui/cart/CartScreen.kt @@ -0,0 +1,75 @@ +package com.example.yido.ui.cart + +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.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.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.BiasAlignment +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.yido.R + +@Composable +fun CartScreen(onNavigateToShop: () -> Unit) { + Box( + modifier = Modifier + .fillMaxSize() + .background(Color.White) + ) { + Column( + modifier = Modifier.align(BiasAlignment(0f, -0.24f)), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Icon( + painter = painterResource(R.drawable.ic_cart_empty), + contentDescription = "์žฅ๋ฐ”๊ตฌ๋‹ˆ", + tint = Color.Unspecified, + modifier = Modifier.size(60.dp) + ) + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = "์žฅ๋ฐ”๊ตฌ๋‹ˆ๊ฐ€ ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค.\n์ œํ’ˆ์„ ์ถ”๊ฐ€ํ•˜๋ฉด ์—ฌ๊ธฐ์— ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.", + fontSize = 14.sp, + color = Color.Black, + textAlign = TextAlign.Center, + lineHeight = 22.sp + ) + } + Button( + onClick = onNavigateToShop, + modifier = Modifier + .align(Alignment.BottomCenter) + .fillMaxWidth() + .padding(start = 24.dp, end = 24.dp, bottom = 24.dp) + .height(56.dp), + shape = RoundedCornerShape(50), + colors = ButtonDefaults.buttonColors( + containerColor = Color.Black, + contentColor = Color.White + ) + ) { + Text( + text = "์ฃผ๋ฌธํ•˜๊ธฐ", + fontSize = 16.sp, + fontWeight = FontWeight.Bold + ) + } + } +} diff --git a/Week09/Yido/app/src/main/java/com/example/yido/ui/home/HomeScreen.kt b/Week09/Yido/app/src/main/java/com/example/yido/ui/home/HomeScreen.kt new file mode 100644 index 0000000..d60b5f5 --- /dev/null +++ b/Week09/Yido/app/src/main/java/com/example/yido/ui/home/HomeScreen.kt @@ -0,0 +1,149 @@ +package com.example.yido.ui.home + +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.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +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.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.hilt.navigation.compose.hiltViewModel +import com.example.yido.R +import com.example.yido.data.model.Product + +@Composable +fun HomeScreen(viewModel: HomeViewModel = hiltViewModel()) { + val products by viewModel.products.collectAsState() + + Column( + modifier = Modifier + .fillMaxSize() + .background(Color.White) + .verticalScroll(rememberScrollState()) + .padding(top = 24.dp) + ) { + Text( + text = "Discover", + fontSize = 32.sp, + fontWeight = FontWeight.Bold, + color = Color.Black, + modifier = Modifier.padding(horizontal = 24.dp) + ) + Text( + text = "9์›” 4์ผ ๋ชฉ์š”์ผ", + fontSize = 14.sp, + color = Color(0xFF999999), + modifier = Modifier + .padding(horizontal = 24.dp) + .padding(top = 4.dp) + ) + Image( + painter = painterResource(R.drawable.img_nike_hero), + contentDescription = "Discover", + contentScale = ContentScale.Crop, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 24.dp) + .padding(top = 24.dp) + ) + Text( + text = "What's new", + fontSize = 13.sp, + color = Color(0xFF999999), + modifier = Modifier + .padding(horizontal = 24.dp) + .padding(top = 24.dp) + ) + Text( + text = "๋‚˜์ดํ‚ค ์ตœ์‹  ์ƒํ’ˆ", + fontSize = 20.sp, + fontWeight = FontWeight.Bold, + color = Color.Black, + modifier = Modifier + .padding(horizontal = 24.dp) + .padding(top = 4.dp) + ) + LazyRow( + contentPadding = PaddingValues(start = 24.dp, top = 16.dp, end = 16.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp) + ) { + items(products, key = { it.name }) { product -> + HomeProductItem(product) + } + } + Spacer(modifier = Modifier.height(200.dp)) + } +} + +@Composable +private fun HomeProductItem(product: Product) { + Column(modifier = Modifier.width(200.dp)) { + Image( + painter = painterResource(product.imageResId), + contentDescription = product.name, + contentScale = ContentScale.Crop, + modifier = Modifier.size(200.dp) + ) + Spacer(modifier = Modifier.height(8.dp)) + if (product.isBestSeller) { + Text( + text = "BestSeller", + fontSize = 12.sp, + fontWeight = FontWeight.Bold, + color = Color(0xFFE85D04) + ) + Spacer(modifier = Modifier.height(2.dp)) + } + Text( + text = product.name, + fontSize = 14.sp, + fontWeight = FontWeight.Bold, + color = Color.Black, + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) + if (product.subtitle.isNotEmpty()) { + Text( + text = product.subtitle, + fontSize = 13.sp, + color = Color(0xFF999999) + ) + } + if (product.colours.isNotEmpty()) { + Text( + text = product.colours, + fontSize = 13.sp, + color = Color(0xFF999999) + ) + } + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = product.price, + fontSize = 13.sp, + fontWeight = FontWeight.Bold, + color = Color.Black + ) + Spacer(modifier = Modifier.height(8.dp)) + } +} diff --git a/Week09/Yido/app/src/main/java/com/example/yido/ui/home/HomeViewModel.kt b/Week09/Yido/app/src/main/java/com/example/yido/ui/home/HomeViewModel.kt new file mode 100644 index 0000000..c1bac82 --- /dev/null +++ b/Week09/Yido/app/src/main/java/com/example/yido/ui/home/HomeViewModel.kt @@ -0,0 +1,37 @@ +package com.example.yido.ui.home + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.example.yido.data.model.Product +import com.example.yido.domain.repository.ProductRepository +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class HomeViewModel @Inject constructor( + private val productRepository: ProductRepository +) : ViewModel() { + + private val _products = MutableStateFlow>(emptyList()) + val products: StateFlow> = _products.asStateFlow() + + init { + loadProducts() + } + + private fun loadProducts() { + viewModelScope.launch { + productRepository.getProductsStream().collect { products -> + if (products.isEmpty()) { + _products.value = productRepository.getDefaultProducts() + } else { + _products.value = products + } + } + } + } +} diff --git a/Week09/Yido/app/src/main/java/com/example/yido/ui/main/MainActivity.kt b/Week09/Yido/app/src/main/java/com/example/yido/ui/main/MainActivity.kt new file mode 100644 index 0000000..20625fb --- /dev/null +++ b/Week09/Yido/app/src/main/java/com/example/yido/ui/main/MainActivity.kt @@ -0,0 +1,22 @@ +package com.example.yido.ui.main + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import androidx.compose.material3.MaterialTheme +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class MainActivity : ComponentActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + enableEdgeToEdge() + setContent { + MaterialTheme { + MainScreen() + } + } + } +} diff --git a/Week09/Yido/app/src/main/java/com/example/yido/ui/main/MainScreen.kt b/Week09/Yido/app/src/main/java/com/example/yido/ui/main/MainScreen.kt new file mode 100644 index 0000000..d104919 --- /dev/null +++ b/Week09/Yido/app/src/main/java/com/example/yido/ui/main/MainScreen.kt @@ -0,0 +1,151 @@ +package com.example.yido.ui.main + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +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.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.navigation.NavController +import androidx.navigation.NavDestination.Companion.hasRoute +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.yido.ui.cart.CartScreen +import com.example.yido.ui.home.HomeScreen +import com.example.yido.ui.navigation.AppDestination +import com.example.yido.ui.navigation.BottomNavItem +import com.example.yido.ui.navigation.bottomNavItems +import com.example.yido.ui.profile.ProfileScreen +import com.example.yido.ui.shop.ShopScreen +import com.example.yido.ui.wishlist.WishlistScreen + +@Composable +fun MainScreen() { + val navController = rememberNavController() + + Scaffold( + containerColor = Color.White, + bottomBar = { AppBottomBar(navController) } + ) { innerPadding -> + NavHost( + navController = navController, + startDestination = AppDestination.Home, + modifier = Modifier.padding(innerPadding) + ) { + composable { + HomeScreen() + } + composable { + ShopScreen() + } + composable { + WishlistScreen() + } + composable { + CartScreen( + onNavigateToShop = { + navController.navigate(AppDestination.Shop) { + popUpTo(navController.graph.findStartDestination().id) { + saveState = true + } + launchSingleTop = true + restoreState = true + } + } + ) + } + composable { + ProfileScreen() + } + } + } +} + +@Composable +private fun AppBottomBar(navController: NavController) { + val backStackEntry by navController.currentBackStackEntryAsState() + val currentDestination = backStackEntry?.destination + + Column { + HorizontalDivider(color = Color(0xFFEEEEEE), thickness = 1.dp) + Row( + modifier = Modifier + .fillMaxWidth() + .background(Color.White) + .navigationBarsPadding() + .padding(vertical = 8.dp), + horizontalArrangement = Arrangement.SpaceAround + ) { + bottomNavItems.forEach { item -> + val isSelected = currentDestination?.hasRoute(item.destination::class) == true + BottomNavItemView( + item = item, + isSelected = isSelected, + onClick = { + navController.navigate(item.destination) { + popUpTo(navController.graph.findStartDestination().id) { + saveState = true + } + launchSingleTop = true + restoreState = true + } + } + ) + } + } + } +} + +@Composable +private fun BottomNavItemView( + item: BottomNavItem, + isSelected: Boolean, + onClick: () -> Unit +) { + val activeColor = Color(0xFF000000) + val inactiveColor = Color(0xFF767676) + val tint = if (isSelected) activeColor else inactiveColor + + Column( + modifier = Modifier + .clickable(onClick = onClick) + .padding(horizontal = 8.dp, vertical = 4.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Icon( + painter = painterResource(item.iconRes), + contentDescription = item.label, + tint = tint, + modifier = Modifier.size(24.dp) + ) + Spacer(modifier = Modifier.height(2.dp)) + Text( + text = item.label, + fontSize = 10.sp, + color = tint, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } +} diff --git a/Week09/Yido/app/src/main/java/com/example/yido/ui/navigation/AppDestination.kt b/Week09/Yido/app/src/main/java/com/example/yido/ui/navigation/AppDestination.kt new file mode 100644 index 0000000..f95796e --- /dev/null +++ b/Week09/Yido/app/src/main/java/com/example/yido/ui/navigation/AppDestination.kt @@ -0,0 +1,27 @@ +package com.example.yido.ui.navigation + +import androidx.annotation.DrawableRes +import com.example.yido.R +import kotlinx.serialization.Serializable + +sealed interface AppDestination { + @Serializable data object Home : AppDestination + @Serializable data object Shop : AppDestination + @Serializable data object Wishlist : AppDestination + @Serializable data object Cart : AppDestination + @Serializable data object Profile : AppDestination +} + +data class BottomNavItem( + val destination: AppDestination, + val label: String, + @DrawableRes val iconRes: Int +) + +val bottomNavItems = listOf( + BottomNavItem(AppDestination.Home, "ํ™ˆ", R.drawable.ic_nav_home), + BottomNavItem(AppDestination.Shop, "๊ตฌ๋งคํ•˜๊ธฐ", R.drawable.ic_nav_shop), + BottomNavItem(AppDestination.Wishlist, "์œ„์‹œ๋ฆฌ์ŠคํŠธ", R.drawable.ic_nav_wishlist), + BottomNavItem(AppDestination.Cart, "์žฅ๋ฐ”๊ตฌ๋‹ˆ", R.drawable.ic_nav_cart), + BottomNavItem(AppDestination.Profile, "ํ”„๋กœํ•„", R.drawable.ic_nav_profile), +) diff --git a/Week09/Yido/app/src/main/java/com/example/yido/ui/profile/ProfileScreen.kt b/Week09/Yido/app/src/main/java/com/example/yido/ui/profile/ProfileScreen.kt new file mode 100644 index 0000000..220d42a --- /dev/null +++ b/Week09/Yido/app/src/main/java/com/example/yido/ui/profile/ProfileScreen.kt @@ -0,0 +1,266 @@ +package com.example.yido.ui.profile + +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.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.PagerState +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.Icon +import androidx.compose.material3.OutlinedButton +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.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.hilt.navigation.compose.hiltViewModel +import coil.compose.AsyncImage +import com.example.yido.R +import com.example.yido.data.model.UserData + +@Composable +fun ProfileScreen(viewModel: ProfileViewModel = hiltViewModel()) { + val uiState by viewModel.uiState.collectAsState() + + Column( + modifier = Modifier + .fillMaxSize() + .background(Color.White) + ) { + Column( + modifier = Modifier + .weight(1f) + .verticalScroll(rememberScrollState()) + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(top = 40.dp, bottom = 24.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + AsyncImage( + model = uiState.myProfile?.avatar, + contentDescription = "ํ”„๋กœํ•„ ์ด๋ฏธ์ง€", + modifier = Modifier + .size(80.dp) + .clip(CircleShape) + .background(Color(0xFFD9D9D9)) + ) + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = uiState.myProfile?.let { "${it.firstName} ${it.lastName}" } ?: "", + fontSize = 20.sp, + fontWeight = FontWeight.Bold, + color = Color.Black + ) + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = uiState.myProfile?.email ?: "", + fontSize = 14.sp, + color = Color(0xFF888888) + ) + Spacer(modifier = Modifier.height(16.dp)) + OutlinedButton( + onClick = {}, + shape = RoundedCornerShape(50), + contentPadding = PaddingValues(horizontal = 32.dp, vertical = 10.dp) + ) { + Text( + text = "ํ”„๋กœํ•„ ์ˆ˜์ •", + fontSize = 14.sp, + color = Color.Black + ) + } + } + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + ProfileMenuItem(R.drawable.ic_order, "์ฃผ๋ฌธ", Modifier.weight(1f)) + VerticalDivider() + ProfileMenuItem(R.drawable.ic_pass, "ํŒจ์Šค", Modifier.weight(1f)) + VerticalDivider() + ProfileMenuItem(R.drawable.ic_event, "์ด๋ฒคํŠธ", Modifier.weight(1f)) + VerticalDivider() + ProfileMenuItem(R.drawable.ic_settings, "์„ค์ •", Modifier.weight(1f)) + } + + Spacer( + modifier = Modifier + .fillMaxWidth() + .height(8.dp) + .background(Color(0xFFF5F5F5)) + ) + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(20.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Column(modifier = Modifier.weight(1f)) { + Text( + text = "๋‚˜์ดํ‚ค ๋ฉค๋ฒ„ ํ˜œํƒ", + fontSize = 16.sp, + fontWeight = FontWeight.Bold, + color = Color.Black + ) + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = "0๊ฐœ ์‚ฌ์šฉ ๊ฐ€๋Šฅ", + fontSize = 13.sp, + color = Color(0xFF888888) + ) + } + Icon( + painter = painterResource(R.drawable.ic_chevron_right), + contentDescription = null, + tint = Color.Black, + modifier = Modifier.size(24.dp) + ) + } + + Spacer( + modifier = Modifier + .fillMaxWidth() + .height(8.dp) + .background(Color(0xFFF5F5F5)) + ) + + Row( + modifier = Modifier + .fillMaxWidth() + .background(Color.White) + .padding(start = 20.dp, end = 20.dp, top = 20.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = if (uiState.followingList.isNotEmpty()) "ํŒ”๋กœ์ž‰ ${uiState.followingList.size}" else "", + fontSize = 16.sp, + fontWeight = FontWeight.Bold, + color = Color.Black, + modifier = Modifier.weight(1f) + ) + Text( + text = "ํŽธ์ง‘", + fontSize = 14.sp, + color = Color(0xFF888888) + ) + } + + // HorizontalPager๋กœ ํŒ”๋กœ์ž‰ ๋ฆฌ์ŠคํŠธ ๊ตฌํ˜„ + if (uiState.followingList.isNotEmpty()) { + val pagerState = rememberPagerState( + pageCount = { uiState.followingList.size } + ) + + androidx.compose.foundation.pager.HorizontalPager( + state = pagerState, + contentPadding = PaddingValues(horizontal = 16.dp), + pageSize = androidx.compose.foundation.pager.PageSize.Fixed(96.dp), + pageSpacing = 16.dp, + verticalAlignment = Alignment.Top, + modifier = Modifier + .fillMaxWidth() + .padding(top = 16.dp, bottom = 24.dp) + ) { page -> + FollowingUserItem(uiState.followingList[page]) + } + } + } + + Text( + text = "ํšŒ์› ๊ฐ€์ž…์ผ: 2025๋…„ 9์›”", + fontSize = 12.sp, + color = Color(0xFF888888), + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .background(Color(0xFFF5F5F5)) + .padding(vertical = 12.dp) + ) + } +} + + + +@Composable +private fun ProfileMenuItem(iconRes: Int, label: String, modifier: Modifier = Modifier) { + Column( + modifier = modifier, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Icon( + painter = painterResource(iconRes), + contentDescription = label, + tint = Color.Unspecified, + modifier = Modifier.size(28.dp) + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = label, + fontSize = 12.sp, + fontWeight = FontWeight.Bold, + color = Color(0xFF333333) + ) + } +} + +@Composable +private fun VerticalDivider() { + Box( + modifier = Modifier + .width(1.dp) + .height(32.dp) + .background(Color(0xFFEEEEEE)) + ) +} + +@Composable +private fun FollowingUserItem(user: UserData) { + Column( + modifier = Modifier.width(96.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + AsyncImage( + model = user.avatar, + contentDescription = "${user.firstName} ${user.lastName}", + modifier = Modifier + .size(72.dp) + .clip(CircleShape) + .background(Color(0xFFD9D9D9)) + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = "${user.firstName} ${user.lastName}", + fontSize = 14.sp, + color = Color.Black, + textAlign = TextAlign.Center + ) + } +} diff --git a/Week09/Yido/app/src/main/java/com/example/yido/ui/profile/ProfileViewModel.kt b/Week09/Yido/app/src/main/java/com/example/yido/ui/profile/ProfileViewModel.kt new file mode 100644 index 0000000..ab0185e --- /dev/null +++ b/Week09/Yido/app/src/main/java/com/example/yido/ui/profile/ProfileViewModel.kt @@ -0,0 +1,80 @@ +package com.example.yido.ui.profile + +import android.util.Log +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.example.yido.data.model.UserData +import com.example.yido.domain.repository.UserRepository +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +data class ProfileUiState( + val myProfile: UserData? = null, + val followingList: List = emptyList(), + val errorMessage: String? = null +) + +@HiltViewModel +class ProfileViewModel @Inject constructor( + private val userRepository: UserRepository +) : ViewModel() { + + private val _uiState = MutableStateFlow(ProfileUiState()) + val uiState: StateFlow = _uiState.asStateFlow() + + private val myUserId = 1 + + init { + fetchMyProfile() + fetchFollowingList() + } + + private fun fetchMyProfile() { + viewModelScope.launch { + try { + val response = userRepository.getUserProfile(myUserId) + if (response.isSuccessful) { + val user = response.body()?.data + _uiState.value = _uiState.value.copy(myProfile = user) + } else { + Log.e(TAG, "API fail: ${response.code()}") + _uiState.value = _uiState.value.copy( + errorMessage = "Failed to load profile." + ) + } + } catch (e: Exception) { + Log.e(TAG, "Network error: ${e.message}") + _uiState.value = _uiState.value.copy( + errorMessage = "Network error occurred." + ) + } + } + } + + private fun fetchFollowingList() { + viewModelScope.launch { + try { + val response = userRepository.getUserList(1) + if (response.isSuccessful) { + val body = response.body() + if (body != null) { + val filtered = body.data.filter { it.id != myUserId } + _uiState.value = _uiState.value.copy(followingList = filtered) + } + } else { + Log.e(TAG, "API fail: ${response.code()}") + } + } catch (e: Exception) { + Log.e(TAG, "Network error: ${e.message}") + } + } + } + + companion object { + private const val TAG = "ProfileViewModel" + } +} diff --git a/Week09/Yido/app/src/main/java/com/example/yido/ui/shop/ShopScreen.kt b/Week09/Yido/app/src/main/java/com/example/yido/ui/shop/ShopScreen.kt new file mode 100644 index 0000000..0f85564 --- /dev/null +++ b/Week09/Yido/app/src/main/java/com/example/yido/ui/shop/ShopScreen.kt @@ -0,0 +1,183 @@ +package com.example.yido.ui.shop + +import androidx.compose.foundation.Image +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.Spacer +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.IntrinsicSize +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.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.Icon +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.res.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.hilt.navigation.compose.hiltViewModel +import com.example.yido.R +import com.example.yido.data.model.Product + +@Composable +fun ShopScreen(viewModel: ShopViewModel = hiltViewModel()) { + val uiState by viewModel.uiState.collectAsState() + + Column( + modifier = Modifier + .fillMaxSize() + .background(Color.White) + ) { + ShopTabRow( + currentTabIndex = uiState.currentTabIndex, + onTabSelected = viewModel::selectTab + ) + LazyVerticalGrid( + columns = GridCells.Fixed(2), + contentPadding = PaddingValues(start = 12.dp, end = 12.dp, top = 8.dp, bottom = 16.dp), + modifier = Modifier.fillMaxSize() + ) { + items(uiState.filteredProducts, key = { it.name }) { product -> + ShopProductItem( + product = product, + onWishClick = { viewModel.toggleWish(product) } + ) + } + } + } +} + +@Composable +private fun ShopTabRow(currentTabIndex: Int, onTabSelected: (Int) -> Unit) { + val tabs = listOf("์ „์ฒด", "Tops & T-Shirts", "sale") + val activeColor = Color(0xFF000000) + val inactiveColor = Color(0xFF999999) + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(start = 24.dp, end = 24.dp, top = 24.dp, bottom = 12.dp), + horizontalArrangement = Arrangement.spacedBy(24.dp) + ) { + tabs.forEachIndexed { index, label -> + val isSelected = currentTabIndex == index + Column( + modifier = Modifier + .clickable { onTabSelected(index) } + .width(IntrinsicSize.Max) + ) { + Text( + text = label, + fontSize = 16.sp, + fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal, + color = if (isSelected) activeColor else inactiveColor, + maxLines = 1 + ) + Spacer(modifier = Modifier.height(8.dp)) + Box( + modifier = Modifier + .fillMaxWidth() + .height(2.dp) + .background(if (isSelected) activeColor else Color.Transparent) + ) + } + } + } +} + +@Composable +private fun ShopProductItem(product: Product, onWishClick: () -> Unit) { + Column(modifier = Modifier.padding(8.dp)) { + Box(modifier = Modifier.fillMaxWidth()) { + Image( + painter = painterResource(product.imageResId), + contentDescription = product.name, + contentScale = ContentScale.Crop, + modifier = Modifier + .fillMaxWidth() + .aspectRatio(1f) + ) + Box( + modifier = Modifier + .align(Alignment.TopEnd) + .padding(top = 8.dp, end = 8.dp) + .size(36.dp) + .clip(CircleShape) + .background(Color.White) + .clickable(onClick = onWishClick), + contentAlignment = Alignment.Center + ) { + Icon( + painter = painterResource( + if (product.isWished) R.drawable.ic_heart_filled_red + else R.drawable.ic_heart_outline + ), + contentDescription = "์œ„์‹œ", + tint = Color.Unspecified, + modifier = Modifier.size(20.dp) + ) + } + } + Spacer(modifier = Modifier.height(12.dp)) + if (product.isBestSeller) { + Text( + text = "BestSeller", + fontSize = 13.sp, + fontWeight = FontWeight.Bold, + color = Color(0xFFE85D04) + ) + Spacer(modifier = Modifier.height(2.dp)) + } + Text( + text = product.name, + fontSize = 16.sp, + fontWeight = FontWeight.Bold, + color = Color.Black, + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) + if (product.subtitle.isNotEmpty()) { + Text( + text = product.subtitle, + fontSize = 13.sp, + color = Color(0xFF999999) + ) + } + if (product.colours.isNotEmpty()) { + Text( + text = product.colours, + fontSize = 13.sp, + color = Color(0xFF999999) + ) + } + Spacer(modifier = Modifier.height(6.dp)) + Text( + text = product.price, + fontSize = 16.sp, + fontWeight = FontWeight.Bold, + color = Color.Black + ) + Spacer(modifier = Modifier.height(12.dp)) + } +} diff --git a/Week09/Yido/app/src/main/java/com/example/yido/ui/shop/ShopViewModel.kt b/Week09/Yido/app/src/main/java/com/example/yido/ui/shop/ShopViewModel.kt new file mode 100644 index 0000000..979d6b9 --- /dev/null +++ b/Week09/Yido/app/src/main/java/com/example/yido/ui/shop/ShopViewModel.kt @@ -0,0 +1,68 @@ +package com.example.yido.ui.shop + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.example.yido.data.model.Product +import com.example.yido.domain.repository.ProductRepository +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +data class ShopUiState( + val allProducts: List = emptyList(), + val filteredProducts: List = emptyList(), + val currentTabIndex: Int = 0 +) + +@HiltViewModel +class ShopViewModel @Inject constructor( + private val productRepository: ProductRepository +) : ViewModel() { + + private val _uiState = MutableStateFlow(ShopUiState()) + val uiState: StateFlow = _uiState.asStateFlow() + + init { + loadProducts() + } + + private fun loadProducts() { + viewModelScope.launch { + productRepository.getProductsStream().collect { products -> + if (products.isEmpty()) { + productRepository.saveProducts(productRepository.getDefaultProducts()) + } else { + _uiState.value = _uiState.value.copy(allProducts = products) + applyFilter(_uiState.value.currentTabIndex) + } + } + } + } + + fun toggleWish(product: Product) { + viewModelScope.launch { + val updated = _uiState.value.allProducts.map { + if (it.name == product.name) it.copy(isWished = !it.isWished) else it + } + productRepository.saveProducts(updated) + } + } + + fun selectTab(index: Int) { + _uiState.value = _uiState.value.copy(currentTabIndex = index) + applyFilter(index) + } + + private fun applyFilter(index: Int) { + val all = _uiState.value.allProducts + val filtered = when (index) { + 1 -> all.filter { it.category == "Tops & T-Shirts" } + 2 -> all.filter { it.category == "sale" } + else -> all + } + _uiState.value = _uiState.value.copy(filteredProducts = filtered) + } +} diff --git a/Week09/Yido/app/src/main/java/com/example/yido/ui/wishlist/WishlistScreen.kt b/Week09/Yido/app/src/main/java/com/example/yido/ui/wishlist/WishlistScreen.kt new file mode 100644 index 0000000..26cd527 --- /dev/null +++ b/Week09/Yido/app/src/main/java/com/example/yido/ui/wishlist/WishlistScreen.kt @@ -0,0 +1,113 @@ +package com.example.yido.ui.wishlist + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +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.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.collectAsState +import androidx.compose.runtime.getValue +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.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.hilt.navigation.compose.hiltViewModel +import com.example.yido.data.model.Product + +@Composable +fun WishlistScreen(viewModel: WishlistViewModel = hiltViewModel()) { + val wishProducts by viewModel.wishProducts.collectAsState() + + Column( + modifier = Modifier + .fillMaxSize() + .background(Color.White) + ) { + Text( + text = "์œ„์‹œ๋ฆฌ์ŠคํŠธ", + fontSize = 28.sp, + fontWeight = FontWeight.Bold, + color = Color.Black, + modifier = Modifier + .padding(horizontal = 24.dp) + .padding(top = 24.dp) + ) + LazyVerticalGrid( + columns = GridCells.Fixed(2), + contentPadding = PaddingValues(start = 8.dp, end = 8.dp, top = 8.dp, bottom = 16.dp), + modifier = Modifier.fillMaxSize() + ) { + items(wishProducts, key = { it.name }) { product -> + WishlistProductItem(product) + } + } + } +} + +@Composable +private fun WishlistProductItem(product: Product) { + Column(modifier = Modifier.padding(8.dp)) { + Image( + painter = painterResource(product.imageResId), + contentDescription = product.name, + contentScale = ContentScale.Crop, + modifier = Modifier + .fillMaxWidth() + .aspectRatio(1f) + ) + Spacer(modifier = Modifier.height(8.dp)) + if (product.isBestSeller) { + Text( + text = "BestSeller", + fontSize = 12.sp, + fontWeight = FontWeight.Bold, + color = Color(0xFFE85D04) + ) + Spacer(modifier = Modifier.height(2.dp)) + } + Text( + text = product.name, + fontSize = 14.sp, + fontWeight = FontWeight.Bold, + color = Color.Black, + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) + if (product.subtitle.isNotEmpty()) { + Text( + text = product.subtitle, + fontSize = 13.sp, + color = Color(0xFF999999) + ) + } + if (product.colours.isNotEmpty()) { + Text( + text = product.colours, + fontSize = 13.sp, + color = Color(0xFF999999) + ) + } + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = product.price, + fontSize = 14.sp, + fontWeight = FontWeight.Bold, + color = Color.Black + ) + Spacer(modifier = Modifier.height(8.dp)) + } +} diff --git a/Week09/Yido/app/src/main/java/com/example/yido/ui/wishlist/WishlistViewModel.kt b/Week09/Yido/app/src/main/java/com/example/yido/ui/wishlist/WishlistViewModel.kt new file mode 100644 index 0000000..d5e82df --- /dev/null +++ b/Week09/Yido/app/src/main/java/com/example/yido/ui/wishlist/WishlistViewModel.kt @@ -0,0 +1,33 @@ +package com.example.yido.ui.wishlist + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.example.yido.data.model.Product +import com.example.yido.domain.repository.ProductRepository +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class WishlistViewModel @Inject constructor( + private val productRepository: ProductRepository +) : ViewModel() { + + private val _wishProducts = MutableStateFlow>(emptyList()) + val wishProducts: StateFlow> = _wishProducts.asStateFlow() + + init { + loadWishProducts() + } + + private fun loadWishProducts() { + viewModelScope.launch { + productRepository.getProductsStream().collect { products -> + _wishProducts.value = products.filter { it.isWished } + } + } + } +} diff --git a/Week09/Yido/app/src/main/res/color/color_bottom_nav.xml b/Week09/Yido/app/src/main/res/color/color_bottom_nav.xml new file mode 100644 index 0000000..d9a615e --- /dev/null +++ b/Week09/Yido/app/src/main/res/color/color_bottom_nav.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/Week09/Yido/app/src/main/res/drawable/bg_btn_outline_round.xml b/Week09/Yido/app/src/main/res/drawable/bg_btn_outline_round.xml new file mode 100644 index 0000000..c0d5f8a --- /dev/null +++ b/Week09/Yido/app/src/main/res/drawable/bg_btn_outline_round.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/Week09/Yido/app/src/main/res/drawable/bg_circle_white.xml b/Week09/Yido/app/src/main/res/drawable/bg_circle_white.xml new file mode 100644 index 0000000..9f3c01d --- /dev/null +++ b/Week09/Yido/app/src/main/res/drawable/bg_circle_white.xml @@ -0,0 +1,5 @@ + + + + diff --git a/Week09/Yido/app/src/main/res/drawable/ic_arrow_back.xml b/Week09/Yido/app/src/main/res/drawable/ic_arrow_back.xml new file mode 100644 index 0000000..d2d84e6 --- /dev/null +++ b/Week09/Yido/app/src/main/res/drawable/ic_arrow_back.xml @@ -0,0 +1,10 @@ + + + diff --git a/Week09/Yido/app/src/main/res/drawable/ic_cart_empty.xml b/Week09/Yido/app/src/main/res/drawable/ic_cart_empty.xml new file mode 100644 index 0000000..398fa4b --- /dev/null +++ b/Week09/Yido/app/src/main/res/drawable/ic_cart_empty.xml @@ -0,0 +1,22 @@ + + + + + diff --git a/Week09/Yido/app/src/main/res/drawable/ic_chevron_right.xml b/Week09/Yido/app/src/main/res/drawable/ic_chevron_right.xml new file mode 100644 index 0000000..9193cb0 --- /dev/null +++ b/Week09/Yido/app/src/main/res/drawable/ic_chevron_right.xml @@ -0,0 +1,9 @@ + + + diff --git a/Week09/Yido/app/src/main/res/drawable/ic_event.xml b/Week09/Yido/app/src/main/res/drawable/ic_event.xml new file mode 100644 index 0000000..b1cfa9d --- /dev/null +++ b/Week09/Yido/app/src/main/res/drawable/ic_event.xml @@ -0,0 +1,9 @@ + + + diff --git a/Week09/Yido/app/src/main/res/drawable/ic_heart_filled.xml b/Week09/Yido/app/src/main/res/drawable/ic_heart_filled.xml new file mode 100644 index 0000000..66c398c --- /dev/null +++ b/Week09/Yido/app/src/main/res/drawable/ic_heart_filled.xml @@ -0,0 +1,10 @@ + + + + diff --git a/Week09/Yido/app/src/main/res/drawable/ic_heart_filled_red.xml b/Week09/Yido/app/src/main/res/drawable/ic_heart_filled_red.xml new file mode 100644 index 0000000..7d99d1a --- /dev/null +++ b/Week09/Yido/app/src/main/res/drawable/ic_heart_filled_red.xml @@ -0,0 +1,9 @@ + + + diff --git a/Week09/Yido/app/src/main/res/drawable/ic_heart_outline.xml b/Week09/Yido/app/src/main/res/drawable/ic_heart_outline.xml new file mode 100644 index 0000000..6205712 --- /dev/null +++ b/Week09/Yido/app/src/main/res/drawable/ic_heart_outline.xml @@ -0,0 +1,12 @@ + + + + diff --git a/Week09/Yido/app/src/main/res/drawable/ic_launcher_background.xml b/Week09/Yido/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/Week09/Yido/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Week09/Yido/app/src/main/res/drawable/ic_launcher_foreground.xml b/Week09/Yido/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/Week09/Yido/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/Week09/Yido/app/src/main/res/drawable/ic_nav_cart.xml b/Week09/Yido/app/src/main/res/drawable/ic_nav_cart.xml new file mode 100644 index 0000000..7403168 --- /dev/null +++ b/Week09/Yido/app/src/main/res/drawable/ic_nav_cart.xml @@ -0,0 +1,17 @@ + + + + diff --git a/Week09/Yido/app/src/main/res/drawable/ic_nav_home.xml b/Week09/Yido/app/src/main/res/drawable/ic_nav_home.xml new file mode 100644 index 0000000..2426478 --- /dev/null +++ b/Week09/Yido/app/src/main/res/drawable/ic_nav_home.xml @@ -0,0 +1,13 @@ + + + diff --git a/Week09/Yido/app/src/main/res/drawable/ic_nav_profile.xml b/Week09/Yido/app/src/main/res/drawable/ic_nav_profile.xml new file mode 100644 index 0000000..d778944 --- /dev/null +++ b/Week09/Yido/app/src/main/res/drawable/ic_nav_profile.xml @@ -0,0 +1,19 @@ + + + + diff --git a/Week09/Yido/app/src/main/res/drawable/ic_nav_shop.xml b/Week09/Yido/app/src/main/res/drawable/ic_nav_shop.xml new file mode 100644 index 0000000..4d2398a --- /dev/null +++ b/Week09/Yido/app/src/main/res/drawable/ic_nav_shop.xml @@ -0,0 +1,34 @@ + + + + + + diff --git a/Week09/Yido/app/src/main/res/drawable/ic_nav_wishlist.xml b/Week09/Yido/app/src/main/res/drawable/ic_nav_wishlist.xml new file mode 100644 index 0000000..2a9c23e --- /dev/null +++ b/Week09/Yido/app/src/main/res/drawable/ic_nav_wishlist.xml @@ -0,0 +1,13 @@ + + + diff --git a/Week09/Yido/app/src/main/res/drawable/ic_order.xml b/Week09/Yido/app/src/main/res/drawable/ic_order.xml new file mode 100644 index 0000000..e785308 --- /dev/null +++ b/Week09/Yido/app/src/main/res/drawable/ic_order.xml @@ -0,0 +1,9 @@ + + + diff --git a/Week09/Yido/app/src/main/res/drawable/ic_pass.xml b/Week09/Yido/app/src/main/res/drawable/ic_pass.xml new file mode 100644 index 0000000..3d370fd --- /dev/null +++ b/Week09/Yido/app/src/main/res/drawable/ic_pass.xml @@ -0,0 +1,9 @@ + + + diff --git a/Week09/Yido/app/src/main/res/drawable/ic_search.xml b/Week09/Yido/app/src/main/res/drawable/ic_search.xml new file mode 100644 index 0000000..edad596 --- /dev/null +++ b/Week09/Yido/app/src/main/res/drawable/ic_search.xml @@ -0,0 +1,11 @@ + + + + diff --git a/Week09/Yido/app/src/main/res/drawable/ic_settings.xml b/Week09/Yido/app/src/main/res/drawable/ic_settings.xml new file mode 100644 index 0000000..7fba4bb --- /dev/null +++ b/Week09/Yido/app/src/main/res/drawable/ic_settings.xml @@ -0,0 +1,9 @@ + + + diff --git a/Week09/Yido/app/src/main/res/drawable/img_nike_hero.png b/Week09/Yido/app/src/main/res/drawable/img_nike_hero.png new file mode 100644 index 0000000..f42aa9d Binary files /dev/null and b/Week09/Yido/app/src/main/res/drawable/img_nike_hero.png differ diff --git a/Week09/Yido/app/src/main/res/drawable/img_product_placeholder.xml b/Week09/Yido/app/src/main/res/drawable/img_product_placeholder.xml new file mode 100644 index 0000000..c2dc42b --- /dev/null +++ b/Week09/Yido/app/src/main/res/drawable/img_product_placeholder.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/Week09/Yido/app/src/main/res/drawable/img_shoe_1.png b/Week09/Yido/app/src/main/res/drawable/img_shoe_1.png new file mode 100644 index 0000000..18a863f Binary files /dev/null and b/Week09/Yido/app/src/main/res/drawable/img_shoe_1.png differ diff --git a/Week09/Yido/app/src/main/res/drawable/img_shoe_2.png b/Week09/Yido/app/src/main/res/drawable/img_shoe_2.png new file mode 100644 index 0000000..286ad63 Binary files /dev/null and b/Week09/Yido/app/src/main/res/drawable/img_shoe_2.png differ diff --git a/Week09/Yido/app/src/main/res/drawable/img_shoe_3.png b/Week09/Yido/app/src/main/res/drawable/img_shoe_3.png new file mode 100644 index 0000000..cc5b915 Binary files /dev/null and b/Week09/Yido/app/src/main/res/drawable/img_shoe_3.png differ diff --git a/Week09/Yido/app/src/main/res/drawable/img_shoe_4.png b/Week09/Yido/app/src/main/res/drawable/img_shoe_4.png new file mode 100644 index 0000000..22db145 Binary files /dev/null and b/Week09/Yido/app/src/main/res/drawable/img_shoe_4.png differ diff --git a/Week09/Yido/app/src/main/res/drawable/img_shoe_5.png b/Week09/Yido/app/src/main/res/drawable/img_shoe_5.png new file mode 100644 index 0000000..4328d6e Binary files /dev/null and b/Week09/Yido/app/src/main/res/drawable/img_shoe_5.png differ diff --git a/Week09/Yido/app/src/main/res/drawable/shape_btn_pill_black.xml b/Week09/Yido/app/src/main/res/drawable/shape_btn_pill_black.xml new file mode 100644 index 0000000..0949add --- /dev/null +++ b/Week09/Yido/app/src/main/res/drawable/shape_btn_pill_black.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/Week09/Yido/app/src/main/res/drawable/shape_btn_pill_black_ripple.xml b/Week09/Yido/app/src/main/res/drawable/shape_btn_pill_black_ripple.xml new file mode 100644 index 0000000..bb09a21 --- /dev/null +++ b/Week09/Yido/app/src/main/res/drawable/shape_btn_pill_black_ripple.xml @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/Week09/Yido/app/src/main/res/menu/bottom_nav_menu.xml b/Week09/Yido/app/src/main/res/menu/bottom_nav_menu.xml new file mode 100644 index 0000000..be3ed4a --- /dev/null +++ b/Week09/Yido/app/src/main/res/menu/bottom_nav_menu.xml @@ -0,0 +1,23 @@ + + + + + + + + diff --git a/Week09/Yido/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/Week09/Yido/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/Week09/Yido/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Week09/Yido/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/Week09/Yido/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/Week09/Yido/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Week09/Yido/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/Week09/Yido/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..c209e78 Binary files /dev/null and b/Week09/Yido/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/Week09/Yido/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/Week09/Yido/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..b2dfe3d Binary files /dev/null and b/Week09/Yido/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/Week09/Yido/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/Week09/Yido/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..4f0f1d6 Binary files /dev/null and b/Week09/Yido/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/Week09/Yido/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/Week09/Yido/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..62b611d Binary files /dev/null and b/Week09/Yido/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/Week09/Yido/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/Week09/Yido/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..948a307 Binary files /dev/null and b/Week09/Yido/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/Week09/Yido/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/Week09/Yido/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..1b9a695 Binary files /dev/null and b/Week09/Yido/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/Week09/Yido/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/Week09/Yido/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..28d4b77 Binary files /dev/null and b/Week09/Yido/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/Week09/Yido/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/Week09/Yido/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9287f50 Binary files /dev/null and b/Week09/Yido/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/Week09/Yido/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/Week09/Yido/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..aa7d642 Binary files /dev/null and b/Week09/Yido/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/Week09/Yido/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/Week09/Yido/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9126ae3 Binary files /dev/null and b/Week09/Yido/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/Week09/Yido/app/src/main/res/values-night/themes.xml b/Week09/Yido/app/src/main/res/values-night/themes.xml new file mode 100644 index 0000000..5edeae4 --- /dev/null +++ b/Week09/Yido/app/src/main/res/values-night/themes.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/Week09/Yido/app/src/main/res/values/colors.xml b/Week09/Yido/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..fa8289d --- /dev/null +++ b/Week09/Yido/app/src/main/res/values/colors.xml @@ -0,0 +1,11 @@ + + + #FF000000 + #FFFFFFFF + #767676 + #999999 + #000000 + #000000 + #999999 + #E85D04 + diff --git a/Week09/Yido/app/src/main/res/values/strings.xml b/Week09/Yido/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..45c0790 --- /dev/null +++ b/Week09/Yido/app/src/main/res/values/strings.xml @@ -0,0 +1,44 @@ + + Yido + + + ํ™ˆ + ๊ตฌ๋งคํ•˜๊ธฐ + ์œ„์‹œ๋ฆฌ์ŠคํŠธ + ์žฅ๋ฐ”๊ตฌ๋‹ˆ + ํ”„๋กœํ•„ + + + Discover + 9์›” 4์ผ ๋ชฉ์š”์ผ + What\'s new + ๋‚˜์ดํ‚ค ์ตœ์‹  ์ƒํ’ˆ + + + ์ „์ฒด + Tops & T-Shirts + sale + + + ์œ„์‹œ๋ฆฌ์ŠคํŠธ + + + ์žฅ๋ฐ”๊ตฌ๋‹ˆ๊ฐ€ ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค.\n์ œํ’ˆ์„ ์ถ”๊ฐ€ํ•˜๋ฉด ์—ฌ๊ธฐ์— ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. + ์ฃผ๋ฌธํ•˜๊ธฐ + + + ์ƒํ’ˆ ์ด๋ฏธ์ง€ + + + ํŒ”๋กœ์ž‰ %d + ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ + ํ”„๋กœํ•„ ์ˆ˜์ • + ์ฃผ๋ฌธ + ํŒจ์Šค + ์ด๋ฒคํŠธ + ์„ค์ • + ๋‚˜์ดํ‚ค ๋ฉค๋ฒ„ ํ˜œํƒ + 0๊ฐœ ์‚ฌ์šฉ ๊ฐ€๋Šฅ + ํŽธ์ง‘ + ํšŒ์› ๊ฐ€์ž…์ผ: 2025๋…„ 9์›” + diff --git a/Week09/Yido/app/src/main/res/values/themes.xml b/Week09/Yido/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..347d6ad --- /dev/null +++ b/Week09/Yido/app/src/main/res/values/themes.xml @@ -0,0 +1,9 @@ + + + + +