diff --git a/Week3/.gitignore b/Week3/.gitignore
new file mode 100644
index 0000000..aa724b7
--- /dev/null
+++ b/Week3/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/Week3/app/.gitignore b/Week3/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/Week3/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/Week3/app/build.gradle.kts b/Week3/app/build.gradle.kts
new file mode 100644
index 0000000..961c9cc
--- /dev/null
+++ b/Week3/app/build.gradle.kts
@@ -0,0 +1,56 @@
+plugins {
+ alias(libs.plugins.android.application)
+}
+
+android {
+ namespace = "com.example.week3"
+ compileSdk {
+ version = release(36) {
+ minorApiLevel = 1
+ }
+ }
+
+ defaultConfig {
+ applicationId = "com.example.week3"
+ 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 {
+ viewBinding = true
+ }
+}
+
+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)
+ implementation(libs.androidx.navigation.fragment.ktx)
+ implementation(libs.androidx.navigation.ui.ktx)
+ implementation("com.google.code.gson:gson:2.10.1")
+ implementation("com.google.code.gson:gson:2.10.1")
+ implementation("androidx.datastore:datastore-preferences:1.0.0")
+}
\ No newline at end of file
diff --git a/Week3/app/proguard-rules.pro b/Week3/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/Week3/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/Week3/app/src/androidTest/java/com/example/week3/ExampleInstrumentedTest.kt b/Week3/app/src/androidTest/java/com/example/week3/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..460ce62
--- /dev/null
+++ b/Week3/app/src/androidTest/java/com/example/week3/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.example.week3
+
+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.week3", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/Week3/app/src/main/AndroidManifest.xml b/Week3/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..718dab1
--- /dev/null
+++ b/Week3/app/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
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..dd865d3
--- /dev/null
+++ b/Week3/app/src/main/java/com/example/week3/BuyAllFragment.kt
@@ -0,0 +1,52 @@
+package com.example.week3
+
+import android.os.Bundle
+import android.view.View
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.lifecycleScope
+import androidx.recyclerview.widget.GridLayoutManager
+import com.example.week3.ProductRepository
+import com.example.week3.databinding.FragmentBuyAllBinding
+import kotlinx.coroutines.launch
+
+class BuyAllFragment : Fragment(R.layout.fragment_buy_all) {
+ private var _binding: FragmentBuyAllBinding? = null
+ private val binding get() = _binding!!
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ _binding = FragmentBuyAllBinding.bind(view)
+
+ val productAdapter = ProductAdapter { clickedItem ->
+ handleWishClick(clickedItem)
+ }
+
+ binding.recyclerGrid.apply {
+ adapter = productAdapter
+ layoutManager = GridLayoutManager(requireContext(), 2)
+ }
+ loadProducts()
+ }
+
+ private fun loadProducts() {
+ viewLifecycleOwner.lifecycleScope.launch {
+ val allProducts = ProductRepository.getProductsOnce(requireContext())
+
+ (binding.recyclerGrid.adapter as ProductAdapter).submitList(allProducts)
+ }
+ }
+
+ private fun handleWishClick(clickedItem: ProductData) {
+ val adapter = binding.recyclerGrid.adapter as ProductAdapter
+ val currentList = adapter.currentList.toMutableList()
+ val index = currentList.indexOfFirst { it.id == clickedItem.id }
+ if (index != -1) {
+ currentList[index] = clickedItem.copy(isWished = !clickedItem.isWished)
+ adapter.submitList(currentList)
+
+ viewLifecycleOwner.lifecycleScope.launch {
+ ProductRepository.saveProducts(requireContext(), currentList)
+ }
+ }
+ }
+}
\ 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..f0275e2
--- /dev/null
+++ b/Week3/app/src/main/java/com/example/week3/BuyFragment.kt
@@ -0,0 +1,71 @@
+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
+
+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..312c22f
--- /dev/null
+++ b/Week3/app/src/main/java/com/example/week3/BuyShoesFragment.kt
@@ -0,0 +1,23 @@
+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
+
+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..d4d8101
--- /dev/null
+++ b/Week3/app/src/main/java/com/example/week3/BuyTopsFragment.kt
@@ -0,0 +1,23 @@
+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
+
+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/HomeFragment.kt b/Week3/app/src/main/java/com/example/week3/HomeFragment.kt
new file mode 100644
index 0000000..9021097
--- /dev/null
+++ b/Week3/app/src/main/java/com/example/week3/HomeFragment.kt
@@ -0,0 +1,61 @@
+package com.example.week3
+
+import android.os.Bundle
+import android.view.View
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.lifecycleScope
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.example.week3.ProductRepository
+import com.example.week3.databinding.FragmentHomeBinding
+import kotlinx.coroutines.launch
+
+class HomeFragment : Fragment(R.layout.fragment_home) {
+ private var _binding: FragmentHomeBinding? = null
+ private val binding get() = _binding!!
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ _binding = FragmentHomeBinding.bind(view)
+
+ val productAdapter = ProductAdapter { clickedItem ->
+ handleWishClick(clickedItem)
+ }
+
+ binding.recyclerViewHome.apply {
+ adapter = productAdapter
+ layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false)
+ isNestedScrollingEnabled = false
+ }
+
+ loadProducts()
+ }
+
+ private fun loadProducts() {
+ viewLifecycleOwner.lifecycleScope.launch {
+ val allProducts = ProductRepository.getProductsOnce(requireContext())
+ val adapter = binding.recyclerViewHome.adapter as ProductAdapter
+ adapter.submitList(allProducts)
+ }
+ }
+
+ private fun handleWishClick(clickedItem: ProductData) {
+ val adapter = binding.recyclerViewHome.adapter as ProductAdapter
+ val newList = adapter.currentList.toMutableList()
+
+ val index = newList.indexOfFirst { it.id == clickedItem.id }
+ if (index != -1) {
+ newList[index] = clickedItem.copy(isWished = !clickedItem.isWished)
+
+ adapter.submitList(newList)
+
+ viewLifecycleOwner.lifecycleScope.launch {
+ ProductRepository.saveProducts(requireContext(), newList)
+ }
+ }
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+}
\ 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..bd5f2f3
--- /dev/null
+++ b/Week3/app/src/main/java/com/example/week3/MainActivity.kt
@@ -0,0 +1,35 @@
+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.core.view.ViewCompat
+import androidx.core.view.WindowInsetsCompat
+import androidx.navigation.fragment.NavHostFragment
+import androidx.navigation.ui.setupWithNavController
+
+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.bottomBarInclude.bottomNav.setupWithNavController(navController)
+
+ ViewCompat.setOnApplyWindowInsetsListener(binding.root) { v, insets ->
+ val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
+ v.setPadding(systemBars.left, systemBars.top, systemBars.right, 0)
+ insets
+ }
+ }
+}
\ 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..fb16935
--- /dev/null
+++ b/Week3/app/src/main/java/com/example/week3/ProductAdapter.kt
@@ -0,0 +1,49 @@
+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) {
+ imgProduct.setImageResource(item.imageRes)
+ 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 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..8e84223
--- /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: Int,
+ 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/ProductRepository.kt b/Week3/app/src/main/java/com/example/week3/ProductRepository.kt
new file mode 100644
index 0000000..8dfbcaa
--- /dev/null
+++ b/Week3/app/src/main/java/com/example/week3/ProductRepository.kt
@@ -0,0 +1,56 @@
+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 kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.map
+
+private val Context.dataStore by preferencesDataStore(name = "product_prefs")
+
+object ProductRepository {
+ private val PRODUCTS_KEY = stringPreferencesKey("products_json")
+ private val gson = Gson()
+
+ private val initialList = listOf(
+ ProductData(1, R.drawable.image4, "상품1", "설명1", 1, "₩20,000"),
+ ProductData(2, R.drawable.image2, "상품2", "설명2", 2, "₩40,000"),
+ ProductData(3, R.drawable.image3, "상품3", "설명3", 4, "₩80,000"),
+ ProductData(4, R.drawable.image1, "상품4", "설명4", 5, "₩100,000")
+ )
+
+ fun getProductsFlow(context: Context): Flow> {
+ return context.dataStore.data.map { preferences ->
+ val json = preferences[PRODUCTS_KEY]
+ if (json == null) {
+ initialList
+ } else {
+ val type = object : TypeToken>() {}.type
+ gson.fromJson(json, type)
+ }
+ }
+ }
+
+ suspend fun getProductsOnce(context: Context): List {
+ val preferences = context.dataStore.data.first()
+ val json = preferences[PRODUCTS_KEY]
+ return if (json == null) {
+ saveProducts(context, initialList)
+ initialList
+ } else {
+ val type = object : TypeToken>() {}.type
+ gson.fromJson(json, type)
+ }
+ }
+
+ suspend fun saveProducts(context: Context, 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/ProfileFragment.kt b/Week3/app/src/main/java/com/example/week3/ProfileFragment.kt
new file mode 100644
index 0000000..dfecb9f
--- /dev/null
+++ b/Week3/app/src/main/java/com/example/week3/ProfileFragment.kt
@@ -0,0 +1,23 @@
+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
+
+class ProfileFragment : Fragment() {
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ return inflater.inflate(R.layout.fragment_profile, 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/ShoppingcartFragment.kt b/Week3/app/src/main/java/com/example/week3/ShoppingcartFragment.kt
new file mode 100644
index 0000000..e00e775
--- /dev/null
+++ b/Week3/app/src/main/java/com/example/week3/ShoppingcartFragment.kt
@@ -0,0 +1,37 @@
+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
+
+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