From 50a9a6678c6558e0c6a7377e930aeeb1e7e8c22d Mon Sep 17 00:00:00 2001 From: SangEun Date: Sat, 16 Dec 2023 00:44:16 +0900 Subject: [PATCH] =?UTF-8?q?=EB=82=A0=EC=A7=9C=20=EC=9D=B4=EC=83=81?= =?UTF-8?q?=ED=95=98=EA=B2=8C=20=EB=82=98=EC=98=A4=EB=8A=94=20=ED=98=84?= =?UTF-8?q?=EC=83=81=20=EC=88=98=EC=A0=95,=20=EB=88=84=EB=9D=BD=EB=90=9C?= =?UTF-8?q?=20=EC=B2=B4=ED=81=AC=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20=EC=B6=94=EA=B0=80=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/model/mapper/FormattedTimeMapper.kt | 2 +- .../presentation/common/ext/LocalDateExt.kt | 4 +- .../home/contents/HomeTabContents.kt | 1 + .../contents/component/MyChecklistItem.kt | 9 ++- .../user/mylist/ChecklistMoreBottomSheet.kt | 73 +++++++++++++++++++ .../home/contents/user/mylist/MyCheckList.kt | 49 +++++++++++-- .../user/mylist/MyCheckListContract.kt | 3 +- .../contents/user/mylist/MyCheckListScreen.kt | 7 +- .../user/mylist/MyCheckListViewModel.kt | 33 ++++++++- 9 files changed, 166 insertions(+), 15 deletions(-) create mode 100644 presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/mylist/ChecklistMoreBottomSheet.kt diff --git a/data/src/main/java/com/dkin/chevit/data/model/mapper/FormattedTimeMapper.kt b/data/src/main/java/com/dkin/chevit/data/model/mapper/FormattedTimeMapper.kt index 93ce929..c4bbb1d 100644 --- a/data/src/main/java/com/dkin/chevit/data/model/mapper/FormattedTimeMapper.kt +++ b/data/src/main/java/com/dkin/chevit/data/model/mapper/FormattedTimeMapper.kt @@ -10,7 +10,7 @@ import java.util.TimeZone internal object FormattedTimeMapper { fun mapDomain(unixMillis: Long, format: String = "yyyy.MM.dd"): FormattedTime { - val formatted = SimpleDateFormat(format, Locale.getDefault()).format(unixMillis) + val formatted = SimpleDateFormat(format, Locale.getDefault()).format(unixMillis * 1000) return FormattedTime(unixMillis = unixMillis, formatted = formatted) } diff --git a/presentation/common/src/main/java/com/dkin/chevit/presentation/common/ext/LocalDateExt.kt b/presentation/common/src/main/java/com/dkin/chevit/presentation/common/ext/LocalDateExt.kt index 559925b..d8508c3 100644 --- a/presentation/common/src/main/java/com/dkin/chevit/presentation/common/ext/LocalDateExt.kt +++ b/presentation/common/src/main/java/com/dkin/chevit/presentation/common/ext/LocalDateExt.kt @@ -1,8 +1,8 @@ package com.dkin.chevit.presentation.common.ext import java.time.LocalDate -import java.time.ZoneId +import java.time.ZoneOffset fun LocalDate.unixMillis(): Long { - return atStartOfDay(ZoneId.systemDefault()).toEpochSecond() + return atStartOfDay().toEpochSecond(ZoneOffset.UTC) } \ No newline at end of file diff --git a/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/HomeTabContents.kt b/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/HomeTabContents.kt index 1ea4ada..ee96ce5 100644 --- a/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/HomeTabContents.kt +++ b/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/HomeTabContents.kt @@ -147,6 +147,7 @@ fun HomeTabContents( MyChecklistItem( item = it, onClickItem = { id -> homeViewModel.onClickChecklist(id) }, + onLongClickItem = { _, _ -> }, modifier = Modifier.fillMaxWidth(), ) Spacer(modifier = Modifier.height(20.dp)) diff --git a/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/component/MyChecklistItem.kt b/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/component/MyChecklistItem.kt index 08379b7..c05d47d 100644 --- a/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/component/MyChecklistItem.kt +++ b/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/component/MyChecklistItem.kt @@ -1,8 +1,10 @@ package com.dkin.chevit.presentation.home.contents.component +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable +import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth @@ -23,17 +25,22 @@ import com.dkin.chevit.presentation.home.HomeState import com.dkin.chevit.presentation.home.model.CheckListItem import com.dkin.chevit.presentation.resource.ChevitTheme +@OptIn(ExperimentalFoundationApi::class) @Composable fun MyChecklistItem( item: CheckListItem, onClickItem: (id: String) -> Unit, + onLongClickItem: (id: String, title: String) -> Unit, modifier: Modifier = Modifier, ) { Box(modifier = Modifier .fillMaxWidth() .clip(RoundedCornerShape(12.dp)) .background(color = ChevitTheme.colors.grey4) - .clickable { onClickItem(item.id) } + .combinedClickable( + onClick = { onClickItem(item.id) }, + onLongClick = { onLongClickItem(item.id, item.title) } + ) ) { AsyncImage( modifier = Modifier.fillMaxWidth(), diff --git a/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/mylist/ChecklistMoreBottomSheet.kt b/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/mylist/ChecklistMoreBottomSheet.kt new file mode 100644 index 0000000..1715f53 --- /dev/null +++ b/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/mylist/ChecklistMoreBottomSheet.kt @@ -0,0 +1,73 @@ +package com.dkin.chevit.presentation.home.contents.user.mylist + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.material3.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.dkin.chevit.presentation.resource.ChevitBottomsheet +import com.dkin.chevit.presentation.resource.ChevitTheme +import com.dkin.chevit.presentation.resource.icon.ChevitIcon +import com.dkin.chevit.presentation.resource.icon.IconCheckboxCircleFill + +@Composable +fun ChecklistMoreBottomSheet( + title: String, + deleteItem: () -> Unit, + onClose: () -> Unit +) { + ChevitBottomsheet( + modifier = Modifier.fillMaxSize(), + onClickBackground = onClose + ) { + Column(modifier = Modifier.fillMaxWidth()) { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Image( + imageVector = ChevitIcon.IconCheckboxCircleFill, + contentDescription = "", + ) + Spacer(modifier = Modifier.width(4.dp)) + Text( + text = title, + style = ChevitTheme.typhography.headlineMedium.copy(color = ChevitTheme.colors.textPrimary), + overflow = TextOverflow.Ellipsis, + maxLines = 1 + ) + } + Spacer(modifier = Modifier.height(4.dp)) + Column( + modifier = Modifier.fillMaxWidth().clickable { deleteItem() } + ) { + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = "삭제하기", + style = ChevitTheme.typhography.bodyLarge.copy(color = ChevitTheme.colors.textPrimary), + overflow = TextOverflow.Ellipsis, + maxLines = 1 + ) + Spacer(modifier = Modifier.height(16.dp)) + } + Box( + modifier = Modifier + .fillMaxWidth() + .height(1.dp) + .background(color = ChevitTheme.colors.grey0) + ) + } + } +} \ No newline at end of file diff --git a/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/mylist/MyCheckList.kt b/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/mylist/MyCheckList.kt index ceba2aa..b2777ac 100644 --- a/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/mylist/MyCheckList.kt +++ b/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/mylist/MyCheckList.kt @@ -4,12 +4,20 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.Toast import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.compose.ui.window.DialogProperties import androidx.fragment.app.viewModels +import androidx.navigation.NavType +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.dialog +import androidx.navigation.compose.rememberNavController import androidx.navigation.findNavController +import androidx.navigation.navArgument import com.dkin.chevit.core.mvi.MVIComposeFragment import com.dkin.chevit.presentation.deeplink.DeepLink import com.dkin.chevit.presentation.deeplink.deepLink @@ -29,12 +37,37 @@ class MyCheckList : return ComposeView(requireContext()).apply { setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { - val state by viewModel.state.collectAsState() - MyCheckListScreen( - onClickBack = { findNavController().popBackStack() }, - checkList = state.checkList, - onClickChecklist = { id -> viewModel.onClickChecklist(id) } - ) + val navController = rememberNavController() + NavHost(navController = navController, startDestination = "main") { + composable("main") { + val state by viewModel.state.collectAsState() + MyCheckListScreen( + onClickBack = { findNavController().popBackStack() }, + checkList = state.checkList, + onClickChecklist = { id -> viewModel.onClickChecklist(id) }, + onLongClickChecklist = { id, title -> navController.navigate("more/${id}?title=${title}") } + ) + } + dialog( + route = "more/{itemId}?title={title}", + arguments = listOf( + navArgument("itemId") { type = NavType.StringType }, + navArgument("title") { type = NavType.StringType; defaultValue = "" }, + ), + dialogProperties = DialogProperties(usePlatformDefaultWidth = false), + ) { + val itemId = it.arguments?.getString("itemId") ?: "" + val title = it.arguments?.getString("title") ?: "" + ChecklistMoreBottomSheet( + title = title, + deleteItem = { + navController.popBackStack() + viewModel.dispatch(MyCheckListIntent.DeleteCheckList(itemId)) + }, + onClose = { navController.popBackStack() } + ) + } + } } } } @@ -51,6 +84,10 @@ class MyCheckList : R.id.myChecklist ) } + + MyCheckListEffect.DeletePlanFailed -> { + Toast.makeText(requireContext(), "체크리스트 삭제에 실패하였습니다.", Toast.LENGTH_SHORT).show() + } } } diff --git a/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/mylist/MyCheckListContract.kt b/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/mylist/MyCheckListContract.kt index 8bfd05d..622af60 100644 --- a/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/mylist/MyCheckListContract.kt +++ b/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/mylist/MyCheckListContract.kt @@ -7,7 +7,7 @@ import com.dkin.chevit.core.mvi.ViewState import com.dkin.chevit.presentation.home.model.CheckListItem sealed interface MyCheckListIntent : ViewIntent { - + data class DeleteCheckList(val id: String) : MyCheckListIntent } @Stable @@ -47,4 +47,5 @@ data class MyCheckListState( sealed interface MyCheckListEffect : ViewEffect { data class NavigateToCheckList(val id: String) : MyCheckListEffect + object DeletePlanFailed : MyCheckListEffect } diff --git a/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/mylist/MyCheckListScreen.kt b/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/mylist/MyCheckListScreen.kt index 6249873..1b4ab99 100644 --- a/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/mylist/MyCheckListScreen.kt +++ b/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/mylist/MyCheckListScreen.kt @@ -33,7 +33,8 @@ import com.dkin.chevit.presentation.resource.util.clickableNoRipple fun MyCheckListScreen( onClickBack: () -> Unit, checkList: List, - onClickChecklist: (id: String) -> Unit + onClickChecklist: (id: String) -> Unit, + onLongClickChecklist: (id: String, title: String) -> Unit ) { Column( modifier = Modifier.fillMaxSize() @@ -91,7 +92,9 @@ fun MyCheckListScreen( items(count = checkList.size) { MyChecklistItem( item = checkList[it], - onClickItem = { id -> onClickChecklist(id) }) + onClickItem = { id -> onClickChecklist(id) }, + onLongClickItem = { id, title -> onLongClickChecklist(id, title) } + ) } } } diff --git a/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/mylist/MyCheckListViewModel.kt b/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/mylist/MyCheckListViewModel.kt index 7d9523f..c7de937 100644 --- a/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/mylist/MyCheckListViewModel.kt +++ b/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/mylist/MyCheckListViewModel.kt @@ -3,6 +3,8 @@ package com.dkin.chevit.presentation.home.contents.user.mylist import androidx.lifecycle.viewModelScope import com.dkin.chevit.core.mvi.MVIViewModel import com.dkin.chevit.domain.base.get +import com.dkin.chevit.domain.base.onComplete +import com.dkin.chevit.domain.usecase.plan.DeletePlanUseCase import com.dkin.chevit.domain.usecase.plan.GetMyChecklistUseCase import com.dkin.chevit.presentation.home.model.CheckListItem import dagger.hilt.android.lifecycle.HiltViewModel @@ -11,12 +13,17 @@ import javax.inject.Inject @HiltViewModel class MyCheckListViewModel @Inject constructor( - private val getMyChecklistUseCase: GetMyChecklistUseCase + private val getMyChecklistUseCase: GetMyChecklistUseCase, + private val deletePlanUseCase: DeletePlanUseCase ) : MVIViewModel() { override fun createInitialState() = MyCheckListState.empty() - override suspend fun processIntent(intent: MyCheckListIntent) {} + override suspend fun processIntent(intent: MyCheckListIntent) { + when (intent) { + is MyCheckListIntent.DeleteCheckList -> deleteChecklist(intent.id) + } + } fun onClickChecklist(id: String) { @@ -41,4 +48,26 @@ class MyCheckListViewModel @Inject constructor( } } } + + private suspend fun deleteChecklist(id: String) { + val deleteItem = deletePlanUseCase( + DeletePlanUseCase.Param( + planId = id, + ) + ) + deleteItem.onComplete( + doOnComplete = {}, + doOnError = { + if (it is NullPointerException) { + //성공했을 때 응답값이 null로 내려옴 + initMyChecklist() + } else { + setEffect { MyCheckListEffect.DeletePlanFailed } + } + }, + doOnSuccess = { + initMyChecklist() + } + ) + } } \ No newline at end of file