Skip to content

feat: 4주차 미션_묵은지#32

Open
mookeunji05 wants to merge 16 commits intomainfrom
mookeunji-4
Open

feat: 4주차 미션_묵은지#32
mookeunji05 wants to merge 16 commits intomainfrom
mookeunji-4

Conversation

@mookeunji05
Copy link
Copy Markdown
Collaborator

@mookeunji05 mookeunji05 commented Apr 8, 2026

📌 PR 제목

4주차 미션

🔗 관련 이슈

Closes #이슈번호

✨ 변경 사항

  • 기능1 추가
  • UI 수정
  • 버그 수정

🔍 테스트

  • 테스트 완료
  • 에러 없음

📸 스크린샷 (선택)

첫번쨰 두번째 세번째
image image image

🚨 추가 이슈

@mookeunji05 mookeunji05 changed the title 4주차 미션_묵은 4주차 미션_묵은지 Apr 8, 2026
@mookeunji05 mookeunji05 changed the title 4주차 미션_묵은지 feat:4주차 미션_묵은지 Apr 8, 2026
@mookeunji05 mookeunji05 changed the title feat:4주차 미션_묵은지 feat: 4주차 미션_묵은지 Apr 8, 2026
Copy link
Copy Markdown
Collaborator

@sua710 sua710 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

잘 구현 하신 것 같아요!

Comment on lines +14 to +17
class DataStoreManager(private val context: Context) {
private val gson = Gson()
private val PRODUCT_LIST_KEY = stringPreferencesKey("product_list")
private val WISHLIST_KEY = stringPreferencesKey("wishlist_key")
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DataStore로 변경 잘하신거 같아요!!

Comment on lines +13 to +70
class PurchaseFragment : Fragment(R.layout.fragment_purchase) {

private lateinit var dataStoreManager: DataStoreManager

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
dataStoreManager = DataStoreManager(requireContext())


val tabLayout = view.findViewById<TabLayout>(R.id.purchase_tab_layout)


if (tabLayout.tabCount < 4) {
tabLayout.addTab(tabLayout.newTab().setText("Sale"))
}

tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab?) {

when (tab?.position) {
0 -> { /* 전체 */ }
1 -> { /* Tops & T-Shirts */ }
2 -> { /* Shoes */ }
3 -> { /* Sale */ }
}
}
override fun onTabUnselected(tab: TabLayout.Tab?) {}
override fun onTabReselected(tab: TabLayout.Tab?) {}
})


val heartBtn = view.findViewById<ImageView>(R.id.iv_socks)

heartBtn.setOnClickListener {
val currentProduct = Product("Nike Everyday Plus", "$10", R.drawable.socks)
toggleWishlist(currentProduct)
}
}

private fun toggleWishlist(product: Product) {
viewLifecycleOwner.lifecycleScope.launch {
// 1. 현재 위시리스트 가져오기
val currentWishlist = dataStoreManager.getWishlist().first().toMutableList()

// 2. 이미 있으면 제거, 없으면 추가
val isExist = currentWishlist.any { it.name == product.name }
if (isExist) {
currentWishlist.removeAll { it.name == product.name }
Toast.makeText(context, "위시리스트에서 제거되었습니다.", Toast.LENGTH_SHORT).show()
} else {
currentWishlist.add(product)
Toast.makeText(context, "위시리스트에 추가되었습니다!", Toast.LENGTH_SHORT).show()
}

dataStoreManager.saveWishlist(currentWishlist)
}
}
} No newline at end of file
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아이템이 가로로 2개씩 뜨도록 만드는게 좋을 것 같아용

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="9월 4일 목요일"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

android:text 항목들 strings에 옮기면 유지보수에 좋을 거 같아요 ! 과제 수행하시느라 수고 많으셨습니다~!!

Copy link
Copy Markdown
Collaborator

@kimdoyeon1234 kimdoyeon1234 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TabLayout과 DataStore를 연동해서 실시간으로 하트 아이콘 상태를 바꾸는 로직을 아주 잘 구현하셨네요! 다만 현재 레이아웃이 하드코딩 방식이라 레몬 말씀대로 가로 2줄 배치를 구현하기엔 한계가 있어 보입니다!!

이번 기회에 RecyclerView를 도입해서 더 유연하고 효율적인 리스트 구조로 3주차 피드백 반영과 함께 리팩토링 해보는것도 좋을거 같습니다! 고생 많으셨습니다!

Comment on lines +31 to +135
<ScrollView
android:id="@+id/purchase_scroll_view"
android:layout_width="match_parent"
android:layout_height="match_parent">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">

<FrameLayout
android:layout_width="match_parent"
android:layout_height="300dp">
<ImageView
android:id="@+id/iv_socks"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/socks"
android:scaleType="centerInside"
android:background="#F9F9F9" />
<ImageView
android:id="@+id/btn_heart1"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="bottom|end"
android:layout_margin="12dp"
android:src="@drawable/ic_heart_empty"
android:padding="8dp"/>
</FrameLayout>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Training Crew Socks" android:textColor="#888888" android:textSize="14sp" android:layout_marginTop="16dp" />
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Nike Everyday Plus Cushioned" android:textColor="@color/black" android:textSize="22sp" android:textStyle="bold" android:layout_marginTop="4dp" />
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="$10" android:textColor="@color/black" android:textSize="14sp" android:layout_marginTop="8dp" android:layout_marginBottom="40dp" />

