diff --git a/README.md b/README.md
deleted file mode 100644
index 2c7613b..0000000
--- a/README.md
+++ /dev/null
@@ -1,94 +0,0 @@
-# 10th_Android
-
-
-
팀원 구성
-
-
-### Git 작업 순서
-
-본인의 브랜치에서만 작업하고, `main` 브랜치는 직접 수정하지 않습니다.
-
-#### 1. 현재 브랜치 확인
-```sh
-git branch
-```
-
-#### 2. 변경 사항 확인
-```sh
-git status
-```
-
-#### 3. 변경된 파일 추가
-```sh
-git add .
-```
-
-#### 4. 커밋 메시지 작성
-```sh
-git commit -m "Commit Message"
-```
-
-#### 5. 원격 저장소에 본인 브랜치로 푸시
-```sh
-git push origin <브랜치명>
-```
-
-### Commit Convention
-
-커밋 메시지는 `타입: n주차 미션 설명`의 형식을 갖추어 작성합니다.
-
-| 타입 | 설명 |
-|-----------|--------------------------------|
-| feat | 새로운 기능 추가 |
-| fix | 버그 수정 |
-| refactor | 코드 리팩토링 |
-| docs | 문서 수정 (README 등) |
-| style | 코드 스타일 변경 (세미콜론 추가 등)|
-| chore | 빌드 및 패키지 설정 변경 |
-| test | 테스트 코드 추가 |
-
-#### Commit Example
-```sh
-git commit -m "feat: 1주차 미션 화면 전환 기능"
-git commit -m "fix: 5주차 미션 API 응답 오류 수정"
-```
-
-### PR Convention
-
-- Pull Request(PR)은 미션 별로 생성합니다.
-- PR 제목은 `n주차 미션` 형식으로 작성합니다.
-- 파트장이 승인한 후, main 브랜치로 Merge 합니다.
diff --git a/Week1/app/build.gradle.kts b/Week1/app/build.gradle.kts
deleted file mode 100644
index cfcfc78..0000000
--- a/Week1/app/build.gradle.kts
+++ /dev/null
@@ -1,47 +0,0 @@
-plugins {
- alias(libs.plugins.android.application)
-}
-
-android {
- namespace = "com.example.week1"
- compileSdk {
- version = release(36) {
- minorApiLevel = 1
- }
- }
-
- defaultConfig {
- applicationId = "com.example.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/Week1/app/src/main/java/com/example/week1/MainActivity.kt b/Week1/app/src/main/java/com/example/week1/MainActivity.kt
deleted file mode 100644
index 865a367..0000000
--- a/Week1/app/src/main/java/com/example/week1/MainActivity.kt
+++ /dev/null
@@ -1,68 +0,0 @@
-package com.example.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.widget.TextView
-import android.widget.ImageView
-import android.graphics.Color
-
-class MainActivity : AppCompatActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- enableEdgeToEdge()
- setContentView(R.layout.activity_main)
- val text1 : TextView = findViewById(R.id.text1)
- val text2 : TextView = findViewById(R.id.text2)
- val text3 : TextView = findViewById(R.id.text3)
- val text4 : TextView = findViewById(R.id.text4)
- val text5 : TextView = findViewById(R.id.text5)
- val good : ImageView = findViewById(R.id.good)
- val happy : ImageView = findViewById(R.id.happy)
- val soso : ImageView = findViewById(R.id.soso)
- val bad : ImageView = findViewById(R.id.bad)
- val angry : ImageView = findViewById(R.id.angry)
-
- fun resetTextColors() {
- text1.setTextColor(Color.BLACK)
- text2.setTextColor(Color.BLACK)
- text3.setTextColor(Color.BLACK)
- text4.setTextColor(Color.BLACK)
- text5.setTextColor(Color.BLACK)
- }
-
- good.setOnClickListener {
- resetTextColors()
- text1.setTextColor(Color.parseColor("#F9DC77"))
- }
-
- happy.setOnClickListener {
- resetTextColors()
- text2.setTextColor(Color.parseColor("#AEE9FE"))
- }
-
- soso.setOnClickListener {
- resetTextColors()
- text3.setTextColor(Color.parseColor("#94A5FE"))
- }
-
- bad.setOnClickListener {
- resetTextColors()
- text4.setTextColor(Color.parseColor("#77C48D"))
- }
-
- angry.setOnClickListener {
- resetTextColors()
- text5.setTextColor(Color.parseColor("#D94F49"))
- }
-
- 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
- }
- }
-
-}
\ No newline at end of file
diff --git a/Week1/app/src/main/res/drawable/angry.png b/Week1/app/src/main/res/drawable/angry.png
deleted file mode 100644
index 217a46b..0000000
Binary files a/Week1/app/src/main/res/drawable/angry.png and /dev/null differ
diff --git a/Week1/app/src/main/res/drawable/arrow_back.xml b/Week1/app/src/main/res/drawable/arrow_back.xml
deleted file mode 100644
index 99f85de..0000000
--- a/Week1/app/src/main/res/drawable/arrow_back.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/Week1/app/src/main/res/drawable/bad.png b/Week1/app/src/main/res/drawable/bad.png
deleted file mode 100644
index f8aea2f..0000000
Binary files a/Week1/app/src/main/res/drawable/bad.png and /dev/null differ
diff --git a/Week1/app/src/main/res/drawable/good.png b/Week1/app/src/main/res/drawable/good.png
deleted file mode 100644
index 039a3ac..0000000
Binary files a/Week1/app/src/main/res/drawable/good.png and /dev/null differ
diff --git a/Week1/app/src/main/res/drawable/happy.png b/Week1/app/src/main/res/drawable/happy.png
deleted file mode 100644
index 90e8e66..0000000
Binary files a/Week1/app/src/main/res/drawable/happy.png and /dev/null differ
diff --git a/Week1/app/src/main/res/drawable/soso.png b/Week1/app/src/main/res/drawable/soso.png
deleted file mode 100644
index dc37241..0000000
Binary files a/Week1/app/src/main/res/drawable/soso.png and /dev/null differ
diff --git a/Week1/app/src/main/res/layout/activity_main.xml b/Week1/app/src/main/res/layout/activity_main.xml
deleted file mode 100644
index a8d8000..0000000
--- a/Week1/app/src/main/res/layout/activity_main.xml
+++ /dev/null
@@ -1,166 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/Week1/app/src/main/res/values/strings.xml b/Week1/app/src/main/res/values/strings.xml
deleted file mode 100644
index ade1634..0000000
--- a/Week1/app/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-
- Week1
-
\ No newline at end of file
diff --git a/Week1/app/src/main/res/values/themes.xml b/Week1/app/src/main/res/values/themes.xml
deleted file mode 100644
index fb78203..0000000
--- a/Week1/app/src/main/res/values/themes.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/Week1/build.gradle.kts b/Week1/build.gradle.kts
deleted file mode 100644
index 3756278..0000000
--- a/Week1/build.gradle.kts
+++ /dev/null
@@ -1,4 +0,0 @@
-// Top-level build file where you can add configuration options common to all sub-projects/modules.
-plugins {
- alias(libs.plugins.android.application) apply false
-}
\ No newline at end of file
diff --git a/Week1/.gitignore b/Week3/.gitignore
similarity index 100%
rename from Week1/.gitignore
rename to Week3/.gitignore
diff --git a/Week1/app/.gitignore b/Week3/app/.gitignore
similarity index 100%
rename from Week1/app/.gitignore
rename to Week3/app/.gitignore
diff --git a/Week3/app/build.gradle.kts b/Week3/app/build.gradle.kts
new file mode 100644
index 0000000..74aa6be
--- /dev/null
+++ b/Week3/app/build.gradle.kts
@@ -0,0 +1,83 @@
+plugins {
+ id("com.android.application")
+ id("org.jetbrains.kotlin.android")
+ id("kotlin-kapt")
+ id("com.google.dagger.hilt.android")
+}
+
+android {
+ namespace = "com.example.week3"
+ compileSdk = 34
+
+ defaultConfig {
+ applicationId = "com.example.week3"
+ minSdk = 24
+ targetSdk = 34
+ 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_17
+ targetCompatibility = JavaVersion.VERSION_17
+ }
+
+ buildFeatures {
+ viewBinding = true
+ }
+}
+
+dependencies {
+ // AndroidX & UI
+ implementation("androidx.core:core-ktx:1.12.0")
+ implementation("androidx.appcompat:appcompat:1.6.1")
+ implementation("com.google.android.material:material:1.11.0")
+ implementation("androidx.constraintlayout:constraintlayout:2.1.4")
+ implementation("androidx.activity:activity-ktx:1.8.2")
+ implementation("androidx.fragment:fragment-ktx:1.6.2")
+
+ // Hilt
+ implementation("com.google.dagger:hilt-android:2.48.1")
+ kapt("com.google.dagger:hilt-android-compiler:2.48.1")
+
+ // Navigation
+ implementation("androidx.navigation:navigation-fragment-ktx:2.7.7")
+ implementation("androidx.navigation:navigation-ui-ktx:2.7.7")
+
+ // DataStore & Gson
+ implementation("androidx.datastore:datastore-preferences:1.0.0")
+ implementation("com.google.code.gson:gson:2.10.1")
+
+ // Retrofit
+ 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")
+
+ // Room
+ implementation("androidx.room:room-runtime:2.6.1")
+ kapt("androidx.room:room-compiler:2.6.1")
+ implementation("androidx.room:room-ktx:2.6.1")
+
+ // Glide
+ implementation("com.github.bumptech.glide:glide:4.16.0")
+ kapt("com.github.bumptech.glide:compiler:4.16.0")
+
+ // Coroutines
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0")
+
+ // Test
+ testImplementation("junit:junit:4.13.2")
+ androidTestImplementation("androidx.test.ext:junit:1.1.5")
+ androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
+}
\ No newline at end of file
diff --git a/Week1/app/proguard-rules.pro b/Week3/app/proguard-rules.pro
similarity index 100%
rename from Week1/app/proguard-rules.pro
rename to Week3/app/proguard-rules.pro
diff --git a/Week1/app/src/androidTest/java/com/example/week1/ExampleInstrumentedTest.kt b/Week3/app/src/androidTest/java/com/example/week3/ExampleInstrumentedTest.kt
similarity index 86%
rename from Week1/app/src/androidTest/java/com/example/week1/ExampleInstrumentedTest.kt
rename to Week3/app/src/androidTest/java/com/example/week3/ExampleInstrumentedTest.kt
index bc55096..460ce62 100644
--- a/Week1/app/src/androidTest/java/com/example/week1/ExampleInstrumentedTest.kt
+++ b/Week3/app/src/androidTest/java/com/example/week3/ExampleInstrumentedTest.kt
@@ -1,4 +1,4 @@
-package com.example.week1
+package com.example.week3
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -19,6 +19,6 @@ class ExampleInstrumentedTest {
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
- assertEquals("com.example.week1", appContext.packageName)
+ assertEquals("com.example.week3", appContext.packageName)
}
}
\ No newline at end of file
diff --git a/Week1/app/src/main/AndroidManifest.xml b/Week3/app/src/main/AndroidManifest.xml
similarity index 81%
rename from Week1/app/src/main/AndroidManifest.xml
rename to Week3/app/src/main/AndroidManifest.xml
index c891bc8..a9ea48c 100644
--- a/Week1/app/src/main/AndroidManifest.xml
+++ b/Week3/app/src/main/AndroidManifest.xml
@@ -1,8 +1,9 @@
-
+
+ android:theme="@style/Theme.Week3"
+ android:usesCleartextTraffic="true">
diff --git a/Week3/app/src/main/java/com/example/week3/BuyAllFragment.kt b/Week3/app/src/main/java/com/example/week3/BuyAllFragment.kt
new file mode 100644
index 0000000..1576c5c
--- /dev/null
+++ b/Week3/app/src/main/java/com/example/week3/BuyAllFragment.kt
@@ -0,0 +1,42 @@
+package com.example.week3
+
+import android.os.Bundle
+import android.view.View
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
+import androidx.recyclerview.widget.GridLayoutManager
+import com.example.week3.databinding.FragmentBuyAllBinding
+import dagger.hilt.android.AndroidEntryPoint
+
+@AndroidEntryPoint
+class BuyAllFragment : Fragment(R.layout.fragment_buy_all) {
+ private var _binding: FragmentBuyAllBinding? = null
+ private val binding get() = _binding!!
+
+ private val viewModel: MainViewModel by viewModels()
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ _binding = FragmentBuyAllBinding.bind(view)
+
+ val productAdapter = ProductAdapter { clickedItem ->
+ viewModel.toggleWishStatus(clickedItem.id)
+ }
+
+ binding.recyclerGrid.apply {
+ adapter = productAdapter
+ layoutManager = GridLayoutManager(requireContext(), 2)
+ }
+
+ viewModel.productList.observe(viewLifecycleOwner) { updatedList ->
+ productAdapter.submitList(updatedList)
+ }
+
+ viewModel.fetchLocalProducts()
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+}
\ No newline at end of file
diff --git a/Week3/app/src/main/java/com/example/week3/BuyFragment.kt b/Week3/app/src/main/java/com/example/week3/BuyFragment.kt
new file mode 100644
index 0000000..8c64b14
--- /dev/null
+++ b/Week3/app/src/main/java/com/example/week3/BuyFragment.kt
@@ -0,0 +1,73 @@
+package com.example.week3
+
+import android.graphics.Typeface
+import android.os.Bundle
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.fragment.app.Fragment
+import com.example.week3.databinding.FragmentBuyBinding
+import com.google.android.material.tabs.TabLayout
+import dagger.hilt.android.AndroidEntryPoint
+
+@AndroidEntryPoint
+class BuyFragment : Fragment(R.layout.fragment_buy) {
+
+ private var _binding: FragmentBuyBinding? = null
+ private val binding get() = _binding!!
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ _binding = FragmentBuyBinding.bind(view)
+
+ setupTabs()
+ }
+
+ private fun setupTabs() {
+ binding.tabLayout.addTab(binding.tabLayout.newTab().setText(getString(R.string.all)))
+ binding.tabLayout.addTab(binding.tabLayout.newTab().setText(getString(R.string.Shirts)))
+ binding.tabLayout.addTab(binding.tabLayout.newTab().setText(getString(R.string.Shoes)))
+
+ replaceTabFragment(BuyAllFragment())
+
+ binding.tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
+ override fun onTabSelected(tab: TabLayout.Tab?) {
+ updateTabStyle(tab, true)
+
+ when (tab?.position) {
+ 0 -> replaceTabFragment(BuyAllFragment())
+ 1 -> replaceTabFragment(BuyTopsFragment())
+ 2 -> replaceTabFragment(BuyShoesFragment())
+ }
+ }
+
+ override fun onTabUnselected(tab: TabLayout.Tab?) {
+ updateTabStyle(tab, false)
+ }
+
+ override fun onTabReselected(tab: TabLayout.Tab?) {}
+ })
+
+ updateTabStyle(binding.tabLayout.getTabAt(0), true)
+ }
+
+ private fun replaceTabFragment(fragment: Fragment) {
+ childFragmentManager.beginTransaction()
+ .replace(R.id.tabContent, fragment)
+ .commit()
+ }
+
+ private fun updateTabStyle(tab: TabLayout.Tab?, isBold: Boolean) {
+ val tabView = (binding.tabLayout.getChildAt(0) as ViewGroup).getChildAt(tab?.position ?: 0) as ViewGroup
+ val textView = tabView.getChildAt(1) as? TextView
+
+ textView?.let {
+ it.typeface = if (isBold) Typeface.DEFAULT_BOLD else Typeface.DEFAULT
+ }
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+}
\ No newline at end of file
diff --git a/Week3/app/src/main/java/com/example/week3/BuyShoesFragment.kt b/Week3/app/src/main/java/com/example/week3/BuyShoesFragment.kt
new file mode 100644
index 0000000..fe2c655
--- /dev/null
+++ b/Week3/app/src/main/java/com/example/week3/BuyShoesFragment.kt
@@ -0,0 +1,25 @@
+package com.example.week3
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.Fragment
+import dagger.hilt.android.AndroidEntryPoint
+
+@AndroidEntryPoint
+class BuyShoesFragment : Fragment() {
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ return inflater.inflate(R.layout.fragment_buy_shoes, container, false)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ }
+}
\ No newline at end of file
diff --git a/Week3/app/src/main/java/com/example/week3/BuyTopsFragment.kt b/Week3/app/src/main/java/com/example/week3/BuyTopsFragment.kt
new file mode 100644
index 0000000..fddbcaa
--- /dev/null
+++ b/Week3/app/src/main/java/com/example/week3/BuyTopsFragment.kt
@@ -0,0 +1,25 @@
+package com.example.week3
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.Fragment
+import dagger.hilt.android.AndroidEntryPoint
+
+@AndroidEntryPoint
+class BuyTopsFragment : Fragment() {
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ return inflater.inflate(R.layout.fragment_buy_tops, container, false)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ }
+}
\ No newline at end of file
diff --git a/Week3/app/src/main/java/com/example/week3/FollowingAdapter.kt b/Week3/app/src/main/java/com/example/week3/FollowingAdapter.kt
new file mode 100644
index 0000000..7c19fec
--- /dev/null
+++ b/Week3/app/src/main/java/com/example/week3/FollowingAdapter.kt
@@ -0,0 +1,35 @@
+package com.example.week3
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import com.bumptech.glide.Glide
+
+class FollowingAdapter(private val userList: List) :
+ RecyclerView.Adapter() {
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FollowingViewHolder {
+ val view = LayoutInflater.from(parent.context)
+ .inflate(R.layout.item_following, parent, false)
+ return FollowingViewHolder(view)
+ }
+
+ override fun onBindViewHolder(holder: FollowingViewHolder, position: Int) {
+ val user = userList[position]
+ holder.name.text = user.firstName
+
+ Glide.with(holder.itemView.context)
+ .load(user.avatar)
+ .into(holder.image)
+ }
+
+ override fun getItemCount(): Int = userList.size
+
+ class FollowingViewHolder(view: View) : RecyclerView.ViewHolder(view) {
+ val image: ImageView = view.findViewById(R.id.ivFollowingProfile)
+ val name: TextView = view.findViewById(R.id.tvFollowingName)
+ }
+}
\ No newline at end of file
diff --git a/Week3/app/src/main/java/com/example/week3/HomeFragment.kt b/Week3/app/src/main/java/com/example/week3/HomeFragment.kt
new file mode 100644
index 0000000..08a39a5
--- /dev/null
+++ b/Week3/app/src/main/java/com/example/week3/HomeFragment.kt
@@ -0,0 +1,42 @@
+package com.example.week3
+
+import android.os.Bundle
+import android.view.View
+import androidx.fragment.app.Fragment
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.example.week3.databinding.FragmentHomeBinding
+import dagger.hilt.android.AndroidEntryPoint
+import androidx.fragment.app.viewModels
+
+@AndroidEntryPoint
+class HomeFragment : Fragment(R.layout.fragment_home) {
+ private var _binding: FragmentHomeBinding? = null
+ private val binding get() = _binding!!
+
+ private val viewModel: MainViewModel by viewModels()
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ _binding = FragmentHomeBinding.bind(view)
+
+ val productAdapter = ProductAdapter { clickedItem ->
+ viewModel.toggleWishStatus(clickedItem.id)
+ }
+
+ binding.recyclerViewHome.apply {
+ adapter = productAdapter
+ layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false)
+ isNestedScrollingEnabled = false
+ }
+
+ viewModel.productList.observe(viewLifecycleOwner) { allProducts ->
+ productAdapter.submitList(allProducts)
+ }
+ viewModel.fetchLocalProducts()
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+}
\ No newline at end of file
diff --git a/Week3/app/src/main/java/com/example/week3/LocalRepository.kt b/Week3/app/src/main/java/com/example/week3/LocalRepository.kt
new file mode 100644
index 0000000..4e0730f
--- /dev/null
+++ b/Week3/app/src/main/java/com/example/week3/LocalRepository.kt
@@ -0,0 +1,48 @@
+package com.example.week3
+
+import android.content.Context
+import androidx.datastore.preferences.core.edit
+import androidx.datastore.preferences.core.stringPreferencesKey
+import androidx.datastore.preferences.preferencesDataStore
+import com.google.gson.Gson
+import com.google.gson.reflect.TypeToken
+import dagger.hilt.android.qualifiers.ApplicationContext
+import kotlinx.coroutines.flow.first
+import javax.inject.Inject
+import javax.inject.Singleton
+
+private val Context.dataStore by preferencesDataStore(name = "product_prefs")
+
+@Singleton
+class LocalRepository @Inject constructor(
+ @ApplicationContext private val context: Context,
+ private val gson: Gson
+) {
+ private val PRODUCTS_KEY = stringPreferencesKey("products_json")
+
+ private val initialList = listOf(
+ ProductData(1, "image4", "상품1", "설명1", 1, "₩20,000"),
+ ProductData(2, "image2", "상품2", "설명2", 2, "₩40,000"),
+ ProductData(3, "image3", "상품3", "설명3", 4, "₩80,000"),
+ ProductData(4, "image1", "상품4", "설명4", 5, "₩100,000")
+ )
+
+ suspend fun getProducts(): List {
+ val preferences = context.dataStore.data.first()
+ val json = preferences[PRODUCTS_KEY]
+ return if (json == null) {
+ saveProducts(initialList)
+ initialList
+ } else {
+ val type = object : TypeToken>() {}.type
+ gson.fromJson(json, type)
+ }
+ }
+
+ suspend fun saveProducts(productList: List) {
+ val jsonString = gson.toJson(productList)
+ context.dataStore.edit { preferences ->
+ preferences[PRODUCTS_KEY] = jsonString
+ }
+ }
+}
\ No newline at end of file
diff --git a/Week3/app/src/main/java/com/example/week3/MainActivity.kt b/Week3/app/src/main/java/com/example/week3/MainActivity.kt
new file mode 100644
index 0000000..4dd824d
--- /dev/null
+++ b/Week3/app/src/main/java/com/example/week3/MainActivity.kt
@@ -0,0 +1,30 @@
+package com.example.week3
+
+import com.example.week3.databinding.ActivityMainBinding
+
+import android.os.Bundle
+import androidx.activity.enableEdgeToEdge
+import androidx.appcompat.app.AppCompatActivity
+import androidx.navigation.fragment.NavHostFragment
+import androidx.navigation.ui.setupWithNavController
+import dagger.hilt.android.AndroidEntryPoint
+
+@AndroidEntryPoint
+class MainActivity : AppCompatActivity() {
+
+ private lateinit var binding: ActivityMainBinding
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ enableEdgeToEdge()
+
+ binding = ActivityMainBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+
+ val navHostFragment = supportFragmentManager
+ .findFragmentById(R.id.nav_host_fragment) as NavHostFragment
+ val navController = navHostFragment.navController
+
+ binding.bottomNav.setupWithNavController(navController)
+ }
+}
\ No newline at end of file
diff --git a/Week3/app/src/main/java/com/example/week3/MainViewModel.kt b/Week3/app/src/main/java/com/example/week3/MainViewModel.kt
new file mode 100644
index 0000000..73e4ae8
--- /dev/null
+++ b/Week3/app/src/main/java/com/example/week3/MainViewModel.kt
@@ -0,0 +1,63 @@
+package com.example.week3
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class MainViewModel @Inject constructor(
+ private val remoteRepository: RemoteRepository,
+ private val localRepository: LocalRepository
+) : ViewModel() {
+ private val _productList = MutableLiveData>()
+ val productList: LiveData> = _productList
+
+ private val _userProfile = MutableLiveData()
+ val userProfile: LiveData = _userProfile
+
+ private val _userList = MutableLiveData>()
+ val userList: LiveData> = _userList
+
+ private val _errorMessage = MutableLiveData()
+ val errorMessage: LiveData = _errorMessage
+
+ fun fetchProfileData() {
+ viewModelScope.launch {
+ try {
+ val profileResponse = remoteRepository.getProfile()
+ _userProfile.value = profileResponse.data
+
+ val listResponse = remoteRepository.getFollowingList()
+ _userList.value = listResponse.data.filter { it.id != 1 }
+
+ } catch (e: Exception) {
+ }
+ }
+ }
+
+ fun fetchLocalProducts() {
+ viewModelScope.launch {
+ val products = localRepository.getProducts()
+ _productList.value = products
+ }
+ }
+
+ fun toggleWishStatus(productId: Int) {
+ viewModelScope.launch {
+ val currentList = _productList.value?.toMutableList() ?: return@launch
+ val index = currentList.indexOfFirst { it.id == productId }
+
+ if (index != -1) {
+ val targetItem = currentList[index]
+ currentList[index] = targetItem.copy(isWished = !targetItem.isWished)
+
+ localRepository.saveProducts(currentList)
+ _productList.value = currentList
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Week3/app/src/main/java/com/example/week3/MyApplication.kt b/Week3/app/src/main/java/com/example/week3/MyApplication.kt
new file mode 100644
index 0000000..0bbb5ae
--- /dev/null
+++ b/Week3/app/src/main/java/com/example/week3/MyApplication.kt
@@ -0,0 +1,9 @@
+package com.example.week3
+
+import android.app.Application
+import dagger.hilt.android.HiltAndroidApp
+
+@HiltAndroidApp
+class MyApplication : Application() {
+
+}
\ No newline at end of file
diff --git a/Week3/app/src/main/java/com/example/week3/ProductAdapter.kt b/Week3/app/src/main/java/com/example/week3/ProductAdapter.kt
new file mode 100644
index 0000000..6ef5f30
--- /dev/null
+++ b/Week3/app/src/main/java/com/example/week3/ProductAdapter.kt
@@ -0,0 +1,58 @@
+package com.example.week3
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.ListAdapter
+import androidx.recyclerview.widget.RecyclerView
+import com.example.week3.databinding.ItemGridProductBinding
+
+
+class ProductAdapter(private val onWishClick: (ProductData) -> Unit
+) : ListAdapter(ProductDiffCallback()) {
+
+ inner class ViewHolder(val binding: ItemGridProductBinding) : RecyclerView.ViewHolder(binding.root)
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ val binding = ItemGridProductBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+ return ViewHolder(binding)
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ val item = getItem(position)
+ with(holder.binding) {
+ val context = root.context
+ val resourceId = context.resources.getIdentifier(
+ item.imageRes, "drawable", context.packageName
+ )
+
+ if (resourceId != 0) {
+ imgProduct.setImageResource(resourceId)
+ } else {
+ imgProduct.setImageResource(R.drawable.img_nemo)
+ }
+ txtName.text = item.name
+ txtDesc.text = item.desc
+ txtColor.text = "색상 ${item.colorCount}개"
+ txtPrice.text = item.price
+
+ btnWish.setImageResource(
+ if (item.isWished) R.drawable.ic_wish_heart_fill else R.drawable.ic_wish_heart
+ )
+
+ btnWish.setOnClickListener {
+ onWishClick(item)
+ }
+ }
+ }
+}
+
+class ProductDiffCallback : DiffUtil.ItemCallback() {
+ override fun areItemsTheSame(oldItem: ProductData, newItem: ProductData): Boolean {
+ return oldItem.id == newItem.id
+ }
+
+ override fun areContentsTheSame(oldItem: ProductData, newItem: ProductData): Boolean {
+ return oldItem == newItem
+ }
+}
\ No newline at end of file
diff --git a/Week3/app/src/main/java/com/example/week3/ProductData.kt b/Week3/app/src/main/java/com/example/week3/ProductData.kt
new file mode 100644
index 0000000..cd5788e
--- /dev/null
+++ b/Week3/app/src/main/java/com/example/week3/ProductData.kt
@@ -0,0 +1,11 @@
+package com.example.week3
+
+data class ProductData (
+ val id: Int,
+ val imageRes: String,
+ val name: String,
+ val desc: String,
+ val colorCount: Int,
+ val price: String,
+ var isWished: Boolean = false
+)
\ No newline at end of file
diff --git a/Week3/app/src/main/java/com/example/week3/ProfileFragment.kt b/Week3/app/src/main/java/com/example/week3/ProfileFragment.kt
new file mode 100644
index 0000000..98cf872
--- /dev/null
+++ b/Week3/app/src/main/java/com/example/week3/ProfileFragment.kt
@@ -0,0 +1,38 @@
+package com.example.week3
+
+import android.os.Bundle
+import android.view.View
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.fragment.app.Fragment
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.bumptech.glide.Glide
+import dagger.hilt.android.AndroidEntryPoint
+import androidx.fragment.app.viewModels
+
+@AndroidEntryPoint
+class ProfileFragment : Fragment(R.layout.fragment_profile) {
+
+ private val viewModel: MainViewModel by viewModels()
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ val imgProfile = view.findViewById(R.id.imgProfile)
+ val tvNickname = view.findViewById(R.id.nickname)
+ val rvFollowing = view.findViewById(R.id.rvFollowing)
+
+ viewModel.userProfile.observe(viewLifecycleOwner) { user ->
+ tvNickname.text = "${user.firstName} ${user.lastName}"
+ Glide.with(this).load(user.avatar).into(imgProfile)
+ }
+
+ viewModel.userList.observe(viewLifecycleOwner) { users ->
+ rvFollowing.adapter = FollowingAdapter(users)
+ rvFollowing.layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
+ }
+
+ viewModel.fetchProfileData()
+ }
+}
\ No newline at end of file
diff --git a/Week3/app/src/main/java/com/example/week3/RemoteRepository.kt b/Week3/app/src/main/java/com/example/week3/RemoteRepository.kt
new file mode 100644
index 0000000..0fe66e2
--- /dev/null
+++ b/Week3/app/src/main/java/com/example/week3/RemoteRepository.kt
@@ -0,0 +1,13 @@
+package com.example.week3
+
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class RemoteRepository @Inject constructor(
+ private val reqResService: ReqResService
+) {
+ suspend fun getProfile() = reqResService.getProfile()
+
+ suspend fun getFollowingList(page: Int = 1) = reqResService.getFollowingList(page)
+}
\ No newline at end of file
diff --git a/Week3/app/src/main/java/com/example/week3/ReqResData.kt b/Week3/app/src/main/java/com/example/week3/ReqResData.kt
new file mode 100644
index 0000000..443a43d
--- /dev/null
+++ b/Week3/app/src/main/java/com/example/week3/ReqResData.kt
@@ -0,0 +1,19 @@
+package com.example.week3
+
+import com.google.gson.annotations.SerializedName
+
+data class SingleUserResponse(
+ val data: UserData
+)
+
+data class UserListResponse(
+ val data: List
+)
+
+data class UserData(
+ val id: Int,
+ val email: String,
+ @SerializedName("first_name") val firstName: String,
+ @SerializedName("last_name") val lastName: String,
+ val avatar: String
+)
\ No newline at end of file
diff --git a/Week3/app/src/main/java/com/example/week3/ReqResService.kt b/Week3/app/src/main/java/com/example/week3/ReqResService.kt
new file mode 100644
index 0000000..67b4df5
--- /dev/null
+++ b/Week3/app/src/main/java/com/example/week3/ReqResService.kt
@@ -0,0 +1,12 @@
+package com.example.week3
+
+import retrofit2.http.GET
+import retrofit2.http.Query
+
+interface ReqResService {
+ @GET("api/users/1")
+ suspend fun getProfile(): SingleUserResponse
+
+ @GET("api/users")
+ suspend fun getFollowingList(@Query("page") page: Int = 1): UserListResponse
+}
\ No newline at end of file
diff --git a/Week3/app/src/main/java/com/example/week3/ShoppingcartFragment.kt b/Week3/app/src/main/java/com/example/week3/ShoppingcartFragment.kt
new file mode 100644
index 0000000..f5c1007
--- /dev/null
+++ b/Week3/app/src/main/java/com/example/week3/ShoppingcartFragment.kt
@@ -0,0 +1,39 @@
+package com.example.week3
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.Fragment
+import android.widget.Button
+import androidx.navigation.fragment.findNavController
+import androidx.navigation.NavOptions
+import dagger.hilt.android.AndroidEntryPoint
+
+@AndroidEntryPoint
+class ShoppingcartFragment : Fragment() {
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+
+ ): View {
+ return inflater.inflate(R.layout.fragment_shoppingcart, container, false)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ val button = view.findViewById