Skip to content
Merged
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
3 changes: 2 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ dependencies {
implementation("androidx.hilt:hilt-navigation-fragment:1.2.0")
implementation(libs.androidx.annotation)
implementation(libs.androidx.lifecycle.livedata.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
ksp("com.google.dagger:hilt-compiler:2.54")
implementation("androidx.hilt:hilt-navigation-compose:1.2.0")
implementation("androidx.navigation:navigation-fragment-ktx:2.8.5")
Expand Down Expand Up @@ -165,4 +166,4 @@ dependencies {
ksp {
arg("dagger.fastInit", "enabled")
arg("dagger.hilt.android.internal.projectType", "APP")
}
}
28 changes: 28 additions & 0 deletions app/src/main/java/com/egobook/app/ui/shop/StoreFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@ import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController
import androidx.viewpager2.widget.ViewPager2
import coil.load
Expand All @@ -27,6 +30,7 @@ class StoreFragment: Fragment() {
private var _binding: FragmentStoreBinding? = null
private val binding get() = checkNotNull(_binding) { "Fragment가 제거되었습니다." }
private lateinit var viewPager: ViewPager2
private val viewModel: StoreViewModel by activityViewModels()

private var lastSelected = -1

Expand Down Expand Up @@ -71,6 +75,19 @@ class StoreFragment: Fragment() {
dialog.show(parentFragmentManager, "StoreLeavingDialog")
}

binding.tvPurchase.setOnClickListener {
if(viewModel.loadPurchaseItem() != null) {
applyScreenBlur(BlurLevel.BASE)
val dialog = StorePurchasingItemDialog()
dialog.isCancelable = false
dialog.show(parentFragmentManager, "StorePurchasingItemDialog")
}
}

binding.ivReset.setOnClickListener {
viewModel.loadEquippedItems()
}



viewLifecycleOwner.lifecycleScope.launch {
Expand All @@ -81,6 +98,8 @@ class StoreFragment: Fragment() {
}
}

observeViewModel()

}

override fun onDestroyView() {
Expand Down Expand Up @@ -127,6 +146,15 @@ class StoreFragment: Fragment() {
}
}

private fun observeViewModel() {
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.toastEvent.collect { message ->
Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show()
}
}
}
}
}


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.egobook.app.ui.shop

import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import com.egobook.app.R
import com.egobook.app.databinding.DialogLeavingStoreBinding
import com.egobook.app.databinding.DialogPurchasingItemBinding
import com.egobook.app.removeScreenBlur

class StorePurchasingItemDialog: DialogFragment() {
private var _binding: DialogPurchasingItemBinding? = null
private val binding get() = checkNotNull(_binding) { "Fragment가 제거되었습니다." }

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
dialog?.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
_binding = DialogPurchasingItemBinding.inflate(inflater, container, false)
return binding.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

val viewModel: StoreViewModel by activityViewModels()

val item = viewModel.loadPurchaseItem()

binding.tvPrice.text = checkNotNull(item).price.value.toString()

binding.btnBack.setOnClickListener {
removeScreenBlur()
dismiss()
}


binding.btnBuy.setOnClickListener {
if (item != null) {
viewModel.purchaseItem(item)
}
removeScreenBlur()
dismiss()
}
}


override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
29 changes: 29 additions & 0 deletions app/src/main/java/com/egobook/app/ui/shop/StoreRepository.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package com.egobook.app.ui.shop
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import retrofit2.Retrofit
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.Query
import javax.inject.Inject
import javax.inject.Singleton
Expand All @@ -19,6 +21,7 @@ private fun String.toItemType(): ItemType = when(this) {

interface StoreRepository {
fun loadStoreItems(itemType: ItemType): Flow<CustomItem>
suspend fun purchaseItems(item: CustomItem): PurchaseState
suspend fun loadEquippedItems(): List<CustomItem>
}

Expand All @@ -29,6 +32,14 @@ data class BaseResponse<T>(
val data: T
)

data class PurchaseRequest(
val itemId: Int
)

data class PurchaseState(
val isSuccess: Boolean
)

interface NetworkStoreItemService {
@GET("/shop/items")
suspend fun loadItemsResponse(
Expand All @@ -38,6 +49,11 @@ interface NetworkStoreItemService {
): BaseResponse<CustomItemGroupDto>
}

interface NetworkPurchaseItemService {
@POST("/shop/purchase")
suspend fun loadPurchaseItemsResponse(@Body request: PurchaseRequest): BaseResponse<EquippedItemDto>
}

interface NetworkEquippedItemService {
@GET("/shop/items/equipped")
suspend fun loadEquippedItemsResponse(): BaseResponse<List<EquippedItemDto>>
Expand Down Expand Up @@ -104,6 +120,10 @@ class NetworkStoreRepository @Inject constructor(
retrofit.create(NetworkEquippedItemService::class.java)
}

private val purchaseItemService by lazy {
retrofit.create(NetworkPurchaseItemService::class.java)
}

override fun loadStoreItems(itemType: ItemType): Flow<CustomItem> {
return flow {
var currentPage = 1
Expand Down Expand Up @@ -144,6 +164,15 @@ class NetworkStoreRepository @Inject constructor(
}
}

override suspend fun purchaseItems(item: CustomItem): PurchaseState {
try {
val response = purchaseItemService.loadPurchaseItemsResponse(PurchaseRequest(item.id.toInt()))
return PurchaseState(isSuccess = true)
} catch(err: Exception) {
return PurchaseState(isSuccess = false)
}
}

override suspend fun loadEquippedItems(): List<CustomItem> {
val equippedItemsResponse: BaseResponse<List<EquippedItemDto>> =
equippedItemService.loadEquippedItemsResponse()
Expand Down
60 changes: 45 additions & 15 deletions app/src/main/java/com/egobook/app/ui/shop/StoreViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
Expand All @@ -22,11 +24,12 @@ data class CustomItemState(
class StoreViewModel @Inject constructor(
private val storeRepository: StoreRepository
) : ViewModel() {

private val _toastEvent = MutableSharedFlow<String>()
val toastEvent = _toastEvent.asSharedFlow()
private val _equippedItems = MutableStateFlow<List<CustomItem>>(emptyList())
val equippedItems: StateFlow<List<CustomItem>> = _equippedItems
private val _items = MutableStateFlow<Map<ItemType, List<CustomItem>>>(mutableMapOf())
val items: StateFlow<Map<ItemType, List<CustomItem>>> = _items.asStateFlow()

val itemStates: StateFlow<Map<ItemType, List<CustomItemState>>> =
combine(_items, _equippedItems) { itemsMap, equippedList ->

Expand All @@ -45,20 +48,21 @@ class StoreViewModel @Inject constructor(
started = SharingStarted.WhileSubscribed(5000),
initialValue = emptyMap()
)
fun loadItems(type: ItemType) {
if (_items.value.containsKey(type)) return
viewModelScope.launch {
storeRepository.loadStoreItems(type)
.collect { newItem ->
val loadedList = mutableListOf<CustomItem>()
storeRepository.loadStoreItems(type).collect { newItem ->
loadedList.add(newItem)
}
fun loadItems(type: ItemType, forceRefresh: Boolean = false) {
if (!forceRefresh && _items.value.containsKey(type)) return

_items.update { currentMap ->
currentMap + (type to loadedList)
}
viewModelScope.launch {
try {
val loadedList = mutableListOf<CustomItem>()
storeRepository.loadStoreItems(type).collect { newItem ->
loadedList.add(newItem)
}
_items.update { currentMap ->
currentMap + (type to loadedList)
}
} catch (err: Exception) {
Log.e("jang", "$err")
}
}
}

Expand All @@ -67,7 +71,6 @@ class StoreViewModel @Inject constructor(
_equippedItems.value = storeRepository.loadEquippedItems()
Log.d("jang", "load: ${_equippedItems.value}")
}

}

fun equipItem(item: CustomItem) {
Expand All @@ -76,6 +79,33 @@ class StoreViewModel @Inject constructor(
}
}

fun loadPurchaseItem(): CustomItem? {
val purchasableItems = _equippedItems.value.filter { it.itemStatus == ItemStatus.PURCHASABLE }
if (purchasableItems.isEmpty()){
viewModelScope.launch {
_toastEvent.emit("구매할 수 없는 상품입니다")
}
return null
}
return purchasableItems.first()
}

fun purchaseItem(item: CustomItem) {
val purchasableItems = _equippedItems.value.filter { it.itemStatus == ItemStatus.PURCHASABLE }
if (purchasableItems.isEmpty()) { return }
val item = purchasableItems.first()
viewModelScope.launch {
val purchaseState = storeRepository.purchaseItems(item)
if (purchaseState.isSuccess) {
loadItems(item.type, true)
_toastEvent.emit("구매가 완료되었어요")
} else {
_toastEvent.emit("잉크가 부족해요")
}
}
}


fun resetEquipItems() {
_equippedItems.value = emptyList()
}
Expand Down
Loading