From e6c5692b7167f61f309d0baac7abd42922bd131f Mon Sep 17 00:00:00 2001 From: EvergreenTree97 Date: Mon, 30 Sep 2024 13:36:44 +0900 Subject: [PATCH 1/7] =?UTF-8?q?[Feat]=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EA=B0=A4=EB=9F=AC=EB=A6=AC=EC=97=90=EC=84=9C=20=EB=B6=88?= =?UTF-8?q?=EB=9F=AC=EC=98=A4=EA=B8=B0=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../team/ppac/navigation/FarmemeNavHost.kt | 3 +- .../data/repository/MemeRepositoryImpl.kt | 9 ++++++ .../ppac/domain/repository/MemeRepository.kt | 6 ++++ .../ppac/domain/usecase/UpLoadImageUseCase.kt | 11 +++++++ .../recommendation/RecommendationRoute.kt | 8 +++++ .../recommendation/RecommendationScreen.kt | 3 +- .../recommendation/RecommendationViewModel.kt | 4 +++ .../mvi/RecommendationIntent.kt | 2 ++ .../mvi/RecommendationSideEffect.kt | 2 ++ .../navigation/RecommendationNavigation.kt | 4 ++- .../java/team/ppac/register/RegisterRoute.kt | 1 + .../java/team/ppac/register/RegisterScreen.kt | 17 ++++++++-- .../team/ppac/register/RegisterViewModel.kt | 10 ++++-- .../register/component/RegisterImageArea.kt | 32 ++++++++++++++----- .../team/ppac/register/mvi/RegisterIntent.kt | 2 +- .../team/ppac/register/mvi/RegisterUiState.kt | 4 ++- 16 files changed, 100 insertions(+), 18 deletions(-) create mode 100644 core/domain/src/main/java/team/ppac/domain/usecase/UpLoadImageUseCase.kt diff --git a/app/src/main/java/team/ppac/navigation/FarmemeNavHost.kt b/app/src/main/java/team/ppac/navigation/FarmemeNavHost.kt index 2d7f3ab5..da492dc9 100644 --- a/app/src/main/java/team/ppac/navigation/FarmemeNavHost.kt +++ b/app/src/main/java/team/ppac/navigation/FarmemeNavHost.kt @@ -37,7 +37,8 @@ fun FarmemeNavHost( exitTransition = { ExitTransition.None }, ) { recommendationScreen( - analyticsHelper = analyticsHelper + analyticsHelper = analyticsHelper, + navigateToRegister = navigateToRegister, ) searchScreen( analyticsHelper = analyticsHelper, diff --git a/core/data/src/main/java/team/ppac/data/repository/MemeRepositoryImpl.kt b/core/data/src/main/java/team/ppac/data/repository/MemeRepositoryImpl.kt index 149ece18..f8b44c6e 100644 --- a/core/data/src/main/java/team/ppac/data/repository/MemeRepositoryImpl.kt +++ b/core/data/src/main/java/team/ppac/data/repository/MemeRepositoryImpl.kt @@ -71,6 +71,15 @@ internal class MemeRepositoryImpl @Inject constructor( override val savedMemeEventFlow: Flow get() = _savedMemeEventFlow + override suspend fun uploadMeme( + memeId: String, + memeImageUri: String, + memeTitle: String, + memeSource: String, + ): Boolean { + return false // TODO + } + override suspend fun emitRefreshEvent() { _savedMemeEventFlow.emit(SavedMemeEvent.Refresh) } diff --git a/core/domain/src/main/java/team/ppac/domain/repository/MemeRepository.kt b/core/domain/src/main/java/team/ppac/domain/repository/MemeRepository.kt index 5f5821c2..fc3b3a82 100644 --- a/core/domain/src/main/java/team/ppac/domain/repository/MemeRepository.kt +++ b/core/domain/src/main/java/team/ppac/domain/repository/MemeRepository.kt @@ -23,6 +23,12 @@ interface MemeRepository { suspend fun emitRefreshEvent() val savedMemeEventFlow: Flow + suspend fun uploadMeme( + memeId: String, + memeImageUri: String, + memeTitle: String, + memeSource: String, + ): Boolean } sealed class SavedMemeEvent { diff --git a/core/domain/src/main/java/team/ppac/domain/usecase/UpLoadImageUseCase.kt b/core/domain/src/main/java/team/ppac/domain/usecase/UpLoadImageUseCase.kt new file mode 100644 index 00000000..84cb673b --- /dev/null +++ b/core/domain/src/main/java/team/ppac/domain/usecase/UpLoadImageUseCase.kt @@ -0,0 +1,11 @@ +package team.ppac.domain.usecase + +interface UpLoadImageUseCase { + suspend operator fun invoke(uri: String) +} + +internal class UpLoadImageUseCaseImpl : UpLoadImageUseCase { + override suspend fun invoke(uri: String) { + + } +} \ No newline at end of file diff --git a/feature/recommendation/src/main/java/team/ppac/recommendation/RecommendationRoute.kt b/feature/recommendation/src/main/java/team/ppac/recommendation/RecommendationRoute.kt index 32d5967e..191f5232 100644 --- a/feature/recommendation/src/main/java/team/ppac/recommendation/RecommendationRoute.kt +++ b/feature/recommendation/src/main/java/team/ppac/recommendation/RecommendationRoute.kt @@ -30,6 +30,7 @@ import team.ppac.recommendation.mvi.RecommendationSideEffect @Composable internal fun RecommendationRoute( analyticsHelper: AnalyticsHelper, + navigateToRegister: () -> Unit, viewModel: RecommendationViewModel = hiltViewModel(), ) { val context = LocalContext.current @@ -138,6 +139,10 @@ internal fun RecommendationRoute( screen = ScreenType.RECOMMENDATION, ) } + + RecommendationSideEffect.NavigateToRegister -> { + navigateToRegister() + } } } } @@ -169,6 +174,9 @@ internal fun RecommendationRoute( memeBitmap[index] = bitmap }, onActionButtonsIntentClick = viewModel::intent, + onUpload = { + viewModel.intent(RecommendationIntent.ClickUpload) + } ) } } \ No newline at end of file diff --git a/feature/recommendation/src/main/java/team/ppac/recommendation/RecommendationScreen.kt b/feature/recommendation/src/main/java/team/ppac/recommendation/RecommendationScreen.kt index 41d669ac..55c46c93 100644 --- a/feature/recommendation/src/main/java/team/ppac/recommendation/RecommendationScreen.kt +++ b/feature/recommendation/src/main/java/team/ppac/recommendation/RecommendationScreen.kt @@ -77,6 +77,7 @@ internal fun RecommendationScreen( onRetryClick: () -> Unit, onLoadMeme: (Int, Bitmap) -> Unit, onScrollPager: (Int, Meme) -> Unit, + onUpload: () -> Unit, onActionButtonsIntentClick: (RecommendationIntent.ClickButton) -> Unit, ) { val heroModulePagerState = rememberPagerState { state.thisWeekMemes.size } @@ -151,7 +152,7 @@ internal fun RecommendationScreen( } else { UploadButton( onClick = { - + onUpload() } ) } diff --git a/feature/recommendation/src/main/java/team/ppac/recommendation/RecommendationViewModel.kt b/feature/recommendation/src/main/java/team/ppac/recommendation/RecommendationViewModel.kt index c7856496..e493199f 100644 --- a/feature/recommendation/src/main/java/team/ppac/recommendation/RecommendationViewModel.kt +++ b/feature/recommendation/src/main/java/team/ppac/recommendation/RecommendationViewModel.kt @@ -139,6 +139,10 @@ class RecommendationViewModel @Inject constructor( copy(isError = false) } } + + RecommendationIntent.ClickUpload -> { + postSideEffect(RecommendationSideEffect.NavigateToRegister) + } } } diff --git a/feature/recommendation/src/main/java/team/ppac/recommendation/mvi/RecommendationIntent.kt b/feature/recommendation/src/main/java/team/ppac/recommendation/mvi/RecommendationIntent.kt index 4bb16c7f..3a7b0770 100644 --- a/feature/recommendation/src/main/java/team/ppac/recommendation/mvi/RecommendationIntent.kt +++ b/feature/recommendation/src/main/java/team/ppac/recommendation/mvi/RecommendationIntent.kt @@ -6,6 +6,8 @@ import team.ppac.domain.model.Meme sealed interface RecommendationIntent : UiIntent { data object Init : RecommendationIntent data object PullRefresh : RecommendationIntent + data object ClickUpload : RecommendationIntent + data class MovePage( val meme: Meme, val currentPage: Int, diff --git a/feature/recommendation/src/main/java/team/ppac/recommendation/mvi/RecommendationSideEffect.kt b/feature/recommendation/src/main/java/team/ppac/recommendation/mvi/RecommendationSideEffect.kt index eca1df4b..6a7a3706 100644 --- a/feature/recommendation/src/main/java/team/ppac/recommendation/mvi/RecommendationSideEffect.kt +++ b/feature/recommendation/src/main/java/team/ppac/recommendation/mvi/RecommendationSideEffect.kt @@ -8,6 +8,8 @@ sealed interface RecommendationSideEffect : UiSideEffect { data class CopyClipBoard(val selectedMemeIndex: Int) : RecommendationSideEffect data class ShareLink(val memeId: String) : RecommendationSideEffect data object LogHashTagsClicked : RecommendationSideEffect + data object NavigateToRegister : RecommendationSideEffect + data class LogSaveMeme(val meme: Meme) : RecommendationSideEffect data class LogSaveMemeCancel(val meme: Meme) : RecommendationSideEffect } \ No newline at end of file diff --git a/feature/recommendation/src/main/java/team/ppac/recommendation/navigation/RecommendationNavigation.kt b/feature/recommendation/src/main/java/team/ppac/recommendation/navigation/RecommendationNavigation.kt index 90d9f116..97dc49ae 100644 --- a/feature/recommendation/src/main/java/team/ppac/recommendation/navigation/RecommendationNavigation.kt +++ b/feature/recommendation/src/main/java/team/ppac/recommendation/navigation/RecommendationNavigation.kt @@ -13,12 +13,14 @@ fun NavController.navigateToRecommendation(navOptions: NavOptions) = navigate(RE fun NavGraphBuilder.recommendationScreen( analyticsHelper: AnalyticsHelper, + navigateToRegister: () -> Unit ) { composable( route = RECOMMENDATION_ROUTE ) { RecommendationRoute( - analyticsHelper = analyticsHelper + analyticsHelper = analyticsHelper, + navigateToRegister = navigateToRegister, ) } } \ No newline at end of file diff --git a/feature/register/src/main/java/team/ppac/register/RegisterRoute.kt b/feature/register/src/main/java/team/ppac/register/RegisterRoute.kt index fcd4ce6f..64106eee 100644 --- a/feature/register/src/main/java/team/ppac/register/RegisterRoute.kt +++ b/feature/register/src/main/java/team/ppac/register/RegisterRoute.kt @@ -22,6 +22,7 @@ internal fun RegisterRoute( RegisterScreen( uiState = uiState, navigateToBack = navigateToBack, + onIntent = viewModel::intent, ) } } \ No newline at end of file diff --git a/feature/register/src/main/java/team/ppac/register/RegisterScreen.kt b/feature/register/src/main/java/team/ppac/register/RegisterScreen.kt index d3d05894..7aed4eb6 100644 --- a/feature/register/src/main/java/team/ppac/register/RegisterScreen.kt +++ b/feature/register/src/main/java/team/ppac/register/RegisterScreen.kt @@ -1,5 +1,8 @@ package team.ppac.register +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.PickVisualMediaRequest +import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth @@ -22,13 +25,21 @@ import team.ppac.register.component.RegisterCategoryContent import team.ppac.register.component.RegisterImageArea import team.ppac.register.component.RegisterInputArea import team.ppac.register.component.RegisterKeywordHeader +import team.ppac.register.mvi.RegisterIntent import team.ppac.register.mvi.RegisterUiState @Composable internal fun RegisterScreen( uiState: RegisterUiState, navigateToBack: () -> Unit, + onIntent: (RegisterIntent) -> Unit, ) { + val imagePicker = + rememberLauncherForActivityResult(contract = ActivityResultContracts.PickVisualMedia()) { uri -> + if (uri != null) { + onIntent(RegisterIntent.SetImageFromGallery(uri.toString())) + } + } FarmemeScaffold( modifier = Modifier.navigationBarsPadding(), topBar = { @@ -48,7 +59,7 @@ internal fun RegisterScreen( modifier = Modifier.padding(bottom = 36.dp), text = "등록하기", enabled = true, - onClick = {}, + onClick = { }, ) } ) { @@ -58,7 +69,8 @@ internal fun RegisterScreen( ) { item { RegisterImageArea( - hasImage = false, + loadImage = { imagePicker.launch(PickVisualMediaRequest(mediaType = ActivityResultContracts.PickVisualMedia.ImageOnly)) }, + imageUri = uiState.imageUri, ) } item { RegisterInputArea() } @@ -86,5 +98,6 @@ private fun RegisterScreenPreview() { RegisterScreen( uiState = RegisterUiState.INITIAL_STATE, navigateToBack = {}, + onIntent = {}, ) } \ No newline at end of file diff --git a/feature/register/src/main/java/team/ppac/register/RegisterViewModel.kt b/feature/register/src/main/java/team/ppac/register/RegisterViewModel.kt index f5006d08..a7715061 100644 --- a/feature/register/src/main/java/team/ppac/register/RegisterViewModel.kt +++ b/feature/register/src/main/java/team/ppac/register/RegisterViewModel.kt @@ -27,8 +27,12 @@ class RegisterViewModel @Inject constructor( } override suspend fun handleIntent(intent: RegisterIntent) { -// when (intent) { -// -// } + when (intent) { + is RegisterIntent.SetImageFromGallery -> { + reduce { + copy(imageUri = intent.uri) + } + } + } } } \ No newline at end of file diff --git a/feature/register/src/main/java/team/ppac/register/component/RegisterImageArea.kt b/feature/register/src/main/java/team/ppac/register/component/RegisterImageArea.kt index a1d03791..4e1f464a 100644 --- a/feature/register/src/main/java/team/ppac/register/component/RegisterImageArea.kt +++ b/feature/register/src/main/java/team/ppac/register/component/RegisterImageArea.kt @@ -13,22 +13,27 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import coil.compose.AsyncImage +import coil.request.ImageRequest import team.ppac.designsystem.FarmemeTheme import team.ppac.designsystem.component.button.FarmemeCircleButton import team.ppac.designsystem.component.button.FarmemeWeakButton import team.ppac.designsystem.foundation.FarmemeIcon import team.ppac.designsystem.foundation.FarmemeRadius +import team.ppac.designsystem.util.extension.noRippleClickable @Composable internal fun RegisterImageArea( modifier: Modifier = Modifier, - hasImage: Boolean, - loadImage: () -> Unit = {}, + imageUri: String, + loadImage: () -> Unit, ) { val borderColor = - if (hasImage) FarmemeTheme.borderColor.primary else FarmemeTheme.borderColor.tertiary + if (imageUri.isNotEmpty()) FarmemeTheme.borderColor.primary else FarmemeTheme.borderColor.tertiary Box( modifier = modifier @@ -41,10 +46,11 @@ internal fun RegisterImageArea( color = borderColor, shape = FarmemeRadius.Radius20.shape, ) - .background(FarmemeTheme.backgroundColor.assistive), + .background(FarmemeTheme.backgroundColor.assistive) + .noRippleClickable(onClick = loadImage), contentAlignment = Alignment.Center, ) { - if (!hasImage) { + if (imageUri.isEmpty()) { FarmemeWeakButton( text = "이미지 등록", withStar = true, @@ -56,12 +62,20 @@ internal fun RegisterImageArea( } ) } else { - // AsyncImage Box( modifier = Modifier .fillMaxSize() .background(FarmemeTheme.backgroundColor.brandAssistive), ) + AsyncImage( + modifier = Modifier.matchParentSize(), + model = ImageRequest.Builder(LocalContext.current) + .data(imageUri) + .crossfade(true) + .build(), + contentDescription = null, + contentScale = ContentScale.Crop, + ) FarmemeCircleButton( modifier = Modifier .align(Alignment.BottomEnd) @@ -79,10 +93,12 @@ internal fun RegisterImageArea( private fun RegisterImageAreaPreview() { Column { RegisterImageArea( - hasImage = true, + loadImage = {}, + imageUri = "", ) RegisterImageArea( - hasImage = false, + loadImage = {}, + imageUri = "asdf", ) } } \ No newline at end of file diff --git a/feature/register/src/main/java/team/ppac/register/mvi/RegisterIntent.kt b/feature/register/src/main/java/team/ppac/register/mvi/RegisterIntent.kt index 4bab5f60..c7b4cf31 100644 --- a/feature/register/src/main/java/team/ppac/register/mvi/RegisterIntent.kt +++ b/feature/register/src/main/java/team/ppac/register/mvi/RegisterIntent.kt @@ -3,5 +3,5 @@ package team.ppac.register.mvi import team.ppac.common.android.base.UiIntent sealed interface RegisterIntent : UiIntent { - + data class SetImageFromGallery(val uri: String) : RegisterIntent } \ No newline at end of file diff --git a/feature/register/src/main/java/team/ppac/register/mvi/RegisterUiState.kt b/feature/register/src/main/java/team/ppac/register/mvi/RegisterUiState.kt index 85e0f0e4..f181a78e 100644 --- a/feature/register/src/main/java/team/ppac/register/mvi/RegisterUiState.kt +++ b/feature/register/src/main/java/team/ppac/register/mvi/RegisterUiState.kt @@ -8,7 +8,8 @@ import team.ppac.register.model.RegisterCategoryUiModel data class RegisterUiState( val isLoading: Boolean, val isError: Boolean, - val registerCategories: ImmutableList + val registerCategories: ImmutableList, + val imageUri: String, ) : UiState { companion object { @@ -59,6 +60,7 @@ data class RegisterUiState( ), ) ), + imageUri = "" ) } } \ No newline at end of file From 487914f109ef67ff0ab18dceaf82fb74990670ac Mon Sep 17 00:00:00 2001 From: EvergreenTree97 Date: Mon, 30 Sep 2024 13:47:27 +0900 Subject: [PATCH 2/7] =?UTF-8?q?[Feat]=20=EB=B0=88=20=EB=93=B1=EB=A1=9D=20U?= =?UTF-8?q?I=20=EC=9D=BC=EB=B6=80=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20state?= =?UTF-8?q?=20hoisting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/team/ppac/register/RegisterScreen.kt | 14 ++++++-- .../team/ppac/register/RegisterViewModel.kt | 12 +++++++ .../register/component/RegisterInputArea.kt | 33 ++++--------------- .../component/RegisterKeywordHeader.kt | 5 +-- .../register/component/RegisterListHeader.kt | 4 +-- .../team/ppac/register/mvi/RegisterIntent.kt | 2 ++ .../team/ppac/register/mvi/RegisterUiState.kt | 6 +++- 7 files changed, 40 insertions(+), 36 deletions(-) diff --git a/feature/register/src/main/java/team/ppac/register/RegisterScreen.kt b/feature/register/src/main/java/team/ppac/register/RegisterScreen.kt index 7aed4eb6..3e9ec405 100644 --- a/feature/register/src/main/java/team/ppac/register/RegisterScreen.kt +++ b/feature/register/src/main/java/team/ppac/register/RegisterScreen.kt @@ -73,7 +73,15 @@ internal fun RegisterScreen( imageUri = uiState.imageUri, ) } - item { RegisterInputArea() } + item { + RegisterInputArea( + modifier = Modifier.padding(horizontal = 20.dp), + title = uiState.title, + onTitleChanged = { onIntent(RegisterIntent.InputTitle(it)) }, + source = uiState.source, + onSourceChanged = { onIntent(RegisterIntent.InputSource(it)) }, + ) + } item { Divider( modifier = Modifier.fillMaxWidth(), @@ -81,7 +89,9 @@ internal fun RegisterScreen( thickness = 10.dp, ) } - item { RegisterKeywordHeader() } + item { + RegisterKeywordHeader(modifier = Modifier.padding(horizontal = 20.dp)) + } items(items = uiState.registerCategories) { registerCategory -> RegisterCategoryContent( uiModel = registerCategory, diff --git a/feature/register/src/main/java/team/ppac/register/RegisterViewModel.kt b/feature/register/src/main/java/team/ppac/register/RegisterViewModel.kt index a7715061..69ebca08 100644 --- a/feature/register/src/main/java/team/ppac/register/RegisterViewModel.kt +++ b/feature/register/src/main/java/team/ppac/register/RegisterViewModel.kt @@ -33,6 +33,18 @@ class RegisterViewModel @Inject constructor( copy(imageUri = intent.uri) } } + + is RegisterIntent.InputSource -> { + reduce { + copy(source = intent.source) + } + } + + is RegisterIntent.InputTitle -> { + reduce { + copy(title = intent.title) + } + } } } } \ No newline at end of file diff --git a/feature/register/src/main/java/team/ppac/register/component/RegisterInputArea.kt b/feature/register/src/main/java/team/ppac/register/component/RegisterInputArea.kt index 33a90ff9..fe252383 100644 --- a/feature/register/src/main/java/team/ppac/register/component/RegisterInputArea.kt +++ b/feature/register/src/main/java/team/ppac/register/component/RegisterInputArea.kt @@ -1,22 +1,21 @@ package team.ppac.register.component -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.unit.dp -import team.ppac.designsystem.FarmemeTheme -import team.ppac.designsystem.foundation.FarmemeRadius +import team.ppac.designsystem.component.textfield.FarmemeTextField @Composable internal fun RegisterInputArea( modifier: Modifier = Modifier, + title: String, + onTitleChanged: (String) -> Unit, + source: String, + onSourceChanged: (String) -> Unit, ) { Column( modifier = modifier, @@ -24,29 +23,11 @@ internal fun RegisterInputArea( ) { RegisterListHeader(title = "밈의 제목") Spacer(modifier = Modifier.height(12.dp)) - Spacer(// TextField - modifier = Modifier - .fillMaxWidth() - .height(46.dp) - .padding(horizontal = 20.dp) - .clip(FarmemeRadius.Radius10.shape) - .background( - FarmemeTheme.backgroundColor.assistive, - ) - ) + FarmemeTextField(text = title, onTextChanged = onTitleChanged) Spacer(modifier = Modifier.height(40.dp)) RegisterListHeader(title = "밈의 출처") Spacer(modifier = Modifier.height(12.dp)) - Spacer( // TextField - modifier = Modifier - .fillMaxWidth() - .height(82.dp) - .padding(horizontal = 20.dp) - .clip(FarmemeRadius.Radius10.shape) - .background( - FarmemeTheme.backgroundColor.assistive, - ) - ) + FarmemeTextField(text = source, onTextChanged = onSourceChanged) Spacer(modifier = Modifier.height(35.dp)) } } \ No newline at end of file diff --git a/feature/register/src/main/java/team/ppac/register/component/RegisterKeywordHeader.kt b/feature/register/src/main/java/team/ppac/register/component/RegisterKeywordHeader.kt index 3c764731..d3fa1a63 100644 --- a/feature/register/src/main/java/team/ppac/register/component/RegisterKeywordHeader.kt +++ b/feature/register/src/main/java/team/ppac/register/component/RegisterKeywordHeader.kt @@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -25,9 +24,7 @@ internal fun RegisterKeywordHeader( RegisterListHeader(title = "연관있는 키워드를 골라주세요") Spacer(modifier = Modifier.height(8.dp)) Text( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 20.dp), + modifier = Modifier.fillMaxWidth(), text = "최대 6개까지 선택 가능해요", style = FarmemeTheme.typography.body.medium.medium.copy( color = FarmemeTheme.textColor.secondary, diff --git a/feature/register/src/main/java/team/ppac/register/component/RegisterListHeader.kt b/feature/register/src/main/java/team/ppac/register/component/RegisterListHeader.kt index 69278f8a..1875d009 100644 --- a/feature/register/src/main/java/team/ppac/register/component/RegisterListHeader.kt +++ b/feature/register/src/main/java/team/ppac/register/component/RegisterListHeader.kt @@ -19,9 +19,7 @@ internal fun RegisterListHeader( title: String, ) { Row( - modifier = modifier - .fillMaxWidth() - .padding(horizontal = 20.dp), + modifier = modifier.fillMaxWidth(), ) { Text( text = title, diff --git a/feature/register/src/main/java/team/ppac/register/mvi/RegisterIntent.kt b/feature/register/src/main/java/team/ppac/register/mvi/RegisterIntent.kt index c7b4cf31..2aa13309 100644 --- a/feature/register/src/main/java/team/ppac/register/mvi/RegisterIntent.kt +++ b/feature/register/src/main/java/team/ppac/register/mvi/RegisterIntent.kt @@ -4,4 +4,6 @@ import team.ppac.common.android.base.UiIntent sealed interface RegisterIntent : UiIntent { data class SetImageFromGallery(val uri: String) : RegisterIntent + data class InputTitle(val title: String) : RegisterIntent + data class InputSource(val source: String) : RegisterIntent } \ No newline at end of file diff --git a/feature/register/src/main/java/team/ppac/register/mvi/RegisterUiState.kt b/feature/register/src/main/java/team/ppac/register/mvi/RegisterUiState.kt index f181a78e..24dc1ce5 100644 --- a/feature/register/src/main/java/team/ppac/register/mvi/RegisterUiState.kt +++ b/feature/register/src/main/java/team/ppac/register/mvi/RegisterUiState.kt @@ -10,6 +10,8 @@ data class RegisterUiState( val isError: Boolean, val registerCategories: ImmutableList, val imageUri: String, + val title: String, + val source: String, ) : UiState { companion object { @@ -60,7 +62,9 @@ data class RegisterUiState( ), ) ), - imageUri = "" + imageUri = "", + title = "", + source = "", ) } } \ No newline at end of file From 984eaeb6e6b1895ad041d6325dec0024c61fc1a9 Mon Sep 17 00:00:00 2001 From: evergreen Date: Mon, 30 Sep 2024 23:30:24 +0900 Subject: [PATCH 3/7] =?UTF-8?q?[Feat]=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=EA=B9=8C=EC=A7=80=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/team/ppac/register/RegisterScreen.kt | 14 ++- .../team/ppac/register/RegisterViewModel.kt | 38 ++++++++ .../component/RegisterCategoryContent.kt | 41 ++++----- .../register/component/RegisterInputArea.kt | 92 ++++++++++++++++++- .../register/model/RegisterCategoryUiModel.kt | 3 +- .../team/ppac/register/mvi/RegisterIntent.kt | 3 + .../team/ppac/register/mvi/RegisterUiState.kt | 50 ++-------- 7 files changed, 171 insertions(+), 70 deletions(-) diff --git a/feature/register/src/main/java/team/ppac/register/RegisterScreen.kt b/feature/register/src/main/java/team/ppac/register/RegisterScreen.kt index 3e9ec405..a8b692cf 100644 --- a/feature/register/src/main/java/team/ppac/register/RegisterScreen.kt +++ b/feature/register/src/main/java/team/ppac/register/RegisterScreen.kt @@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn @@ -17,6 +18,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import kotlinx.collections.immutable.toImmutableList import team.ppac.designsystem.FarmemeTheme import team.ppac.designsystem.component.scaffold.FarmemeScaffold import team.ppac.designsystem.component.toolbar.FarmemeBackToolBar @@ -59,12 +61,16 @@ internal fun RegisterScreen( modifier = Modifier.padding(bottom = 36.dp), text = "등록하기", enabled = true, - onClick = { }, + onClick = { + onIntent(RegisterIntent.ClickRegister) + }, ) } ) { LazyColumn( - modifier = Modifier.fillMaxWidth(), + modifier = Modifier + .fillMaxWidth() + .imePadding(), horizontalAlignment = Alignment.CenterHorizontally, ) { item { @@ -95,6 +101,10 @@ internal fun RegisterScreen( items(items = uiState.registerCategories) { registerCategory -> RegisterCategoryContent( uiModel = registerCategory, + selectedKeywords = uiState.selectedKeywords.toImmutableList(), + onKeywordClick = { + onIntent(RegisterIntent.OnKeywordClick(it)) + } ) } item { Spacer(modifier = Modifier.height(120.dp)) } diff --git a/feature/register/src/main/java/team/ppac/register/RegisterViewModel.kt b/feature/register/src/main/java/team/ppac/register/RegisterViewModel.kt index 69ebca08..3041ebbe 100644 --- a/feature/register/src/main/java/team/ppac/register/RegisterViewModel.kt +++ b/feature/register/src/main/java/team/ppac/register/RegisterViewModel.kt @@ -2,8 +2,12 @@ package team.ppac.register import androidx.lifecycle.SavedStateHandle import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.collections.immutable.toImmutableList +import kotlinx.collections.immutable.toImmutableSet import team.ppac.common.android.base.BaseViewModel +import team.ppac.domain.usecase.GetRecommendKeywordsUseCase import team.ppac.errorhandling.FarmemeNetworkException +import team.ppac.register.model.RegisterCategoryUiModel import team.ppac.register.mvi.RegisterIntent import team.ppac.register.mvi.RegisterSideEffect import team.ppac.register.mvi.RegisterUiState @@ -12,8 +16,23 @@ import javax.inject.Inject @HiltViewModel class RegisterViewModel @Inject constructor( savedStateHandle: SavedStateHandle, + private val getRecommendKeywordsUseCase: GetRecommendKeywordsUseCase, ) : BaseViewModel(savedStateHandle) { + init { + launch { + val registerCategories = getRecommendKeywordsUseCase().map { recommendKeyword -> + RegisterCategoryUiModel( + category = recommendKeyword.category, + keywords = recommendKeyword.keywords.toImmutableList(), + ) + }.toImmutableList() + reduce { + copy(registerCategories = registerCategories) + } + } + } + override fun createInitialState(savedStateHandle: SavedStateHandle): RegisterUiState { return RegisterUiState.INITIAL_STATE } @@ -45,6 +64,25 @@ class RegisterViewModel @Inject constructor( copy(title = intent.title) } } + + is RegisterIntent.OnKeywordClick -> { + if (currentState.selectedKeywords.contains(intent.keyword)) { + reduce { + copy(selectedKeywords = (currentState.selectedKeywords - intent.keyword).toImmutableSet()) + } + } else { + if (currentState.selectedKeywords.size < 6) { + reduce { + copy(selectedKeywords = (currentState.selectedKeywords + intent.keyword).toImmutableSet()) + } + } + } + } + + RegisterIntent.ClickRegister -> { + + + } } } } \ No newline at end of file diff --git a/feature/register/src/main/java/team/ppac/register/component/RegisterCategoryContent.kt b/feature/register/src/main/java/team/ppac/register/component/RegisterCategoryContent.kt index aa7fd31a..f119e48f 100644 --- a/feature/register/src/main/java/team/ppac/register/component/RegisterCategoryContent.kt +++ b/feature/register/src/main/java/team/ppac/register/component/RegisterCategoryContent.kt @@ -19,12 +19,15 @@ import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import team.ppac.designsystem.FarmemeTheme import team.ppac.designsystem.component.chip.FarmemeMediumChip +import team.ppac.domain.model.Keyword import team.ppac.register.model.RegisterCategoryUiModel @Composable internal fun RegisterCategoryContent( modifier: Modifier = Modifier, uiModel: RegisterCategoryUiModel, + selectedKeywords: ImmutableList, + onKeywordClick: (Keyword) -> Unit, ) { Column( modifier = modifier, @@ -32,7 +35,8 @@ internal fun RegisterCategoryContent( RegisterCategoryHeader(title = uiModel.category) RegisterCategoryChips( keywords = uiModel.keywords, - onKeywordClick = {}, + selectedKeywords = selectedKeywords, + onKeywordClick = onKeywordClick, ) Spacer(modifier = Modifier.height(24.dp)) } @@ -42,8 +46,9 @@ internal fun RegisterCategoryContent( @Composable internal fun RegisterCategoryChips( modifier: Modifier = Modifier, - keywords: ImmutableList, - onKeywordClick: (String) -> Unit, + keywords: ImmutableList, + selectedKeywords: ImmutableList, + onKeywordClick: (Keyword) -> Unit, ) { FlowRow( modifier = modifier @@ -55,37 +60,31 @@ internal fun RegisterCategoryChips( repeat(keywords.size) { index -> val keyword = keywords[index] FarmemeMediumChip( - text = keyword, + text = keyword.name, + enabled = selectedKeywords.contains(keyword), onClick = { onKeywordClick(keyword) } ) } } } -@Preview -@Composable -private fun RegisterCategoryContentPreview() { - RegisterCategoryContent( - uiModel = RegisterCategoryUiModel.INITIAL_STATE, - ) -} - @Preview @Composable private fun RegisterCategoryChipsPreview() { Box(modifier = Modifier.background(FarmemeTheme.backgroundColor.white)) { RegisterCategoryChips( keywords = persistentListOf( - "행복", - "슬픈", - "분노", - "웃긴", - "피곤", - "절망", - "현타", - "당황", - "무념무상", + Keyword( + id = "", + name = "행복", + searchCount = null, + createdAt = null, + updatedAt = null, + category = null, + imageUrl = null, + ) ), + selectedKeywords = persistentListOf(), onKeywordClick = {} ) } diff --git a/feature/register/src/main/java/team/ppac/register/component/RegisterInputArea.kt b/feature/register/src/main/java/team/ppac/register/component/RegisterInputArea.kt index fe252383..4c92af22 100644 --- a/feature/register/src/main/java/team/ppac/register/component/RegisterInputArea.kt +++ b/feature/register/src/main/java/team/ppac/register/component/RegisterInputArea.kt @@ -1,14 +1,28 @@ package team.ppac.register.component +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.imePadding +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusDirection +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.unit.dp +import team.ppac.designsystem.FarmemeTheme +import team.ppac.designsystem.component.textfield.FarmemeTextArea import team.ppac.designsystem.component.textfield.FarmemeTextField +private const val MAX_TITLE_LENGTH = 18 +private const val MAX_SOURCE_LENGTH = 32 + @Composable internal fun RegisterInputArea( modifier: Modifier = Modifier, @@ -17,17 +31,91 @@ internal fun RegisterInputArea( source: String, onSourceChanged: (String) -> Unit, ) { + val focusManager = LocalFocusManager.current Column( modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally, ) { RegisterListHeader(title = "밈의 제목") Spacer(modifier = Modifier.height(12.dp)) - FarmemeTextField(text = title, onTextChanged = onTitleChanged) + FarmemeTextField( + text = title, + onTextChanged = { + if (it.length <= MAX_TITLE_LENGTH) { + onTitleChanged(it) + } + }, + trailingContent = { + TrailingTextLength( + textCount = title.length, + maxCount = MAX_TITLE_LENGTH, + ) + }, + keyboardOptions = KeyboardOptions.Default.copy( + imeAction = ImeAction.Next + ), + keyboardActions = KeyboardActions( + onNext = { + focusManager.moveFocus(FocusDirection.Down) + } + ) + ) Spacer(modifier = Modifier.height(40.dp)) RegisterListHeader(title = "밈의 출처") Spacer(modifier = Modifier.height(12.dp)) - FarmemeTextField(text = source, onTextChanged = onSourceChanged) + FarmemeTextArea( + text = source, + onTextChanged = { + if (it.length <= MAX_SOURCE_LENGTH) { + onSourceChanged(it) + } + }, + trailingContent = { + TrailingTextLength( + textCount = source.length, + maxCount = MAX_SOURCE_LENGTH, + ) + }, + keyboardOptions = KeyboardOptions.Default.copy( + imeAction = ImeAction.Done + ), + keyboardActions = KeyboardActions( + onDone = { + focusManager.clearFocus() + } + ) + ) Spacer(modifier = Modifier.height(35.dp)) } +} + +@Composable +fun TrailingTextLength( + textCount: Int, + maxCount: Int, +) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(2.dp) + ) { + Text( + text = textCount.toString(), + style = FarmemeTheme.typography.body.medium.medium, + color = when (textCount) { + 0 -> FarmemeTheme.textColor.assistive + maxCount -> FarmemeTheme.textColor.brand + else -> FarmemeTheme.textColor.secondary + } + ) + Text( + text = "/", + style = FarmemeTheme.typography.body.medium.medium, + color = FarmemeTheme.textColor.assistive + ) + Text( + text = maxCount.toString(), + style = FarmemeTheme.typography.body.medium.medium, + color = FarmemeTheme.textColor.assistive + ) + } } \ No newline at end of file diff --git a/feature/register/src/main/java/team/ppac/register/model/RegisterCategoryUiModel.kt b/feature/register/src/main/java/team/ppac/register/model/RegisterCategoryUiModel.kt index 079eef0a..efd7357c 100644 --- a/feature/register/src/main/java/team/ppac/register/model/RegisterCategoryUiModel.kt +++ b/feature/register/src/main/java/team/ppac/register/model/RegisterCategoryUiModel.kt @@ -2,10 +2,11 @@ package team.ppac.register.model import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf +import team.ppac.domain.model.Keyword data class RegisterCategoryUiModel( val category: String = "", - val keywords: ImmutableList = persistentListOf(), + val keywords: ImmutableList = persistentListOf(), ) { companion object { val INITIAL_STATE diff --git a/feature/register/src/main/java/team/ppac/register/mvi/RegisterIntent.kt b/feature/register/src/main/java/team/ppac/register/mvi/RegisterIntent.kt index 2aa13309..e7172bc7 100644 --- a/feature/register/src/main/java/team/ppac/register/mvi/RegisterIntent.kt +++ b/feature/register/src/main/java/team/ppac/register/mvi/RegisterIntent.kt @@ -1,9 +1,12 @@ package team.ppac.register.mvi import team.ppac.common.android.base.UiIntent +import team.ppac.domain.model.Keyword sealed interface RegisterIntent : UiIntent { data class SetImageFromGallery(val uri: String) : RegisterIntent data class InputTitle(val title: String) : RegisterIntent data class InputSource(val source: String) : RegisterIntent + data class OnKeywordClick(val keyword: Keyword) : RegisterIntent + data object ClickRegister : RegisterIntent } \ No newline at end of file diff --git a/feature/register/src/main/java/team/ppac/register/mvi/RegisterUiState.kt b/feature/register/src/main/java/team/ppac/register/mvi/RegisterUiState.kt index 24dc1ce5..baa5d509 100644 --- a/feature/register/src/main/java/team/ppac/register/mvi/RegisterUiState.kt +++ b/feature/register/src/main/java/team/ppac/register/mvi/RegisterUiState.kt @@ -1,14 +1,18 @@ package team.ppac.register.mvi import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.ImmutableSet import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.persistentSetOf import team.ppac.common.android.base.UiState +import team.ppac.domain.model.Keyword import team.ppac.register.model.RegisterCategoryUiModel data class RegisterUiState( val isLoading: Boolean, val isError: Boolean, val registerCategories: ImmutableList, + val selectedKeywords: ImmutableSet, val imageUri: String, val title: String, val source: String, @@ -18,50 +22,8 @@ data class RegisterUiState( val INITIAL_STATE = RegisterUiState( isLoading = false, isError = false, - registerCategories = persistentListOf( - RegisterCategoryUiModel( - category = "감정", - keywords = persistentListOf( - "행복", - "슬픈", - "분노", - "웃긴", - "피곤", - "절망", - "현타", - "당황", - "무념무상", - ), - ), - RegisterCategoryUiModel( - category = "상황", - keywords = persistentListOf( - "행복", - "슬픈", - "분노", - "웃긴", - "피곤", - "절망", - "현타", - "당황", - "무념무상", - ), - ), - RegisterCategoryUiModel( - category = "콘텐츠", - keywords = persistentListOf( - "행복", - "슬픈", - "분노", - "웃긴", - "피곤", - "절망", - "현타", - "당황", - "무념무상", - ), - ) - ), + registerCategories = persistentListOf(), + selectedKeywords = persistentSetOf(), imageUri = "", title = "", source = "", From f22d0d34089ab2994741d433c40ea5ad2a2659ca Mon Sep 17 00:00:00 2001 From: evergreen Date: Tue, 1 Oct 2024 17:36:44 +0900 Subject: [PATCH 4/7] =?UTF-8?q?[Feat]=20=EB=B0=88=EB=93=B1=EB=A1=9D?= =?UTF-8?q?=EA=B9=8C=EC=A7=80=20=EC=84=B1=EA=B3=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/repository/MemeRepositoryImpl.kt | 11 ++- .../java/team/ppac/domain/di/UseCaseModule.kt | 6 ++ .../ppac/domain/repository/MemeRepository.kt | 6 +- .../ppac/domain/usecase/UpLoadImageUseCase.kt | 11 --- .../ppac/domain/usecase/UploadMemeUseCase.kt | 31 +++++++++ .../kotlin/team/ppac/remote/api/MemeApi.kt | 14 ++++ .../ppac/remote/datasource/MemeDataSource.kt | 6 ++ .../datasource/impl/MemeDataSourceImpl.kt | 67 ++++++++++++++++++- .../team/ppac/register/RegisterViewModel.kt | 11 ++- 9 files changed, 143 insertions(+), 20 deletions(-) delete mode 100644 core/domain/src/main/java/team/ppac/domain/usecase/UpLoadImageUseCase.kt create mode 100644 core/domain/src/main/java/team/ppac/domain/usecase/UploadMemeUseCase.kt diff --git a/core/data/src/main/java/team/ppac/data/repository/MemeRepositoryImpl.kt b/core/data/src/main/java/team/ppac/data/repository/MemeRepositoryImpl.kt index f8b44c6e..8b2d3c52 100644 --- a/core/data/src/main/java/team/ppac/data/repository/MemeRepositoryImpl.kt +++ b/core/data/src/main/java/team/ppac/data/repository/MemeRepositoryImpl.kt @@ -72,12 +72,17 @@ internal class MemeRepositoryImpl @Inject constructor( get() = _savedMemeEventFlow override suspend fun uploadMeme( - memeId: String, + keywordIds: List, memeImageUri: String, memeTitle: String, - memeSource: String, + memeSource: String ): Boolean { - return false // TODO + return memeDataSource.uploadMeme( + keywordIds = keywordIds, + memeTitle = memeTitle, + memeImageUri = memeImageUri, + memeSource = memeSource + ) } override suspend fun emitRefreshEvent() { diff --git a/core/domain/src/main/java/team/ppac/domain/di/UseCaseModule.kt b/core/domain/src/main/java/team/ppac/domain/di/UseCaseModule.kt index 1405bd0c..7eb46cac 100644 --- a/core/domain/src/main/java/team/ppac/domain/di/UseCaseModule.kt +++ b/core/domain/src/main/java/team/ppac/domain/di/UseCaseModule.kt @@ -39,6 +39,8 @@ import team.ppac.domain.usecase.SaveMemeUseCase import team.ppac.domain.usecase.SaveMemeUseCaseImpl import team.ppac.domain.usecase.SetLevelUseCase import team.ppac.domain.usecase.SetLevelUseCaseImpl +import team.ppac.domain.usecase.UploadMemeUseCase +import team.ppac.domain.usecase.UploadMemeUseCaseImpl import team.ppac.domain.usecase.WatchMemeUseCase import team.ppac.domain.usecase.WatchMemeUseCaseImpl @@ -116,4 +118,8 @@ internal abstract class UseCaseModule { @Binds @ViewModelScoped abstract fun bindEmitRefreshEventUseCase(impl: EmitRefreshEventUseCaseImpl): EmitRefreshEventUseCase + + @Binds + @ViewModelScoped + abstract fun bindUploadMemeUseCase(impl: UploadMemeUseCaseImpl): UploadMemeUseCase } \ No newline at end of file diff --git a/core/domain/src/main/java/team/ppac/domain/repository/MemeRepository.kt b/core/domain/src/main/java/team/ppac/domain/repository/MemeRepository.kt index fc3b3a82..cae39cf3 100644 --- a/core/domain/src/main/java/team/ppac/domain/repository/MemeRepository.kt +++ b/core/domain/src/main/java/team/ppac/domain/repository/MemeRepository.kt @@ -1,6 +1,5 @@ package team.ppac.domain.repository -import androidx.paging.PagingData import kotlinx.coroutines.flow.Flow import team.ppac.domain.model.Meme import team.ppac.domain.model.MemeWatchType @@ -15,6 +14,7 @@ interface MemeRepository { keyword: String, getCurrentPage: (Int) -> Unit ): MemeWithPagination + suspend fun reactMeme(memeId: String): Boolean suspend fun watchMeme( memeId: String, @@ -24,10 +24,10 @@ interface MemeRepository { suspend fun emitRefreshEvent() val savedMemeEventFlow: Flow suspend fun uploadMeme( - memeId: String, + keywordIds: List, memeImageUri: String, memeTitle: String, - memeSource: String, + memeSource: String ): Boolean } diff --git a/core/domain/src/main/java/team/ppac/domain/usecase/UpLoadImageUseCase.kt b/core/domain/src/main/java/team/ppac/domain/usecase/UpLoadImageUseCase.kt deleted file mode 100644 index 84cb673b..00000000 --- a/core/domain/src/main/java/team/ppac/domain/usecase/UpLoadImageUseCase.kt +++ /dev/null @@ -1,11 +0,0 @@ -package team.ppac.domain.usecase - -interface UpLoadImageUseCase { - suspend operator fun invoke(uri: String) -} - -internal class UpLoadImageUseCaseImpl : UpLoadImageUseCase { - override suspend fun invoke(uri: String) { - - } -} \ No newline at end of file diff --git a/core/domain/src/main/java/team/ppac/domain/usecase/UploadMemeUseCase.kt b/core/domain/src/main/java/team/ppac/domain/usecase/UploadMemeUseCase.kt new file mode 100644 index 00000000..204a80bf --- /dev/null +++ b/core/domain/src/main/java/team/ppac/domain/usecase/UploadMemeUseCase.kt @@ -0,0 +1,31 @@ +package team.ppac.domain.usecase + +import team.ppac.domain.repository.MemeRepository +import javax.inject.Inject + +interface UploadMemeUseCase { + suspend operator fun invoke( + keywordIds: List, + memeImageUri: String, + memeTitle: String, + memeSource: String + ): Boolean +} + +internal class UploadMemeUseCaseImpl @Inject constructor( + private val memeRepository: MemeRepository +) : UploadMemeUseCase { + override suspend fun invoke( + keywordIds: List, + memeImageUri: String, + memeTitle: String, + memeSource: String + ): Boolean { + return memeRepository.uploadMeme( + keywordIds = keywordIds, + memeTitle = memeTitle, + memeImageUri = memeImageUri, + memeSource = memeSource + ) + } +} \ No newline at end of file diff --git a/core/remote/src/main/kotlin/team/ppac/remote/api/MemeApi.kt b/core/remote/src/main/kotlin/team/ppac/remote/api/MemeApi.kt index 10960dbf..7fc0ef7b 100644 --- a/core/remote/src/main/kotlin/team/ppac/remote/api/MemeApi.kt +++ b/core/remote/src/main/kotlin/team/ppac/remote/api/MemeApi.kt @@ -1,8 +1,13 @@ package team.ppac.remote.api +import okhttp3.MultipartBody +import okhttp3.RequestBody import retrofit2.http.DELETE import retrofit2.http.GET +import retrofit2.http.Multipart import retrofit2.http.POST +import retrofit2.http.Part +import retrofit2.http.PartMap import retrofit2.http.Path import retrofit2.http.Query import team.ppac.remote.model.response.meme.MemeResponse @@ -39,4 +44,13 @@ internal interface MemeApi { @Path("memeId") memeId: String, @Path("type") type: String, ): Boolean + + @Multipart + @POST("/api/meme") + suspend fun postMeme( + @Part title: MultipartBody.Part, + @Part image: MultipartBody.Part, + @Part source: MultipartBody.Part, + @Part keywordIds: List, + ): Boolean } diff --git a/core/remote/src/main/kotlin/team/ppac/remote/datasource/MemeDataSource.kt b/core/remote/src/main/kotlin/team/ppac/remote/datasource/MemeDataSource.kt index d4d4c9de..8677c3be 100644 --- a/core/remote/src/main/kotlin/team/ppac/remote/datasource/MemeDataSource.kt +++ b/core/remote/src/main/kotlin/team/ppac/remote/datasource/MemeDataSource.kt @@ -18,4 +18,10 @@ interface MemeDataSource { memeId: String, type: String, ): Boolean + suspend fun uploadMeme( + keywordIds: List, + memeImageUri: String, + memeTitle: String, + memeSource: String + ): Boolean } \ No newline at end of file diff --git a/core/remote/src/main/kotlin/team/ppac/remote/datasource/impl/MemeDataSourceImpl.kt b/core/remote/src/main/kotlin/team/ppac/remote/datasource/impl/MemeDataSourceImpl.kt index ec17967c..8d10cb04 100644 --- a/core/remote/src/main/kotlin/team/ppac/remote/datasource/impl/MemeDataSourceImpl.kt +++ b/core/remote/src/main/kotlin/team/ppac/remote/datasource/impl/MemeDataSourceImpl.kt @@ -1,13 +1,26 @@ package team.ppac.remote.datasource.impl +import android.content.ContentResolver +import android.content.Context +import android.net.Uri +import androidx.core.net.toUri +import dagger.hilt.android.qualifiers.ApplicationContext +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.MultipartBody +import okhttp3.RequestBody.Companion.asRequestBody import team.ppac.remote.api.MemeApi import team.ppac.remote.datasource.MemeDataSource import team.ppac.remote.model.response.meme.MemeResponse import team.ppac.remote.model.response.user.SavedMemesResponse +import java.io.File +import java.io.FileOutputStream +import java.io.InputStream +import java.io.OutputStream import javax.inject.Inject internal class MemeDataSourceImpl @Inject constructor( private val memeApi: MemeApi, + @ApplicationContext private val context: Context, ) : MemeDataSource { override suspend fun getMemeById(memeId: String): MemeResponse { return memeApi.getMemeById(memeId = memeId) @@ -36,4 +49,56 @@ internal class MemeDataSourceImpl @Inject constructor( override suspend fun watchMeme(memeId: String, type: String): Boolean { return memeApi.watchMeme(memeId, type) } -} \ No newline at end of file + + override suspend fun uploadMeme( + keywordIds: List, + memeImageUri: String, + memeTitle: String, + memeSource: String + ): Boolean { + val file = getFileFromUri(memeImageUri.toUri()) ?: return false + val imageBody = file.asRequestBody("image/*".toMediaTypeOrNull()) + val imagePart = MultipartBody.Part.createFormData("image", file.name, imageBody) + + val keywords = keywordIds.map { MultipartBody.Part.createFormData("keywordIds", it) } + val title = MultipartBody.Part.createFormData("title", memeTitle) + val source = MultipartBody.Part.createFormData("source", memeSource) + return memeApi.postMeme( + title = title, + image = imagePart, + source = source, + keywordIds = keywords + ) + } + + private fun getFileFromUri(uri: Uri): File? { + val contentResolver: ContentResolver = context.contentResolver + val fileName = getFileName(contentResolver, uri) ?: return null // 파일 이름 얻기 + val tempFile = File(context.cacheDir, fileName) + + try { + val inputStream: InputStream? = contentResolver.openInputStream(uri) + val outputStream: OutputStream = FileOutputStream(tempFile) + + inputStream?.use { input -> + outputStream.use { output -> + input.copyTo(output) + } + } + return tempFile + } catch (e: Exception) { + e.printStackTrace() + } + return null + } + + private fun getFileName(contentResolver: ContentResolver, uri: Uri): String? { + val cursor = contentResolver.query(uri, null, null, null, null) + return cursor?.use { + if (it.moveToFirst()) { + val index = it.getColumnIndex("_display_name") + it.getString(index) + } else null + } + } +} diff --git a/feature/register/src/main/java/team/ppac/register/RegisterViewModel.kt b/feature/register/src/main/java/team/ppac/register/RegisterViewModel.kt index 3041ebbe..c40dbe33 100644 --- a/feature/register/src/main/java/team/ppac/register/RegisterViewModel.kt +++ b/feature/register/src/main/java/team/ppac/register/RegisterViewModel.kt @@ -6,6 +6,7 @@ import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableSet import team.ppac.common.android.base.BaseViewModel import team.ppac.domain.usecase.GetRecommendKeywordsUseCase +import team.ppac.domain.usecase.UploadMemeUseCase import team.ppac.errorhandling.FarmemeNetworkException import team.ppac.register.model.RegisterCategoryUiModel import team.ppac.register.mvi.RegisterIntent @@ -17,6 +18,7 @@ import javax.inject.Inject class RegisterViewModel @Inject constructor( savedStateHandle: SavedStateHandle, private val getRecommendKeywordsUseCase: GetRecommendKeywordsUseCase, + private val uploadMemeUseCase: UploadMemeUseCase ) : BaseViewModel(savedStateHandle) { init { @@ -38,6 +40,7 @@ class RegisterViewModel @Inject constructor( } override fun handleClientException(throwable: Throwable) { + println(">> $throwable") if (throwable is FarmemeNetworkException) { reduce { copy(isError = true) @@ -80,8 +83,12 @@ class RegisterViewModel @Inject constructor( } RegisterIntent.ClickRegister -> { - - + uploadMemeUseCase( + keywordIds = currentState.selectedKeywords.map { it.id }, + memeTitle = currentState.title, + memeSource = currentState.source, + memeImageUri = currentState.imageUri + ) } } } From 0a1c967ab961956b2692d761db5ae78b63925fb7 Mon Sep 17 00:00:00 2001 From: evergreen Date: Tue, 1 Oct 2024 18:41:21 +0900 Subject: [PATCH 5/7] =?UTF-8?q?[Feat]=20=EC=98=88=EC=99=B8=EC=B2=98?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/team/ppac/remote/api/MemeApi.kt | 5 +- .../ppac/remote/datasource/MemeDataSource.kt | 2 + .../datasource/impl/MemeDataSourceImpl.kt | 6 +- .../model/response/meme/UploadMemeResponse.kt | 30 ++++ .../java/team/ppac/register/RegisterScreen.kt | 139 ++++++++++++------ .../team/ppac/register/RegisterViewModel.kt | 54 +++++-- .../register/component/RegisterImageArea.kt | 4 +- .../component/UploadMemeResultDialog.kt | 67 +++++++++ .../team/ppac/register/mvi/RegisterIntent.kt | 1 + .../team/ppac/register/mvi/RegisterUiState.kt | 2 + 10 files changed, 242 insertions(+), 68 deletions(-) create mode 100644 core/remote/src/main/kotlin/team/ppac/remote/model/response/meme/UploadMemeResponse.kt create mode 100644 feature/register/src/main/java/team/ppac/register/component/UploadMemeResultDialog.kt diff --git a/core/remote/src/main/kotlin/team/ppac/remote/api/MemeApi.kt b/core/remote/src/main/kotlin/team/ppac/remote/api/MemeApi.kt index 7fc0ef7b..e7046227 100644 --- a/core/remote/src/main/kotlin/team/ppac/remote/api/MemeApi.kt +++ b/core/remote/src/main/kotlin/team/ppac/remote/api/MemeApi.kt @@ -1,16 +1,15 @@ package team.ppac.remote.api import okhttp3.MultipartBody -import okhttp3.RequestBody import retrofit2.http.DELETE import retrofit2.http.GET import retrofit2.http.Multipart import retrofit2.http.POST import retrofit2.http.Part -import retrofit2.http.PartMap import retrofit2.http.Path import retrofit2.http.Query import team.ppac.remote.model.response.meme.MemeResponse +import team.ppac.remote.model.response.meme.UploadMemeResponse import team.ppac.remote.model.response.user.SavedMemesResponse internal interface MemeApi { @@ -52,5 +51,5 @@ internal interface MemeApi { @Part image: MultipartBody.Part, @Part source: MultipartBody.Part, @Part keywordIds: List, - ): Boolean + ): UploadMemeResponse } diff --git a/core/remote/src/main/kotlin/team/ppac/remote/datasource/MemeDataSource.kt b/core/remote/src/main/kotlin/team/ppac/remote/datasource/MemeDataSource.kt index 8677c3be..9c6428db 100644 --- a/core/remote/src/main/kotlin/team/ppac/remote/datasource/MemeDataSource.kt +++ b/core/remote/src/main/kotlin/team/ppac/remote/datasource/MemeDataSource.kt @@ -13,11 +13,13 @@ interface MemeDataSource { page: Int, size: Int, ): SavedMemesResponse + suspend fun reactMeme(memeId: String): Boolean suspend fun watchMeme( memeId: String, type: String, ): Boolean + suspend fun uploadMeme( keywordIds: List, memeImageUri: String, diff --git a/core/remote/src/main/kotlin/team/ppac/remote/datasource/impl/MemeDataSourceImpl.kt b/core/remote/src/main/kotlin/team/ppac/remote/datasource/impl/MemeDataSourceImpl.kt index 8d10cb04..ba112e8d 100644 --- a/core/remote/src/main/kotlin/team/ppac/remote/datasource/impl/MemeDataSourceImpl.kt +++ b/core/remote/src/main/kotlin/team/ppac/remote/datasource/impl/MemeDataSourceImpl.kt @@ -56,19 +56,21 @@ internal class MemeDataSourceImpl @Inject constructor( memeTitle: String, memeSource: String ): Boolean { - val file = getFileFromUri(memeImageUri.toUri()) ?: return false + val file = + getFileFromUri(memeImageUri.toUri()) ?: throw IllegalStateException("파일을 찾을 수 없습니다.") val imageBody = file.asRequestBody("image/*".toMediaTypeOrNull()) val imagePart = MultipartBody.Part.createFormData("image", file.name, imageBody) val keywords = keywordIds.map { MultipartBody.Part.createFormData("keywordIds", it) } val title = MultipartBody.Part.createFormData("title", memeTitle) val source = MultipartBody.Part.createFormData("source", memeSource) - return memeApi.postMeme( + memeApi.postMeme( title = title, image = imagePart, source = source, keywordIds = keywords ) + return true } private fun getFileFromUri(uri: Uri): File? { diff --git a/core/remote/src/main/kotlin/team/ppac/remote/model/response/meme/UploadMemeResponse.kt b/core/remote/src/main/kotlin/team/ppac/remote/model/response/meme/UploadMemeResponse.kt new file mode 100644 index 00000000..d9552f46 --- /dev/null +++ b/core/remote/src/main/kotlin/team/ppac/remote/model/response/meme/UploadMemeResponse.kt @@ -0,0 +1,30 @@ +package team.ppac.remote.model.response.meme + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class UploadMemeResponse( + @field:Json(name = "deviceId") + val deviceId: String, + @field:Json(name = "title") + val title: String, + @field:Json(name = "keywordIds") + val keywords: List?, + @field:Json(name = "image") + val image: String, + @field:Json(name = "reaction") + val reaction: Int, + @field:Json(name = "source") + val source: String, + @field:Json(name = "isTodayMeme") + val isTodayMeme: Boolean, + @field:Json(name = "isDeleted") + val isDeleted: Boolean, + @field:Json(name = "_id") + val id: String, + @field:Json(name = "createdAt") + val createdAt: String, + @field:Json(name = "updatedAt") + val updatedAt: String, +) diff --git a/feature/register/src/main/java/team/ppac/register/RegisterScreen.kt b/feature/register/src/main/java/team/ppac/register/RegisterScreen.kt index a8b692cf..941f507a 100644 --- a/feature/register/src/main/java/team/ppac/register/RegisterScreen.kt +++ b/feature/register/src/main/java/team/ppac/register/RegisterScreen.kt @@ -3,8 +3,11 @@ package team.ppac.register import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.PickVisualMediaRequest import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column 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.imePadding @@ -16,9 +19,11 @@ import androidx.compose.material.Divider import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Brush import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import kotlinx.collections.immutable.toImmutableList +import team.ppac.common.android.component.error.FarmemeErrorScreen import team.ppac.designsystem.FarmemeTheme import team.ppac.designsystem.component.scaffold.FarmemeScaffold import team.ppac.designsystem.component.toolbar.FarmemeBackToolBar @@ -27,6 +32,7 @@ import team.ppac.register.component.RegisterCategoryContent import team.ppac.register.component.RegisterImageArea import team.ppac.register.component.RegisterInputArea import team.ppac.register.component.RegisterKeywordHeader +import team.ppac.register.component.UploadMemeResultDialog import team.ppac.register.mvi.RegisterIntent import team.ppac.register.mvi.RegisterUiState @@ -57,59 +63,96 @@ internal fun RegisterScreen( } }, bottomBar = { - RegisterButton( - modifier = Modifier.padding(bottom = 36.dp), - text = "등록하기", - enabled = true, - onClick = { - onIntent(RegisterIntent.ClickRegister) - }, - ) + if (uiState.isError.not()) { + Box(modifier = Modifier.fillMaxWidth()) { + Box( + modifier = Modifier + .height(120.dp) + .fillMaxWidth() + .background( + brush = Brush.verticalGradient( + 0f to FarmemeTheme.backgroundColor.white.copy(alpha = 0f), + 1f to FarmemeTheme.backgroundColor.white.copy(alpha = 1f), + ) + ) + ) + RegisterButton( + modifier = Modifier + .align(Alignment.BottomCenter) + .padding(bottom = 36.dp), + text = "등록하기", + enabled = true, + onClick = { + onIntent(RegisterIntent.ClickRegister) + }, + ) + } + } } ) { - LazyColumn( - modifier = Modifier - .fillMaxWidth() - .imePadding(), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - item { - RegisterImageArea( - loadImage = { imagePicker.launch(PickVisualMediaRequest(mediaType = ActivityResultContracts.PickVisualMedia.ImageOnly)) }, - imageUri = uiState.imageUri, - ) - } - item { - RegisterInputArea( - modifier = Modifier.padding(horizontal = 20.dp), - title = uiState.title, - onTitleChanged = { onIntent(RegisterIntent.InputTitle(it)) }, - source = uiState.source, - onSourceChanged = { onIntent(RegisterIntent.InputSource(it)) }, - ) - } - item { - Divider( - modifier = Modifier.fillMaxWidth(), - color = FarmemeTheme.skeletonColor.primary, - thickness = 10.dp, - ) - } - item { - RegisterKeywordHeader(modifier = Modifier.padding(horizontal = 20.dp)) - } - items(items = uiState.registerCategories) { registerCategory -> - RegisterCategoryContent( - uiModel = registerCategory, - selectedKeywords = uiState.selectedKeywords.toImmutableList(), - onKeywordClick = { - onIntent(RegisterIntent.OnKeywordClick(it)) - } - ) + if (uiState.isError) { + FarmemeErrorScreen( + modifier = Modifier.fillMaxSize(), + title = "정보를 불러오지 못 했어요.\n 새로고침 해주세요.", + onRetryClick = { + onIntent(RegisterIntent.OnRetry) + } + ) + } else { + LazyColumn( + modifier = Modifier + .fillMaxWidth() + .imePadding(), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + item { + RegisterImageArea( + loadImage = { imagePicker.launch(PickVisualMediaRequest(mediaType = ActivityResultContracts.PickVisualMedia.ImageOnly)) }, + imageUri = uiState.imageUri, + ) + } + item { + RegisterInputArea( + modifier = Modifier.padding(horizontal = 20.dp), + title = uiState.title, + onTitleChanged = { onIntent(RegisterIntent.InputTitle(it)) }, + source = uiState.source, + onSourceChanged = { onIntent(RegisterIntent.InputSource(it)) }, + ) + } + item { + Divider( + modifier = Modifier.fillMaxWidth(), + color = FarmemeTheme.skeletonColor.primary, + thickness = 10.dp, + ) + } + item { + RegisterKeywordHeader(modifier = Modifier.padding(horizontal = 20.dp)) + } + items(items = uiState.registerCategories) { registerCategory -> + RegisterCategoryContent( + uiModel = registerCategory, + selectedKeywords = uiState.selectedKeywords.toImmutableList(), + onKeywordClick = { + onIntent(RegisterIntent.OnKeywordClick(it)) + } + ) + } + item { Spacer(modifier = Modifier.height(120.dp)) } } - item { Spacer(modifier = Modifier.height(120.dp)) } } } + if (uiState.uploadMemeResultDialogVisible) { + UploadMemeResultDialog( + onConfirmClick = { + navigateToBack() + }, + onDismiss = { + navigateToBack() + } + ) + } } @Preview diff --git a/feature/register/src/main/java/team/ppac/register/RegisterViewModel.kt b/feature/register/src/main/java/team/ppac/register/RegisterViewModel.kt index c40dbe33..8169192b 100644 --- a/feature/register/src/main/java/team/ppac/register/RegisterViewModel.kt +++ b/feature/register/src/main/java/team/ppac/register/RegisterViewModel.kt @@ -8,6 +8,7 @@ import team.ppac.common.android.base.BaseViewModel import team.ppac.domain.usecase.GetRecommendKeywordsUseCase import team.ppac.domain.usecase.UploadMemeUseCase import team.ppac.errorhandling.FarmemeNetworkException +import team.ppac.errorhandling.FarmemeNetworkException.Companion.UNKNOWN_ERROR import team.ppac.register.model.RegisterCategoryUiModel import team.ppac.register.mvi.RegisterIntent import team.ppac.register.mvi.RegisterSideEffect @@ -22,17 +23,7 @@ class RegisterViewModel @Inject constructor( ) : BaseViewModel(savedStateHandle) { init { - launch { - val registerCategories = getRecommendKeywordsUseCase().map { recommendKeyword -> - RegisterCategoryUiModel( - category = recommendKeyword.category, - keywords = recommendKeyword.keywords.toImmutableList(), - ) - }.toImmutableList() - reduce { - copy(registerCategories = registerCategories) - } - } + getCategories() } override fun createInitialState(savedStateHandle: SavedStateHandle): RegisterUiState { @@ -42,8 +33,32 @@ class RegisterViewModel @Inject constructor( override fun handleClientException(throwable: Throwable) { println(">> $throwable") if (throwable is FarmemeNetworkException) { + if (throwable.code == UNKNOWN_ERROR) { + showSnackbar(message = "밈 등록에 실패했어요") + } else { + reduce { + copy(isError = true) + } + } + } else { + showSnackbar(message = "밈 등록에 실패했어요") + } + } + + private fun getCategories() { + launch { + val registerCategories = getRecommendKeywordsUseCase().map { recommendKeyword -> + RegisterCategoryUiModel( + category = recommendKeyword.category, + keywords = recommendKeyword.keywords.toImmutableList(), + ) + }.toImmutableList() reduce { - copy(isError = true) + copy( + registerCategories = registerCategories, + isLoading = false, + isError = false, + ) } } } @@ -78,17 +93,30 @@ class RegisterViewModel @Inject constructor( reduce { copy(selectedKeywords = (currentState.selectedKeywords + intent.keyword).toImmutableSet()) } + } else { + showSnackbar(message = "최대 개수를 초과했어요") } } } RegisterIntent.ClickRegister -> { - uploadMemeUseCase( + val isUploadSuccess = uploadMemeUseCase( keywordIds = currentState.selectedKeywords.map { it.id }, memeTitle = currentState.title, memeSource = currentState.source, memeImageUri = currentState.imageUri ) + if (isUploadSuccess) { + reduce { + copy(uploadMemeResultDialogVisible = true) + } + } else { + showSnackbar(message = "밈 등록에 실패했어요") + } + } + + RegisterIntent.OnRetry -> { + getCategories() } } } diff --git a/feature/register/src/main/java/team/ppac/register/component/RegisterImageArea.kt b/feature/register/src/main/java/team/ppac/register/component/RegisterImageArea.kt index 4e1f464a..527dafc3 100644 --- a/feature/register/src/main/java/team/ppac/register/component/RegisterImageArea.kt +++ b/feature/register/src/main/java/team/ppac/register/component/RegisterImageArea.kt @@ -65,7 +65,7 @@ internal fun RegisterImageArea( Box( modifier = Modifier .fillMaxSize() - .background(FarmemeTheme.backgroundColor.brandAssistive), + .background(FarmemeTheme.backgroundColor.primary), ) AsyncImage( modifier = Modifier.matchParentSize(), @@ -74,7 +74,7 @@ internal fun RegisterImageArea( .crossfade(true) .build(), contentDescription = null, - contentScale = ContentScale.Crop, + contentScale = ContentScale.Fit, ) FarmemeCircleButton( modifier = Modifier diff --git a/feature/register/src/main/java/team/ppac/register/component/UploadMemeResultDialog.kt b/feature/register/src/main/java/team/ppac/register/component/UploadMemeResultDialog.kt new file mode 100644 index 00000000..620b59b9 --- /dev/null +++ b/feature/register/src/main/java/team/ppac/register/component/UploadMemeResultDialog.kt @@ -0,0 +1,67 @@ +package team.ppac.register.component + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog +import team.ppac.designsystem.FarmemeTheme +import team.ppac.designsystem.foundation.FarmemeRadius +import team.ppac.designsystem.util.extension.noRippleClickable + +@Composable +internal fun UploadMemeResultDialog( + modifier: Modifier = Modifier, + onConfirmClick: () -> Unit, + onDismiss: () -> Unit, +) { + Dialog( + onDismissRequest = onDismiss, + ) { + Column( + modifier = modifier + .clip(FarmemeRadius.Radius20.shape) + .fillMaxWidth() + .background(FarmemeTheme.backgroundColor.white) + .padding( + horizontal = 30.dp, + vertical = 20.dp + ) + ) { + Text( + modifier = Modifier.fillMaxWidth(), + text = "밈 올리기 성공!", + style = FarmemeTheme.typography.heading.medium.semibold.copy( + color = FarmemeTheme.textColor.primary + ), + ) + Spacer(modifier = Modifier.size(8.dp)) + Text( + modifier = Modifier.fillMaxWidth(), + text = "마이페이지에서 확인할 수 있어요", + style = FarmemeTheme.typography.body.large.medium.copy( + color = FarmemeTheme.textColor.secondary + ), + ) + Spacer(modifier = Modifier.size(14.dp)) + Text( + modifier = Modifier + .fillMaxWidth() + .noRippleClickable(onClick = onConfirmClick), + text = "확인", + style = FarmemeTheme.typography.heading.small.bold.copy( + color = FarmemeTheme.textColor.brand + ), + textAlign = TextAlign.End + ) + } + } +} \ No newline at end of file diff --git a/feature/register/src/main/java/team/ppac/register/mvi/RegisterIntent.kt b/feature/register/src/main/java/team/ppac/register/mvi/RegisterIntent.kt index e7172bc7..cbb7a51d 100644 --- a/feature/register/src/main/java/team/ppac/register/mvi/RegisterIntent.kt +++ b/feature/register/src/main/java/team/ppac/register/mvi/RegisterIntent.kt @@ -9,4 +9,5 @@ sealed interface RegisterIntent : UiIntent { data class InputSource(val source: String) : RegisterIntent data class OnKeywordClick(val keyword: Keyword) : RegisterIntent data object ClickRegister : RegisterIntent + data object OnRetry : RegisterIntent } \ No newline at end of file diff --git a/feature/register/src/main/java/team/ppac/register/mvi/RegisterUiState.kt b/feature/register/src/main/java/team/ppac/register/mvi/RegisterUiState.kt index baa5d509..339f5250 100644 --- a/feature/register/src/main/java/team/ppac/register/mvi/RegisterUiState.kt +++ b/feature/register/src/main/java/team/ppac/register/mvi/RegisterUiState.kt @@ -16,6 +16,7 @@ data class RegisterUiState( val imageUri: String, val title: String, val source: String, + val uploadMemeResultDialogVisible: Boolean, ) : UiState { companion object { @@ -27,6 +28,7 @@ data class RegisterUiState( imageUri = "", title = "", source = "", + uploadMemeResultDialogVisible = false, ) } } \ No newline at end of file From f24f50a8d3563b5a73f49c4c0ea2a8ea80409efc Mon Sep 17 00:00:00 2001 From: evergreen Date: Tue, 1 Oct 2024 19:34:31 +0900 Subject: [PATCH 6/7] =?UTF-8?q?[Chore]=20=ED=8C=A8=EB=94=A9=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20=EB=A1=9C=EA=B7=B8=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/team/ppac/register/RegisterScreen.kt | 12 ++++++++---- .../java/team/ppac/register/RegisterViewModel.kt | 1 - 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/feature/register/src/main/java/team/ppac/register/RegisterScreen.kt b/feature/register/src/main/java/team/ppac/register/RegisterScreen.kt index 941f507a..6ee1feeb 100644 --- a/feature/register/src/main/java/team/ppac/register/RegisterScreen.kt +++ b/feature/register/src/main/java/team/ppac/register/RegisterScreen.kt @@ -100,9 +100,7 @@ internal fun RegisterScreen( ) } else { LazyColumn( - modifier = Modifier - .fillMaxWidth() - .imePadding(), + modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally, ) { item { @@ -139,7 +137,13 @@ internal fun RegisterScreen( } ) } - item { Spacer(modifier = Modifier.height(120.dp)) } + item { + Spacer( + modifier = Modifier + .height(120.dp) + .imePadding() + ) + } } } } diff --git a/feature/register/src/main/java/team/ppac/register/RegisterViewModel.kt b/feature/register/src/main/java/team/ppac/register/RegisterViewModel.kt index 8169192b..71a38287 100644 --- a/feature/register/src/main/java/team/ppac/register/RegisterViewModel.kt +++ b/feature/register/src/main/java/team/ppac/register/RegisterViewModel.kt @@ -31,7 +31,6 @@ class RegisterViewModel @Inject constructor( } override fun handleClientException(throwable: Throwable) { - println(">> $throwable") if (throwable is FarmemeNetworkException) { if (throwable.code == UNKNOWN_ERROR) { showSnackbar(message = "밈 등록에 실패했어요") From e73d8b2e975163918dcb909a89f4498f6bc3191d Mon Sep 17 00:00:00 2001 From: evergreen Date: Tue, 1 Oct 2024 21:43:28 +0900 Subject: [PATCH 7/7] =?UTF-8?q?[Fix]=20=ED=83=9C=EA=B7=B8=20=ED=95=98?= =?UTF-8?q?=EB=82=98=EC=9D=BC=EB=95=8C=EB=8F=84=20=EC=97=85=EB=A1=9C?= =?UTF-8?q?=EB=93=9C=20=EA=B0=80=EB=8A=A5=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/kotlin/team/ppac/remote/api/MemeApi.kt | 7 ++++--- .../remote/datasource/impl/MemeDataSourceImpl.kt | 15 +++++++++------ .../java/team/ppac/register/RegisterViewModel.kt | 1 + 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/core/remote/src/main/kotlin/team/ppac/remote/api/MemeApi.kt b/core/remote/src/main/kotlin/team/ppac/remote/api/MemeApi.kt index e7046227..feb465c8 100644 --- a/core/remote/src/main/kotlin/team/ppac/remote/api/MemeApi.kt +++ b/core/remote/src/main/kotlin/team/ppac/remote/api/MemeApi.kt @@ -1,6 +1,7 @@ package team.ppac.remote.api import okhttp3.MultipartBody +import okhttp3.RequestBody import retrofit2.http.DELETE import retrofit2.http.GET import retrofit2.http.Multipart @@ -47,9 +48,9 @@ internal interface MemeApi { @Multipart @POST("/api/meme") suspend fun postMeme( - @Part title: MultipartBody.Part, @Part image: MultipartBody.Part, - @Part source: MultipartBody.Part, - @Part keywordIds: List, + @Part("title") title: RequestBody, + @Part("source") source: RequestBody, + @Part("keywordIds[]") keywordIds: ArrayList, ): UploadMemeResponse } diff --git a/core/remote/src/main/kotlin/team/ppac/remote/datasource/impl/MemeDataSourceImpl.kt b/core/remote/src/main/kotlin/team/ppac/remote/datasource/impl/MemeDataSourceImpl.kt index ba112e8d..90b611ab 100644 --- a/core/remote/src/main/kotlin/team/ppac/remote/datasource/impl/MemeDataSourceImpl.kt +++ b/core/remote/src/main/kotlin/team/ppac/remote/datasource/impl/MemeDataSourceImpl.kt @@ -8,6 +8,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MultipartBody import okhttp3.RequestBody.Companion.asRequestBody +import okhttp3.RequestBody.Companion.toRequestBody import team.ppac.remote.api.MemeApi import team.ppac.remote.datasource.MemeDataSource import team.ppac.remote.model.response.meme.MemeResponse @@ -18,6 +19,7 @@ import java.io.InputStream import java.io.OutputStream import javax.inject.Inject + internal class MemeDataSourceImpl @Inject constructor( private val memeApi: MemeApi, @ApplicationContext private val context: Context, @@ -61,14 +63,15 @@ internal class MemeDataSourceImpl @Inject constructor( val imageBody = file.asRequestBody("image/*".toMediaTypeOrNull()) val imagePart = MultipartBody.Part.createFormData("image", file.name, imageBody) - val keywords = keywordIds.map { MultipartBody.Part.createFormData("keywordIds", it) } - val title = MultipartBody.Part.createFormData("title", memeTitle) - val source = MultipartBody.Part.createFormData("source", memeSource) + val titleRequest = memeTitle.toRequestBody("text/plain".toMediaTypeOrNull()) + val sourceRequest = memeSource.toRequestBody("text/plain".toMediaTypeOrNull()) + val keywordIdRequests = + keywordIds.map { it.toRequestBody("text/plain".toMediaTypeOrNull()) } memeApi.postMeme( - title = title, image = imagePart, - source = source, - keywordIds = keywords + title = titleRequest, + source = sourceRequest, + keywordIds = ArrayList(keywordIdRequests) ) return true } diff --git a/feature/register/src/main/java/team/ppac/register/RegisterViewModel.kt b/feature/register/src/main/java/team/ppac/register/RegisterViewModel.kt index 71a38287..8169192b 100644 --- a/feature/register/src/main/java/team/ppac/register/RegisterViewModel.kt +++ b/feature/register/src/main/java/team/ppac/register/RegisterViewModel.kt @@ -31,6 +31,7 @@ class RegisterViewModel @Inject constructor( } override fun handleClientException(throwable: Throwable) { + println(">> $throwable") if (throwable is FarmemeNetworkException) { if (throwable.code == UNKNOWN_ERROR) { showSnackbar(message = "밈 등록에 실패했어요")