<FrameLayout
android:layout_width="match_parent"
android:layout_height="300dp">
<ImageView
android:id="@+id/iv_socks2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/socks"
android:scaleType="centerInside"
android:background="#F9F9F9" />
<ImageView
android:id="@+id/btn_heart2"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="bottom|end"
android:layout_margin="12dp"
android:src="@drawable/ic_heart_empty"
android:padding="8dp"/>
</FrameLayout>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Training Crew Socks" android:textColor="#888888" android:textSize="14sp" android:layout_marginTop="16dp" />
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Nike Everyday Plus Cushioned2" android:textColor="@color/black" android:textSize="22sp" android:textStyle="bold" android:layout_marginTop="4dp" />
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="$10" android:textColor="@color/black" android:textSize="14sp" android:layout_marginTop="8dp" android:layout_marginBottom="40dp" />

<FrameLayout
android:layout_width="match_parent"
android:layout_height="300dp">
<ImageView
android:id="@+id/iv_socks3"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/socks"
android:scaleType="centerInside"
android:background="#F9F9F9" />
<ImageView
android:id="@+id/btn_heart3"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="bottom|end"
android:layout_margin="12dp"
android:src="@drawable/ic_heart_empty"
android:padding="8dp"/>
</FrameLayout>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Training Crew Socks" android:textColor="#888888" android:textSize="14sp" android:layout_marginTop="16dp" />
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Nike Everyday Plus Cushioned3" android:textColor="@color/black" android:textSize="22sp" android:textStyle="bold" android:layout_marginTop="4dp" />
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="$10" android:textColor="@color/black" android:textSize="14sp" android:layout_marginTop="8dp" android:layout_marginBottom="40dp" />

<FrameLayout
android:layout_width="match_parent"
android:layout_height="300dp">
<ImageView
android:id="@+id/iv_socks4"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/socks"
android:scaleType="centerInside"
android:background="#F9F9F9" />
<ImageView
android:id="@+id/btn_heart4"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="bottom|end"
android:layout_margin="12dp"
android:src="@drawable/ic_heart_empty"
android:padding="8dp"/>
</FrameLayout>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Training Crew Socks" android:textColor="#888888" android:textSize="14sp" android:layout_marginTop="16dp" />
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Nike Everyday Plus Cushioned4" android:textColor="@color/black" android:textSize="22sp" android:textStyle="bold" android:layout_marginTop="4dp" />
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="$10" android:textColor="@color/black" android:textSize="14sp" android:layout_marginTop="8dp" android:layout_marginBottom="40dp" />

</LinearLayout>
</ScrollView>
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

현재 ScrollView 안에 유사한 형태의 아이템 레이아웃(FrameLayout 및 TextView들)이 반복적으로 직접 선언되어 있습니다. 아이템이 늘어날 경우 코드 관리가 어려워지고 메모리 효율도 떨어질 수 있어요. RecyclerView와 GridLayoutManager(spanCount = 2)를 사용하면 레몬 의견처럼 가로 2줄 배치도 쉽게 구현할 수 있고, 코드도 훨씬 깔끔해질 것 같습니다!

Comment on lines +27 to +37

val tabLayout = view.findViewById<TabLayout>(R.id.purchase_tab_layout)
val scrollView = view.findViewById<ScrollView>(R.id.purchase_scroll_view)


val heartButtons = listOf<ImageView>(
view.findViewById(R.id.btn_heart1),
view.findViewById(R.id.btn_heart2),
view.findViewById(R.id.btn_heart3),
view.findViewById(R.id.btn_heart4)
)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

현재 heartButtons를 리스트로 만들기 위해 findViewById를 개별적으로 호출하고 있는데, 만약 아이템이 100개라면 관리가 불가능해집니다. RecyclerView를 도입해서 Adapter 내부에서 ViewBinding으로 처리하도록 개선하면 좋을거같습니다!

Comment on lines +70 to +83
private fun toggleWishlist(product: Product) {
viewLifecycleOwner.lifecycleScope.launch {
val currentWishlist = dataStoreManager.getWishlist().first().toMutableList()
val isExist = currentWishlist.any { it.name == product.name }

if (isExist) {
currentWishlist.removeAll { it.name == product.name }
Toast.makeText(context, "위시리스트 제거", Toast.LENGTH_SHORT).show()
} else {
currentWishlist.add(product)
Toast.makeText(context, "위시리스트 추가!", Toast.LENGTH_SHORT).show()
}
dataStoreManager.saveWishlist(currentWishlist)
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fragment 내부에서 dataStoreManager를 직접 호출해 위시리스트를 추가/삭제하는 로직을 수행하고 있습니다. 나중에 앱이 커지면 유지보수가 힘들 수 있으니, 데이터 처리는 ViewModel로 옮기고 Fragment는 관찰만 하는 구조로 하면은 좋을거 같습니다..

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants