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
18 changes: 15 additions & 3 deletions app/src/main/java/com/egobook/app/ui/home/HomeViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import com.egobook.app.ui.home.repository.UserRepository
import com.egobook.app.ui.home.user.Ink
import com.egobook.app.ui.home.user.Level
import com.egobook.app.ui.home.user.User
import com.egobook.app.ui.shop.CustomItem
import com.egobook.app.ui.shop.StoreRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
Expand All @@ -16,24 +18,34 @@ import javax.inject.Inject

@HiltViewModel
class HomeViewModel @Inject constructor(
private val userRepository: UserRepository
private val userRepository: UserRepository,
private val storeRepository: StoreRepository
) : ViewModel() {
private val _uiState = MutableStateFlow(User(Level(1), Ink(0)))
val uiState: StateFlow<User> = _uiState.asStateFlow()

private val _equippedItems = MutableStateFlow<List<CustomItem>>(emptyList())
val equippedItems: StateFlow<List<CustomItem>> = _equippedItems

init {
fetchUser()
fetchEquipItems()
}

fun fetchEquipItems() {
viewModelScope.launch {
_equippedItems.value = storeRepository.loadEquippedItems()
}
}

private fun fetchUser() {
fun fetchUser() {
viewModelScope.launch {
try {
val user = userRepository.load()
_uiState.value = user
} catch(error: Exception) {
Log.e("HomeViewModel", "Failed to fetch user", error)
}

}
}
}
51 changes: 51 additions & 0 deletions app/src/main/java/com/egobook/app/ui/home/ui/HomeFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController
import coil.load
import com.egobook.app.BlurLevel
import com.egobook.app.NotificationController
import com.egobook.app.R
Expand All @@ -20,6 +21,9 @@ import com.egobook.app.ui.home.repository.UserTendencyRepository
import com.egobook.app.ui.home.user.LevelType
import com.egobook.app.ui.home.ui.RadarDialog
import com.egobook.app.ui.home.ui.StreakDialog
import com.egobook.app.ui.shop.CustomItem
import com.egobook.app.ui.shop.ItemImage
import com.egobook.app.ui.shop.ItemType
import dagger.hilt.android.AndroidEntryPoint
import jakarta.inject.Inject
import kotlinx.coroutines.launch
Expand Down Expand Up @@ -47,6 +51,15 @@ class HomeFragment(): Fragment() {
}
}
}

viewLifecycleOwner.lifecycleScope.launch {
viewModel.equippedItems.collect { equippedList ->
equippedList.forEach { equippedItem ->
updateEquipItemUi(equippedItem)
}
}
}

binding.ivStore.setOnClickListener {
findNavController().navigate(R.id.action_homeFragment_to_storeFragment)
}
Expand Down Expand Up @@ -83,6 +96,44 @@ class HomeFragment(): Fragment() {
}
}

private fun updateEquipItemUi(item: CustomItem) {
when (item.type) {
ItemType.BACK -> {
if (item.outfitImage is ItemImage.Url) {
binding.ivHomeTurtleBack.load(item.outfitImage.path)
}
}
ItemType.SKIN -> {
if (item.outfitImage is ItemImage.Url) {
binding.ivHomeTurtleSkin.load(item.outfitImage.path)
}
}
ItemType.DECO_1 -> {
if (item.outfitImage is ItemImage.Url) {
if(item.outfitImage.path.contains("Default")) {
binding.ivHomeTurtleDeco1.load(null)
return
}
binding.ivHomeTurtleDeco1.load(item.outfitImage.path)
}
}
ItemType.DECO_2 -> {
if (item.outfitImage is ItemImage.Url) {
if(item.outfitImage.path.contains("Default")) {
binding.ivHomeTurtleDeco2.load(null)
return
}
binding.ivHomeTurtleDeco2.load(item.outfitImage.path)
}
}
ItemType.BACKGROUND -> {
if (item.outfitImage is ItemImage.Url) {
binding.ivHomeBackground.load(item.outfitImage.path)
}
}
}
}

