feat(gallery): 로딩 다이얼로그 표시 및 이미지 캐싱 적용
Fragment에서 isLoading에 따라 Loading Dialog를 표시/해제. Glide에 디스크 캐싱 적용으로 스크롤 성능 개선.
This commit is contained in:
		@@ -6,6 +6,7 @@ import android.view.View
 | 
			
		||||
import android.view.ViewGroup
 | 
			
		||||
import androidx.recyclerview.widget.RecyclerView
 | 
			
		||||
import com.bumptech.glide.Glide
 | 
			
		||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
 | 
			
		||||
import kr.co.vividnext.sodalive.databinding.ItemCharacterGalleryBinding
 | 
			
		||||
 | 
			
		||||
class CharacterGalleryAdapter(
 | 
			
		||||
@@ -19,6 +20,7 @@ class CharacterGalleryAdapter(
 | 
			
		||||
        fun bind(item: CharacterImageListItemResponse) {
 | 
			
		||||
            Glide.with(binding.ivImage)
 | 
			
		||||
                .load(item.imageUrl)
 | 
			
		||||
                .diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
 | 
			
		||||
                .into(binding.ivImage)
 | 
			
		||||
 | 
			
		||||
            if (item.isOwned) {
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@ import androidx.recyclerview.widget.RecyclerView
 | 
			
		||||
import kr.co.vividnext.sodalive.base.BaseFragment
 | 
			
		||||
import kr.co.vividnext.sodalive.chat.character.detail.CharacterDetailActivity.Companion.EXTRA_CHARACTER_ID
 | 
			
		||||
import kr.co.vividnext.sodalive.common.GridSpacingItemDecoration
 | 
			
		||||
import kr.co.vividnext.sodalive.common.LoadingDialog
 | 
			
		||||
import kr.co.vividnext.sodalive.databinding.FragmentCharacterGalleryBinding
 | 
			
		||||
import kr.co.vividnext.sodalive.extensions.dpToPx
 | 
			
		||||
import org.koin.androidx.viewmodel.ext.android.viewModel
 | 
			
		||||
@@ -21,7 +22,9 @@ class CharacterGalleryFragment : BaseFragment<FragmentCharacterGalleryBinding>(
 | 
			
		||||
    FragmentCharacterGalleryBinding::inflate
 | 
			
		||||
) {
 | 
			
		||||
    private val viewModel: CharacterGalleryViewModel by viewModel()
 | 
			
		||||
 | 
			
		||||
    private lateinit var adapter: CharacterGalleryAdapter
 | 
			
		||||
    private lateinit var loadingDialog: LoadingDialog
 | 
			
		||||
 | 
			
		||||
    private val characterId: Long by lazy {
 | 
			
		||||
        arguments?.getLong("arg_character_id")
 | 
			
		||||
@@ -30,8 +33,11 @@ class CharacterGalleryFragment : BaseFragment<FragmentCharacterGalleryBinding>(
 | 
			
		||||
 | 
			
		||||
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
 | 
			
		||||
        super.onViewCreated(view, savedInstanceState)
 | 
			
		||||
 | 
			
		||||
        loadingDialog = LoadingDialog(requireActivity(), layoutInflater)
 | 
			
		||||
        setupRecyclerView()
 | 
			
		||||
        observeState()
 | 
			
		||||
 | 
			
		||||
        viewModel.loadInitial(characterId)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -70,6 +76,13 @@ class CharacterGalleryFragment : BaseFragment<FragmentCharacterGalleryBinding>(
 | 
			
		||||
                View.GONE
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // 로딩 다이얼로그 표시/해제
 | 
			
		||||
            if (state.isLoading) {
 | 
			
		||||
                loadingDialog.show(screenWidth)
 | 
			
		||||
            } else {
 | 
			
		||||
                hideLoadingDialog()
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (state.items.isNotEmpty() && !state.isLoading) {
 | 
			
		||||
                binding.rvGallery.visibility = View.VISIBLE
 | 
			
		||||
                binding.clRatio.visibility = View.VISIBLE
 | 
			
		||||
@@ -117,4 +130,13 @@ class CharacterGalleryFragment : BaseFragment<FragmentCharacterGalleryBinding>(
 | 
			
		||||
                viewModel.purchaseImage(item.id, position)
 | 
			
		||||
            }.show()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun hideLoadingDialog() {
 | 
			
		||||
        loadingDialog.dismiss()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onDestroyView() {
 | 
			
		||||
        hideLoadingDialog()
 | 
			
		||||
        super.onDestroyView()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -39,7 +39,6 @@ class CharacterGalleryViewModel(
 | 
			
		||||
        isLastPage = false
 | 
			
		||||
        isRequesting = false
 | 
			
		||||
        accumulatedItems.clear()
 | 
			
		||||
        _uiState.value = UiState(isLoading = true)
 | 
			
		||||
        request(page = currentPage)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -51,6 +50,7 @@ class CharacterGalleryViewModel(
 | 
			
		||||
    private fun request(page: Int) {
 | 
			
		||||
        val token = "Bearer ${SharedPreferenceManager.token}"
 | 
			
		||||
        isRequesting = true
 | 
			
		||||
        _uiState.value = _uiState.value?.copy(isLoading = isRequesting || isPurchasing)
 | 
			
		||||
        compositeDisposable.add(
 | 
			
		||||
            repository.getCharacterImageList(
 | 
			
		||||
                token = token,
 | 
			
		||||
@@ -63,6 +63,9 @@ class CharacterGalleryViewModel(
 | 
			
		||||
                .subscribe({ response ->
 | 
			
		||||
                    val success = response.success
 | 
			
		||||
                    val data = response.data
 | 
			
		||||
 | 
			
		||||
                    isRequesting = false
 | 
			
		||||
 | 
			
		||||
                    if (success && data != null) {
 | 
			
		||||
                        // 누적 처리
 | 
			
		||||
                        val newItems = data.items
 | 
			
		||||
@@ -88,18 +91,17 @@ class CharacterGalleryViewModel(
 | 
			
		||||
                        )
 | 
			
		||||
                    } else {
 | 
			
		||||
                        _uiState.value = _uiState.value?.copy(
 | 
			
		||||
                            isLoading = false,
 | 
			
		||||
                            isLoading = isRequesting || isPurchasing,
 | 
			
		||||
                            error = response.message ?: "갤러리 정보를 불러오지 못했습니다."
 | 
			
		||||
                        )
 | 
			
		||||
                    }
 | 
			
		||||
                    isRequesting = false
 | 
			
		||||
                }, { throwable ->
 | 
			
		||||
                    isRequesting = false
 | 
			
		||||
                    Logger.e(throwable, throwable.message ?: "")
 | 
			
		||||
                    _uiState.value = _uiState.value?.copy(
 | 
			
		||||
                        isLoading = false,
 | 
			
		||||
                        isLoading = isRequesting || isPurchasing,
 | 
			
		||||
                        error = "네트워크 오류가 발생했습니다. 잠시 후 다시 시도해 주세요."
 | 
			
		||||
                    )
 | 
			
		||||
                    isRequesting = false
 | 
			
		||||
                })
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
@@ -112,13 +114,18 @@ class CharacterGalleryViewModel(
 | 
			
		||||
 | 
			
		||||
        val token = "Bearer ${SharedPreferenceManager.token}"
 | 
			
		||||
        isPurchasing = true
 | 
			
		||||
        _uiState.value = _uiState.value?.copy(
 | 
			
		||||
            isLoading = isRequesting || isPurchasing,
 | 
			
		||||
        )
 | 
			
		||||
        compositeDisposable.add(
 | 
			
		||||
            repository.purchaseCharacterImage(token = token, imageId = imageId)
 | 
			
		||||
                .subscribeOn(Schedulers.io())
 | 
			
		||||
                .observeOn(AndroidSchedulers.mainThread())
 | 
			
		||||
                .subscribe({ response ->
 | 
			
		||||
                .subscribe(
 | 
			
		||||
                    { response ->
 | 
			
		||||
                        val success = response.success
 | 
			
		||||
                        val data = response.data
 | 
			
		||||
                        isPurchasing = false
 | 
			
		||||
                        if (success && data != null) {
 | 
			
		||||
                            // 응답 imageUrl로 교체, 소유 상태 true로 변경
 | 
			
		||||
                            val updated = target.copy(
 | 
			
		||||
@@ -141,6 +148,7 @@ class CharacterGalleryViewModel(
 | 
			
		||||
                                ownedCount = ownedAfter,
 | 
			
		||||
                                ratio = ratio,
 | 
			
		||||
                                items = accumulatedItems.toList(),
 | 
			
		||||
                                isLoading = isRequesting || isPurchasing,
 | 
			
		||||
                                error = null
 | 
			
		||||
                            )
 | 
			
		||||
                        } else {
 | 
			
		||||
@@ -148,14 +156,16 @@ class CharacterGalleryViewModel(
 | 
			
		||||
                                error = response.message ?: "구매에 실패했습니다."
 | 
			
		||||
                            )
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                    { throwable ->
 | 
			
		||||
                        isPurchasing = false
 | 
			
		||||
                }, { throwable ->
 | 
			
		||||
                        Logger.e(throwable, throwable.message ?: "")
 | 
			
		||||
                        _uiState.value = _uiState.value?.copy(
 | 
			
		||||
                            isLoading = isRequesting || isPurchasing,
 | 
			
		||||
                            error = "네트워크 오류가 발생했습니다. 잠시 후 다시 시도해 주세요."
 | 
			
		||||
                        )
 | 
			
		||||
                    isPurchasing = false
 | 
			
		||||
                })
 | 
			
		||||
                    }
 | 
			
		||||
                )
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user