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
Expand Up @@ -6,6 +6,7 @@ import android.os.Build
import android.os.Bundle
import android.view.MotionEvent
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
Expand Down Expand Up @@ -50,6 +51,7 @@ class MainActivity : AppCompatActivity(), BottomNavigationController {
private lateinit var requestNotificationPermissionLauncher: ActivityResultLauncher<String>

override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge()
val splashScreen = installSplashScreen()
super.onCreate(savedInstanceState)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import com.google.android.material.snackbar.Snackbar
import com.project200.common.utils.ChatRoomStateRepository
import com.project200.common.utils.CommonDateTimeFormatters.YYYY_MM_DD_KR
import com.project200.feature.chatting.chattingRoom.adapter.ChatRVAdapter
import com.project200.feature.chatting.utils.KeyboardVisibilityHelper
import com.project200.presentation.base.BindingFragment
import com.project200.presentation.utils.KeyboardControlInterface
import com.project200.presentation.utils.KeyboardUtils.hideKeyboard
Expand All @@ -35,7 +34,6 @@ import com.project200.undabang.feature.chatting.R
import com.project200.undabang.feature.chatting.databinding.FragmentChattingRoomBinding
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import java.time.LocalDate
Expand All @@ -57,8 +55,6 @@ class ChattingRoomFragment : BindingFragment<FragmentChattingRoomBinding>(R.layo
private var firstVisibleItemPositionBeforeLoad = 0
private var firstVisibleItemOffsetBeforeLoad = 0

private lateinit var keyboardHelper: KeyboardVisibilityHelper

private lateinit var gestureDetector: GestureDetector

private var lastDisplayedDate: LocalDate? = null
Expand All @@ -72,8 +68,6 @@ class ChattingRoomFragment : BindingFragment<FragmentChattingRoomBinding>(R.layo
setupListeners()
viewModel.setId(args.roomId, args.memberId)
updateSendButtonState(false)
keyboardHelper = KeyboardVisibilityHelper(binding.root, binding.chattingMessageRv)
keyboardHelper.start()
}

private fun setupListeners() {
Expand Down Expand Up @@ -383,11 +377,6 @@ class ChattingRoomFragment : BindingFragment<FragmentChattingRoomBinding>(R.layo
return !sendButtonRect.contains(x, y)
}

override fun onDestroyView() {
keyboardHelper.stop()
super.onDestroyView()
}

override fun onResume() {
super.onResume()
// 현재 채팅방을 활성 채팅방으로 설정
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
android:minHeight="44dp"
android:maxHeight="150dp"
android:background="@color/white300"
app:layout_constraintTop_toBottomOf="@id/chatting_message_rv"
app:layout_constraintBottom_toBottomOf="parent">
<EditText
android:id="@+id/chatting_message_et"
Expand Down
3 changes: 3 additions & 0 deletions feature/exercise/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,7 @@ dependencies {

// lottie animation
implementation(libs.lottie)

// Shimmer
implementation(libs.shimmer)
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,46 +4,62 @@ import android.view.View
import android.widget.TextView
import android.widget.Toast
import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import com.project200.common.utils.CommonDateTimeFormatters
import com.project200.domain.model.BaseResult
import com.project200.domain.model.ExerciseRecord
import com.project200.presentation.base.BaseAlertDialog
import com.project200.presentation.base.BindingFragment
import com.project200.presentation.utils.UiState
import com.project200.presentation.utils.mapFailureToString
import com.project200.presentation.view.MenuBottomSheetDialog
import com.project200.undabang.feature.exercise.R
import com.project200.undabang.feature.exercise.databinding.FragmentExerciseDetailBinding
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch

@AndroidEntryPoint
class ExerciseDetailFragment : BindingFragment<FragmentExerciseDetailBinding>(R.layout.fragment_exercise_detail) {
private val viewModel: ExerciseDetailViewModel by viewModels()
private val args: ExerciseDetailFragmentArgs by navArgs()

override fun getViewBinding(view: View): FragmentExerciseDetailBinding {
return FragmentExerciseDetailBinding.bind(view)
}

override fun setupViews() {
viewModel.getExerciseRecord(args.recordId)
binding.baseToolbar.apply {
setTitle(getString(R.string.exercise_detail))
showBackButton(true) { findNavController().navigateUp() }
setSubButton(R.drawable.ic_menu) { showExerciseDetailMenu() }
}
}

override fun onResume() {
super.onResume()
viewModel.getExerciseRecord()
}

override fun setupObservers() {
viewModel.exerciseRecord.observe(viewLifecycleOwner) { result ->
when (result) {
is BaseResult.Success -> {
bindExerciseRecordData(result.data)
}
is BaseResult.Error -> {
Toast.makeText(requireContext(), result.message, Toast.LENGTH_SHORT).show()
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.exerciseRecord.collect { state ->
binding.shimmerLayout.visibility = if (state is UiState.Loading) View.VISIBLE else View.GONE
binding.scrollView.visibility = if (state is UiState.Success) View.VISIBLE else View.GONE

when (state) {
is UiState.Loading -> {
binding.shimmerLayout.startShimmer()
}
is UiState.Success -> {
binding.shimmerLayout.stopShimmer()
bindExerciseRecordData(state.data)
}
is UiState.Error -> {
binding.shimmerLayout.stopShimmer()
Toast.makeText(requireContext(), requireContext().mapFailureToString(state.failure), Toast.LENGTH_SHORT).show()
}
}
}
}
}
Expand All @@ -58,6 +74,15 @@ class ExerciseDetailFragment : BindingFragment<FragmentExerciseDetailBinding>(R.
}
}
}

// 이전 화면에서 새로고침 요청이 있을 경우에만 데이터를 새로고침합니다.
val savedStateHandle = findNavController().currentBackStackEntry?.savedStateHandle
savedStateHandle?.getLiveData<Boolean>(KEY_RECORD_UPDATED)?.observe(viewLifecycleOwner) { shouldRefresh ->
if (shouldRefresh) {
viewModel.getExerciseRecord(args.recordId)
savedStateHandle.remove<Boolean>(KEY_RECORD_UPDATED)
}
}
}

private fun bindExerciseRecordData(record: ExerciseRecord) {
Expand Down Expand Up @@ -117,7 +142,7 @@ class ExerciseDetailFragment : BindingFragment<FragmentExerciseDetailBinding>(R.
onEditClicked = {
findNavController().navigate(
ExerciseDetailFragmentDirections
.actionExerciseDetailFragmentToExerciseFormFragment(viewModel.recordId),
.actionExerciseDetailFragmentToExerciseFormFragment(args.recordId),
)
},
onDeleteClicked = { showDeleteConfirmationDialog() },
Expand All @@ -129,12 +154,12 @@ class ExerciseDetailFragment : BindingFragment<FragmentExerciseDetailBinding>(R.
title = getString(R.string.exercise_record_delete_alert),
desc = null,
onConfirmClicked = {
viewModel.deleteExerciseRecord()
viewModel.deleteExerciseRecord(args.recordId)
},
).show(parentFragmentManager, BaseAlertDialog::class.java.simpleName)
}

companion object {
const val TAG = "ExerciseDetailFragment"
const val KEY_RECORD_UPDATED = "record_updated"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,56 @@ package com.project200.feature.exercise.detail

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.project200.domain.model.BaseResult
import com.project200.domain.model.ExerciseRecord
import com.project200.domain.usecase.DeleteExerciseRecordUseCase
import com.project200.domain.usecase.GetExerciseRecordDetailUseCase
import com.project200.presentation.utils.UiState
import com.project200.presentation.utils.mapCodeToFailure
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import javax.inject.Inject

@HiltViewModel
class ExerciseDetailViewModel
@Inject
constructor(
savedStateHandle: SavedStateHandle,
private val exerciseRecordDetailUseCase: GetExerciseRecordDetailUseCase,
private val deleteExerciseRecordUseCase: DeleteExerciseRecordUseCase,
) : ViewModel() {
val recordId: Long =
savedStateHandle.get<Long>("recordId")
?: throw IllegalStateException("recordId is required for ExerciseDetailViewModel")

private val _exerciseRecord = MutableLiveData<BaseResult<ExerciseRecord>>()
val exerciseRecord: LiveData<BaseResult<ExerciseRecord>> = _exerciseRecord
private val _exerciseRecord = MutableStateFlow<UiState<ExerciseRecord>>(UiState.Loading)
val exerciseRecord: StateFlow<UiState<ExerciseRecord>> = _exerciseRecord

private val _deleteResult = MutableLiveData<BaseResult<Unit>>()
val deleteResult: LiveData<BaseResult<Unit>> = _deleteResult

fun getExerciseRecord() {
fun getExerciseRecord(recordId: Long) {
viewModelScope.launch {
_exerciseRecord.value = exerciseRecordDetailUseCase(recordId)
delay(LOADING_DELAY)
when (val result = exerciseRecordDetailUseCase(recordId)) {
is BaseResult.Success -> {
_exerciseRecord.value = UiState.Success(result.data)
}
is BaseResult.Error -> {
val failure = mapCodeToFailure(result.errorCode, result.message)
_exerciseRecord.value = UiState.Error(failure)
}
}
}
}

fun deleteExerciseRecord() {
fun deleteExerciseRecord(recordId: Long) {
viewModelScope.launch {
_deleteResult.value = deleteExerciseRecordUseCase(recordId)
}
}

companion object {
private const val LOADING_DELAY = 300L
}
}
Loading