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
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.nextroom.nextroom.presentation.base

import androidx.fragment.app.setFragmentResultListener
import androidx.navigation.fragment.findNavController
import androidx.viewbinding.ViewBinding
import com.nextroom.nextroom.presentation.NavGraphDirections
Expand All @@ -13,22 +14,38 @@ abstract class BaseViewModelFragment<VB : ViewBinding, VM : NewBaseViewModel>(pr
NewBaseFragment<VB>(inflate) {
abstract val viewModel: VM

private var pendingErrorAction: NewBaseViewModel.ErrorAction = NewBaseViewModel.ErrorAction.STAY

override fun initObserve() {
super.initObserve()

setFragmentResultListener(ERROR_DIALOG_KEY) { _, _ ->
val action = pendingErrorAction
pendingErrorAction = NewBaseViewModel.ErrorAction.STAY
if (action == NewBaseViewModel.ErrorAction.POP_BACK_STACK) {
findNavController().popBackStack()
}
}

viewLifecycleOwner.repeatOnStarted {
launch {
viewModel.errorFlow.collect {
viewModel.errorFlow.collect { event ->
pendingErrorAction = event.action
NavGraphDirections.moveToNrOneButtonDialog(
NROneButtonDialog.NROneButtonArgument(
title = getString(R.string.dialog_noti),
message = getString(R.string.error_something),
btnText = getString(R.string.text_confirm),
errorText = it.message,
errorText = event.throwable.message,
dialogKey = ERROR_DIALOG_KEY,
)
).also { findNavController().safeNavigate(it) }
}
}
}
}
}

companion object {
private const val ERROR_DIALOG_KEY = "base_view_model_error_dialog"
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.nextroom.nextroom.presentation.base

import androidx.fragment.app.setFragmentResultListener
import androidx.navigation.fragment.findNavController
import com.nextroom.nextroom.presentation.NavGraphDirections
import com.nextroom.nextroom.presentation.R
Expand All @@ -11,22 +12,38 @@ import kotlinx.coroutines.launch
abstract class ComposeBaseViewModelFragment<VM : NewBaseViewModel> : ComposeBaseFragment() {
abstract val viewModel: VM

private var pendingErrorAction: NewBaseViewModel.ErrorAction = NewBaseViewModel.ErrorAction.STAY

override fun initObserve() {
super.initObserve()

setFragmentResultListener(ERROR_DIALOG_KEY) { _, _ ->
val action = pendingErrorAction
pendingErrorAction = NewBaseViewModel.ErrorAction.STAY
if (action == NewBaseViewModel.ErrorAction.POP_BACK_STACK) {
findNavController().popBackStack()
}
}

viewLifecycleOwner.repeatOnStarted {
launch {
viewModel.errorFlow.collect {
viewModel.errorFlow.collect { event ->
pendingErrorAction = event.action
NavGraphDirections.moveToNrOneButtonDialog(
NROneButtonDialog.NROneButtonArgument(
title = getString(R.string.dialog_noti),
message = getString(R.string.error_something),
btnText = getString(R.string.text_confirm),
errorText = it.message,
errorText = event.throwable.message,
dialogKey = ERROR_DIALOG_KEY,
)
).also { findNavController().safeNavigate(it) }
}
}
}
}
}

companion object {
private const val ERROR_DIALOG_KEY = "compose_base_error_dialog"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import androidx.lifecycle.viewModelScope
import com.nextroom.nextroom.presentation.BuildConfig
import com.nextroom.nextroom.presentation.util.Logger
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.plus
import kotlin.coroutines.cancellation.CancellationException

Expand All @@ -20,19 +20,23 @@ abstract class NewBaseViewModel : ViewModel() {

protected val baseViewModelScope = viewModelScope + exceptionHandler

private val _errorFlow = MutableSharedFlow<Throwable>(extraBufferCapacity = 1)
val errorFlow = _errorFlow.asSharedFlow()
private val _errorChannel = Channel<ErrorEvent>(Channel.BUFFERED)
val errorFlow = _errorChannel.receiveAsFlow()

fun handleError(throwable: Throwable) {
fun handleError(throwable: Throwable, action: ErrorAction = ErrorAction.STAY) {
when (throwable) {
is CancellationException -> Unit
else -> {
if (!BuildConfig.DEBUG) {
Logger.e("${this::class.simpleName} generated\n${throwable.message}")
}

_errorFlow.tryEmit(throwable)
_errorChannel.trySend(ErrorEvent(throwable, action))
}
}
}
}

enum class ErrorAction { STAY, POP_BACK_STACK }

data class ErrorEvent(val throwable: Throwable, val action: ErrorAction)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,22 @@ package com.nextroom.nextroom.presentation.ui.mypage
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import androidx.core.view.isVisible
import android.view.ViewGroup
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.fragment.app.setFragmentResultListener
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import com.nextroom.nextroom.domain.model.SubscribeStatus
import com.nextroom.nextroom.presentation.NavGraphDirections
import com.nextroom.nextroom.presentation.R
import com.nextroom.nextroom.presentation.base.BaseFragment
import com.nextroom.nextroom.presentation.base.ComposeBaseViewModelFragment
import com.nextroom.nextroom.presentation.common.NRTwoButtonDialog
import com.nextroom.nextroom.presentation.databinding.FragmentMypageBinding
import com.nextroom.nextroom.presentation.common.compose.NRLoading
import com.nextroom.nextroom.presentation.extension.repeatOnStarted
import com.nextroom.nextroom.presentation.extension.safeNavigate
import com.nextroom.nextroom.presentation.extension.snackbar
Expand All @@ -22,70 +27,45 @@ import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch

@AndroidEntryPoint
class MypageFragment : BaseFragment<FragmentMypageBinding>(FragmentMypageBinding::inflate) {

private val viewModel: MypageViewModel by viewModels()

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

initViews()
initListeners()
initObserve()
setFragmentResultListeners()
}

private fun initViews() = with(binding) {
tbMypage.apply {
tvButton.isVisible = false
tvTitle.text = getString(R.string.mypage_title)
}
}
class MypageFragment : ComposeBaseViewModelFragment<MypageViewModel>() {

override val screenName: String = "mypage"

override val viewModel: MypageViewModel by viewModels()

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
return ComposeView(requireContext()).apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent {
val uiState by viewModel.uiState.collectAsState()
when (val state = uiState) {
is MypageViewModel.UiState.Loaded -> {
MypageScreen(
state = state,
onBackClick = { findNavController().popBackStack() },
onSubscribeClick = ::onSubscribeClick,
onChangeAppPasswordClick = ::moveToSetPassword,
onCustomerServiceClick = ::openCustomerService,
onLogoutClick = viewModel::logout,
onResignClick = ::showConfirmResignDialog,
)
}

private fun initListeners() = with(binding) {
tbMypage.ivBack.setOnClickListener { findNavController().popBackStack() }
tvLogoutButton.setOnClickListener { viewModel.logout() }
tvResignButton.setOnClickListener { showConfirmResignDialog() }
clSubscribe.setOnClickListener {
(viewModel.uiState.value as? MypageViewModel.UiState.Loaded)?.let { loaded ->
when (loaded.status) {
SubscribeStatus.SUBSCRIPTION_EXPIRATION,
SubscribeStatus.Default -> goToPurchase()

SubscribeStatus.Subscribed -> goToSubscriptionInfo()
MypageViewModel.UiState.Failure,
MypageViewModel.UiState.Loading -> NRLoading(true)
}
}
}
clChangeAppPassword.setOnClickListener {
moveToSetPassword()
}
clCustomerService.setOnClickListener {
try {
getString(R.string.link_official_instagram).let { url ->
Intent(Intent.ACTION_VIEW).apply { data = Uri.parse(url) }
}.also { startActivity(it) }
} catch (e: Exception) {
toast(getString(R.string.error_something))
}
}
}

private fun initObserve() {
override fun initObserve() {
super.initObserve()

viewLifecycleOwner.repeatOnStarted {
launch {
viewModel.uiState.collect { state ->
when (state) {
MypageViewModel.UiState.Failure -> snackbar(R.string.error_something)
is MypageViewModel.UiState.Loaded -> {
binding.tvShopName.text = state.shopName
binding.pbLoading.isVisible = false
binding.tvAppVersion.text = state.appVersion
}

MypageViewModel.UiState.Loading -> binding.pbLoading.isVisible = true
}
}
}
launch {
viewModel.uiEvent.collect { event ->
when (event) {
Expand All @@ -97,12 +77,33 @@ class MypageFragment : BaseFragment<FragmentMypageBinding>(FragmentMypageBinding
}
}

private fun setFragmentResultListeners() {
override fun setFragmentResultListeners() {
setFragmentResultListener(REQUEST_KEY_RESIGN) { _, _ ->
viewModel.resign()
}
}

private fun onSubscribeClick() {
val loaded = viewModel.uiState.value as? MypageViewModel.UiState.Loaded ?: return
when (loaded.status) {
SubscribeStatus.SUBSCRIPTION_EXPIRATION,
SubscribeStatus.Default -> goToPurchase()

SubscribeStatus.Subscribed -> goToSubscriptionInfo()
}
}

private fun openCustomerService() {
try {
val intent = Intent(Intent.ACTION_VIEW).apply {
data = Uri.parse(getString(R.string.link_official_instagram))
}
startActivity(intent)
} catch (e: Exception) {
toast(getString(R.string.error_something))
}
}

private fun goToPurchase() {
val action = NavGraphDirections.moveToPurchaseFragment()
findNavController().safeNavigate(action)
Expand Down
Loading
Loading