diff --git a/common-ui/src/main/java/com/lgtm/android/common_ui/adapter/MissionSuggestionAdapter.kt b/common-ui/src/main/java/com/lgtm/android/common_ui/adapter/MissionSuggestionAdapter.kt deleted file mode 100644 index ee53bc5d..00000000 --- a/common-ui/src/main/java/com/lgtm/android/common_ui/adapter/MissionSuggestionAdapter.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.lgtm.android.common_ui.adapter - -import android.view.ViewGroup -import androidx.recyclerview.widget.ListAdapter -import com.lgtm.android.common_ui.util.ItemDiffCallback -import com.lgtm.android.common_ui.viewholder.MissionSuggestionBaseViewHolder -import com.lgtm.android.common_ui.viewholder.MissionSuggestionContentViewHolder -import com.lgtm.android.common_ui.viewholder.getSuggestionViewHolder -import com.lgtm.domain.mission_suggestion.SuggestionContent -import com.lgtm.domain.mission_suggestion.SuggestionViewType - -class MissionSuggestionAdapter( - private val onSuggestionClickListener: (Int) -> Unit -): ListAdapter( - ItemDiffCallback( - onContentsTheSame = { old, new -> old == new }, - onItemsTheSame = { old, new -> old == new } - ) -) { - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MissionSuggestionBaseViewHolder { - val viewHolder = getSuggestionViewHolder(parent, SuggestionViewType.getViewTypeByOrdinal(viewType)) - - when(viewType) { - SuggestionViewType.CONTENT.ordinal -> setOnSuggestionClickListener(viewHolder) - else -> Unit - } - - return viewHolder - } - - override fun onBindViewHolder(holder: MissionSuggestionBaseViewHolder, position: Int) { - holder.onBind(getItem(position)) - } - - override fun getItemViewType(position: Int): Int { - return getItem(position).viewType.ordinal - } - - private fun setOnSuggestionClickListener(viewHolder: MissionSuggestionBaseViewHolder) { - viewHolder as MissionSuggestionContentViewHolder - viewHolder.setNavigateToSuggestionDetail(onSuggestionClickListener) - } -} \ No newline at end of file diff --git a/common-ui/src/main/java/com/lgtm/android/common_ui/viewholder/MissionSuggestionBaseViewHolder.kt b/common-ui/src/main/java/com/lgtm/android/common_ui/viewholder/MissionSuggestionBaseViewHolder.kt deleted file mode 100644 index 43e5e572..00000000 --- a/common-ui/src/main/java/com/lgtm/android/common_ui/viewholder/MissionSuggestionBaseViewHolder.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.lgtm.android.common_ui.viewholder - -import androidx.databinding.ViewDataBinding -import androidx.recyclerview.widget.RecyclerView -import com.lgtm.domain.mission_suggestion.SuggestionContent - -abstract class MissionSuggestionBaseViewHolder( - binding: ViewDataBinding -): RecyclerView.ViewHolder(binding.root) { - abstract fun onBind(item: SuggestionContent) -} \ No newline at end of file diff --git a/common-ui/src/main/java/com/lgtm/android/common_ui/viewholder/MissionSuggestionContentViewHolder.kt b/common-ui/src/main/java/com/lgtm/android/common_ui/viewholder/MissionSuggestionContentViewHolder.kt deleted file mode 100644 index de7d3691..00000000 --- a/common-ui/src/main/java/com/lgtm/android/common_ui/viewholder/MissionSuggestionContentViewHolder.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.lgtm.android.common_ui.viewholder - -import com.lgtm.android.common_ui.databinding.ItemMissionSuggestionBinding -import com.lgtm.android.common_ui.model.SuggestionUI -import com.lgtm.android.common_ui.util.setOnThrottleClickListener -import com.lgtm.domain.mission_suggestion.SuggestionContent - -class MissionSuggestionContentViewHolder( - private val binding: ItemMissionSuggestionBinding -): MissionSuggestionBaseViewHolder(binding) { - private lateinit var navigateToSuggestionDetail: (Int) -> Unit - - override fun onBind(item: SuggestionContent) { - item as SuggestionUI - binding.data = item - binding.clMissionSuggestion.setOnThrottleClickListener { navigateToSuggestionDetail(item.suggestionId) } - } - - fun setNavigateToSuggestionDetail(navigateToSuggestionDetail: (Int) -> Unit) { - this.navigateToSuggestionDetail = navigateToSuggestionDetail - } -} \ No newline at end of file diff --git a/common-ui/src/main/java/com/lgtm/android/common_ui/viewholder/MissionSuggestionEmptyViewHolder.kt b/common-ui/src/main/java/com/lgtm/android/common_ui/viewholder/MissionSuggestionEmptyViewHolder.kt deleted file mode 100644 index 97e81440..00000000 --- a/common-ui/src/main/java/com/lgtm/android/common_ui/viewholder/MissionSuggestionEmptyViewHolder.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.lgtm.android.common_ui.viewholder - -import com.lgtm.android.common_ui.databinding.ItemMissionSuggestionEmptyBinding -import com.lgtm.domain.mission_suggestion.SuggestionContent - -class MissionSuggestionEmptyViewHolder ( - binding: ItemMissionSuggestionEmptyBinding -): MissionSuggestionBaseViewHolder(binding) { - override fun onBind(item: SuggestionContent) { - /* no-op */ - } -} \ No newline at end of file diff --git a/common-ui/src/main/java/com/lgtm/android/common_ui/viewholder/MissionSuggestionHeaderViewHolder.kt b/common-ui/src/main/java/com/lgtm/android/common_ui/viewholder/MissionSuggestionHeaderViewHolder.kt deleted file mode 100644 index 93a10003..00000000 --- a/common-ui/src/main/java/com/lgtm/android/common_ui/viewholder/MissionSuggestionHeaderViewHolder.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.lgtm.android.common_ui.viewholder - -import com.lgtm.android.common_ui.databinding.ItemMissionSuggestionInfoBinding -import com.lgtm.domain.mission_suggestion.SuggestionContent -import com.lgtm.domain.mission_suggestion.SuggestionHeaderVO - -class MissionSuggestionHeaderViewHolder( - private val binding: ItemMissionSuggestionInfoBinding -): MissionSuggestionBaseViewHolder(binding) { - override fun onBind(item: SuggestionContent) { - binding.data = item as SuggestionHeaderVO - } -} \ No newline at end of file diff --git a/common-ui/src/main/java/com/lgtm/android/common_ui/viewholder/SuggestionViewHolderFactory.kt b/common-ui/src/main/java/com/lgtm/android/common_ui/viewholder/SuggestionViewHolderFactory.kt deleted file mode 100644 index e12ab3cd..00000000 --- a/common-ui/src/main/java/com/lgtm/android/common_ui/viewholder/SuggestionViewHolderFactory.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.lgtm.android.common_ui.viewholder - -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.databinding.DataBindingUtil -import androidx.databinding.ViewDataBinding -import com.lgtm.android.common_ui.R -import com.lgtm.android.common_ui.databinding.ItemMissionSuggestionBinding -import com.lgtm.android.common_ui.databinding.ItemMissionSuggestionEmptyBinding -import com.lgtm.android.common_ui.databinding.ItemMissionSuggestionInfoBinding -import com.lgtm.domain.mission_suggestion.SuggestionViewType - -fun getSuggestionViewHolder(parent: ViewGroup, viewType: SuggestionViewType): MissionSuggestionBaseViewHolder { - val layout = getLayoutByViewType(viewType) - val binding: ViewDataBinding = DataBindingUtil.inflate(LayoutInflater.from(parent.context), layout, parent, false) - - return when(viewType) { - SuggestionViewType.CONTENT -> MissionSuggestionContentViewHolder(binding as ItemMissionSuggestionBinding) - SuggestionViewType.HEADER -> MissionSuggestionHeaderViewHolder(binding as ItemMissionSuggestionInfoBinding) - SuggestionViewType.EMPTY -> MissionSuggestionEmptyViewHolder(binding as ItemMissionSuggestionEmptyBinding) - } - -} - -fun getLayoutByViewType(viewType: SuggestionViewType): Int { - - return when(viewType) { - SuggestionViewType.CONTENT -> R.layout.item_mission_suggestion - SuggestionViewType.HEADER -> R.layout.item_mission_suggestion_info - SuggestionViewType.EMPTY -> R.layout.item_mission_suggestion_empty - } - -} \ No newline at end of file diff --git a/common-ui/src/main/res/layout/item_mission_suggestion.xml b/common-ui/src/main/res/layout/item_mission_suggestion.xml deleted file mode 100644 index f5ca8f37..00000000 --- a/common-ui/src/main/res/layout/item_mission_suggestion.xml +++ /dev/null @@ -1,128 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/common-ui/src/main/res/layout/item_mission_suggestion_empty.xml b/common-ui/src/main/res/layout/item_mission_suggestion_empty.xml deleted file mode 100644 index 1d3e22b7..00000000 --- a/common-ui/src/main/res/layout/item_mission_suggestion_empty.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/common-ui/src/main/res/layout/item_mission_suggestion_info.xml b/common-ui/src/main/res/layout/item_mission_suggestion_info.xml deleted file mode 100644 index ac82d60f..00000000 --- a/common-ui/src/main/res/layout/item_mission_suggestion_info.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/feature/mission_suggestion/src/main/java/com/lgtm/android/mission_suggestion/ui/dashboard/SuggestionDashboardActivity.kt b/feature/mission_suggestion/src/main/java/com/lgtm/android/mission_suggestion/ui/dashboard/SuggestionDashboardActivity.kt index 4517e338..240fafbe 100644 --- a/feature/mission_suggestion/src/main/java/com/lgtm/android/mission_suggestion/ui/dashboard/SuggestionDashboardActivity.kt +++ b/feature/mission_suggestion/src/main/java/com/lgtm/android/mission_suggestion/ui/dashboard/SuggestionDashboardActivity.kt @@ -2,75 +2,68 @@ package com.lgtm.android.mission_suggestion.ui.dashboard import android.os.Bundle import androidx.activity.viewModels -import com.lgtm.android.common_ui.R.dimen -import com.lgtm.android.common_ui.adapter.MissionSuggestionAdapter -import com.lgtm.android.common_ui.base.BaseActivity -import com.lgtm.android.common_ui.util.ItemDecorationUtil -import com.lgtm.android.common_ui.util.setOnThrottleClickListener -import com.lgtm.android.mission_suggestion.R -import com.lgtm.android.mission_suggestion.databinding.ActivitySuggestionDashboardBinding +import androidx.compose.runtime.Composable +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import com.lgtm.android.common_ui.base.BaseComposeActivity +import com.lgtm.android.common_ui.theme.LGTMTheme +import com.lgtm.android.mission_suggestion.ui.dashboard.presentation.SuggestionDashboardScreen +import com.lgtm.android.mission_suggestion.ui.dashboard.presentation.contract.SuggestionDashboardUiEffect import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.launch @AndroidEntryPoint -class SuggestionDashboardActivity : BaseActivity(R.layout.activity_suggestion_dashboard) { +class SuggestionDashboardActivity : BaseComposeActivity() { private val suggestionDashboardViewModel by viewModels() - private lateinit var missionSuggestionAdapter: MissionSuggestionAdapter + override fun initializeViewModel() { viewModel = suggestionDashboardViewModel } + @Composable + override fun Content() { + LGTMTheme { + SuggestionDashboardScreen() + } + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setupBindingData() - addDashboardItemDecoration() - observeMissionSuggestion() - initAdapter() - setBackButtonClickListener() - setCreateSuggestionButtonClickListener() + observeUiEffect() } override fun onResume() { super.onResume() - suggestionDashboardViewModel.fetchSuggestionList() - } - - private fun setupBindingData() { - binding.viewModel = suggestionDashboardViewModel - } - - private fun observeMissionSuggestion() { - suggestionDashboardViewModel.suggestionList.observe(this) { - missionSuggestionAdapter.submitList(it) - } - } - - private fun addDashboardItemDecoration() { - val topMarginItemDecoration = ItemDecorationUtil.TopMarginItemDecoration(dimen.item_top_margin) - binding.rvMissionSuggestion.addItemDecoration(topMarginItemDecoration) - } - - private fun initAdapter() { - missionSuggestionAdapter = MissionSuggestionAdapter(::onClickSuggestionItem) - binding.rvMissionSuggestion.adapter = missionSuggestionAdapter + fetchSuggestionList() } - private fun onClickSuggestionItem(suggestionId: Int) { - moveToSuggestionDetail(suggestionId) + private fun fetchSuggestionList() { + suggestionDashboardViewModel.fetchSuggestionList() } - private fun moveToSuggestionDetail(suggestionId: Int) { - lgtmNavigator.navigateToSuggestionDetail(this, suggestionId) - } + private fun observeUiEffect() { + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + suggestionDashboardViewModel.suggestionDashboardUiEffect.collect { effect -> + when (effect) { + is SuggestionDashboardUiEffect.GoBack -> { + finish() + } - private fun setBackButtonClickListener() { - binding.ivBack.setOnThrottleClickListener { - finish() - } - } + is SuggestionDashboardUiEffect.CreateSuggestion -> { + lgtmNavigator.navigateToCreateSuggestion(this@SuggestionDashboardActivity) + } - private fun setCreateSuggestionButtonClickListener() { - binding.btnCreateSuggestion.setOnThrottleClickListener { - lgtmNavigator.navigateToCreateSuggestion(this) + is SuggestionDashboardUiEffect.SuggestionDetail -> { + lgtmNavigator.navigateToSuggestionDetail( + this@SuggestionDashboardActivity, + effect.suggestionId + ) + } + } + } + } } } } \ No newline at end of file diff --git a/feature/mission_suggestion/src/main/java/com/lgtm/android/mission_suggestion/ui/dashboard/SuggestionDashboardViewModel.kt b/feature/mission_suggestion/src/main/java/com/lgtm/android/mission_suggestion/ui/dashboard/SuggestionDashboardViewModel.kt index be89399c..dbf1aa49 100644 --- a/feature/mission_suggestion/src/main/java/com/lgtm/android/mission_suggestion/ui/dashboard/SuggestionDashboardViewModel.kt +++ b/feature/mission_suggestion/src/main/java/com/lgtm/android/mission_suggestion/ui/dashboard/SuggestionDashboardViewModel.kt @@ -2,39 +2,54 @@ package com.lgtm.android.mission_suggestion.ui.dashboard import android.content.ContentValues.TAG import android.util.Log -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope import com.google.firebase.crashlytics.ktx.crashlytics import com.google.firebase.ktx.Firebase import com.lgtm.android.common_ui.base.BaseViewModel +import com.lgtm.android.common_ui.model.SuggestionUI import com.lgtm.android.common_ui.model.mapper.toUiModel +import com.lgtm.android.common_ui.util.UiState +import com.lgtm.android.mission_suggestion.ui.dashboard.presentation.contract.SuggestionDashboardInputs +import com.lgtm.android.mission_suggestion.ui.dashboard.presentation.contract.SuggestionDashboardOutputs +import com.lgtm.android.mission_suggestion.ui.dashboard.presentation.contract.SuggestionDashboardUiEffect import com.lgtm.domain.constants.Role import com.lgtm.domain.mission_suggestion.SuggestionContent import com.lgtm.domain.mission_suggestion.SuggestionVO import com.lgtm.domain.mission_suggestion.SuggestionViewType import com.lgtm.domain.repository.AuthRepository +import com.lgtm.domain.repository.SuggestionRepository import com.lgtm.domain.usecase.SuggestionUseCase import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel class SuggestionDashboardViewModel @Inject constructor( private val suggestionUseCase: SuggestionUseCase, + private val suggestionRepository: SuggestionRepository, authRepository: AuthRepository -): BaseViewModel() { +): BaseViewModel(), SuggestionDashboardInputs, SuggestionDashboardOutputs { - val suggestionBtnVisibility: Boolean = authRepository.getMemberType() == Role.REVIEWEE + val createBtnVisibility: Boolean = authRepository.getMemberType() == Role.REVIEWEE - private val _suggestionList = MutableLiveData>() - val suggestionList: LiveData> = _suggestionList + private val _suggestionDashboardState = MutableStateFlow>>(UiState.Init) + override val suggestionDashboardState: StateFlow>> + get() = _suggestionDashboardState + private val _suggestionDashboardUiEffect: MutableSharedFlow = MutableSharedFlow(replay = 0) + override val suggestionDashboardUiEffect: SharedFlow + get() = _suggestionDashboardUiEffect + + /* 미션 제안 fetch 기능 */ fun fetchSuggestionList() { viewModelScope.launch(lgtmErrorHandler) { suggestionUseCase.getSuggestionList() .onSuccess { - _suggestionList.postValue(convertToUiModel(it)) + _suggestionDashboardState.value = UiState.Success(data = convertToUiModel(it)) Log.d(TAG, "getSuggestion: $it") }.onFailure { Firebase.crashlytics.recordException(it) @@ -52,4 +67,64 @@ class SuggestionDashboardViewModel @Inject constructor( } } + /* 미션 제안 좋아요 기능 */ + + override fun likeSuggestion(index: Int, suggestionId: Int) { + viewModelScope.launch(lgtmErrorHandler) { + suggestionRepository.likeSuggestion(suggestionId) + .onSuccess { + updateLikeState(index, it.likeNum, it.isLiked) + Log.d(TAG, "likeSuggestion: $it") + }.onFailure { + Firebase.crashlytics.recordException(it) + Log.e(TAG, "likeSuggestion: $it") + } + } + } + + override fun cancelLikeSuggestion(index: Int, suggestionId: Int) { + viewModelScope.launch(lgtmErrorHandler) { + suggestionRepository.cancelLikeSuggestion(suggestionId) + .onSuccess { + updateLikeState(index, it.likeNum, it.isLiked) + Log.d(TAG, "cancelLikeSuggestion: $it") + }.onFailure { + Firebase.crashlytics.recordException(it) + Log.e(TAG, "cancelLikeSuggestion: $it") + } + } + } + + private fun updateLikeState(index: Int, likeNum: String, isLike: Boolean) { + if (suggestionDashboardState.value is UiState.Success) { + val suggestions = (suggestionDashboardState.value as UiState.Success).data.toMutableList() + val suggestion = suggestions[index] as SuggestionUI + suggestions[index] = suggestion.copy( + likeNum = likeNum, + isLiked = isLike + ) + _suggestionDashboardState.value = UiState.Success(data = suggestions) + } + } + + /* uiEffect 핸들 */ + + override fun moveToCreateSuggestion() { + viewModelScope.launch(lgtmErrorHandler) { + _suggestionDashboardUiEffect.emit(SuggestionDashboardUiEffect.CreateSuggestion) + } + } + + override fun moveToSuggestionDetail(suggestionId: Int) { + viewModelScope.launch(lgtmErrorHandler) { + _suggestionDashboardUiEffect.emit(SuggestionDashboardUiEffect.SuggestionDetail(suggestionId)) + } + } + + override fun goBack() { + viewModelScope.launch(lgtmErrorHandler) { + _suggestionDashboardUiEffect.emit(SuggestionDashboardUiEffect.GoBack) + } + } + } \ No newline at end of file diff --git a/feature/mission_suggestion/src/main/java/com/lgtm/android/mission_suggestion/ui/dashboard/presentation/SuggestionDashboardScreen.kt b/feature/mission_suggestion/src/main/java/com/lgtm/android/mission_suggestion/ui/dashboard/presentation/SuggestionDashboardScreen.kt new file mode 100644 index 00000000..eaa694d9 --- /dev/null +++ b/feature/mission_suggestion/src/main/java/com/lgtm/android/mission_suggestion/ui/dashboard/presentation/SuggestionDashboardScreen.kt @@ -0,0 +1,227 @@ +package com.lgtm.android.mission_suggestion.ui.dashboard.presentation + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.layout.layout +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.offset +import androidx.compose.ui.zIndex +import androidx.constraintlayout.compose.ConstraintLayout +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.lgtm.android.common_ui.R +import com.lgtm.android.common_ui.components.buttons.BackButton +import com.lgtm.android.common_ui.model.SuggestionUI +import com.lgtm.android.common_ui.theme.LGTMTheme +import com.lgtm.android.common_ui.util.UiState +import com.lgtm.android.common_ui.util.throttleClickable +import com.lgtm.android.mission_suggestion.ui.dashboard.SuggestionDashboardViewModel +import com.lgtm.android.mission_suggestion.ui.dashboard.presentation.item.SuggestionContent +import com.lgtm.android.mission_suggestion.ui.dashboard.presentation.item.SuggestionInfo +import com.lgtm.android.mission_suggestion.ui.dashboard.presentation.item.SuggestionListEmpty +import com.lgtm.domain.mission_suggestion.SuggestionContent +import com.lgtm.domain.mission_suggestion.SuggestionHeaderVO +import com.lgtm.domain.mission_suggestion.SuggestionViewType + +const val LAZYCOLUMN_PADDING = 20 + +@Composable +fun SuggestionDashboardScreen( + viewModel: SuggestionDashboardViewModel = hiltViewModel() +) { + val suggestionDashboardState: UiState> by viewModel.suggestionDashboardState.collectAsStateWithLifecycle() + + ConstraintLayout( + modifier = Modifier + .background(color = LGTMTheme.colors.gray_3) + .fillMaxSize() + ) { + val (suggestionSection, createBtn) = createRefs() + + Column( + modifier = Modifier.fillMaxHeight().constrainAs(suggestionSection){ + top.linkTo(parent.top) + start.linkTo(parent.start) + end.linkTo(parent.end) + bottom.linkTo(parent.bottom) + }, + verticalArrangement = Arrangement.Top + ) { + SuggestionHeader{ viewModel.goBack() } + + when(suggestionDashboardState) { + is UiState.Init -> { /* no-op */ } + is UiState.Success -> { + SuggestionList( + modifier = Modifier + .fillMaxHeight() + .layout { measurable, constraints -> + val placeable = measurable.measure(constraints.offset(vertical = LAZYCOLUMN_PADDING*2.dp.roundToPx())) + layout( + placeable.width, + placeable.height + ){ placeable.place(0, 0) } + }, + suggestionList = (suggestionDashboardState as UiState.Success).data, + onSuggestionClick = viewModel::moveToSuggestionDetail, + onSuggestionLike = viewModel::likeSuggestion, + onSuggestionCancelLike = viewModel::cancelLikeSuggestion + ) + } + is UiState.Failure -> { /* no-op */ } + } + } + + CreateSuggestionButton( + modifier = Modifier.constrainAs(createBtn) { + bottom.linkTo(parent.bottom, margin = 25.dp) + start.linkTo(parent.start) + end.linkTo(parent.end) + }, + visibility = viewModel.createBtnVisibility + ) { viewModel.moveToCreateSuggestion() } + } +} + +/* 헤더 부분 */ +@Composable +fun SuggestionHeader( + modifier: Modifier = Modifier, + onBackButtonClick: () -> Unit +) { + ConstraintLayout( + modifier = modifier + .fillMaxWidth() + .background( + color = LGTMTheme.colors.white, + shape = RoundedCornerShape( + bottomStart = 20.dp, + bottomEnd = 20.dp + ) + ) + .shadow( + elevation = 1.dp, + shape = RoundedCornerShape( + bottomStart = 20.dp, + bottomEnd = 20.dp + ) + ) + .zIndex(1f) + ) { + val (backBtn, title) = createRefs() + + BackButton( + modifier = Modifier.constrainAs(backBtn) { + top.linkTo(parent.top, margin = 30.dp) + start.linkTo(parent.start, margin = 20.dp) + bottom.linkTo(parent.bottom, margin = 20.dp) + } + ) { onBackButtonClick() } + + Text( + modifier = Modifier.constrainAs(title) { + top.linkTo(parent.top) + start.linkTo(parent.start) + end.linkTo(parent.end) + bottom.linkTo(parent.bottom) + }, + text = stringResource(id = R.string.mission_recommendation), + color = LGTMTheme.colors.black, + style = LGTMTheme.typography.body1B + ) + } +} + +/* 미션 제안 리스트 부분 */ + +@Composable +fun SuggestionList( + modifier: Modifier = Modifier, + suggestionList: List, + onSuggestionClick: (Int) -> Unit, + onSuggestionLike: (Int, Int) -> Unit, + onSuggestionCancelLike: (Int, Int) -> Unit +) { + LazyColumn( + modifier = modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(5.dp) + ) { + items(suggestionList.size) { index -> + // 첫 번째 요소 위에 Spacer 배치 + if (index == 0) { + Spacer(modifier = Modifier.height(25.dp)) + } + getSuggestionViewByType(index, suggestionList[index], onSuggestionClick, onSuggestionLike, onSuggestionCancelLike) + // 마지막 요소 아래에 Spacer 배치 + if (index == suggestionList.size - 1) { + Spacer(modifier = Modifier.height(20.dp)) + } + } + } +} + +@Composable +fun getSuggestionViewByType( + index: Int, + suggestionContent: SuggestionContent, + onSuggestionClick: (Int) -> Unit, + onSuggestionLike: (Int, Int) -> Unit, + onSuggestionCancelLike: (Int, Int) -> Unit +) { + when(suggestionContent.viewType) { + SuggestionViewType.HEADER -> SuggestionInfo(suggestionContent as SuggestionHeaderVO) + SuggestionViewType.CONTENT -> SuggestionContent(index, suggestionContent as SuggestionUI, onSuggestionClick, onSuggestionLike, onSuggestionCancelLike) + SuggestionViewType.EMPTY -> SuggestionListEmpty() + } +} + +/* 미션 제안 생성 */ + +@Composable +fun CreateSuggestionButton( + modifier: Modifier = Modifier, + visibility: Boolean, + onCreateSuggestionClick: () -> Unit +) { + if (visibility) { + Box( + modifier = modifier + .fillMaxWidth() + .padding(horizontal = 20.dp) + .background( + color = LGTMTheme.colors.green, + shape = RoundedCornerShape(5.dp) + ) + .zIndex(10f) + .throttleClickable(true) { + onCreateSuggestionClick() + }, + contentAlignment = Alignment.Center, + ) { + Text( + modifier = Modifier + .padding(vertical = 14.dp), + text = stringResource(id = R.string.recommend_mission), + style = LGTMTheme.typography.body1M, + color = LGTMTheme.colors.black + ) + } + } +} \ No newline at end of file diff --git a/feature/mission_suggestion/src/main/java/com/lgtm/android/mission_suggestion/ui/dashboard/presentation/contract/SuggestionDashboardInputs.kt b/feature/mission_suggestion/src/main/java/com/lgtm/android/mission_suggestion/ui/dashboard/presentation/contract/SuggestionDashboardInputs.kt new file mode 100644 index 00000000..971049c3 --- /dev/null +++ b/feature/mission_suggestion/src/main/java/com/lgtm/android/mission_suggestion/ui/dashboard/presentation/contract/SuggestionDashboardInputs.kt @@ -0,0 +1,9 @@ +package com.lgtm.android.mission_suggestion.ui.dashboard.presentation.contract + +interface SuggestionDashboardInputs { + fun likeSuggestion(index: Int, suggestionId: Int) + fun cancelLikeSuggestion(index: Int, suggestionId: Int) + fun moveToCreateSuggestion() + fun moveToSuggestionDetail(suggestionId: Int) + fun goBack() +} \ No newline at end of file diff --git a/feature/mission_suggestion/src/main/java/com/lgtm/android/mission_suggestion/ui/dashboard/presentation/contract/SuggestionDashboardOutputs.kt b/feature/mission_suggestion/src/main/java/com/lgtm/android/mission_suggestion/ui/dashboard/presentation/contract/SuggestionDashboardOutputs.kt new file mode 100644 index 00000000..83186521 --- /dev/null +++ b/feature/mission_suggestion/src/main/java/com/lgtm/android/mission_suggestion/ui/dashboard/presentation/contract/SuggestionDashboardOutputs.kt @@ -0,0 +1,11 @@ +package com.lgtm.android.mission_suggestion.ui.dashboard.presentation.contract + +import com.lgtm.android.common_ui.util.UiState +import com.lgtm.domain.mission_suggestion.SuggestionContent +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.StateFlow + +interface SuggestionDashboardOutputs { + val suggestionDashboardState: StateFlow>> + val suggestionDashboardUiEffect: SharedFlow +} \ No newline at end of file diff --git a/feature/mission_suggestion/src/main/java/com/lgtm/android/mission_suggestion/ui/dashboard/presentation/contract/SuggestionDashboardUiEffect.kt b/feature/mission_suggestion/src/main/java/com/lgtm/android/mission_suggestion/ui/dashboard/presentation/contract/SuggestionDashboardUiEffect.kt new file mode 100644 index 00000000..bf2c76ee --- /dev/null +++ b/feature/mission_suggestion/src/main/java/com/lgtm/android/mission_suggestion/ui/dashboard/presentation/contract/SuggestionDashboardUiEffect.kt @@ -0,0 +1,7 @@ +package com.lgtm.android.mission_suggestion.ui.dashboard.presentation.contract + +sealed class SuggestionDashboardUiEffect { + object GoBack: SuggestionDashboardUiEffect() + object CreateSuggestion: SuggestionDashboardUiEffect() + data class SuggestionDetail(val suggestionId: Int): SuggestionDashboardUiEffect() +} \ No newline at end of file diff --git a/feature/mission_suggestion/src/main/java/com/lgtm/android/mission_suggestion/ui/dashboard/presentation/item/SuggestionContent.kt b/feature/mission_suggestion/src/main/java/com/lgtm/android/mission_suggestion/ui/dashboard/presentation/item/SuggestionContent.kt new file mode 100644 index 00000000..97deee55 --- /dev/null +++ b/feature/mission_suggestion/src/main/java/com/lgtm/android/mission_suggestion/ui/dashboard/presentation/item/SuggestionContent.kt @@ -0,0 +1,92 @@ +package com.lgtm.android.mission_suggestion.ui.dashboard.presentation.item + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Divider +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import com.lgtm.android.common_ui.components.buttons.LikeButton +import com.lgtm.android.common_ui.components.texts.DateTimeText +import com.lgtm.android.common_ui.model.SuggestionUI +import com.lgtm.android.common_ui.theme.LGTMTheme +import com.lgtm.android.common_ui.util.throttleClickable + +@Composable +fun SuggestionContent( + index: Int, + suggestionUI: SuggestionUI, + onSuggestionClick: (Int) -> Unit, + onSuggestionLike: (Int, Int) -> Unit, + onSuggestionCancelLike: (Int, Int) -> Unit +) { + Column( + modifier = Modifier + .fillMaxWidth() + .background( + color = LGTMTheme.colors.white, + shape = RoundedCornerShape(20.dp), + ) + .border( + width = 1.dp, + color = LGTMTheme.colors.gray_2, + shape = RoundedCornerShape(20.dp), + ) + .padding( + horizontal = 20.dp, + vertical = 16.dp + ) + .throttleClickable( + enabled = true, + onClick = { onSuggestionClick(suggestionUI.suggestionId) } + ) + ) { + Text( + text = suggestionUI.title, + color = LGTMTheme.colors.black, + style = LGTMTheme.typography.body2, + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) + + Divider( + modifier = Modifier + .padding(top = 16.dp) + .background(color = LGTMTheme.colors.gray_2) + ) + + Text( + modifier = Modifier.padding(top = 10.dp), + text = suggestionUI.description, + color = LGTMTheme.colors.black, + style = LGTMTheme.typography.body3R, + maxLines = 3, + overflow = TextOverflow.Ellipsis + ) + + Row( + modifier = Modifier.padding(top = 10.dp).fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + DateTimeText(date = suggestionUI.date, time = suggestionUI.time) + LikeButton(likeNum = suggestionUI.likeNum, isLiked = suggestionUI.isLiked) { + if (suggestionUI.isLiked) { + onSuggestionCancelLike(index, suggestionUI.suggestionId) + } else { + onSuggestionLike(index, suggestionUI.suggestionId) + } + } + } + + } +} \ No newline at end of file diff --git a/feature/mission_suggestion/src/main/java/com/lgtm/android/mission_suggestion/ui/dashboard/presentation/item/SuggestionInfo.kt b/feature/mission_suggestion/src/main/java/com/lgtm/android/mission_suggestion/ui/dashboard/presentation/item/SuggestionInfo.kt new file mode 100644 index 00000000..4262806e --- /dev/null +++ b/feature/mission_suggestion/src/main/java/com/lgtm/android/mission_suggestion/ui/dashboard/presentation/item/SuggestionInfo.kt @@ -0,0 +1,47 @@ +package com.lgtm.android.mission_suggestion.ui.dashboard.presentation.item + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.lgtm.android.common_ui.theme.LGTMTheme +import com.lgtm.domain.mission_suggestion.SuggestionHeaderVO + +@Composable +fun SuggestionInfo( + suggestionHeader: SuggestionHeaderVO +) { + Column( + modifier = Modifier + .fillMaxWidth() + .background( + color = LGTMTheme.colors.gray_7, + shape = RoundedCornerShape(20.dp) + ) + .padding( + horizontal = 20.dp, + vertical = 16.dp + ) + ) { + Text( + text = suggestionHeader.title, + style = LGTMTheme.typography.body2, + color = LGTMTheme.colors.white + ) + Text( + modifier = Modifier.padding( + top = 8.dp, + start = 4.dp, + end = 4.dp + ), + text = suggestionHeader.description, + style = LGTMTheme.typography.body2, + color = LGTMTheme.colors.white + ) + } +} \ No newline at end of file diff --git a/feature/mission_suggestion/src/main/java/com/lgtm/android/mission_suggestion/ui/dashboard/presentation/item/SuggstionListEmpty.kt b/feature/mission_suggestion/src/main/java/com/lgtm/android/mission_suggestion/ui/dashboard/presentation/item/SuggstionListEmpty.kt new file mode 100644 index 00000000..84b46f1a --- /dev/null +++ b/feature/mission_suggestion/src/main/java/com/lgtm/android/mission_suggestion/ui/dashboard/presentation/item/SuggstionListEmpty.kt @@ -0,0 +1,40 @@ +package com.lgtm.android.mission_suggestion.ui.dashboard.presentation.item + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.lgtm.android.common_ui.R +import com.lgtm.android.common_ui.theme.LGTMTheme + +@Composable +fun SuggestionListEmpty() { + Column( + modifier = Modifier + .fillMaxWidth() + .background( + color = LGTMTheme.colors.gray_3, + ), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Image( + modifier = Modifier.padding(top = 43.dp), + painter = painterResource(id = R.drawable.img_empty_gray_1), + contentDescription = null + ) + Text( + modifier = Modifier.padding(top = 5.dp), + text = stringResource(id = R.string.no_recommendation_yet), + style = LGTMTheme.typography.body2, + color = LGTMTheme.colors.gray_6 + ) + } +} \ No newline at end of file diff --git a/feature/mission_suggestion/src/main/java/com/lgtm/android/mission_suggestion/ui/detail/SuggestionDetailViewModel.kt b/feature/mission_suggestion/src/main/java/com/lgtm/android/mission_suggestion/ui/detail/SuggestionDetailViewModel.kt index 62c71a6f..791db50b 100644 --- a/feature/mission_suggestion/src/main/java/com/lgtm/android/mission_suggestion/ui/detail/SuggestionDetailViewModel.kt +++ b/feature/mission_suggestion/src/main/java/com/lgtm/android/mission_suggestion/ui/detail/SuggestionDetailViewModel.kt @@ -17,6 +17,7 @@ import com.lgtm.domain.repository.SuggestionRepository import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch import javax.inject.Inject @@ -32,7 +33,7 @@ class SuggestionDetailViewModel @Inject constructor( get() = _detailState private val _detailUiEffect: MutableSharedFlow = MutableSharedFlow(replay = 0) - override val detailUiEffect: MutableSharedFlow + override val detailUiEffect: SharedFlow get() = _detailUiEffect /* 미션 제안 상세 내용 */ diff --git a/feature/mission_suggestion/src/main/res/layout/activity_suggestion_dashboard.xml b/feature/mission_suggestion/src/main/res/layout/activity_suggestion_dashboard.xml deleted file mode 100644 index 2deb202a..00000000 --- a/feature/mission_suggestion/src/main/res/layout/activity_suggestion_dashboard.xml +++ /dev/null @@ -1,111 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file