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/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/.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/Week1/app/build.gradle.kts b/Week3/app/build.gradle.kts
similarity index 53%
rename from Week1/app/build.gradle.kts
rename to Week3/app/build.gradle.kts
index cfcfc78..b2e7d1b 100644
--- a/Week1/app/build.gradle.kts
+++ b/Week3/app/build.gradle.kts
@@ -3,7 +3,7 @@ plugins {
}
android {
- namespace = "com.example.week1"
+ namespace = "com.example.week3"
compileSdk {
version = release(36) {
minorApiLevel = 1
@@ -11,7 +11,7 @@ android {
}
defaultConfig {
- applicationId = "com.example.week1"
+ applicationId = "com.example.week3"
minSdk = 24
targetSdk = 36
versionCode = 1
@@ -33,6 +33,10 @@ android {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
+
+ buildFeatures {
+ viewBinding = true
+ }
}
dependencies {
@@ -44,4 +48,19 @@ dependencies {
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")
+ implementation("androidx.room:room-runtime:2.6.0")
+ annotationProcessor("androidx.room:room-compiler:2.6.0")
+ implementation("com.squareup.retrofit2:retrofit:2.9.0")
+ implementation("com.squareup.retrofit2:converter-gson:2.9.0")
+
+ implementation("com.github.bumptech.glide:glide:4.16.0")
+
+ implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0")
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0")
}
\ 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 84%
rename from Week1/app/src/main/AndroidManifest.xml
rename to Week3/app/src/main/AndroidManifest.xml
index c891bc8..49d9def 100644
--- a/Week1/app/src/main/AndroidManifest.xml
+++ b/Week3/app/src/main/AndroidManifest.xml
@@ -1,7 +1,7 @@
-
+
+ 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..f1bf049
--- /dev/null
+++ b/Week3/app/src/main/java/com/example/week3/BuyAllFragment.kt
@@ -0,0 +1,56 @@
+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)
+ }
+
+ observeProducts()
+ }
+
+ private fun observeProducts() {
+ viewLifecycleOwner.lifecycleScope.launch {
+ ProductRepository.getProductsFlow(requireContext()).collect { updatedList ->
+ (binding.recyclerGrid.adapter as ProductAdapter).submitList(updatedList)
+ }
+ }
+ }
+
+ private fun handleWishClick(clickedItem: ProductData) {
+ viewLifecycleOwner.lifecycleScope.launch {
+ val currentList = ProductRepository.getProductsOnce(requireContext()).toMutableList()
+ val index = currentList.indexOfFirst { it.id == clickedItem.id }
+ if (index != -1) {
+ currentList[index] = clickedItem.copy(isWished = !clickedItem.isWished)
+
+ ProductRepository.saveProducts(requireContext(), currentList)
+ }
+ }
+ }
+
+ 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..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/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..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..b7e4cc1
--- /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.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.bottomNav.setupWithNavController(navController)
+ }
+}
\ 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/ProductRepository.kt b/Week3/app/src/main/java/com/example/week3/ProductRepository.kt
new file mode 100644
index 0000000..79e7e2c
--- /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, "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")
+ )
+
+ 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..0dfcbb5
--- /dev/null
+++ b/Week3/app/src/main/java/com/example/week3/ProfileFragment.kt
@@ -0,0 +1,81 @@
+package com.example.week3
+
+import android.os.Bundle
+import android.view.View
+import android.widget.ImageView
+import android.widget.TextView
+import android.widget.Toast
+import androidx.fragment.app.Fragment
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.bumptech.glide.Glide
+import retrofit2.Call
+import retrofit2.Callback
+import retrofit2.Response
+import retrofit2.Retrofit
+import retrofit2.converter.gson.GsonConverterFactory
+import okhttp3.OkHttpClient
+import okhttp3.logging.HttpLoggingInterceptor
+
+class ProfileFragment : Fragment(R.layout.fragment_profile) {
+
+ 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)
+
+ val okHttpClient = OkHttpClient.Builder()
+ .addInterceptor { chain ->
+ val original = chain.request()
+ val requestBuilder = original.newBuilder()
+ .header("x-api-key", "reqres_286fae62df9643f089a91de5a72a2ef4")
+ .method(original.method, original.body)
+ val request = requestBuilder.build()
+ chain.proceed(request)
+ }
+ .addInterceptor(HttpLoggingInterceptor().apply {
+ level = HttpLoggingInterceptor.Level.BODY
+ })
+ .build()
+
+ val retrofit = Retrofit.Builder()
+ .baseUrl("https://reqres.in/")
+ .client(okHttpClient)
+ .addConverterFactory(GsonConverterFactory.create())
+ .build()
+ val service = retrofit.create(ReqResService::class.java)
+
+ service.getProfile().enqueue(object : Callback {
+ override fun onResponse(call: Call, response: Response) {
+ if (response.isSuccessful) {
+ val user = response.body()?.data
+ user?.let {
+ tvNickname.text = "${it.firstName} ${it.lastName}"
+ Glide.with(this@ProfileFragment).load(it.avatar).into(imgProfile)
+ }
+ }
+ }
+ override fun onFailure(call: Call, t: Throwable) {}
+ })
+
+ service.getFollowingList().enqueue(object : Callback {
+ override fun onResponse(call: Call, response: Response) {
+ if (response.isSuccessful) {
+ val allUsers = response.body()?.data ?: listOf()
+
+ val filteredUsers = allUsers.filter { it.id != 1 }
+
+ val adapter = FollowingAdapter(filteredUsers)
+ rvFollowing.adapter = adapter
+ rvFollowing.layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
+ }
+ }
+
+ override fun onFailure(call: Call, t: Throwable) {
+ Toast.makeText(context, "목록 로드 실패", Toast.LENGTH_SHORT).show()
+ }
+ })
+ }
+}
\ 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..7f1d1e5
--- /dev/null
+++ b/Week3/app/src/main/java/com/example/week3/ReqResService.kt
@@ -0,0 +1,13 @@
+package com.example.week3
+
+import retrofit2.Call
+import retrofit2.http.GET
+import retrofit2.http.Query
+
+interface ReqResService {
+ @GET("api/users/1")
+ fun getProfile(): Call
+
+ @GET("api/users")
+ fun getFollowingList(@Query("page") page: Int = 1): Call
+}
\ 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