Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.sungs.myapplication.data.UserData
import com.sungs.myapplication.data.remote.dto.UserData
import com.sungs.myapplication.databinding.ItemFollowBinding

class FollowAdapter
Expand Down
Comment thread
sungs25 marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ package com.sungs.myapplication.adapter

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.sungs.myapplication.data.ProductData
import com.sungs.myapplication.data.model.ProductData
import com.sungs.myapplication.databinding.ItemProductBinding

class ProductAdapter(private val productList: List<ProductData>)
: RecyclerView.Adapter<ProductAdapter.ProductViewHolder>() {
class ProductAdapter
: ListAdapter<ProductData, ProductAdapter.ProductViewHolder>(ProductDiffCallback()) {

inner class ProductViewHolder(private val binding: ItemProductBinding)
: RecyclerView.ViewHolder(binding.root) {
Expand All @@ -28,8 +30,16 @@ class ProductAdapter(private val productList: List<ProductData>)
}

override fun onBindViewHolder(holder: ProductViewHolder, position: Int) {
holder.bind(productList[position])
holder.bind(getItem(position))
}

override fun getItemCount(): Int = productList.size
class ProductDiffCallback : DiffUtil.ItemCallback<ProductData>() {
override fun areItemsTheSame(oldItem: ProductData, newItem: ProductData): Boolean {
return oldItem.name == newItem.name
}

override fun areContentsTheSame(oldItem: ProductData, newItem: ProductData): Boolean {
return oldItem == newItem
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.sungs.myapplication.R
import com.sungs.myapplication.data.ProductData
import com.sungs.myapplication.data.model.ProductData
import com.sungs.myapplication.databinding.ItemShopProductBinding

class ShopProductAdapter(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package com.sungs.myapplication.data.local

import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.sungs.myapplication.data.model.ProductData
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import javax.inject.Inject
import javax.inject.Singleton

val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "product_prefs")

@Singleton
class ProductDataStore @Inject constructor(
@ApplicationContext private val context: Context
) {

private val gson = Gson()

// 키 정의
private companion object {
val HOME_PRODUCTS_KEY = stringPreferencesKey("home_products")
val SHOP_PRODUCTS_KEY = stringPreferencesKey("shop_products")
val FAVORITE_NAMES_KEY = stringPreferencesKey("favorite_names")
}

// ── 상품 리스트 저장/로드 ──

suspend fun saveHomeProducts(products: List<ProductData>) {
context.dataStore.edit { prefs ->
prefs[HOME_PRODUCTS_KEY] = gson.toJson(products)
}
}

fun getHomeProducts(): Flow<List<ProductData>> {
return context.dataStore.data.map { prefs ->
val json = prefs[HOME_PRODUCTS_KEY]
if (json != null) {
gson.fromJson(json, object : TypeToken<List<ProductData>>() {}.type)
} else emptyList()
}
}

suspend fun saveShopProducts(products: List<ProductData>) {
context.dataStore.edit { prefs ->
prefs[SHOP_PRODUCTS_KEY] = gson.toJson(products)
}
}

fun getShopProducts(): Flow<List<ProductData>> {
return context.dataStore.data.map { prefs ->
val json = prefs[SHOP_PRODUCTS_KEY]
if (json != null) {
gson.fromJson(json, object : TypeToken<List<ProductData>>() {}.type)
} else emptyList()
}
}

// ── 즐겨찾기(하트) 관리 ──

suspend fun saveFavorites(favoriteNames: Set<String>) {
context.dataStore.edit { prefs ->
prefs[FAVORITE_NAMES_KEY] = gson.toJson(favoriteNames.toList())
}
}

fun getFavorites(): Flow<Set<String>> {
return context.dataStore.data.map { prefs ->
val json = prefs[FAVORITE_NAMES_KEY]
if (json != null) {
val list: List<String> = gson.fromJson(json, object : TypeToken<List<String>>() {}.type)
list.toSet()
} else emptySet()
}
}

suspend fun toggleFavorite(productName: String) {
context.dataStore.edit { prefs ->
val json = prefs[FAVORITE_NAMES_KEY]
val current: MutableSet<String> = if (json != null) {
val list: List<String> = gson.fromJson(json, object : TypeToken<List<String>>() {}.type)
list.toMutableSet()
} else mutableSetOf()

if (current.contains(productName)) current.remove(productName)
else current.add(productName)

prefs[FAVORITE_NAMES_KEY] = gson.toJson(current.toList())
}
}
}
Comment thread
sungs25 marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.sungs.myapplication.data.model

data class ProductData(
val name: String,
val category: String,
val price: String,
val imageResId: Int,
var isFavorite: Boolean = false
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.sungs.myapplication.data.remote.dto

import com.google.gson.annotations.SerializedName

// GET /api/users/{id} 응답
data class UserResponse(
val data: UserData,
val support: SupportData? = null
)

// GET /api/users?page=N 응답
data class UserListResponse(
val page: Int,
@SerializedName("per_page") val perPage: Int,
val total: Int,
@SerializedName("total_pages") val totalPages: Int,
val data: List<UserData>,
val support: SupportData? = null
)

// 실제 유저 데이터
data class UserData(
val id: Int,
val email: String,
@SerializedName("first_name") val firstName: String,
@SerializedName("last_name") val lastName: String,
val avatar: String
)

// ReqRes 응답에 붙어오는 홍보 문구
data class SupportData(
val url: String,
val text: String
)
Original file line number Diff line number Diff line change
@@ -1,37 +1,35 @@
package com.sungs.myapplication.data.repository

import android.content.Context
import com.sungs.myapplication.data.ProductData
import com.sungs.myapplication.data.ProductDataStore
import com.sungs.myapplication.data.model.ProductData
import com.sungs.myapplication.data.local.ProductDataStore
import com.sungs.myapplication.domain.repository.ProductLocalRepository
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class ProductLocalRepositoryImpl @Inject constructor(
@ApplicationContext private val context: Context
private val productDataStore: ProductDataStore
) : ProductLocalRepository {

override fun getHomeProducts(): Flow<List<ProductData>> =
ProductDataStore.getHomeProducts(context)
productDataStore.getHomeProducts()

Comment thread
sungs25 marked this conversation as resolved.
override suspend fun saveHomeProducts(products: List<ProductData>) {
ProductDataStore.saveHomeProducts(context, products)
productDataStore.saveHomeProducts(products)
}

override fun getShopProducts(): Flow<List<ProductData>> =
ProductDataStore.getShopProducts(context)
productDataStore.getShopProducts()

override suspend fun saveShopProducts(products: List<ProductData>) {
ProductDataStore.saveShopProducts(context, products)
productDataStore.saveShopProducts(products)
}

override fun getFavorites(): Flow<Set<String>> =
ProductDataStore.getFavorites(context)
productDataStore.getFavorites()

override suspend fun toggleFavorite(productName: String) {
ProductDataStore.toggleFavorite(context, productName)
productDataStore.toggleFavorite(productName)
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.sungs.myapplication.data.repository

import com.sungs.myapplication.data.UserData
import com.sungs.myapplication.data.remote.dto.UserData
import com.sungs.myapplication.domain.repository.UserRemoteRepository
import com.sungs.myapplication.network.ReqResService
import javax.inject.Inject
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.sungs.myapplication.domain.repository

import com.sungs.myapplication.data.ProductData
import com.sungs.myapplication.data.model.ProductData
import kotlinx.coroutines.flow.Flow


Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.sungs.myapplication.domain.repository

import com.sungs.myapplication.data.UserData
import com.sungs.myapplication.data.remote.dto.UserData

interface UserRemoteRepository {
suspend fun getUser(id: Int): UserData
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ class HomeFragment : Fragment() {

private val viewModel: HomeViewModel by viewModels()

private val productAdapter = ProductAdapter()

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
Expand All @@ -35,12 +37,22 @@ class HomeFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

binding.rvHomeProducts.layoutManager = GridLayoutManager(requireContext(), 2)
setupRecyclerView()
observeUiState()
}

private fun setupRecyclerView() {
binding.rvHomeProducts.apply {
layoutManager = GridLayoutManager(requireContext(), 2)
adapter = productAdapter
}
}

private fun observeUiState() {
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { state ->
binding.rvHomeProducts.adapter = ProductAdapter(state.products)
productAdapter.submitList(state.products)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,56 +5,23 @@ import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.sungs.myapplication.R
import com.sungs.myapplication.databinding.FragmentProfileEditBinding

// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private const val ARG_PARAM1 = "param1"
private const val ARG_PARAM2 = "param2"

/**
* A simple [Fragment] subclass.
* Use the [ProfileEditFragment.newInstance] factory method to
* create an instance of this fragment.
*/
class ProfileEditFragment : Fragment() {
// TODO: Rename and change types of parameters
private var param1: String? = null
private var param2: String? = null

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
param1 = it.getString(ARG_PARAM1)
param2 = it.getString(ARG_PARAM2)
}
}
private var _binding: FragmentProfileEditBinding? = null
private val binding get() = _binding!!

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_profile_edit, container, false)
): View {
_binding = FragmentProfileEditBinding.inflate(inflater, container, false)
return binding.root
}

companion object {
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @param param1 Parameter 1.
* @param param2 Parameter 2.
* @return A new instance of fragment ProfileEditFragment.
*/
// TODO: Rename and change types and number of parameters
@JvmStatic
fun newInstance(param1: String, param2: String) =
ProfileEditFragment().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
putString(ARG_PARAM2, param2)
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,15 @@ class ProfileFragment : Fragment() {
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { state ->
// 유저 프로필
binding.tvProfileNickname.text = state.nicknameText
state.user?.let { user ->
binding.tvProfileNickname.text = "${user.firstName} ${user.lastName}"
Glide.with(binding.ivProfileAvatar)
.load(user.avatar)
.into(binding.ivProfileAvatar)
Comment thread
sungs25 marked this conversation as resolved.
}

followAdapter.submitList(state.following)
binding.tvFollowingCount.text = "팔로잉 (${state.following.size})"
binding.tvFollowingCount.text = state.followingCountText
}
Comment thread
sungs25 marked this conversation as resolved.
}
}
Expand Down
Loading