From 775e042a88258aec8448293f7a821ae24929194f Mon Sep 17 00:00:00 2001 From: Ham BeomJoon Date: Sat, 30 May 2026 23:16:27 +0900 Subject: [PATCH 01/10] =?UTF-8?q?feat:=20=EC=85=80=ED=94=84=20=ED=94=BC?= =?UTF-8?q?=EB=93=9C=EB=B0=B1=20=EC=9E=91=EC=84=B1=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `feature:feedback` 모듈(api, impl) 추가 및 의존성 설정 - 셀프 피드백 작성을 위한 `FeedbackScreen`, `FeedbackViewModel` 구현 - `WriteSelfFeedbackUseCase` 및 관련 Repository, RemoteDataSource 로직 추가 - 홈 화면에서 피드백 작성 화면으로의 내비게이션 연결 - `POST recording/{presentationId}/review` API 엔드포인트 정의 및 `SelfFeedbackRequest` DTO 추가 - 피드백 작성 중 이탈 시 확인 다이얼로그 표시 로직 구현 - 피드백 저장 실패 시 스낵바 알림 표시 추가 --- Prezel/app/build.gradle.kts | 2 + .../repository/PresentationRepositoryImpl.kt | 11 + .../presentation/PresentationRepository.kt | 5 + .../presentation/WriteSelfFeedbackUseCase.kt | 17 ++ .../PresentationRemoteDataSource.kt | 5 + .../PresentationRemoteDataSourceImpl.kt | 12 + .../review/SelfFeedbackRequest.kt | 10 + .../network/service/PresentationService.kt | 7 + Prezel/feature/feedback/api/build.gradle.kts | 7 + .../feature/feedback/api/consumer-rules.pro | 0 .../feature/feedback/api/proguard-rules.pro | 0 .../feature/feedback/api/FeedbackNavKey.kt | 10 + .../api/src/main/res/values/strings.xml | 2 + Prezel/feature/feedback/impl/build.gradle.kts | 13 + .../feature/feedback/impl/consumer-rules.pro | 0 .../feature/feedback/impl/proguard-rules.pro | 0 .../feature/feedback/impl/FeedbackScreen.kt | 240 ++++++++++++++++++ .../feedback/impl/FeedbackViewModel.kt | 76 ++++++ .../impl/contract/FeedbackUiEffect.kt | 12 + .../impl/contract/FeedbackUiIntent.kt | 17 ++ .../feedback/impl/contract/FeedbackUiState.kt | 17 ++ .../feedback/impl/model/FeedbackUiMessage.kt | 5 + .../impl/navigation/FeedbackEntryBuilder.kt | 39 +++ .../impl/src/main/res/values/strings.xml | 12 + Prezel/feature/home/impl/build.gradle.kts | 1 + .../feature/home/impl/main/HomeScreen.kt | 3 +- .../home/impl/navigation/HomeEntryBuilder.kt | 9 + Prezel/settings.gradle.kts | 2 + 28 files changed, 533 insertions(+), 1 deletion(-) create mode 100644 Prezel/core/domain/src/main/kotlin/com/team/prezel/core/domain/usecase/presentation/WriteSelfFeedbackUseCase.kt create mode 100644 Prezel/core/network/src/main/java/com/team/prezel/core/network/model/presentation/review/SelfFeedbackRequest.kt create mode 100644 Prezel/feature/feedback/api/build.gradle.kts create mode 100644 Prezel/feature/feedback/api/consumer-rules.pro create mode 100644 Prezel/feature/feedback/api/proguard-rules.pro create mode 100644 Prezel/feature/feedback/api/src/main/java/com/team/prezel/feature/feedback/api/FeedbackNavKey.kt create mode 100644 Prezel/feature/feedback/api/src/main/res/values/strings.xml create mode 100644 Prezel/feature/feedback/impl/build.gradle.kts create mode 100644 Prezel/feature/feedback/impl/consumer-rules.pro create mode 100644 Prezel/feature/feedback/impl/proguard-rules.pro create mode 100644 Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/FeedbackScreen.kt create mode 100644 Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/FeedbackViewModel.kt create mode 100644 Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/contract/FeedbackUiEffect.kt create mode 100644 Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/contract/FeedbackUiIntent.kt create mode 100644 Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/contract/FeedbackUiState.kt create mode 100644 Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/model/FeedbackUiMessage.kt create mode 100644 Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/navigation/FeedbackEntryBuilder.kt create mode 100644 Prezel/feature/feedback/impl/src/main/res/values/strings.xml diff --git a/Prezel/app/build.gradle.kts b/Prezel/app/build.gradle.kts index 7146cf96..81b57e6b 100644 --- a/Prezel/app/build.gradle.kts +++ b/Prezel/app/build.gradle.kts @@ -46,6 +46,8 @@ dependencies { implementation(projects.featureHomeImpl) implementation(projects.featureHistoryApi) implementation(projects.featureHistoryImpl) + implementation(projects.featureFeedbackApi) + implementation(projects.featureFeedbackImpl) implementation(projects.featureMyApi) implementation(projects.featureMyImpl) diff --git a/Prezel/core/data/src/main/java/com/team/prezel/core/data/repository/PresentationRepositoryImpl.kt b/Prezel/core/data/src/main/java/com/team/prezel/core/data/repository/PresentationRepositoryImpl.kt index 0787871e..e1e2e491 100644 --- a/Prezel/core/data/src/main/java/com/team/prezel/core/data/repository/PresentationRepositoryImpl.kt +++ b/Prezel/core/data/src/main/java/com/team/prezel/core/data/repository/PresentationRepositoryImpl.kt @@ -125,4 +125,15 @@ internal class PresentationRepositoryImpl @Inject constructor( }.mapCatching { response -> response.map(GetMainDataResponse::toDomain) }.mapDomainFailure() + + override suspend fun writeSelfFeedback( + presentationId: Long, + content: String, + ): Result = + runCatching { + presentationRemoteDataSource.writeSelfFeedback( + presentationId = presentationId, + content = content, + ) + }.mapDomainFailure() } diff --git a/Prezel/core/domain/src/main/kotlin/com/team/prezel/core/domain/repository/presentation/PresentationRepository.kt b/Prezel/core/domain/src/main/kotlin/com/team/prezel/core/domain/repository/presentation/PresentationRepository.kt index 6e0e3b3e..9379e68f 100644 --- a/Prezel/core/domain/src/main/kotlin/com/team/prezel/core/domain/repository/presentation/PresentationRepository.kt +++ b/Prezel/core/domain/src/main/kotlin/com/team/prezel/core/domain/repository/presentation/PresentationRepository.kt @@ -48,4 +48,9 @@ interface PresentationRepository { suspend fun getPracticeRecords(presentationId: Long): Result suspend fun getMainData(): Result> + + suspend fun writeSelfFeedback( + presentationId: Long, + content: String, + ): Result } diff --git a/Prezel/core/domain/src/main/kotlin/com/team/prezel/core/domain/usecase/presentation/WriteSelfFeedbackUseCase.kt b/Prezel/core/domain/src/main/kotlin/com/team/prezel/core/domain/usecase/presentation/WriteSelfFeedbackUseCase.kt new file mode 100644 index 00000000..ccf93568 --- /dev/null +++ b/Prezel/core/domain/src/main/kotlin/com/team/prezel/core/domain/usecase/presentation/WriteSelfFeedbackUseCase.kt @@ -0,0 +1,17 @@ +package com.team.prezel.core.domain.usecase.presentation + +import com.team.prezel.core.domain.repository.presentation.PresentationRepository +import javax.inject.Inject + +class WriteSelfFeedbackUseCase @Inject constructor( + private val presentationRepository: PresentationRepository, +) { + suspend operator fun invoke( + presentationId: Long, + content: String, + ): Result = + presentationRepository.writeSelfFeedback( + presentationId = presentationId, + content = content, + ) +} diff --git a/Prezel/core/network/src/main/java/com/team/prezel/core/network/datasource/PresentationRemoteDataSource.kt b/Prezel/core/network/src/main/java/com/team/prezel/core/network/datasource/PresentationRemoteDataSource.kt index 6ab70f33..3d5fb6e0 100644 --- a/Prezel/core/network/src/main/java/com/team/prezel/core/network/datasource/PresentationRemoteDataSource.kt +++ b/Prezel/core/network/src/main/java/com/team/prezel/core/network/datasource/PresentationRemoteDataSource.kt @@ -44,4 +44,9 @@ interface PresentationRemoteDataSource { suspend fun getPracticeRecords(presentationId: Long): GetPracticeRecordsResponse suspend fun getMainData(): List + + suspend fun writeSelfFeedback( + presentationId: Long, + content: String, + ) } diff --git a/Prezel/core/network/src/main/java/com/team/prezel/core/network/datasource/PresentationRemoteDataSourceImpl.kt b/Prezel/core/network/src/main/java/com/team/prezel/core/network/datasource/PresentationRemoteDataSourceImpl.kt index ec62b5cf..7d3bce54 100644 --- a/Prezel/core/network/src/main/java/com/team/prezel/core/network/datasource/PresentationRemoteDataSourceImpl.kt +++ b/Prezel/core/network/src/main/java/com/team/prezel/core/network/datasource/PresentationRemoteDataSourceImpl.kt @@ -6,6 +6,7 @@ import com.team.prezel.core.network.model.presentation.GetPresentationsResponse import com.team.prezel.core.network.model.presentation.PresentationScriptDetailResponse import com.team.prezel.core.network.model.presentation.PresentationSummaryResponse import com.team.prezel.core.network.model.presentation.PresentationWordDetailResponse +import com.team.prezel.core.network.model.presentation.review.SelfFeedbackRequest import com.team.prezel.core.network.model.requireData import com.team.prezel.core.network.model.requireSuccess import com.team.prezel.core.network.service.PresentationService @@ -105,6 +106,17 @@ internal class PresentationRemoteDataSourceImpl @Inject constructor( presentationService.getPracticeRecords(presentationId = presentationId).requireData() override suspend fun getMainData(): List = presentationService.getMainData().requireData() + + override suspend fun writeSelfFeedback( + presentationId: Long, + content: String, + ) { + presentationService + .writeSelfFeedback( + presentationId = presentationId, + request = SelfFeedbackRequest(content = content), + ).requireSuccess() + } } private fun FormBuilder.appendAudioPart(audioFilePath: String) { diff --git a/Prezel/core/network/src/main/java/com/team/prezel/core/network/model/presentation/review/SelfFeedbackRequest.kt b/Prezel/core/network/src/main/java/com/team/prezel/core/network/model/presentation/review/SelfFeedbackRequest.kt new file mode 100644 index 00000000..9eb77ac5 --- /dev/null +++ b/Prezel/core/network/src/main/java/com/team/prezel/core/network/model/presentation/review/SelfFeedbackRequest.kt @@ -0,0 +1,10 @@ +package com.team.prezel.core.network.model.presentation.review + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class SelfFeedbackRequest( + @SerialName("content") + val content: String, +) diff --git a/Prezel/core/network/src/main/java/com/team/prezel/core/network/service/PresentationService.kt b/Prezel/core/network/src/main/java/com/team/prezel/core/network/service/PresentationService.kt index ed5fbfa5..ddf1a13d 100644 --- a/Prezel/core/network/src/main/java/com/team/prezel/core/network/service/PresentationService.kt +++ b/Prezel/core/network/src/main/java/com/team/prezel/core/network/service/PresentationService.kt @@ -8,6 +8,7 @@ import com.team.prezel.core.network.model.presentation.GetPresentationsResponse import com.team.prezel.core.network.model.presentation.PresentationScriptDetailResponse import com.team.prezel.core.network.model.presentation.PresentationSummaryResponse import com.team.prezel.core.network.model.presentation.PresentationWordDetailResponse +import com.team.prezel.core.network.model.presentation.review.SelfFeedbackRequest import de.jensklingenberg.ktorfit.http.Body import de.jensklingenberg.ktorfit.http.DELETE import de.jensklingenberg.ktorfit.http.GET @@ -65,4 +66,10 @@ interface PresentationService { @GET("main") suspend fun getMainData(): BaseResponse> + + @POST("recording/{presentationId}/review") + suspend fun writeSelfFeedback( + @Path("presentationId") presentationId: Long, + @Body request: SelfFeedbackRequest, + ): BaseResponse } diff --git a/Prezel/feature/feedback/api/build.gradle.kts b/Prezel/feature/feedback/api/build.gradle.kts new file mode 100644 index 00000000..cc170aa8 --- /dev/null +++ b/Prezel/feature/feedback/api/build.gradle.kts @@ -0,0 +1,7 @@ +plugins { + alias(libs.plugins.prezel.android.feature.api) +} + +android { + namespace = "com.team.prezel.feature.feedback.api" +} diff --git a/Prezel/feature/feedback/api/consumer-rules.pro b/Prezel/feature/feedback/api/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/Prezel/feature/feedback/api/proguard-rules.pro b/Prezel/feature/feedback/api/proguard-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/Prezel/feature/feedback/api/src/main/java/com/team/prezel/feature/feedback/api/FeedbackNavKey.kt b/Prezel/feature/feedback/api/src/main/java/com/team/prezel/feature/feedback/api/FeedbackNavKey.kt new file mode 100644 index 00000000..ac485159 --- /dev/null +++ b/Prezel/feature/feedback/api/src/main/java/com/team/prezel/feature/feedback/api/FeedbackNavKey.kt @@ -0,0 +1,10 @@ +package com.team.prezel.feature.feedback.api + +import androidx.navigation3.runtime.NavKey +import kotlinx.serialization.Serializable + +@Serializable +data class FeedbackNavKey( + val presentationId: Long, + val title: String, +) : NavKey diff --git a/Prezel/feature/feedback/api/src/main/res/values/strings.xml b/Prezel/feature/feedback/api/src/main/res/values/strings.xml new file mode 100644 index 00000000..545704f2 --- /dev/null +++ b/Prezel/feature/feedback/api/src/main/res/values/strings.xml @@ -0,0 +1,2 @@ + + diff --git a/Prezel/feature/feedback/impl/build.gradle.kts b/Prezel/feature/feedback/impl/build.gradle.kts new file mode 100644 index 00000000..f59d8783 --- /dev/null +++ b/Prezel/feature/feedback/impl/build.gradle.kts @@ -0,0 +1,13 @@ +plugins { + alias(libs.plugins.prezel.android.feature.impl) +} + +android { + namespace = "com.team.prezel.feature.feedback.impl" +} + +dependencies { + implementation(projects.coreDomain) + + implementation(projects.featureFeedbackApi) +} diff --git a/Prezel/feature/feedback/impl/consumer-rules.pro b/Prezel/feature/feedback/impl/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/Prezel/feature/feedback/impl/proguard-rules.pro b/Prezel/feature/feedback/impl/proguard-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/FeedbackScreen.kt b/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/FeedbackScreen.kt new file mode 100644 index 00000000..7f871c85 --- /dev/null +++ b/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/FeedbackScreen.kt @@ -0,0 +1,240 @@ +package com.team.prezel.feature.feedback.impl + +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.background +import androidx.compose.foundation.gestures.detectTapGestures +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.padding +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.platform.LocalResources +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.team.prezel.core.designsystem.component.PrezelTopAppBar +import com.team.prezel.core.designsystem.component.actions.area.PrezelButtonArea +import com.team.prezel.core.designsystem.component.actions.button.PrezelButton +import com.team.prezel.core.designsystem.component.actions.button.config.ButtonHierarchy +import com.team.prezel.core.designsystem.component.actions.button.config.ButtonType +import com.team.prezel.core.designsystem.component.feedback.dialog.PrezelDialog +import com.team.prezel.core.designsystem.component.feedback.dialog.PrezelDialogScope.ActionType +import com.team.prezel.core.designsystem.component.feedback.snackbar.showPrezelSnackbar +import com.team.prezel.core.designsystem.component.textfield.PrezelTextArea +import com.team.prezel.core.designsystem.icon.PrezelIcons +import com.team.prezel.core.designsystem.preview.BasicPreview +import com.team.prezel.core.designsystem.theme.PrezelTheme +import com.team.prezel.core.ui.state.LocalSnackbarHostState +import com.team.prezel.core.ui.util.advancedImePadding +import com.team.prezel.feature.feedback.impl.contract.FeedbackUiEffect +import com.team.prezel.feature.feedback.impl.contract.FeedbackUiIntent +import com.team.prezel.feature.feedback.impl.contract.FeedbackUiState +import com.team.prezel.feature.feedback.impl.model.FeedbackUiMessage + +@Composable +internal fun FeedbackScreen( + title: String, + navigateBack: () -> Unit, + modifier: Modifier = Modifier, + viewModel: FeedbackViewModel = hiltViewModel(), +) { + val uiState by viewModel.uiState.collectAsStateWithLifecycle() + val snackbarHostState = LocalSnackbarHostState.current + val resources = LocalResources.current + + LaunchedEffect(Unit) { + viewModel.uiEffect.collect { effect -> + when (effect) { + FeedbackUiEffect.NavigateBack -> navigateBack() + is FeedbackUiEffect.ShowMessage -> { + val resId = when (effect.message) { + FeedbackUiMessage.SAVE_FAILED -> R.string.feature_feedback_impl_save_failed + } + snackbarHostState.showPrezelSnackbar( + message = resources.getString(resId), + ) + } + } + } + } + + BackHandler { + viewModel.onIntent(FeedbackUiIntent.ClickClose) + } + + FeedbackScreen( + title = title, + uiState = uiState, + onContentChanged = { content -> viewModel.onIntent(FeedbackUiIntent.ChangeContent(content)) }, + onClickClose = { viewModel.onIntent(FeedbackUiIntent.ClickClose) }, + onClickSave = { viewModel.onIntent(FeedbackUiIntent.ClickSave) }, + onDismissExitDialog = { viewModel.onIntent(FeedbackUiIntent.ClickDialogClose) }, + onConfirmExit = { viewModel.onIntent(FeedbackUiIntent.ClickDialogExit) }, + modifier = modifier, + ) +} + +@Composable +private fun FeedbackScreen( + title: String, + uiState: FeedbackUiState, + onContentChanged: (String) -> Unit, + onClickClose: () -> Unit, + onClickSave: () -> Unit, + onDismissExitDialog: () -> Unit, + onConfirmExit: () -> Unit, + modifier: Modifier = Modifier, +) { + if (uiState.isExitDialogVisible) { + FeedbackExitDialog( + onDismiss = onDismissExitDialog, + onConfirmExit = onConfirmExit, + ) + } + + Column( + modifier = modifier + .fillMaxSize() + .background(PrezelTheme.colors.bgRegular), + ) { + FeedbackTopAppBar(onClickClose = onClickClose) + + FeedbackContent( + title = title, + content = uiState.content, + onContentChanged = onContentChanged, + modifier = Modifier.weight(1f), + ) + + PrezelButtonArea( + modifier = Modifier.advancedImePadding(), + mainButton = { buttonModifier -> + PrezelButton( + text = stringResource(R.string.feature_feedback_impl_save), + onClick = onClickSave, + enabled = uiState.isSaveEnabled, + type = ButtonType.FILLED, + hierarchy = ButtonHierarchy.PRIMARY, + modifier = buttonModifier, + ) + }, + ) + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun FeedbackTopAppBar( + onClickClose: () -> Unit, + modifier: Modifier = Modifier, +) { + PrezelTopAppBar( + title = { Text(text = stringResource(R.string.feature_feedback_impl_top_app_bar_title)) }, + trailingIcons = { + IconButton(onClick = onClickClose) { + Icon( + painter = painterResource(PrezelIcons.Cancel), + contentDescription = stringResource(R.string.feature_feedback_impl_close), + tint = PrezelTheme.colors.iconRegular, + ) + } + }, + modifier = modifier, + ) +} + +@Composable +private fun FeedbackContent( + title: String, + content: String, + onContentChanged: (String) -> Unit, + modifier: Modifier = Modifier, +) { + val focusManager = LocalFocusManager.current + + Column( + modifier = modifier + .fillMaxWidth() + .pointerInput(Unit) { detectTapGestures(onTap = { focusManager.clearFocus() }) } + .padding(horizontal = PrezelTheme.spacing.V20) + .padding(top = PrezelTheme.spacing.V32), + ) { + Text( + text = stringResource(R.string.feature_feedback_impl_question, title), + style = PrezelTheme.typography.title2Bold, + color = PrezelTheme.colors.textLarge, + ) + + Spacer(modifier = Modifier.height(PrezelTheme.spacing.V16)) + + PrezelTextArea( + value = content, + onValueChange = onContentChanged, + placeholder = stringResource(R.string.feature_feedback_impl_placeholder), + maxLength = 200, + showCount = true, + ) + } +} + +@Composable +private fun FeedbackExitDialog( + onDismiss: () -> Unit, + onConfirmExit: () -> Unit, +) { + PrezelDialog( + title = stringResource(R.string.feature_feedback_impl_exit_dialog_title), + description = stringResource(R.string.feature_feedback_impl_exit_dialog_description), + onDismiss = onDismiss, + ) { + Action(label = stringResource(R.string.feature_feedback_impl_exit_dialog_cancel)) { onDismiss() } + Action( + label = stringResource(R.string.feature_feedback_impl_exit_dialog_confirm), + type = ActionType.BAD, + ) { + onConfirmExit() + } + } +} + +@BasicPreview +@Composable +private fun FeedbackScreenPreview() { + PrezelTheme { + FeedbackScreen( + title = "제 7회 컨셉발표회", + uiState = FeedbackUiState(), + onContentChanged = {}, + onClickClose = {}, + onClickSave = {}, + onDismissExitDialog = {}, + onConfirmExit = {}, + ) + } +} + +@BasicPreview +@Composable +private fun FeedbackExitDialogPreview() { + PrezelTheme { + Box(modifier = Modifier.fillMaxSize()) { + FeedbackExitDialog( + onDismiss = {}, + onConfirmExit = {}, + ) + } + } +} diff --git a/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/FeedbackViewModel.kt b/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/FeedbackViewModel.kt new file mode 100644 index 00000000..8b5e8172 --- /dev/null +++ b/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/FeedbackViewModel.kt @@ -0,0 +1,76 @@ +package com.team.prezel.feature.feedback.impl + +import androidx.lifecycle.viewModelScope +import com.team.prezel.core.domain.usecase.presentation.WriteSelfFeedbackUseCase +import com.team.prezel.core.ui.base.BaseViewModel +import com.team.prezel.feature.feedback.api.FeedbackNavKey +import com.team.prezel.feature.feedback.impl.contract.FeedbackUiEffect +import com.team.prezel.feature.feedback.impl.contract.FeedbackUiIntent +import com.team.prezel.feature.feedback.impl.contract.FeedbackUiState +import com.team.prezel.feature.feedback.impl.model.FeedbackUiMessage +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch + +@HiltViewModel(assistedFactory = FeedbackViewModel.Factory::class) +internal class FeedbackViewModel @AssistedInject constructor( + @Assisted private val navKey: FeedbackNavKey, + private val writeSelfFeedbackUseCase: WriteSelfFeedbackUseCase, +) : BaseViewModel(FeedbackUiState()) { + @AssistedFactory + interface Factory { + fun create(navKey: FeedbackNavKey): FeedbackViewModel + } + + override fun onIntent(intent: FeedbackUiIntent) { + when (intent) { + is FeedbackUiIntent.ChangeContent -> changeContent(intent.content) + FeedbackUiIntent.ClickClose -> handleClose() + FeedbackUiIntent.ClickSave -> save() + FeedbackUiIntent.ClickDialogClose -> updateState { copy(isExitDialogVisible = false) } + FeedbackUiIntent.ClickDialogExit -> navigateBack() + } + } + + private fun changeContent(content: String) { + updateState { copy(content = content.take(MAX_CONTENT_COUNT)) } + } + + private fun handleClose() { + if (currentState.hasUnsavedContent) { + updateState { copy(isExitDialogVisible = true) } + return + } + + navigateBack() + } + + private fun save() { + val content = currentState.content.trim() + if (content.isBlank() || currentState.isSaving) return + + updateState { copy(isSaving = true) } + + viewModelScope.launch { + writeSelfFeedbackUseCase( + presentationId = navKey.presentationId, + content = content, + ).onSuccess { + sendEffect(FeedbackUiEffect.NavigateBack) + }.onFailure { + updateState { copy(isSaving = false) } + sendEffect(FeedbackUiEffect.ShowMessage(FeedbackUiMessage.SAVE_FAILED)) + } + } + } + + private fun navigateBack() { + viewModelScope.launch { sendEffect(FeedbackUiEffect.NavigateBack) } + } + + companion object { + private const val MAX_CONTENT_COUNT = 200 + } +} diff --git a/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/contract/FeedbackUiEffect.kt b/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/contract/FeedbackUiEffect.kt new file mode 100644 index 00000000..472640ee --- /dev/null +++ b/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/contract/FeedbackUiEffect.kt @@ -0,0 +1,12 @@ +package com.team.prezel.feature.feedback.impl.contract + +import com.team.prezel.core.ui.base.UiEffect +import com.team.prezel.feature.feedback.impl.model.FeedbackUiMessage + +internal sealed interface FeedbackUiEffect : UiEffect { + data object NavigateBack : FeedbackUiEffect + + data class ShowMessage( + val message: FeedbackUiMessage, + ) : FeedbackUiEffect +} diff --git a/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/contract/FeedbackUiIntent.kt b/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/contract/FeedbackUiIntent.kt new file mode 100644 index 00000000..7072ec0b --- /dev/null +++ b/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/contract/FeedbackUiIntent.kt @@ -0,0 +1,17 @@ +package com.team.prezel.feature.feedback.impl.contract + +import com.team.prezel.core.ui.base.UiIntent + +internal sealed interface FeedbackUiIntent : UiIntent { + data class ChangeContent( + val content: String, + ) : FeedbackUiIntent + + data object ClickClose : FeedbackUiIntent + + data object ClickSave : FeedbackUiIntent + + data object ClickDialogClose : FeedbackUiIntent + + data object ClickDialogExit : FeedbackUiIntent +} diff --git a/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/contract/FeedbackUiState.kt b/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/contract/FeedbackUiState.kt new file mode 100644 index 00000000..69809084 --- /dev/null +++ b/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/contract/FeedbackUiState.kt @@ -0,0 +1,17 @@ +package com.team.prezel.feature.feedback.impl.contract + +import androidx.compose.runtime.Immutable +import com.team.prezel.core.ui.base.UiState + +@Immutable +internal data class FeedbackUiState( + val content: String = "", + val isSaving: Boolean = false, + val isExitDialogVisible: Boolean = false, +) : UiState { + val isSaveEnabled: Boolean + get() = content.isNotBlank() && !isSaving + + val hasUnsavedContent: Boolean + get() = content.isNotBlank() +} diff --git a/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/model/FeedbackUiMessage.kt b/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/model/FeedbackUiMessage.kt new file mode 100644 index 00000000..586c0be7 --- /dev/null +++ b/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/model/FeedbackUiMessage.kt @@ -0,0 +1,5 @@ +package com.team.prezel.feature.feedback.impl.model + +internal enum class FeedbackUiMessage { + SAVE_FAILED, +} diff --git a/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/navigation/FeedbackEntryBuilder.kt b/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/navigation/FeedbackEntryBuilder.kt new file mode 100644 index 00000000..041814d6 --- /dev/null +++ b/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/navigation/FeedbackEntryBuilder.kt @@ -0,0 +1,39 @@ +package com.team.prezel.feature.feedback.impl.navigation + +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel +import androidx.navigation3.runtime.EntryProviderScope +import androidx.navigation3.runtime.NavKey +import com.team.prezel.core.navigation.LocalNavigator +import com.team.prezel.feature.feedback.api.FeedbackNavKey +import com.team.prezel.feature.feedback.impl.FeedbackScreen +import com.team.prezel.feature.feedback.impl.FeedbackViewModel +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.components.ActivityRetainedComponent +import dagger.multibindings.IntoSet + +internal fun EntryProviderScope.featureFeedbackEntryBuilder() { + entry { key -> + val navigator = LocalNavigator.current + + FeedbackScreen( + title = key.title, + navigateBack = { navigator.goBack() }, + viewModel = hiltViewModel( + creationCallback = { factory -> factory.create(key) }, + ), + ) + } +} + +@Module +@InstallIn(ActivityRetainedComponent::class) +object FeatureFeedbackModule { + @IntoSet + @Provides + fun provideFeatureFeedbackEntryBuilder(): EntryProviderScope.() -> Unit = + { + featureFeedbackEntryBuilder() + } +} diff --git a/Prezel/feature/feedback/impl/src/main/res/values/strings.xml b/Prezel/feature/feedback/impl/src/main/res/values/strings.xml new file mode 100644 index 00000000..2a9fdc66 --- /dev/null +++ b/Prezel/feature/feedback/impl/src/main/res/values/strings.xml @@ -0,0 +1,12 @@ + + 셀프 피드백 + 닫기 + \'%1$s\'는 어떠셨나요? + 다음 발표에는 어떤 부분을 신경쓰고 싶나요? + 저장하기 + 셀프 피드백 저장에 실패했어요. + 저장하지 않고 나가시겠어요? + 작성된 셀프 피드백이 모두 삭제됩니다. + 닫기 + 나가기 + diff --git a/Prezel/feature/home/impl/build.gradle.kts b/Prezel/feature/home/impl/build.gradle.kts index c8d37800..d8bd2026 100644 --- a/Prezel/feature/home/impl/build.gradle.kts +++ b/Prezel/feature/home/impl/build.gradle.kts @@ -11,6 +11,7 @@ dependencies { implementation(projects.coreDomain) implementation(projects.featureAnalysisApi) + implementation(projects.featureFeedbackApi) implementation(projects.featureHomeApi) implementation(projects.featurePracticeApi) diff --git a/Prezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/main/HomeScreen.kt b/Prezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/main/HomeScreen.kt index 7996c65c..fed02c95 100644 --- a/Prezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/main/HomeScreen.kt +++ b/Prezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/main/HomeScreen.kt @@ -34,6 +34,7 @@ internal fun HomeScreen( navigateToPracticeRecording: (presentationId: Long) -> Unit, navigateToFileUploadAnalysis: () -> Unit, navigateToVoiceRecordingAnalysis: () -> Unit, + navigateToFeedback: (presentationId: Long, title: String) -> Unit, modifier: Modifier = Modifier, viewModel: HomeViewModel = hiltViewModel(), ) { @@ -63,7 +64,7 @@ internal fun HomeScreen( onClickAddPresentation = navigateToVoiceRecordingAnalysis, onClickPracticeRecording = { presentationId -> navigateToPracticeRecording(presentationId) }, onClickAnalyzePresentation = { }, - onClickWriteFeedback = { }, + onClickWriteFeedback = { presentation -> navigateToFeedback(presentation.id, presentation.title) }, onClickVoiceRecordingAnalysis = navigateToVoiceRecordingAnalysis, onClickFileUploadAnalysis = navigateToFileUploadAnalysis, onClickCardGraphItemIndex = { presentationId, index -> diff --git a/Prezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/navigation/HomeEntryBuilder.kt b/Prezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/navigation/HomeEntryBuilder.kt index 5cf4ddaf..7f92811f 100644 --- a/Prezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/navigation/HomeEntryBuilder.kt +++ b/Prezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/navigation/HomeEntryBuilder.kt @@ -5,6 +5,7 @@ import androidx.navigation3.runtime.NavKey import com.team.prezel.core.navigation.LocalNavigator import com.team.prezel.feature.analysis.api.AnalysisNavKey import com.team.prezel.feature.analysis.api.AnalysisStartType +import com.team.prezel.feature.feedback.api.FeedbackNavKey import com.team.prezel.feature.home.api.HomeNavKey import com.team.prezel.feature.home.impl.main.HomeScreen import com.team.prezel.feature.practice.api.PracticeNavKey @@ -28,6 +29,14 @@ internal fun EntryProviderScope.featureHomeEntryBuilder() { navigateToVoiceRecordingAnalysis = { navigator.navigate(AnalysisNavKey.Schedule(startType = AnalysisStartType.VOICE_RECORDING)) }, + navigateToFeedback = { presentationId, title -> + navigator.navigate( + FeedbackNavKey( + presentationId = presentationId, + title = title, + ), + ) + }, ) } } diff --git a/Prezel/settings.gradle.kts b/Prezel/settings.gradle.kts index 83ede2fb..f7b56c39 100644 --- a/Prezel/settings.gradle.kts +++ b/Prezel/settings.gradle.kts @@ -58,6 +58,8 @@ includeAuto( ":feature:analysis:impl", ":feature:history:api", ":feature:history:impl", + ":feature:feedback:api", + ":feature:feedback:impl", ":feature:my:api", ":feature:my:impl", ":feature:setting:api", From 6b3ee7a4c8b02b9f792cc2ebf3d5c3bfb73faf53 Mon Sep 17 00:00:00 2001 From: Ham BeomJoon Date: Sat, 30 May 2026 23:28:31 +0900 Subject: [PATCH 02/10] =?UTF-8?q?feat:=20=ED=94=BC=EB=93=9C=EB=B0=B1=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=20=EB=B0=8F=20=EB=B6=84=EC=84=9D=20=EB=A6=AC?= =?UTF-8?q?=ED=8F=AC=ED=8A=B8=20=EB=82=B4=20=EB=84=A4=EB=B9=84=EA=B2=8C?= =?UTF-8?q?=EC=9D=B4=EC=85=98=20=EB=A1=9C=EC=A7=81=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 피드백 작성 완료 시 이전 화면이 아닌 홈 화면으로 이동하도록 변경 - 분석 리포트에서 피드백 작성 화면으로 이동 시 발표 제목(`title`) 파라미터 추가 - `FeedbackUiEffect`에 `NavigateToHome` 상태 추가 - `feature:feedback:impl` 및 `feature:report:impl` 모듈의 의존성 업데이트 (`feature:home:api`, `feature:feedback:api`) - `FeedbackEntryBuilder` 및 `ReportEntryBuilder` 내 네비게이션 핸들러 구현 수정 --- Prezel/detekt-config.yml | 2 +- Prezel/feature/feedback/impl/build.gradle.kts | 1 + .../prezel/feature/feedback/impl/FeedbackScreen.kt | 2 ++ .../prezel/feature/feedback/impl/FeedbackViewModel.kt | 2 +- .../feature/feedback/impl/contract/FeedbackUiEffect.kt | 2 ++ .../feedback/impl/navigation/FeedbackEntryBuilder.kt | 2 ++ Prezel/feature/report/impl/build.gradle.kts | 1 + .../prezel/feature/report/impl/AnalysisReportScreen.kt | 6 ++++-- .../feature/report/impl/AnalysisReportViewModel.kt | 7 ++++++- .../report/impl/contract/AnalysisReportUiEffect.kt | 1 + .../report/impl/navigation/ReportEntryBuilder.kt | 10 +++++++++- 11 files changed, 30 insertions(+), 6 deletions(-) diff --git a/Prezel/detekt-config.yml b/Prezel/detekt-config.yml index 5589ae0d..9c9dbffc 100644 --- a/Prezel/detekt-config.yml +++ b/Prezel/detekt-config.yml @@ -37,7 +37,7 @@ complexity: active: true thresholdInFiles: 20 thresholdInClasses: 15 - thresholdInInterfaces: 12 + thresholdInInterfaces: 15 thresholdInObjects: 20 thresholdInEnums: 10 ignoreAnnotatedFunctions: diff --git a/Prezel/feature/feedback/impl/build.gradle.kts b/Prezel/feature/feedback/impl/build.gradle.kts index f59d8783..d8ac2b95 100644 --- a/Prezel/feature/feedback/impl/build.gradle.kts +++ b/Prezel/feature/feedback/impl/build.gradle.kts @@ -10,4 +10,5 @@ dependencies { implementation(projects.coreDomain) implementation(projects.featureFeedbackApi) + implementation(projects.featureHomeApi) } diff --git a/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/FeedbackScreen.kt b/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/FeedbackScreen.kt index 7f871c85..9d3326bb 100644 --- a/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/FeedbackScreen.kt +++ b/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/FeedbackScreen.kt @@ -48,6 +48,7 @@ import com.team.prezel.feature.feedback.impl.model.FeedbackUiMessage internal fun FeedbackScreen( title: String, navigateBack: () -> Unit, + navigateToHome: () -> Unit, modifier: Modifier = Modifier, viewModel: FeedbackViewModel = hiltViewModel(), ) { @@ -59,6 +60,7 @@ internal fun FeedbackScreen( viewModel.uiEffect.collect { effect -> when (effect) { FeedbackUiEffect.NavigateBack -> navigateBack() + FeedbackUiEffect.NavigateToHome -> navigateToHome() is FeedbackUiEffect.ShowMessage -> { val resId = when (effect.message) { FeedbackUiMessage.SAVE_FAILED -> R.string.feature_feedback_impl_save_failed diff --git a/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/FeedbackViewModel.kt b/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/FeedbackViewModel.kt index 8b5e8172..435afa27 100644 --- a/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/FeedbackViewModel.kt +++ b/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/FeedbackViewModel.kt @@ -58,7 +58,7 @@ internal class FeedbackViewModel @AssistedInject constructor( presentationId = navKey.presentationId, content = content, ).onSuccess { - sendEffect(FeedbackUiEffect.NavigateBack) + sendEffect(FeedbackUiEffect.NavigateToHome) }.onFailure { updateState { copy(isSaving = false) } sendEffect(FeedbackUiEffect.ShowMessage(FeedbackUiMessage.SAVE_FAILED)) diff --git a/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/contract/FeedbackUiEffect.kt b/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/contract/FeedbackUiEffect.kt index 472640ee..bd548377 100644 --- a/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/contract/FeedbackUiEffect.kt +++ b/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/contract/FeedbackUiEffect.kt @@ -6,6 +6,8 @@ import com.team.prezel.feature.feedback.impl.model.FeedbackUiMessage internal sealed interface FeedbackUiEffect : UiEffect { data object NavigateBack : FeedbackUiEffect + data object NavigateToHome : FeedbackUiEffect + data class ShowMessage( val message: FeedbackUiMessage, ) : FeedbackUiEffect diff --git a/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/navigation/FeedbackEntryBuilder.kt b/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/navigation/FeedbackEntryBuilder.kt index 041814d6..79114c19 100644 --- a/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/navigation/FeedbackEntryBuilder.kt +++ b/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/navigation/FeedbackEntryBuilder.kt @@ -7,6 +7,7 @@ import com.team.prezel.core.navigation.LocalNavigator import com.team.prezel.feature.feedback.api.FeedbackNavKey import com.team.prezel.feature.feedback.impl.FeedbackScreen import com.team.prezel.feature.feedback.impl.FeedbackViewModel +import com.team.prezel.feature.home.api.HomeNavKey import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -20,6 +21,7 @@ internal fun EntryProviderScope.featureFeedbackEntryBuilder() { FeedbackScreen( title = key.title, navigateBack = { navigator.goBack() }, + navigateToHome = { navigator.navigate(HomeNavKey) }, viewModel = hiltViewModel( creationCallback = { factory -> factory.create(key) }, ), diff --git a/Prezel/feature/report/impl/build.gradle.kts b/Prezel/feature/report/impl/build.gradle.kts index 9b714254..ab9dc26c 100644 --- a/Prezel/feature/report/impl/build.gradle.kts +++ b/Prezel/feature/report/impl/build.gradle.kts @@ -11,6 +11,7 @@ dependencies { implementation(projects.coreDomain) implementation(projects.coreUi) implementation(projects.featureAnalysisApi) + implementation(projects.featureFeedbackApi) implementation(projects.featureReportApi) implementation(libs.kotlinx.datetime) diff --git a/Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/AnalysisReportScreen.kt b/Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/AnalysisReportScreen.kt index d6b531af..663db855 100644 --- a/Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/AnalysisReportScreen.kt +++ b/Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/AnalysisReportScreen.kt @@ -33,7 +33,7 @@ internal fun AnalysisReportScreen( onBack: () -> Unit, navigateToAnalysisScript: (presentationId: Long, isPast: Boolean) -> Unit, navigateToAnalysisRecording: (presentationId: Long, isPast: Boolean) -> Unit, - navigateToSelfFeedbackWrite: (presentationId: Long) -> Unit, + navigateToSelfFeedbackWrite: (presentationId: Long, title: String) -> Unit, modifier: Modifier = Modifier, viewModel: AnalysisReportViewModel = hiltViewModel(), ) { @@ -54,7 +54,9 @@ internal fun AnalysisReportScreen( is AnalysisReportUiEffect.NavigateToAnalysisScript -> navigateToAnalysisScript(effect.presentationId, effect.isPast) is AnalysisReportUiEffect.NavigateToAnalysisRecording -> navigateToAnalysisRecording(effect.presentationId, effect.isPast) - is AnalysisReportUiEffect.NavigateToSelfFeedbackWrite -> navigateToSelfFeedbackWrite(effect.presentationId) + is AnalysisReportUiEffect.NavigateToSelfFeedbackWrite -> { + navigateToSelfFeedbackWrite(effect.presentationId, effect.title) + } } } } diff --git a/Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/AnalysisReportViewModel.kt b/Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/AnalysisReportViewModel.kt index 8a5e3b62..29e6e9d4 100644 --- a/Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/AnalysisReportViewModel.kt +++ b/Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/AnalysisReportViewModel.kt @@ -117,7 +117,12 @@ internal class AnalysisReportViewModel @AssistedInject constructor( } private fun navigateToSelfFeedbackWrite() { - val effect = presentationId?.let(AnalysisReportUiEffect::NavigateToSelfFeedbackWrite) ?: return + val presentationId = presentationId ?: return + val title = contentState?.presentationInfo?.title ?: return + val effect = AnalysisReportUiEffect.NavigateToSelfFeedbackWrite( + presentationId = presentationId, + title = title, + ) viewModelScope.launch { sendEffect(effect) } } diff --git a/Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/contract/AnalysisReportUiEffect.kt b/Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/contract/AnalysisReportUiEffect.kt index c5b26bde..a1b5b51f 100644 --- a/Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/contract/AnalysisReportUiEffect.kt +++ b/Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/contract/AnalysisReportUiEffect.kt @@ -22,5 +22,6 @@ internal sealed interface AnalysisReportUiEffect : UiEffect { data class NavigateToSelfFeedbackWrite( val presentationId: Long, + val title: String, ) : AnalysisReportUiEffect } diff --git a/Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/navigation/ReportEntryBuilder.kt b/Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/navigation/ReportEntryBuilder.kt index 9bf39fc6..93f221f8 100644 --- a/Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/navigation/ReportEntryBuilder.kt +++ b/Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/navigation/ReportEntryBuilder.kt @@ -5,6 +5,7 @@ import androidx.navigation3.runtime.EntryProviderScope import androidx.navigation3.runtime.NavKey import com.team.prezel.core.navigation.LocalNavigator import com.team.prezel.feature.analysis.api.AnalysisNavKey +import com.team.prezel.feature.feedback.api.FeedbackNavKey import com.team.prezel.feature.report.api.ReportNavKey import com.team.prezel.feature.report.impl.AnalysisReportScreen import com.team.prezel.feature.report.impl.AnalysisReportViewModel @@ -36,7 +37,14 @@ internal fun EntryProviderScope.featureAnalysisReportEntryBuilder() { ), ) }, - navigateToSelfFeedbackWrite = {}, + navigateToSelfFeedbackWrite = { presentationId, title -> + navigator.navigate( + FeedbackNavKey( + presentationId = presentationId, + title = title, + ), + ) + }, viewModel = hiltViewModel( creationCallback = { factory -> factory.create(key) }, ), From bbbbbad2ca10c6f6431519768d1f5c07d2b832a0 Mon Sep 17 00:00:00 2001 From: Ham BeomJoon Date: Sun, 31 May 2026 00:09:57 +0900 Subject: [PATCH 03/10] =?UTF-8?q?fix:=20=ED=94=84=EB=A0=88=EC=A0=A0?= =?UTF-8?q?=ED=85=8C=EC=9D=B4=EC=85=98=20=EC=9A=94=EC=95=BD=20=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=20null=20=EC=B2=98=EB=A6=AC=20=EB=B0=8F=20?= =?UTF-8?q?=ED=99=88=20=ED=99=94=EB=A9=B4=20UI=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `PresentationSummaryResponse`의 `growthGraph` 필드에 null 허용 설정 및 매퍼 로직 수정 - `HomeScreenContent`의 `HorizontalPager`에서 인덱스 참조 시 발생할 수 있는 잠재적 오류 방지 로직 추가 - `PresentationHero`에서 사용하던 `PrezelChip`을 커스텀 `HomeCategoryChip`으로 교체하여 디자인 시스템 요구사항 반영 --- .../core/data/mapper/PresentationMapper.kt | 2 +- .../PresentationSummaryResponse.kt | 2 +- .../impl/main/component/HomeScreenContent.kt | 4 +-- .../main/component/title/PresentationHero.kt | 29 +++++++++++++++---- 4 files changed, 27 insertions(+), 10 deletions(-) diff --git a/Prezel/core/data/src/main/java/com/team/prezel/core/data/mapper/PresentationMapper.kt b/Prezel/core/data/src/main/java/com/team/prezel/core/data/mapper/PresentationMapper.kt index 085758ec..6d71c932 100644 --- a/Prezel/core/data/src/main/java/com/team/prezel/core/data/mapper/PresentationMapper.kt +++ b/Prezel/core/data/src/main/java/com/team/prezel/core/data/mapper/PresentationMapper.kt @@ -47,7 +47,7 @@ internal fun PresentationSummaryResponse.toDomain(): PresentationAnalysisSummary spellErrorCount = spellErrorCount, grammarErrorCount = grammarErrorCount, totalErrorCount = totalErrorCount, - growth = growthGraph.map { item -> item.toDomain() }, + growth = growthGraph?.map { item -> item.toDomain() }.orEmpty(), expectedQuestions = expectedQuestions.map { item -> item.toDomain() }, selfFeedback = reviewContent, ) diff --git a/Prezel/core/network/src/main/java/com/team/prezel/core/network/model/presentation/PresentationSummaryResponse.kt b/Prezel/core/network/src/main/java/com/team/prezel/core/network/model/presentation/PresentationSummaryResponse.kt index 54876839..6784a02c 100644 --- a/Prezel/core/network/src/main/java/com/team/prezel/core/network/model/presentation/PresentationSummaryResponse.kt +++ b/Prezel/core/network/src/main/java/com/team/prezel/core/network/model/presentation/PresentationSummaryResponse.kt @@ -42,7 +42,7 @@ data class PresentationSummaryResponse( @SerialName("totalErrorCount") val totalErrorCount: Int, @SerialName("growthGraph") - val growthGraph: List, + val growthGraph: List? = null, @SerialName("expectedQuestions") val expectedQuestions: List, @SerialName("reviewContent") diff --git a/Prezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/main/component/HomeScreenContent.kt b/Prezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/main/component/HomeScreenContent.kt index b2c1cc92..03c266b8 100644 --- a/Prezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/main/component/HomeScreenContent.kt +++ b/Prezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/main/component/HomeScreenContent.kt @@ -189,9 +189,9 @@ private fun HomeMultipleContent( modifier = Modifier.fillMaxSize(), overscrollEffect = null, userScrollEnabled = false, - key = { pageIndex -> uiState.presentations[pageIndex].id }, + key = { pageIndex -> uiState.presentations.getOrNull(pageIndex)?.id ?: pageIndex }, ) { pageIndex -> - val presentation = uiState.presentations[pageIndex] + val presentation = uiState.presentations.getOrNull(pageIndex) ?: return@HorizontalPager HomePresentationContent( presentation = presentation, diff --git a/Prezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/main/component/title/PresentationHero.kt b/Prezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/main/component/title/PresentationHero.kt index b4ad0866..4834cc37 100644 --- a/Prezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/main/component/title/PresentationHero.kt +++ b/Prezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/main/component/title/PresentationHero.kt @@ -2,17 +2,17 @@ package com.team.prezel.feature.home.impl.main.component.title import androidx.annotation.DrawableRes import androidx.annotation.StringRes +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding import androidx.compose.material3.Text 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.res.stringResource -import com.team.prezel.core.designsystem.component.chip.chip.ChipSize -import com.team.prezel.core.designsystem.component.chip.chip.ChipType -import com.team.prezel.core.designsystem.component.chip.chip.PrezelChip import com.team.prezel.core.designsystem.preview.BasicPreview import com.team.prezel.core.designsystem.theme.PrezelTheme import com.team.prezel.core.model.presentation.Category @@ -34,10 +34,8 @@ internal fun PresentationHero( backgroundResId = presentation.category.backgroundResId(), modifier = modifier, ) { - PrezelChip( + HomeCategoryChip( text = stringResource(id = presentation.category.labelResId()), - type = ChipType.OUTLINED, - size = ChipSize.SMALL, ) Spacer(modifier = Modifier.weight(1f)) @@ -69,6 +67,25 @@ internal fun PresentationHero( } } +@Composable +private fun HomeCategoryChip( + text: String, + modifier: Modifier = Modifier, +) { + Text( + text = text, + color = PrezelTheme.colors.interactiveRegular, + style = PrezelTheme.typography.caption2Regular, + modifier = modifier + .clip(PrezelTheme.shapes.V4) + .background(PrezelTheme.colors.bgRegular) + .padding( + horizontal = PrezelTheme.spacing.V6, + vertical = PrezelTheme.spacing.V4, + ), + ) +} + @Composable private fun HomePresentationDate(date: LocalDate) { Text( From 707eb3873a1523f0a7f3d8408688450be91db46c Mon Sep 17 00:00:00 2001 From: Ham BeomJoon Date: Sun, 31 May 2026 00:46:30 +0900 Subject: [PATCH 04/10] =?UTF-8?q?feat:=20=EB=B0=9C=ED=91=9C=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=B2=98=EB=A6=AC=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EA=B0=9C=EC=84=A0=20=EB=B0=8F=20UI=20?= =?UTF-8?q?=ED=91=9C=EC=8B=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `GetPresentationDetailResponse`에 `reviewContent` 필드 추가 - `PresentationRemoteDataSourceImpl`에서 상세 응답 데이터 변환 시 `reviewContent`를 우선적으로 매핑하도록 로직 개선 - `FeedbackEntryBuilder`에서 홈 화면 이동 시 기존 스택을 제거하고 루트를 교체하도록 변경 (`replaceRoot`) - `PresentationHero` 컴포넌트 프리뷰의 D-Day 표시 형식을 서비스 스펙에 맞게 수정 (`D-3`, `D+5`) --- .../datasource/PresentationRemoteDataSourceImpl.kt | 10 ++++++++-- .../presentation/GetPresentationDetailResponse.kt | 2 ++ .../feedback/impl/navigation/FeedbackEntryBuilder.kt | 2 +- .../home/impl/main/component/title/PresentationHero.kt | 4 ++-- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/Prezel/core/network/src/main/java/com/team/prezel/core/network/datasource/PresentationRemoteDataSourceImpl.kt b/Prezel/core/network/src/main/java/com/team/prezel/core/network/datasource/PresentationRemoteDataSourceImpl.kt index 7d3bce54..3fd9c211 100644 --- a/Prezel/core/network/src/main/java/com/team/prezel/core/network/datasource/PresentationRemoteDataSourceImpl.kt +++ b/Prezel/core/network/src/main/java/com/team/prezel/core/network/datasource/PresentationRemoteDataSourceImpl.kt @@ -2,6 +2,7 @@ package com.team.prezel.core.network.datasource import com.team.prezel.core.network.model.presentation.GetMainDataResponse import com.team.prezel.core.network.model.presentation.GetPracticeRecordsResponse +import com.team.prezel.core.network.model.presentation.GetPresentationDetailResponse import com.team.prezel.core.network.model.presentation.GetPresentationsResponse import com.team.prezel.core.network.model.presentation.PresentationScriptDetailResponse import com.team.prezel.core.network.model.presentation.PresentationSummaryResponse @@ -97,10 +98,10 @@ internal class PresentationRemoteDataSourceImpl @Inject constructor( override suspend fun getPastPresentations(): List = presentationService.getPastPresentations().requireData() override suspend fun getUpcomingPresentationDetail(presentationId: Long): PresentationSummaryResponse = - presentationService.getUpcomingPresentationDetail(presentationId = presentationId).requireData().analysisResult + presentationService.getUpcomingPresentationDetail(presentationId = presentationId).requireData().toPresentationSummaryResponse() override suspend fun getPastPresentationDetail(presentationId: Long): PresentationSummaryResponse = - presentationService.getPastPresentationDetail(presentationId = presentationId).requireData().analysisResult + presentationService.getPastPresentationDetail(presentationId = presentationId).requireData().toPresentationSummaryResponse() override suspend fun getPracticeRecords(presentationId: Long): GetPracticeRecordsResponse = presentationService.getPracticeRecords(presentationId = presentationId).requireData() @@ -131,6 +132,11 @@ private fun FormBuilder.appendAudioPart(audioFilePath: String) { ) } +private fun GetPresentationDetailResponse.toPresentationSummaryResponse(): PresentationSummaryResponse = + analysisResult.copy( + reviewContent = reviewContent ?: analysisResult.reviewContent, + ) + private fun FormBuilder.appendScriptPart(scriptFilePath: String) { val file = File(scriptFilePath) diff --git a/Prezel/core/network/src/main/java/com/team/prezel/core/network/model/presentation/GetPresentationDetailResponse.kt b/Prezel/core/network/src/main/java/com/team/prezel/core/network/model/presentation/GetPresentationDetailResponse.kt index 2a78412c..741590e4 100644 --- a/Prezel/core/network/src/main/java/com/team/prezel/core/network/model/presentation/GetPresentationDetailResponse.kt +++ b/Prezel/core/network/src/main/java/com/team/prezel/core/network/model/presentation/GetPresentationDetailResponse.kt @@ -7,4 +7,6 @@ import kotlinx.serialization.Serializable data class GetPresentationDetailResponse( @SerialName("analysisResult") val analysisResult: PresentationSummaryResponse, + @SerialName("reviewContent") + val reviewContent: String? = null, ) diff --git a/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/navigation/FeedbackEntryBuilder.kt b/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/navigation/FeedbackEntryBuilder.kt index 79114c19..f3cdb0c8 100644 --- a/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/navigation/FeedbackEntryBuilder.kt +++ b/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/navigation/FeedbackEntryBuilder.kt @@ -21,7 +21,7 @@ internal fun EntryProviderScope.featureFeedbackEntryBuilder() { FeedbackScreen( title = key.title, navigateBack = { navigator.goBack() }, - navigateToHome = { navigator.navigate(HomeNavKey) }, + navigateToHome = { navigator.replaceRoot(HomeNavKey) }, viewModel = hiltViewModel( creationCallback = { factory -> factory.create(key) }, ), diff --git a/Prezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/main/component/title/PresentationHero.kt b/Prezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/main/component/title/PresentationHero.kt index 4834cc37..922bed9c 100644 --- a/Prezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/main/component/title/PresentationHero.kt +++ b/Prezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/main/component/title/PresentationHero.kt @@ -145,7 +145,7 @@ private fun HomePresentationPagePreview() { category = Category.OFFER, title = "설득하는 발표", date = LocalDate(2026, 10, 1), - dDay = "-3", + dDay = "D-3", practiceRecords = PracticeRecordsUiModel( practicedDates = listOf(LocalDate(2026, 9, 28)), startDate = LocalDate(2026, 9, 26), @@ -168,7 +168,7 @@ private fun HomePresentationPagePastPreview() { category = Category.EDUCATION, title = "교육 발표", date = LocalDate(2026, 9, 20), - dDay = "+5", + dDay = "D+5", practiceRecords = PracticeRecordsUiModel( practicedDates = listOf(LocalDate(2026, 9, 18), LocalDate(2026, 9, 19)), startDate = LocalDate(2026, 9, 15), From 7be98063c2c0854445c433a5683626069d0426db Mon Sep 17 00:00:00 2001 From: Ham BeomJoon Date: Sun, 31 May 2026 19:19:54 +0900 Subject: [PATCH 05/10] =?UTF-8?q?feat:=20=ED=99=88=20=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?=EB=B0=9C=ED=91=9C=20=EB=B6=84=EC=84=9D=ED=95=98=EA=B8=B0=20?= =?UTF-8?q?=EB=82=B4=EB=B9=84=EA=B2=8C=EC=9D=B4=EC=85=98=20=EC=97=B0?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `HomeScreen`에 발표 분석 페이지로 이동하는 `navigateToAnalyzePresentation` 콜백 추가 - 발표 목록 아이템 클릭 시 해당 발표의 ID와 과거 데이터 여부(`isPastPresentation`)를 전달하도록 구현 - `HomeEntryBuilder`에서 `AnalysisNavKey.ReRecording`을 사용하여 실제 내비게이션 로직 연결 --- .../com/team/prezel/feature/home/impl/main/HomeScreen.kt | 5 ++++- .../prezel/feature/home/impl/navigation/HomeEntryBuilder.kt | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Prezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/main/HomeScreen.kt b/Prezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/main/HomeScreen.kt index fed02c95..400cb2dd 100644 --- a/Prezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/main/HomeScreen.kt +++ b/Prezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/main/HomeScreen.kt @@ -34,6 +34,7 @@ internal fun HomeScreen( navigateToPracticeRecording: (presentationId: Long) -> Unit, navigateToFileUploadAnalysis: () -> Unit, navigateToVoiceRecordingAnalysis: () -> Unit, + navigateToAnalyzePresentation: (presentationId: Long, isPast: Boolean) -> Unit, navigateToFeedback: (presentationId: Long, title: String) -> Unit, modifier: Modifier = Modifier, viewModel: HomeViewModel = hiltViewModel(), @@ -63,7 +64,9 @@ internal fun HomeScreen( pagerState = pagerState, onClickAddPresentation = navigateToVoiceRecordingAnalysis, onClickPracticeRecording = { presentationId -> navigateToPracticeRecording(presentationId) }, - onClickAnalyzePresentation = { }, + onClickAnalyzePresentation = { presentation -> + navigateToAnalyzePresentation(presentation.id, presentation.isPastPresentation) + }, onClickWriteFeedback = { presentation -> navigateToFeedback(presentation.id, presentation.title) }, onClickVoiceRecordingAnalysis = navigateToVoiceRecordingAnalysis, onClickFileUploadAnalysis = navigateToFileUploadAnalysis, diff --git a/Prezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/navigation/HomeEntryBuilder.kt b/Prezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/navigation/HomeEntryBuilder.kt index 7f92811f..faabce34 100644 --- a/Prezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/navigation/HomeEntryBuilder.kt +++ b/Prezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/navigation/HomeEntryBuilder.kt @@ -29,6 +29,9 @@ internal fun EntryProviderScope.featureHomeEntryBuilder() { navigateToVoiceRecordingAnalysis = { navigator.navigate(AnalysisNavKey.Schedule(startType = AnalysisStartType.VOICE_RECORDING)) }, + navigateToAnalyzePresentation = { presentationId, isPast -> + navigator.navigate(AnalysisNavKey.ReRecording(presentationId = presentationId, isPast = isPast)) + }, navigateToFeedback = { presentationId, title -> navigator.navigate( FeedbackNavKey( From 12223402364d47b03121a6bfbaa4f2ac792618b3 Mon Sep 17 00:00:00 2001 From: Ham BeomJoon Date: Mon, 1 Jun 2026 02:19:35 +0900 Subject: [PATCH 06/10] =?UTF-8?q?feat:=20=ED=94=BC=EB=93=9C=EB=B0=B1=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=20=EA=B8=B0=EB=8A=A5=20=EA=B0=9C=EC=84=A0=20?= =?UTF-8?q?=EB=B0=8F=20=EB=84=A4=EB=B9=84=EA=B2=8C=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=ED=99=95=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `FeedbackNavKey`에 `isPast`, `returnToReportOnSave` 파라미터 추가 - 피드백 저장 완료 시 홈 화면 또는 리포트 화면으로 이동하는 분기 로직 구현 - 피드백 작성 시 기존 저장된 내용을 불러오는 `fetchInitialContent` 로직 추가 - 변경 사항 여부를 판단하여 종료 다이얼로그 노출 로직 개선 - `AnalysisReport` 및 `Home` 화면에서 피드백 화면 이동 시 필요한 파라미터 전달 로직 수정 - `feature:feedback:impl` 모듈에 필요한 의존성 추가 (`coreModel`, `featureReportApi`) --- .../feature/feedback/api/FeedbackNavKey.kt | 2 ++ Prezel/feature/feedback/impl/build.gradle.kts | 2 ++ .../feature/feedback/impl/FeedbackScreen.kt | 4 +-- .../feedback/impl/FeedbackViewModel.kt | 32 +++++++++++++++++-- .../impl/contract/FeedbackUiEffect.kt | 2 +- .../feedback/impl/contract/FeedbackUiState.kt | 3 -- .../impl/navigation/FeedbackEntryBuilder.kt | 17 +++++++++- .../feature/home/impl/main/HomeScreen.kt | 10 ++++-- .../home/impl/navigation/HomeEntryBuilder.kt | 3 +- .../report/impl/AnalysisReportScreen.kt | 4 +-- .../report/impl/AnalysisReportViewModel.kt | 1 + .../impl/contract/AnalysisReportUiEffect.kt | 1 + .../impl/navigation/ReportEntryBuilder.kt | 4 ++- 13 files changed, 70 insertions(+), 15 deletions(-) diff --git a/Prezel/feature/feedback/api/src/main/java/com/team/prezel/feature/feedback/api/FeedbackNavKey.kt b/Prezel/feature/feedback/api/src/main/java/com/team/prezel/feature/feedback/api/FeedbackNavKey.kt index ac485159..b075ecfc 100644 --- a/Prezel/feature/feedback/api/src/main/java/com/team/prezel/feature/feedback/api/FeedbackNavKey.kt +++ b/Prezel/feature/feedback/api/src/main/java/com/team/prezel/feature/feedback/api/FeedbackNavKey.kt @@ -7,4 +7,6 @@ import kotlinx.serialization.Serializable data class FeedbackNavKey( val presentationId: Long, val title: String, + val isPast: Boolean = false, + val returnToReportOnSave: Boolean = false, ) : NavKey diff --git a/Prezel/feature/feedback/impl/build.gradle.kts b/Prezel/feature/feedback/impl/build.gradle.kts index d8ac2b95..d6aa31da 100644 --- a/Prezel/feature/feedback/impl/build.gradle.kts +++ b/Prezel/feature/feedback/impl/build.gradle.kts @@ -7,8 +7,10 @@ android { } dependencies { + implementation(projects.coreModel) implementation(projects.coreDomain) implementation(projects.featureFeedbackApi) implementation(projects.featureHomeApi) + implementation(projects.featureReportApi) } diff --git a/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/FeedbackScreen.kt b/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/FeedbackScreen.kt index 9d3326bb..ead5aef4 100644 --- a/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/FeedbackScreen.kt +++ b/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/FeedbackScreen.kt @@ -48,7 +48,7 @@ import com.team.prezel.feature.feedback.impl.model.FeedbackUiMessage internal fun FeedbackScreen( title: String, navigateBack: () -> Unit, - navigateToHome: () -> Unit, + onSaveComplete: () -> Unit, modifier: Modifier = Modifier, viewModel: FeedbackViewModel = hiltViewModel(), ) { @@ -60,7 +60,7 @@ internal fun FeedbackScreen( viewModel.uiEffect.collect { effect -> when (effect) { FeedbackUiEffect.NavigateBack -> navigateBack() - FeedbackUiEffect.NavigateToHome -> navigateToHome() + FeedbackUiEffect.SaveComplete -> onSaveComplete() is FeedbackUiEffect.ShowMessage -> { val resId = when (effect.message) { FeedbackUiMessage.SAVE_FAILED -> R.string.feature_feedback_impl_save_failed diff --git a/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/FeedbackViewModel.kt b/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/FeedbackViewModel.kt index 435afa27..835382e9 100644 --- a/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/FeedbackViewModel.kt +++ b/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/FeedbackViewModel.kt @@ -1,6 +1,7 @@ package com.team.prezel.feature.feedback.impl import androidx.lifecycle.viewModelScope +import com.team.prezel.core.domain.usecase.presentation.FetchPresentationDetailUseCase import com.team.prezel.core.domain.usecase.presentation.WriteSelfFeedbackUseCase import com.team.prezel.core.ui.base.BaseViewModel import com.team.prezel.feature.feedback.api.FeedbackNavKey @@ -17,6 +18,7 @@ import kotlinx.coroutines.launch @HiltViewModel(assistedFactory = FeedbackViewModel.Factory::class) internal class FeedbackViewModel @AssistedInject constructor( @Assisted private val navKey: FeedbackNavKey, + private val fetchPresentationDetailUseCase: FetchPresentationDetailUseCase, private val writeSelfFeedbackUseCase: WriteSelfFeedbackUseCase, ) : BaseViewModel(FeedbackUiState()) { @AssistedFactory @@ -24,6 +26,12 @@ internal class FeedbackViewModel @AssistedInject constructor( fun create(navKey: FeedbackNavKey): FeedbackViewModel } + private var initialContent: String = "" + + init { + fetchInitialContent() + } + override fun onIntent(intent: FeedbackUiIntent) { when (intent) { is FeedbackUiIntent.ChangeContent -> changeContent(intent.content) @@ -34,12 +42,30 @@ internal class FeedbackViewModel @AssistedInject constructor( } } + private fun fetchInitialContent() { + viewModelScope.launch { + fetchPresentationDetailUseCase( + presentationId = navKey.presentationId, + isPast = navKey.isPast, + ).onSuccess { result -> + val selfFeedback = result.analysisSummary.selfFeedback + .orEmpty() + .take(MAX_CONTENT_COUNT) + initialContent = selfFeedback + + if (currentState.content.isBlank()) { + updateState { copy(content = selfFeedback) } + } + } + } + } + private fun changeContent(content: String) { updateState { copy(content = content.take(MAX_CONTENT_COUNT)) } } private fun handleClose() { - if (currentState.hasUnsavedContent) { + if (hasUnsavedContent()) { updateState { copy(isExitDialogVisible = true) } return } @@ -47,6 +73,8 @@ internal class FeedbackViewModel @AssistedInject constructor( navigateBack() } + private fun hasUnsavedContent(): Boolean = currentState.content != initialContent + private fun save() { val content = currentState.content.trim() if (content.isBlank() || currentState.isSaving) return @@ -58,7 +86,7 @@ internal class FeedbackViewModel @AssistedInject constructor( presentationId = navKey.presentationId, content = content, ).onSuccess { - sendEffect(FeedbackUiEffect.NavigateToHome) + sendEffect(FeedbackUiEffect.SaveComplete) }.onFailure { updateState { copy(isSaving = false) } sendEffect(FeedbackUiEffect.ShowMessage(FeedbackUiMessage.SAVE_FAILED)) diff --git a/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/contract/FeedbackUiEffect.kt b/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/contract/FeedbackUiEffect.kt index bd548377..bf6feb08 100644 --- a/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/contract/FeedbackUiEffect.kt +++ b/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/contract/FeedbackUiEffect.kt @@ -6,7 +6,7 @@ import com.team.prezel.feature.feedback.impl.model.FeedbackUiMessage internal sealed interface FeedbackUiEffect : UiEffect { data object NavigateBack : FeedbackUiEffect - data object NavigateToHome : FeedbackUiEffect + data object SaveComplete : FeedbackUiEffect data class ShowMessage( val message: FeedbackUiMessage, diff --git a/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/contract/FeedbackUiState.kt b/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/contract/FeedbackUiState.kt index 69809084..9476a13f 100644 --- a/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/contract/FeedbackUiState.kt +++ b/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/contract/FeedbackUiState.kt @@ -11,7 +11,4 @@ internal data class FeedbackUiState( ) : UiState { val isSaveEnabled: Boolean get() = content.isNotBlank() && !isSaving - - val hasUnsavedContent: Boolean - get() = content.isNotBlank() } diff --git a/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/navigation/FeedbackEntryBuilder.kt b/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/navigation/FeedbackEntryBuilder.kt index f3cdb0c8..86742eee 100644 --- a/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/navigation/FeedbackEntryBuilder.kt +++ b/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/navigation/FeedbackEntryBuilder.kt @@ -8,11 +8,13 @@ import com.team.prezel.feature.feedback.api.FeedbackNavKey import com.team.prezel.feature.feedback.impl.FeedbackScreen import com.team.prezel.feature.feedback.impl.FeedbackViewModel import com.team.prezel.feature.home.api.HomeNavKey +import com.team.prezel.feature.report.api.ReportNavKey import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.components.ActivityRetainedComponent import dagger.multibindings.IntoSet +import java.util.UUID internal fun EntryProviderScope.featureFeedbackEntryBuilder() { entry { key -> @@ -21,7 +23,20 @@ internal fun EntryProviderScope.featureFeedbackEntryBuilder() { FeedbackScreen( title = key.title, navigateBack = { navigator.goBack() }, - navigateToHome = { navigator.replaceRoot(HomeNavKey) }, + onSaveComplete = { + if (key.returnToReportOnSave) { + navigator.navigate( + key = ReportNavKey( + presentationId = key.presentationId, + isPast = key.isPast, + refreshKey = UUID.randomUUID().toString(), + ), + clearStack = true, + ) + } else { + navigator.replaceRoot(HomeNavKey) + } + }, viewModel = hiltViewModel( creationCallback = { factory -> factory.create(key) }, ), diff --git a/Prezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/main/HomeScreen.kt b/Prezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/main/HomeScreen.kt index 400cb2dd..30dd9b66 100644 --- a/Prezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/main/HomeScreen.kt +++ b/Prezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/main/HomeScreen.kt @@ -35,7 +35,7 @@ internal fun HomeScreen( navigateToFileUploadAnalysis: () -> Unit, navigateToVoiceRecordingAnalysis: () -> Unit, navigateToAnalyzePresentation: (presentationId: Long, isPast: Boolean) -> Unit, - navigateToFeedback: (presentationId: Long, title: String) -> Unit, + navigateToFeedback: (presentationId: Long, title: String, isPast: Boolean) -> Unit, modifier: Modifier = Modifier, viewModel: HomeViewModel = hiltViewModel(), ) { @@ -67,7 +67,13 @@ internal fun HomeScreen( onClickAnalyzePresentation = { presentation -> navigateToAnalyzePresentation(presentation.id, presentation.isPastPresentation) }, - onClickWriteFeedback = { presentation -> navigateToFeedback(presentation.id, presentation.title) }, + onClickWriteFeedback = { presentation -> + navigateToFeedback( + presentation.id, + presentation.title, + presentation.isPastPresentation, + ) + }, onClickVoiceRecordingAnalysis = navigateToVoiceRecordingAnalysis, onClickFileUploadAnalysis = navigateToFileUploadAnalysis, onClickCardGraphItemIndex = { presentationId, index -> diff --git a/Prezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/navigation/HomeEntryBuilder.kt b/Prezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/navigation/HomeEntryBuilder.kt index faabce34..f7de6e70 100644 --- a/Prezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/navigation/HomeEntryBuilder.kt +++ b/Prezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/navigation/HomeEntryBuilder.kt @@ -32,11 +32,12 @@ internal fun EntryProviderScope.featureHomeEntryBuilder() { navigateToAnalyzePresentation = { presentationId, isPast -> navigator.navigate(AnalysisNavKey.ReRecording(presentationId = presentationId, isPast = isPast)) }, - navigateToFeedback = { presentationId, title -> + navigateToFeedback = { presentationId, title, isPast -> navigator.navigate( FeedbackNavKey( presentationId = presentationId, title = title, + isPast = isPast, ), ) }, diff --git a/Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/AnalysisReportScreen.kt b/Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/AnalysisReportScreen.kt index 663db855..0e309e5b 100644 --- a/Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/AnalysisReportScreen.kt +++ b/Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/AnalysisReportScreen.kt @@ -33,7 +33,7 @@ internal fun AnalysisReportScreen( onBack: () -> Unit, navigateToAnalysisScript: (presentationId: Long, isPast: Boolean) -> Unit, navigateToAnalysisRecording: (presentationId: Long, isPast: Boolean) -> Unit, - navigateToSelfFeedbackWrite: (presentationId: Long, title: String) -> Unit, + navigateToSelfFeedbackWrite: (presentationId: Long, title: String, isPast: Boolean) -> Unit, modifier: Modifier = Modifier, viewModel: AnalysisReportViewModel = hiltViewModel(), ) { @@ -55,7 +55,7 @@ internal fun AnalysisReportScreen( is AnalysisReportUiEffect.NavigateToAnalysisScript -> navigateToAnalysisScript(effect.presentationId, effect.isPast) is AnalysisReportUiEffect.NavigateToAnalysisRecording -> navigateToAnalysisRecording(effect.presentationId, effect.isPast) is AnalysisReportUiEffect.NavigateToSelfFeedbackWrite -> { - navigateToSelfFeedbackWrite(effect.presentationId, effect.title) + navigateToSelfFeedbackWrite(effect.presentationId, effect.title, effect.isPast) } } } diff --git a/Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/AnalysisReportViewModel.kt b/Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/AnalysisReportViewModel.kt index 29e6e9d4..ce0430a6 100644 --- a/Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/AnalysisReportViewModel.kt +++ b/Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/AnalysisReportViewModel.kt @@ -122,6 +122,7 @@ internal class AnalysisReportViewModel @AssistedInject constructor( val effect = AnalysisReportUiEffect.NavigateToSelfFeedbackWrite( presentationId = presentationId, title = title, + isPast = isPast, ) viewModelScope.launch { sendEffect(effect) } } diff --git a/Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/contract/AnalysisReportUiEffect.kt b/Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/contract/AnalysisReportUiEffect.kt index a1b5b51f..9e50bc37 100644 --- a/Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/contract/AnalysisReportUiEffect.kt +++ b/Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/contract/AnalysisReportUiEffect.kt @@ -23,5 +23,6 @@ internal sealed interface AnalysisReportUiEffect : UiEffect { data class NavigateToSelfFeedbackWrite( val presentationId: Long, val title: String, + val isPast: Boolean, ) : AnalysisReportUiEffect } diff --git a/Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/navigation/ReportEntryBuilder.kt b/Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/navigation/ReportEntryBuilder.kt index 93f221f8..9f22021c 100644 --- a/Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/navigation/ReportEntryBuilder.kt +++ b/Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/navigation/ReportEntryBuilder.kt @@ -37,11 +37,13 @@ internal fun EntryProviderScope.featureAnalysisReportEntryBuilder() { ), ) }, - navigateToSelfFeedbackWrite = { presentationId, title -> + navigateToSelfFeedbackWrite = { presentationId, title, isPast -> navigator.navigate( FeedbackNavKey( presentationId = presentationId, title = title, + isPast = isPast, + returnToReportOnSave = true, ), ) }, From b92972347c7153104c6ed2a6145ee625c7f80b44 Mon Sep 17 00:00:00 2001 From: Ham BeomJoon Date: Mon, 1 Jun 2026 09:04:31 +0900 Subject: [PATCH 07/10] =?UTF-8?q?refactor:=20=EB=A6=AC=ED=8F=AC=ED=8A=B8?= =?UTF-8?q?=20=EC=A1=B0=ED=9A=8C=20=EB=A1=9C=EC=A7=81=20=EA=B0=9C=EC=84=A0?= =?UTF-8?q?=20=EB=B0=8F=20=EB=82=B4=EB=B9=84=EA=B2=8C=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=ED=9D=90=EB=A6=84=20=EB=8B=A8=EC=88=9C=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `AnalysisReportUiIntent.FetchData` 인텐트를 추가하고 `LaunchedEffect`를 통해 데이터를 호출하도록 변경 - `ReportNavKey`와 `FeedbackNavKey`에서 불필요한 `refreshKey`, `returnToReportOnSave` 파라미터 제거 - 피드백 저장 완료 시 복잡한 화면 전환 로직을 `navigator.goBack()`으로 단순화 - `AnalysisReportViewModel`의 초기 데이터 로드 로직을 `init` 블록에서 `onIntent` 처리 방식으로 이전 - `feature:feedback:impl` 모듈의 불필요한 의존성(`feature:home:api`, `feature:report:api`) 제거 --- .../impl/navigation/AnalysisEntryBuilder.kt | 4 ---- .../feature/feedback/api/FeedbackNavKey.kt | 1 - Prezel/feature/feedback/impl/build.gradle.kts | 2 -- .../impl/navigation/FeedbackEntryBuilder.kt | 18 +----------------- .../prezel/feature/report/api/ReportNavKey.kt | 1 - .../report/impl/AnalysisReportScreen.kt | 4 ++++ .../report/impl/AnalysisReportViewModel.kt | 13 ++++--------- .../impl/contract/AnalysisReportUiIntent.kt | 2 ++ .../impl/navigation/ReportEntryBuilder.kt | 1 - 9 files changed, 11 insertions(+), 35 deletions(-) diff --git a/Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/navigation/AnalysisEntryBuilder.kt b/Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/navigation/AnalysisEntryBuilder.kt index 01e480fc..e1cb486b 100644 --- a/Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/navigation/AnalysisEntryBuilder.kt +++ b/Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/navigation/AnalysisEntryBuilder.kt @@ -21,7 +21,6 @@ import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.components.ActivityRetainedComponent import dagger.multibindings.IntoSet -import java.util.UUID internal fun EntryProviderScope.featureAnalysisEntryBuilder() { analysisEntry { key -> key.enterStep(AnalysisFlowStep.PRESENTATION_SCHEDULE) } @@ -84,7 +83,6 @@ private fun AnalysisRoute( navigator.navigate( key = ReportNavKey( presentationId = presentationId, - refreshKey = newReportRefreshKey(), ), clearStack = true, ) @@ -127,5 +125,3 @@ object FeatureAnalysisModule { featureAnalysisEntryBuilder() } } - -private fun newReportRefreshKey(): String = UUID.randomUUID().toString() diff --git a/Prezel/feature/feedback/api/src/main/java/com/team/prezel/feature/feedback/api/FeedbackNavKey.kt b/Prezel/feature/feedback/api/src/main/java/com/team/prezel/feature/feedback/api/FeedbackNavKey.kt index b075ecfc..7df01fc0 100644 --- a/Prezel/feature/feedback/api/src/main/java/com/team/prezel/feature/feedback/api/FeedbackNavKey.kt +++ b/Prezel/feature/feedback/api/src/main/java/com/team/prezel/feature/feedback/api/FeedbackNavKey.kt @@ -8,5 +8,4 @@ data class FeedbackNavKey( val presentationId: Long, val title: String, val isPast: Boolean = false, - val returnToReportOnSave: Boolean = false, ) : NavKey diff --git a/Prezel/feature/feedback/impl/build.gradle.kts b/Prezel/feature/feedback/impl/build.gradle.kts index d6aa31da..bf5b39c0 100644 --- a/Prezel/feature/feedback/impl/build.gradle.kts +++ b/Prezel/feature/feedback/impl/build.gradle.kts @@ -11,6 +11,4 @@ dependencies { implementation(projects.coreDomain) implementation(projects.featureFeedbackApi) - implementation(projects.featureHomeApi) - implementation(projects.featureReportApi) } diff --git a/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/navigation/FeedbackEntryBuilder.kt b/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/navigation/FeedbackEntryBuilder.kt index 86742eee..95a1bc4c 100644 --- a/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/navigation/FeedbackEntryBuilder.kt +++ b/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/navigation/FeedbackEntryBuilder.kt @@ -7,14 +7,11 @@ import com.team.prezel.core.navigation.LocalNavigator import com.team.prezel.feature.feedback.api.FeedbackNavKey import com.team.prezel.feature.feedback.impl.FeedbackScreen import com.team.prezel.feature.feedback.impl.FeedbackViewModel -import com.team.prezel.feature.home.api.HomeNavKey -import com.team.prezel.feature.report.api.ReportNavKey import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.components.ActivityRetainedComponent import dagger.multibindings.IntoSet -import java.util.UUID internal fun EntryProviderScope.featureFeedbackEntryBuilder() { entry { key -> @@ -23,20 +20,7 @@ internal fun EntryProviderScope.featureFeedbackEntryBuilder() { FeedbackScreen( title = key.title, navigateBack = { navigator.goBack() }, - onSaveComplete = { - if (key.returnToReportOnSave) { - navigator.navigate( - key = ReportNavKey( - presentationId = key.presentationId, - isPast = key.isPast, - refreshKey = UUID.randomUUID().toString(), - ), - clearStack = true, - ) - } else { - navigator.replaceRoot(HomeNavKey) - } - }, + onSaveComplete = { navigator.goBack() }, viewModel = hiltViewModel( creationCallback = { factory -> factory.create(key) }, ), diff --git a/Prezel/feature/report/api/src/main/java/com/team/prezel/feature/report/api/ReportNavKey.kt b/Prezel/feature/report/api/src/main/java/com/team/prezel/feature/report/api/ReportNavKey.kt index 2825db54..5cb4de82 100644 --- a/Prezel/feature/report/api/src/main/java/com/team/prezel/feature/report/api/ReportNavKey.kt +++ b/Prezel/feature/report/api/src/main/java/com/team/prezel/feature/report/api/ReportNavKey.kt @@ -7,5 +7,4 @@ import kotlinx.serialization.Serializable data class ReportNavKey( val presentationId: Long, val isPast: Boolean = false, - val refreshKey: String = "", ) : NavKey diff --git a/Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/AnalysisReportScreen.kt b/Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/AnalysisReportScreen.kt index 0e309e5b..8af05514 100644 --- a/Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/AnalysisReportScreen.kt +++ b/Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/AnalysisReportScreen.kt @@ -41,6 +41,10 @@ internal fun AnalysisReportScreen( val snackbarHostState = LocalSnackbarHostState.current val resources = LocalResources.current + LaunchedEffect(Unit) { + viewModel.onIntent(AnalysisReportUiIntent.FetchData) + } + LaunchedEffect(Unit) { viewModel.uiEffect.collect { effect -> when (effect) { diff --git a/Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/AnalysisReportViewModel.kt b/Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/AnalysisReportViewModel.kt index ce0430a6..d2b0fdda 100644 --- a/Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/AnalysisReportViewModel.kt +++ b/Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/AnalysisReportViewModel.kt @@ -28,16 +28,14 @@ internal class AnalysisReportViewModel @AssistedInject constructor( fun create(navKey: ReportNavKey): AnalysisReportViewModel } + private val requestedPresentationId: Long = navKey.presentationId private var presentationId: Long? = null private var analysisResultId: Long? = null private val isPast: Boolean = navKey.isPast - init { - fetchData(presentationId = navKey.presentationId, isPast = navKey.isPast) - } - override fun onIntent(intent: AnalysisReportUiIntent) { when (intent) { + AnalysisReportUiIntent.FetchData -> fetchData() AnalysisReportUiIntent.ClickDelete -> updateContent { copy(reportDialog = AnalysisReportDialog.DELETE_REPORT) } is AnalysisReportUiIntent.ClickGrowthGraphItem -> updateGrowthGraphSelectedItem(index = intent.index) AnalysisReportUiIntent.ClickDialogConform -> handleClickDialogConform() @@ -48,12 +46,9 @@ internal class AnalysisReportViewModel @AssistedInject constructor( } } - private fun fetchData( - presentationId: Long, - isPast: Boolean, - ) { + private fun fetchData() { viewModelScope.launch { - fetchPresentationDetailUseCase(presentationId = presentationId, isPast = isPast) + fetchPresentationDetailUseCase(presentationId = requestedPresentationId, isPast = isPast) .onSuccess { result -> this@AnalysisReportViewModel.presentationId = result.analysisSummary.presentationId analysisResultId = result.analysisSummary.analysisResultId diff --git a/Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/contract/AnalysisReportUiIntent.kt b/Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/contract/AnalysisReportUiIntent.kt index 85f3480f..34c4dc16 100644 --- a/Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/contract/AnalysisReportUiIntent.kt +++ b/Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/contract/AnalysisReportUiIntent.kt @@ -3,6 +3,8 @@ package com.team.prezel.feature.report.impl.contract import com.team.prezel.core.ui.base.UiIntent internal sealed interface AnalysisReportUiIntent : UiIntent { + data object FetchData : AnalysisReportUiIntent + data object ClickDelete : AnalysisReportUiIntent data class ClickGrowthGraphItem( diff --git a/Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/navigation/ReportEntryBuilder.kt b/Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/navigation/ReportEntryBuilder.kt index 9f22021c..6b74eee9 100644 --- a/Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/navigation/ReportEntryBuilder.kt +++ b/Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/navigation/ReportEntryBuilder.kt @@ -43,7 +43,6 @@ internal fun EntryProviderScope.featureAnalysisReportEntryBuilder() { presentationId = presentationId, title = title, isPast = isPast, - returnToReportOnSave = true, ), ) }, From 321cd3e9c9ef2ae0a6ece8f7d1d7eb5d7a95b791 Mon Sep 17 00:00:00 2001 From: Ham BeomJoon Date: Mon, 1 Jun 2026 09:17:39 +0900 Subject: [PATCH 08/10] =?UTF-8?q?feat:=20=ED=94=BC=EB=93=9C=EB=B0=B1=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=20=EA=B8=B0=EB=8A=A5=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EA=B0=95=ED=99=94=20=EB=B0=8F=20UI=20?= =?UTF-8?q?=EB=A9=94=EC=8B=9C=EC=A7=80=20=EC=84=B8=EB=B6=84=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 서버 에러 코드(`ServerErrorCode`) 및 앱 에러(`AppError`) 매핑 로직 추가 - `SELF_FEEDBACK_ALREADY_WRITTEN`, `PRESENTATION_REVIEW_NOT_FOUND`, `PRESENTATION_REVIEW_FORBIDDEN` 추가 - `FeedbackUiMessage` enum 클래스에 에러 케이스 추가 - `FETCH_FEEDBACK_FAILED`, `PRESENTATION_FORBIDDEN`, `PRESENTATION_NOT_FOUND`, `SELF_FEEDBACK_ALREADY_WRITTEN` 정의 - `FeedbackViewModel` 내 에러 핸들링 로직 개선 - 발표 상세 조회 및 피드백 저장 실패 시 `FeedbackUiMessage`로 변환하여 UI 에러 메시지 표시 - `AppException` 기반의 `toFeedbackUiMessage` 확장 함수 구현 - 피드백 화면(`FeedbackScreen`) 에러 메시지 스낵바 연동 및 다국어 리소스 추가 - `feature:feedback:impl` 모듈에 `coreCommon` 의존성 추가 및 최대 글자 수 상수명 변경(`FEEDBACK_CONTENT_MAX_LENGTH`) --- .../prezel/core/data/error/AppErrorExt.kt | 6 +++- .../prezel/core/network/model/BaseResponse.kt | 3 ++ Prezel/feature/feedback/impl/build.gradle.kts | 1 + .../feature/feedback/impl/FeedbackScreen.kt | 8 +++++ .../feedback/impl/FeedbackViewModel.kt | 33 +++++++++++++++---- .../feedback/impl/model/FeedbackUiMessage.kt | 4 +++ .../impl/src/main/res/values/strings.xml | 4 +++ 7 files changed, 52 insertions(+), 7 deletions(-) diff --git a/Prezel/core/data/src/main/java/com/team/prezel/core/data/error/AppErrorExt.kt b/Prezel/core/data/src/main/java/com/team/prezel/core/data/error/AppErrorExt.kt index 8e94c8b9..eac35399 100644 --- a/Prezel/core/data/src/main/java/com/team/prezel/core/data/error/AppErrorExt.kt +++ b/Prezel/core/data/src/main/java/com/team/prezel/core/data/error/AppErrorExt.kt @@ -57,13 +57,17 @@ private fun ServerErrorCode.toDomainError(): AppError = ServerErrorCode.TERMS_NOT_FOUND, ServerErrorCode.PRESENTATION_NOT_FOUND, + ServerErrorCode.PRESENTATION_REVIEW_NOT_FOUND, ServerErrorCode.ANALYSIS_RESULT_NOT_FOUND, -> AppError.NOT_FOUND - ServerErrorCode.DUPLICATE_NICKNAME -> AppError.DUPLICATE + ServerErrorCode.DUPLICATE_NICKNAME, + ServerErrorCode.SELF_FEEDBACK_ALREADY_WRITTEN, + -> AppError.DUPLICATE ServerErrorCode.UNAUTHORIZED, ServerErrorCode.FORBIDDEN, + ServerErrorCode.PRESENTATION_REVIEW_FORBIDDEN, ServerErrorCode.INVALID_TOKEN, ServerErrorCode.TOKEN_STOLEN, ServerErrorCode.USER_NOT_FOUND, diff --git a/Prezel/core/network/src/main/java/com/team/prezel/core/network/model/BaseResponse.kt b/Prezel/core/network/src/main/java/com/team/prezel/core/network/model/BaseResponse.kt index e6a18ef6..6037d95b 100644 --- a/Prezel/core/network/src/main/java/com/team/prezel/core/network/model/BaseResponse.kt +++ b/Prezel/core/network/src/main/java/com/team/prezel/core/network/model/BaseResponse.kt @@ -55,9 +55,12 @@ enum class ServerErrorCode( INVALID_ID_TOKEN("T003"), TERMS_NOT_FOUND("TR001"), REQUIRED_TERMS_DISAGREED("TR002"), + SELF_FEEDBACK_ALREADY_WRITTEN("R002"), FILE_IS_EMPTY("F001"), FILE_UPLOAD_FAILED("F002"), UNSUPPORTED_FILE_FORMAT("F004"), + PRESENTATION_REVIEW_NOT_FOUND("PR001"), + PRESENTATION_REVIEW_FORBIDDEN("PR002"), PRESENTATION_NOT_FOUND("P001"), ANALYSIS_RESULT_NOT_FOUND("A001"), SENTENCE_NOT_FOUND("S001"), diff --git a/Prezel/feature/feedback/impl/build.gradle.kts b/Prezel/feature/feedback/impl/build.gradle.kts index bf5b39c0..024a012a 100644 --- a/Prezel/feature/feedback/impl/build.gradle.kts +++ b/Prezel/feature/feedback/impl/build.gradle.kts @@ -7,6 +7,7 @@ android { } dependencies { + implementation(projects.coreCommon) implementation(projects.coreModel) implementation(projects.coreDomain) diff --git a/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/FeedbackScreen.kt b/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/FeedbackScreen.kt index ead5aef4..04bf67ee 100644 --- a/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/FeedbackScreen.kt +++ b/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/FeedbackScreen.kt @@ -63,6 +63,14 @@ internal fun FeedbackScreen( FeedbackUiEffect.SaveComplete -> onSaveComplete() is FeedbackUiEffect.ShowMessage -> { val resId = when (effect.message) { + FeedbackUiMessage.FETCH_FEEDBACK_FAILED -> + R.string.feature_feedback_impl_fetch_feedback_failed + FeedbackUiMessage.PRESENTATION_FORBIDDEN -> + R.string.feature_feedback_impl_presentation_forbidden + FeedbackUiMessage.PRESENTATION_NOT_FOUND -> + R.string.feature_feedback_impl_presentation_not_found + FeedbackUiMessage.SELF_FEEDBACK_ALREADY_WRITTEN -> + R.string.feature_feedback_impl_self_feedback_already_written FeedbackUiMessage.SAVE_FAILED -> R.string.feature_feedback_impl_save_failed } snackbarHostState.showPrezelSnackbar( diff --git a/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/FeedbackViewModel.kt b/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/FeedbackViewModel.kt index 835382e9..047712a4 100644 --- a/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/FeedbackViewModel.kt +++ b/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/FeedbackViewModel.kt @@ -1,6 +1,8 @@ package com.team.prezel.feature.feedback.impl import androidx.lifecycle.viewModelScope +import com.team.prezel.core.common.error.AppError +import com.team.prezel.core.common.error.AppException import com.team.prezel.core.domain.usecase.presentation.FetchPresentationDetailUseCase import com.team.prezel.core.domain.usecase.presentation.WriteSelfFeedbackUseCase import com.team.prezel.core.ui.base.BaseViewModel @@ -14,6 +16,7 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch +import timber.log.Timber @HiltViewModel(assistedFactory = FeedbackViewModel.Factory::class) internal class FeedbackViewModel @AssistedInject constructor( @@ -50,18 +53,20 @@ internal class FeedbackViewModel @AssistedInject constructor( ).onSuccess { result -> val selfFeedback = result.analysisSummary.selfFeedback .orEmpty() - .take(MAX_CONTENT_COUNT) initialContent = selfFeedback if (currentState.content.isBlank()) { updateState { copy(content = selfFeedback) } } + }.onFailure { throwable -> + sendEffect(FeedbackUiEffect.ShowMessage(FeedbackUiMessage.FETCH_FEEDBACK_FAILED)) + Timber.e(throwable) } } } private fun changeContent(content: String) { - updateState { copy(content = content.take(MAX_CONTENT_COUNT)) } + updateState { copy(content = content) } } private fun handleClose() { @@ -87,9 +92,10 @@ internal class FeedbackViewModel @AssistedInject constructor( content = content, ).onSuccess { sendEffect(FeedbackUiEffect.SaveComplete) - }.onFailure { + }.onFailure { throwable -> updateState { copy(isSaving = false) } - sendEffect(FeedbackUiEffect.ShowMessage(FeedbackUiMessage.SAVE_FAILED)) + sendEffect(FeedbackUiEffect.ShowMessage(throwable.toFeedbackUiMessage())) + Timber.e(throwable) } } } @@ -97,8 +103,23 @@ internal class FeedbackViewModel @AssistedInject constructor( private fun navigateBack() { viewModelScope.launch { sendEffect(FeedbackUiEffect.NavigateBack) } } +} - companion object { - private const val MAX_CONTENT_COUNT = 200 +private fun Throwable.toFeedbackUiMessage(): FeedbackUiMessage { + val error = (this as? AppException)?.error + + return when (error) { + AppError.UNAUTHORIZED -> FeedbackUiMessage.PRESENTATION_FORBIDDEN + AppError.NOT_FOUND -> FeedbackUiMessage.PRESENTATION_NOT_FOUND + AppError.DUPLICATE -> FeedbackUiMessage.SELF_FEEDBACK_ALREADY_WRITTEN + AppError.INVALID_REQUEST, + AppError.SERVER_ERROR, + AppError.VOICE_RECOGNITION_FAILED, + AppError.VOICE_ANALYSIS_FAILED, + AppError.SCRIPT_FILE_RECOGNITION_FAILED, + AppError.NETWORK, + AppError.UNKNOWN, + null, + -> FeedbackUiMessage.SAVE_FAILED } } diff --git a/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/model/FeedbackUiMessage.kt b/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/model/FeedbackUiMessage.kt index 586c0be7..f408ef84 100644 --- a/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/model/FeedbackUiMessage.kt +++ b/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/model/FeedbackUiMessage.kt @@ -1,5 +1,9 @@ package com.team.prezel.feature.feedback.impl.model internal enum class FeedbackUiMessage { + FETCH_FEEDBACK_FAILED, + PRESENTATION_FORBIDDEN, + PRESENTATION_NOT_FOUND, + SELF_FEEDBACK_ALREADY_WRITTEN, SAVE_FAILED, } diff --git a/Prezel/feature/feedback/impl/src/main/res/values/strings.xml b/Prezel/feature/feedback/impl/src/main/res/values/strings.xml index 2a9fdc66..c5db6ad0 100644 --- a/Prezel/feature/feedback/impl/src/main/res/values/strings.xml +++ b/Prezel/feature/feedback/impl/src/main/res/values/strings.xml @@ -4,6 +4,10 @@ \'%1$s\'는 어떠셨나요? 다음 발표에는 어떤 부분을 신경쓰고 싶나요? 저장하기 + 셀프 피드백을 불러오지 못했어요. + 해당 데이터에 접근할 권한이 없습니다. + 존재하지 않는 발표입니다. + 이미 회고를 작성한 발표입니다. 셀프 피드백 저장에 실패했어요. 저장하지 않고 나가시겠어요? 작성된 셀프 피드백이 모두 삭제됩니다. From ea81fa0dfc9453fee525bbfcf4af781f912c999c Mon Sep 17 00:00:00 2001 From: Ham BeomJoon Date: Mon, 1 Jun 2026 12:53:15 +0900 Subject: [PATCH 09/10] =?UTF-8?q?fix:=20=ED=94=BC=EB=93=9C=EB=B0=B1=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=20=EC=84=B1=EA=B3=B5=20=EC=8B=9C=20isSaving?= =?UTF-8?q?=20=EC=83=81=ED=83=9C=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - FeedbackViewModel에서 피드백 저장 성공(onSuccess) 시 isSaving 상태를 false로 변경하도록 수정하여 저장 중 상태를 해제함 --- .../com/team/prezel/feature/feedback/impl/FeedbackViewModel.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/FeedbackViewModel.kt b/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/FeedbackViewModel.kt index 047712a4..a821b980 100644 --- a/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/FeedbackViewModel.kt +++ b/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/FeedbackViewModel.kt @@ -91,6 +91,7 @@ internal class FeedbackViewModel @AssistedInject constructor( presentationId = navKey.presentationId, content = content, ).onSuccess { + updateState { copy(isSaving = false) } sendEffect(FeedbackUiEffect.SaveComplete) }.onFailure { throwable -> updateState { copy(isSaving = false) } @@ -120,6 +121,6 @@ private fun Throwable.toFeedbackUiMessage(): FeedbackUiMessage { AppError.NETWORK, AppError.UNKNOWN, null, - -> FeedbackUiMessage.SAVE_FAILED + -> FeedbackUiMessage.SAVE_FAILED } } From 9061c4867832427e38e393f015b95eb1a77212f4 Mon Sep 17 00:00:00 2001 From: Ham BeomJoon Date: Mon, 1 Jun 2026 12:56:26 +0900 Subject: [PATCH 10/10] =?UTF-8?q?style:=20FeedbackViewModel=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=A0=95=EB=A0=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `when` 문 내 화살표(`->`) 앞의 불필요한 들여쓰기 제거 --- .../com/team/prezel/feature/feedback/impl/FeedbackViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/FeedbackViewModel.kt b/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/FeedbackViewModel.kt index a821b980..c63a91b7 100644 --- a/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/FeedbackViewModel.kt +++ b/Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/FeedbackViewModel.kt @@ -121,6 +121,6 @@ private fun Throwable.toFeedbackUiMessage(): FeedbackUiMessage { AppError.NETWORK, AppError.UNKNOWN, null, - -> FeedbackUiMessage.SAVE_FAILED + -> FeedbackUiMessage.SAVE_FAILED } }