private fun LevelType.getResId(): Int {
return when (this) {
LevelType.ONE -> R.drawable.level_type_1
Expand Down
39 changes: 36 additions & 3 deletions app/src/main/java/com/egobook/app/ui/shop/StoreRepository.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ import kotlinx.coroutines.flow.flow
import retrofit2.Retrofit
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.PATCH
import retrofit2.http.POST
import retrofit2.http.Query
import javax.inject.Inject
import javax.inject.Singleton

private fun String.toItemType(): ItemType = when(this) {
private fun String.toItemType(): ItemType = when (this) {
"BACK" -> ItemType.BACK
"SKIN" -> ItemType.SKIN
"DECOR_ONE" -> ItemType.DECO_1
Expand All @@ -23,6 +24,7 @@ interface StoreRepository {
fun loadStoreItems(itemType: ItemType): Flow<CustomItem>
suspend fun purchaseItems(item: CustomItem): PurchaseState
suspend fun loadEquippedItems(): List<CustomItem>
suspend fun permanentEquipItem(item: CustomItem): EquipState
}

data class BaseResponse<T>(
Expand All @@ -40,6 +42,15 @@ data class PurchaseState(
val isSuccess: Boolean
)

data class PermanentEquipRequest(
val itemId: Int,
val isEquipped: Boolean
)

data class EquipState(
val isSuccess: Boolean
)

interface NetworkStoreItemService {
@GET("/shop/items")
suspend fun loadItemsResponse(
Expand All @@ -59,6 +70,11 @@ interface NetworkEquippedItemService {
suspend fun loadEquippedItemsResponse(): BaseResponse<List<EquippedItemDto>>
}

interface NetworkPermanentEquipService {
@PATCH("/shop/equip")
suspend fun loadPermanentEquipResponse(@Body request: PermanentEquipRequest): BaseResponse<EquippedItemDto>
}

data class EquippedItemDto(
val itemId: Int,
val itemCategory: String,
Expand Down Expand Up @@ -124,6 +140,11 @@ class NetworkStoreRepository @Inject constructor(
retrofit.create(NetworkPurchaseItemService::class.java)
}

private val permanentEquipService by lazy {
retrofit.create(NetworkPermanentEquipService::class.java)
}


override fun loadStoreItems(itemType: ItemType): Flow<CustomItem> {
return flow {
var currentPage = 1
Expand Down Expand Up @@ -166,9 +187,10 @@ class NetworkStoreRepository @Inject constructor(

override suspend fun purchaseItems(item: CustomItem): PurchaseState {
try {
val response = purchaseItemService.loadPurchaseItemsResponse(PurchaseRequest(item.id.toInt()))
val response =
purchaseItemService.loadPurchaseItemsResponse(PurchaseRequest(item.id.toInt()))
return PurchaseState(isSuccess = true)
} catch(err: Exception) {
} catch (err: Exception) {
return PurchaseState(isSuccess = false)
}
}
Expand All @@ -178,5 +200,16 @@ class NetworkStoreRepository @Inject constructor(
equippedItemService.loadEquippedItemsResponse()
return equippedItemsResponse.data.map { it.toDomain() }
}

override suspend fun permanentEquipItem(item: CustomItem): EquipState {
try {
permanentEquipService.loadPermanentEquipResponse(
PermanentEquipRequest(itemId = item.id.toInt(), isEquipped = true)
)
return EquipState(isSuccess = true)
} catch (err: Exception) {
return EquipState(isSuccess = false)
}
}
}

22 changes: 21 additions & 1 deletion app/src/main/java/com/egobook/app/ui/shop/StoreViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,24 @@ class StoreViewModel @Inject constructor(
_equippedItems.update { currentList ->
currentList.filter { it.type != item.type } + item
}

if (item.itemStatus != ItemStatus.PURCHASABLE) {
viewModelScope.launch {
try {
val result = storeRepository.permanentEquipItem(item)
if (result.isSuccess) {
Log.d("StoreViewModel", "서버 착용 성공: ${item.id}")
loadEquippedItems()
} else {
_toastEvent.emit("아이템(${item.id}) 착용에 실패했습니다.")
}
} catch (e: Exception) {
Log.e("StoreViewModel", "서버 통신 에러", e)
}
}
} else {
Log.d("StoreViewModel", "미구매 아이템 - 프리뷰 모드")
}
}

fun loadPurchaseItem(): CustomItem? {
Expand All @@ -114,6 +132,8 @@ class StoreViewModel @Inject constructor(
if (purchaseState.isSuccess) {
loadItems(item.type, true)
loadInk()
val purchasedItem = item.copy(itemStatus = ItemStatus.PURCHASED)
equipItem(purchasedItem)
_toastEvent.emit("구매가 완료되었어요")
} else {
_toastEvent.emit("잉크가 부족해요")
Expand All @@ -123,6 +143,6 @@ class StoreViewModel @Inject constructor(


fun resetEquipItems() {
_equippedItems.value = emptyList()
loadEquippedItems()
}
}
47 changes: 47 additions & 0 deletions app/src/main/res/layout/fragment_home.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="@drawable/default_background">

<ImageView
android:id="@+id/iv_home_background"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleType="fitXY"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />

<ImageView
android:id="@+id/iv_level_type"
android:layout_width="48dp"
Expand Down Expand Up @@ -141,11 +151,48 @@
android:layout_width="304dp"
android:layout_height="197dp"
android:scaleType="fitXY"
android:visibility="invisible"
android:src="@drawable/default_turtle"
android:layout_marginTop="170dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/iv_store"/>

<ImageView
android:id="@+id/iv_home_turtle_back"
android:layout_width="304dp"
android:layout_height="197dp"
app:layout_constraintTop_toTopOf="@id/iv_turtle"
app:layout_constraintBottom_toBottomOf="@id/iv_turtle"
app:layout_constraintStart_toStartOf="@id/iv_turtle"
app:layout_constraintEnd_toEndOf="@id/iv_turtle"/>

<ImageView
android:id="@+id/iv_home_turtle_skin"
android:layout_width="304dp"
android:layout_height="197dp"
app:layout_constraintTop_toTopOf="@id/iv_turtle"
app:layout_constraintBottom_toBottomOf="@id/iv_turtle"
app:layout_constraintStart_toStartOf="@id/iv_turtle"
app:layout_constraintEnd_toEndOf="@id/iv_turtle"/>

<ImageView
android:id="@+id/iv_home_turtle_deco1"
android:layout_width="304dp"
android:layout_height="197dp"
app:layout_constraintTop_toTopOf="@id/iv_turtle"
app:layout_constraintBottom_toBottomOf="@id/iv_turtle"
app:layout_constraintStart_toStartOf="@id/iv_turtle"
app:layout_constraintEnd_toEndOf="@id/iv_turtle"/>

<ImageView
android:id="@+id/iv_home_turtle_deco2"
android:layout_width="304dp"
android:layout_height="197dp"
app:layout_constraintTop_toTopOf="@id/iv_turtle"
app:layout_constraintBottom_toBottomOf="@id/iv_turtle"
app:layout_constraintStart_toStartOf="@id/iv_turtle"
app:layout_constraintEnd_toEndOf="@id/iv_turtle"/>

<ImageView
android:id="@+id/iv_question"
Expand Down