From 9434cff751927d36117b27f8d58c6bd986d801f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=BC=ED=98=81?= Date: Mon, 24 Aug 2026 02:52:40 +0900 Subject: [PATCH] =?UTF-8?q?fix(afternote):=20=EC=97=90=EB=94=94=ED=84=B0?= =?UTF-8?q?=20=EC=88=98=EC=8B=A0=EC=9E=90=20=EB=AA=A9=EB=A1=9D=EC=9D=84=20?= =?UTF-8?q?UserRepository=20=EB=A1=9C=20=EC=97=B0=EA=B2=B0=20=E2=80=94=20?= =?UTF-8?q?=EC=A3=BD=EC=9D=80=20AuthorReceiverRepository=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AuthorReceiverRepositoryImpl 은 currentAuthorUserId() 가 항상 null 이라 refreshReceivers() 가 늘 실패하고 observeReceivers()·currentReceivers() 는 빈 리스트만 반환하는 죽은 구현이었다. 에디터가 이를 프로덕션 경로에서 소비해 신규 작성 시 수신자 목록이 항상 비었고, 수신자 최소 1명이 서버 필수값(475)이라 등록 자체가 막혔다. 같은 기능을 하는 core:domain 의 UserRepository 로 전환한다. GET users/receivers 는 파라미터가 없고 서버가 액세스 토큰으로 호출자를 식별하므로 userId 개념 자체가 불필요하다 — 코드베이스 어디에도 저장되지 않던 값이다. - AfternoteEditorViewModel: 죽은 flow 구독을 걷고 refreshAuthorReceivers() 가 userRepository.getReceivers() 로 목록을 채운다. 조회 실패는 삼키지 않고 AUTHOR_RECEIVER_LOAD 단계로 계측하며, runCatchingCancellable 로 취소는 보존한다 - AuthorReceiverEntry 를 core:model 의 Receiver 로 통합하고 매퍼를 EditorReceiverMapping 으로 옮긴다 (수신 타입도 호출처에 맞춰 List 로 좁힘) - AuthorReceiverRepository·Impl·Entry 와 DI 바인딩 삭제, androidTest 전용 Fake 3종은 공용 FakeUserRepository 로 교체 수신자 선택 화면 라우팅(#540)은 #791 선행이 남아 별건이다. --- .../AfternoteAuthorAndroidTest.kt | 22 +-------- .../AfternoteAuthorExtendedAndroidTest.kt | 21 +-------- ...oteAuthorImplementedCoverageAndroidTest.kt | 22 +-------- .../di/AfternoteAuthorRepositoryModule.kt | 7 --- .../author/AuthorReceiverRepositoryImpl.kt | 45 ------------------- .../model/author/AuthorReceiverEntry.kt | 8 ---- .../author/AuthorReceiverRepository.kt | 22 --------- .../author/editor/AfternoteEditorViewModel.kt | 31 +++++++------ .../editor/mapper/AuthorReceiverMapping.kt | 15 ------- .../editor/mapper/EditorReceiverMapping.kt | 14 ++++++ .../navigation/AfternoteNavGraphEditor.kt | 2 +- .../reporting/AfternoteFailureReporting.kt | 9 ++++ 12 files changed, 47 insertions(+), 171 deletions(-) delete mode 100644 feature/afternote/data/src/main/kotlin/com/afternote/feature/afternote/data/repositoryimpl/author/AuthorReceiverRepositoryImpl.kt delete mode 100644 feature/afternote/domain/src/main/java/com/afternote/feature/afternote/domain/model/author/AuthorReceiverEntry.kt delete mode 100644 feature/afternote/domain/src/main/java/com/afternote/feature/afternote/domain/repository/author/AuthorReceiverRepository.kt delete mode 100644 feature/afternote/presentation/src/main/kotlin/com/afternote/feature/afternote/presentation/author/editor/mapper/AuthorReceiverMapping.kt create mode 100644 feature/afternote/presentation/src/main/kotlin/com/afternote/feature/afternote/presentation/author/editor/mapper/EditorReceiverMapping.kt diff --git a/app/src/androidTest/java/com/afternote/afternote_fe/AfternoteAuthorAndroidTest.kt b/app/src/androidTest/java/com/afternote/afternote_fe/AfternoteAuthorAndroidTest.kt index 8b5d711d0e..a95ac2c06c 100644 --- a/app/src/androidTest/java/com/afternote/afternote_fe/AfternoteAuthorAndroidTest.kt +++ b/app/src/androidTest/java/com/afternote/afternote_fe/AfternoteAuthorAndroidTest.kt @@ -15,10 +15,10 @@ import androidx.paging.PagingData import androidx.test.ext.junit.runners.AndroidJUnit4 import com.afternote.afternote_fe.test.FailureArtifactRule import com.afternote.afternote_fe.test.FakeErrorReporter +import com.afternote.afternote_fe.test.FakeUserRepository import com.afternote.core.ui.theme.AfternoteTheme import com.afternote.feature.afternote.domain.AfternoteType import com.afternote.feature.afternote.domain.model.author.AfternoteUpdatePayload -import com.afternote.feature.afternote.domain.model.author.AuthorReceiverEntry import com.afternote.feature.afternote.domain.model.author.CreateAccountPayload import com.afternote.feature.afternote.domain.model.author.CreateGalleryPayload import com.afternote.feature.afternote.domain.model.author.CreateMemorialPayload @@ -26,7 +26,6 @@ import com.afternote.feature.afternote.domain.model.author.Detail import com.afternote.feature.afternote.domain.model.author.ListItem import com.afternote.feature.afternote.domain.model.author.ProcessingMethod import com.afternote.feature.afternote.domain.repository.author.AfternoteRepository -import com.afternote.feature.afternote.domain.repository.author.AuthorReceiverRepository import com.afternote.feature.afternote.domain.repository.author.MediaInput import com.afternote.feature.afternote.domain.repository.author.MemorialPhotoUploadRepository import com.afternote.feature.afternote.domain.repository.author.MemorialThumbnailUploadRepository @@ -41,7 +40,6 @@ import com.afternote.feature.afternote.presentation.author.editor.model.EditorCa import com.afternote.feature.afternote.presentation.author.editor.model.RegisterAfternotePayload import com.afternote.feature.afternote.presentation.author.editor.state.AfternoteValidationError import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.flowOf import org.junit.Assert.assertEquals import org.junit.Assert.assertNull @@ -173,7 +171,7 @@ class AfternoteAuthorAndroidTest { ): AfternoteEditorViewModel = AfternoteEditorViewModel( savedStateHandle = savedStateHandle, - authorReceiverRepository = FakeAuthorReceiverRepository(), + userRepository = FakeUserRepository(), afternoteRepository = repository, memorialThumbnailUploadRepository = MemorialThumbnailUploadRepository { Result.success("https://cdn.test/thumb.jpg") }, resolveMemorialMediaForSave = @@ -203,22 +201,6 @@ class AfternoteAuthorAndroidTest { ) } -private class FakeAuthorReceiverRepository : AuthorReceiverRepository { - private val receivers = MutableStateFlow(listOf(AuthorReceiverEntry(7L, "김수신", "가족"))) - - override fun currentAuthorUserId(): Long = 1L - - override fun observeReceivers(): Flow> = receivers - - override fun currentReceivers(): List = receivers.value - - override suspend fun refreshReceivers(): Result = Result.success(Unit) - - override suspend fun clearReceivers() { - receivers.value = emptyList() - } -} - private class FakeAfternoteRepository : AfternoteRepository { val createSocialPayloads = mutableListOf() val createSocialResults = ArrayDeque>() diff --git a/app/src/androidTest/java/com/afternote/afternote_fe/AfternoteAuthorExtendedAndroidTest.kt b/app/src/androidTest/java/com/afternote/afternote_fe/AfternoteAuthorExtendedAndroidTest.kt index 147f4dcbb9..6632e2d186 100644 --- a/app/src/androidTest/java/com/afternote/afternote_fe/AfternoteAuthorExtendedAndroidTest.kt +++ b/app/src/androidTest/java/com/afternote/afternote_fe/AfternoteAuthorExtendedAndroidTest.kt @@ -46,7 +46,6 @@ import com.afternote.core.ui.theme.AfternoteTheme import com.afternote.feature.afternote.domain.AfternoteType import com.afternote.feature.afternote.domain.model.LeaveMessageBlock import com.afternote.feature.afternote.domain.model.author.AfternoteUpdatePayload -import com.afternote.feature.afternote.domain.model.author.AuthorReceiverEntry import com.afternote.feature.afternote.domain.model.author.CreateAccountPayload import com.afternote.feature.afternote.domain.model.author.CreateGalleryPayload import com.afternote.feature.afternote.domain.model.author.CreateMemorialPayload @@ -58,7 +57,6 @@ import com.afternote.feature.afternote.domain.model.author.ListItem import com.afternote.feature.afternote.domain.model.author.ProcessingMethod import com.afternote.feature.afternote.domain.model.author.ReceiverRefPayload import com.afternote.feature.afternote.domain.repository.author.AfternoteRepository -import com.afternote.feature.afternote.domain.repository.author.AuthorReceiverRepository import com.afternote.feature.afternote.domain.repository.author.MediaInput import com.afternote.feature.afternote.domain.repository.author.MemorialPhotoUploadRepository import com.afternote.feature.afternote.domain.repository.author.MemorialThumbnailUploadRepository @@ -83,7 +81,6 @@ import com.afternote.feature.afternote.presentation.author.home.AfternoteHomeEnt import com.afternote.feature.afternote.presentation.author.home.AfternoteHomeViewModel import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.launch import org.junit.Assert.assertEquals @@ -518,22 +515,6 @@ private class AdvancedAfternoteRepository( } } -private class ExtendedAuthorReceiverRepository : AuthorReceiverRepository { - private val receivers = MutableStateFlow(listOf(AuthorReceiverEntry(7L, "김수신", "가족"))) - - override fun currentAuthorUserId(): Long = 1L - - override fun observeReceivers(): Flow> = receivers - - override fun currentReceivers(): List = receivers.value - - override suspend fun refreshReceivers(): Result = Result.success(Unit) - - override suspend fun clearReceivers() { - receivers.value = emptyList() - } -} - private fun detailViewModel( repository: AdvancedAfternoteRepository, itemId: Long, @@ -551,7 +532,7 @@ private fun editorViewModel( ): AfternoteEditorViewModel = AfternoteEditorViewModel( savedStateHandle = SavedStateHandle(mapOf("itemId" to itemId.toString())), - authorReceiverRepository = ExtendedAuthorReceiverRepository(), + userRepository = FakeUserRepository(), afternoteRepository = repository, memorialThumbnailUploadRepository = MemorialThumbnailUploadRepository { diff --git a/app/src/androidTest/java/com/afternote/afternote_fe/AfternoteAuthorImplementedCoverageAndroidTest.kt b/app/src/androidTest/java/com/afternote/afternote_fe/AfternoteAuthorImplementedCoverageAndroidTest.kt index 4e4e17d051..775fa1dfef 100644 --- a/app/src/androidTest/java/com/afternote/afternote_fe/AfternoteAuthorImplementedCoverageAndroidTest.kt +++ b/app/src/androidTest/java/com/afternote/afternote_fe/AfternoteAuthorImplementedCoverageAndroidTest.kt @@ -12,12 +12,12 @@ import androidx.paging.PagingData import androidx.test.ext.junit.runners.AndroidJUnit4 import com.afternote.afternote_fe.test.FailureArtifactRule import com.afternote.afternote_fe.test.FakeErrorReporter +import com.afternote.afternote_fe.test.FakeUserRepository import com.afternote.core.ui.theme.AfternoteTheme import com.afternote.feature.afternote.domain.AfternoteType import com.afternote.feature.afternote.domain.model.LeaveMessageBlock import com.afternote.feature.afternote.domain.model.author.AfternoteAccountCredentials import com.afternote.feature.afternote.domain.model.author.AfternoteUpdatePayload -import com.afternote.feature.afternote.domain.model.author.AuthorReceiverEntry import com.afternote.feature.afternote.domain.model.author.CreateAccountPayload import com.afternote.feature.afternote.domain.model.author.CreateGalleryPayload import com.afternote.feature.afternote.domain.model.author.CreateMemorialPayload @@ -28,7 +28,6 @@ import com.afternote.feature.afternote.domain.model.author.MemorialVideoPayload import com.afternote.feature.afternote.domain.model.author.MemorialWritePayload import com.afternote.feature.afternote.domain.model.author.ProcessingMethod import com.afternote.feature.afternote.domain.repository.author.AfternoteRepository -import com.afternote.feature.afternote.domain.repository.author.AuthorReceiverRepository import com.afternote.feature.afternote.domain.repository.author.MediaInput import com.afternote.feature.afternote.domain.repository.author.MemorialPhotoUploadRepository import com.afternote.feature.afternote.domain.repository.author.MemorialThumbnailUploadRepository @@ -43,7 +42,6 @@ import com.afternote.feature.afternote.presentation.author.editor.message.Editor import com.afternote.feature.afternote.presentation.author.editor.model.EditorCategory import com.afternote.feature.afternote.presentation.author.editor.model.RegisterAfternotePayload import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.flowOf import org.junit.Assert.assertEquals import org.junit.Rule @@ -312,22 +310,6 @@ private class ImplementedCoverageAfternoteRepository : AfternoteRepository { override suspend fun delete(id: Long): Result = error("unexpected delete") } -private class ImplementedCoverageAuthorReceiverRepository : AuthorReceiverRepository { - private val receivers = MutableStateFlow(listOf(AuthorReceiverEntry(7L, "김수신", "가족"))) - - override fun currentAuthorUserId(): Long = 1L - - override fun observeReceivers(): Flow> = receivers - - override fun currentReceivers(): List = receivers.value - - override suspend fun refreshReceivers(): Result = Result.success(Unit) - - override suspend fun clearReceivers() { - receivers.value = emptyList() - } -} - private fun implementedCoverageViewModel( repository: ImplementedCoverageAfternoteRepository, savedStateHandle: SavedStateHandle = SavedStateHandle(), @@ -336,7 +318,7 @@ private fun implementedCoverageViewModel( ): AfternoteEditorViewModel = AfternoteEditorViewModel( savedStateHandle = savedStateHandle, - authorReceiverRepository = ImplementedCoverageAuthorReceiverRepository(), + userRepository = FakeUserRepository(), afternoteRepository = repository, memorialThumbnailUploadRepository = MemorialThumbnailUploadRepository { diff --git a/feature/afternote/data/src/main/kotlin/com/afternote/feature/afternote/data/di/AfternoteAuthorRepositoryModule.kt b/feature/afternote/data/src/main/kotlin/com/afternote/feature/afternote/data/di/AfternoteAuthorRepositoryModule.kt index 4a6a71755a..8c4dd1a8e8 100644 --- a/feature/afternote/data/src/main/kotlin/com/afternote/feature/afternote/data/di/AfternoteAuthorRepositoryModule.kt +++ b/feature/afternote/data/src/main/kotlin/com/afternote/feature/afternote/data/di/AfternoteAuthorRepositoryModule.kt @@ -1,13 +1,11 @@ package com.afternote.feature.afternote.data.di import com.afternote.feature.afternote.data.repositoryimpl.author.AfternoteRepositoryImpl -import com.afternote.feature.afternote.data.repositoryimpl.author.AuthorReceiverRepositoryImpl import com.afternote.feature.afternote.data.repositoryimpl.author.MemorialPhotoUploadRepositoryImpl import com.afternote.feature.afternote.data.repositoryimpl.author.MemorialThumbnailUploadRepositoryImpl import com.afternote.feature.afternote.data.repositoryimpl.author.MemorialVideoUploadRepositoryImpl import com.afternote.feature.afternote.data.repositoryimpl.author.MusicSearchRepositoryImpl import com.afternote.feature.afternote.domain.repository.author.AfternoteRepository -import com.afternote.feature.afternote.domain.repository.author.AuthorReceiverRepository import com.afternote.feature.afternote.domain.repository.author.MemorialPhotoUploadRepository import com.afternote.feature.afternote.domain.repository.author.MemorialThumbnailUploadRepository import com.afternote.feature.afternote.domain.repository.author.MemorialVideoUploadRepository @@ -26,11 +24,6 @@ interface AfternoteAuthorRepositoryModule { @Singleton fun bindAfternoteRepository(impl: AfternoteRepositoryImpl): AfternoteRepository - @Suppress("unused") - @Binds - @Singleton - fun bindAuthorReceiverRepository(impl: AuthorReceiverRepositoryImpl): AuthorReceiverRepository - @Suppress("unused") @Binds @Singleton diff --git a/feature/afternote/data/src/main/kotlin/com/afternote/feature/afternote/data/repositoryimpl/author/AuthorReceiverRepositoryImpl.kt b/feature/afternote/data/src/main/kotlin/com/afternote/feature/afternote/data/repositoryimpl/author/AuthorReceiverRepositoryImpl.kt deleted file mode 100644 index 9d54bf7d22..0000000000 --- a/feature/afternote/data/src/main/kotlin/com/afternote/feature/afternote/data/repositoryimpl/author/AuthorReceiverRepositoryImpl.kt +++ /dev/null @@ -1,45 +0,0 @@ -package com.afternote.feature.afternote.data.repositoryimpl.author - -import com.afternote.feature.afternote.domain.model.author.AuthorReceiverEntry -import com.afternote.feature.afternote.domain.repository.author.AuthorReceiverRepository -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.update -import javax.inject.Inject -import javax.inject.Singleton - -/** - * 작성자 수신자 목록 SSOT. API 연동 시 [refreshReceivers]에서 원격을 받아 갱신합니다. - */ -@Singleton -class AuthorReceiverRepositoryImpl - @Inject - constructor() : AuthorReceiverRepository { - private val receivers = - MutableStateFlow>(emptyList()) - - override fun observeReceivers(): Flow> = receivers.asStateFlow() - - override fun currentReceivers(): List = receivers.value - - // TODO: AuthDataSource 등 사용자 정보 의존성 주입 후 실제 값으로 교체 - override fun currentAuthorUserId(): Long? = null - - override suspend fun refreshReceivers(): Result { - val userId = - currentAuthorUserId() ?: run { - clearReceivers() - return Result.failure(IllegalStateException("No current author user id")) - } - return fetchReceiversForUser(userId) - .onSuccess { entries -> receivers.update { entries } } - .map { } - } - - override suspend fun clearReceivers() { - receivers.update { emptyList() } - } - - private suspend fun fetchReceiversForUser(userId: Long): Result> = Result.success(emptyList()) - } diff --git a/feature/afternote/domain/src/main/java/com/afternote/feature/afternote/domain/model/author/AuthorReceiverEntry.kt b/feature/afternote/domain/src/main/java/com/afternote/feature/afternote/domain/model/author/AuthorReceiverEntry.kt deleted file mode 100644 index 1851f135ab..0000000000 --- a/feature/afternote/domain/src/main/java/com/afternote/feature/afternote/domain/model/author/AuthorReceiverEntry.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.afternote.feature.afternote.domain.model.author - -data class AuthorReceiverEntry( - val receiverId: Long, - val name: String, - val relation: String, - val phone: String = "", -) diff --git a/feature/afternote/domain/src/main/java/com/afternote/feature/afternote/domain/repository/author/AuthorReceiverRepository.kt b/feature/afternote/domain/src/main/java/com/afternote/feature/afternote/domain/repository/author/AuthorReceiverRepository.kt deleted file mode 100644 index 4b444d57e7..0000000000 --- a/feature/afternote/domain/src/main/java/com/afternote/feature/afternote/domain/repository/author/AuthorReceiverRepository.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.afternote.feature.afternote.domain.repository.author - -import com.afternote.feature.afternote.domain.model.author.AuthorReceiverEntry -import kotlinx.coroutines.flow.Flow - -/** - * 작성자 본인의 userId 및 수신자 목록(이름/관계 조회용) 접근. - * - * 수신자 목록의 SSOT는 데이터 레이어 구현의 [observeReceivers] / [currentReceivers] 입니다. - * 로그아웃·계정 전환 시 [clearReceivers]로 비워 두면 UI 상태 오염을 막을 수 있습니다. - */ -interface AuthorReceiverRepository { - fun currentAuthorUserId(): Long? - - fun observeReceivers(): Flow> - - fun currentReceivers(): List - - suspend fun refreshReceivers(): Result - - suspend fun clearReceivers() -} diff --git a/feature/afternote/presentation/src/main/kotlin/com/afternote/feature/afternote/presentation/author/editor/AfternoteEditorViewModel.kt b/feature/afternote/presentation/src/main/kotlin/com/afternote/feature/afternote/presentation/author/editor/AfternoteEditorViewModel.kt index 890477520d..43bb7afea9 100644 --- a/feature/afternote/presentation/src/main/kotlin/com/afternote/feature/afternote/presentation/author/editor/AfternoteEditorViewModel.kt +++ b/feature/afternote/presentation/src/main/kotlin/com/afternote/feature/afternote/presentation/author/editor/AfternoteEditorViewModel.kt @@ -6,13 +6,13 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.navigation.toRoute import com.afternote.core.common.reporting.ErrorReporter +import com.afternote.core.common.result.runCatchingCancellable +import com.afternote.core.domain.repository.UserRepository import com.afternote.feature.afternote.domain.error.AfternoteAuthoringValidationException import com.afternote.feature.afternote.domain.error.AfternoteAuthoringValidationKind -import com.afternote.feature.afternote.domain.model.author.AuthorReceiverEntry import com.afternote.feature.afternote.domain.model.author.CreateAfternoteInput import com.afternote.feature.afternote.domain.model.author.SaveAfternoteCommand import com.afternote.feature.afternote.domain.repository.author.AfternoteRepository -import com.afternote.feature.afternote.domain.repository.author.AuthorReceiverRepository import com.afternote.feature.afternote.domain.repository.author.MediaInput import com.afternote.feature.afternote.domain.repository.author.MemorialThumbnailUploadRepository import com.afternote.feature.afternote.domain.usecase.editor.ResolveMemorialMediaForSaveUseCase @@ -194,7 +194,7 @@ class AfternoteEditorViewModel @Inject constructor( private val savedStateHandle: SavedStateHandle, - private val authorReceiverRepository: AuthorReceiverRepository, + private val userRepository: UserRepository, private val afternoteRepository: AfternoteRepository, private val memorialThumbnailUploadRepository: MemorialThumbnailUploadRepository, private val resolveMemorialMediaForSave: ResolveMemorialMediaForSaveUseCase, @@ -272,14 +272,6 @@ class AfternoteEditorViewModel ) = mutateForm { it.withProcessingMethodEdited(itemId = itemId, newText = newText) } init { - viewModelScope.launch { - authorReceiverRepository - .observeReceivers() - .map { it.toAfternoteEditorReceivers() } - .collect { mapped -> - internalState.update { it.copy(authorReceivers = mapped) } - } - } val editItemId = readEditItemId() if (editItemId == null) { savedStateHandle.remove(EDITOR_ORIGINAL_CATEGORY_FOR_API_KEY) @@ -307,9 +299,21 @@ class AfternoteEditorViewModel } } + /** + * 작성자가 등록한 수신자 전체를 받아 [InternalState.authorReceivers] 에 채운다. + * + * 신규 작성 진입 시 1회 호출된다. 폼이 비어 있으면 화면이 이 목록으로 수신자를 채우고 + * (`AfternoteNavGraphEditor` 의 `replaceReceiversIfEmpty`), 사용자는 불필요한 수신자를 지운다. + * 수정 진입은 상세 응답 prefill 이 지정 수신자를 채우므로 이 목록을 쓰지 않는다. + */ fun refreshAuthorReceivers() { viewModelScope.launch { - authorReceiverRepository.refreshReceivers() + runCatchingCancellable { userRepository.getReceivers() } + .onSuccess { receivers -> + internalState.update { it.copy(authorReceivers = receivers.toAfternoteEditorReceivers()) } + }.onFailure { e -> + errorReporter.recordAfternoteFailure(AfternoteFailureStage.AUTHOR_RECEIVER_LOAD, e) + } } } @@ -555,7 +559,8 @@ class AfternoteEditorViewModel } } - fun getReceiverById(id: Long): AuthorReceiverEntry? = authorReceiverRepository.currentReceivers().find { it.receiverId == id } + /** 수신자 선택 결과(id)를 폼에 넣기 위해 [refreshAuthorReceivers] 로 받아 둔 목록에서 이름·관계를 찾는다. */ + fun getReceiverById(id: Long): AfternoteEditorReceiver? = internalState.value.authorReceivers.find { it.id == id.toString() } private fun readOriginalCategoryForApiFromSavedState(): EditorCategory? = savedStateHandle diff --git a/feature/afternote/presentation/src/main/kotlin/com/afternote/feature/afternote/presentation/author/editor/mapper/AuthorReceiverMapping.kt b/feature/afternote/presentation/src/main/kotlin/com/afternote/feature/afternote/presentation/author/editor/mapper/AuthorReceiverMapping.kt deleted file mode 100644 index 4a07922bbc..0000000000 --- a/feature/afternote/presentation/src/main/kotlin/com/afternote/feature/afternote/presentation/author/editor/mapper/AuthorReceiverMapping.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.afternote.feature.afternote.presentation.author.editor.mapper - -import com.afternote.feature.afternote.domain.model.author.AuthorReceiverEntry -import com.afternote.feature.afternote.presentation.author.editor.receiver.model.AfternoteEditorReceiver - -internal fun AuthorReceiverEntry.toAfternoteEditorReceiver(): AfternoteEditorReceiver = - AfternoteEditorReceiver( - id = receiverId.toString(), - name = name, - label = relation, - ) - -/** [AuthorReceiverEntry] 목록을 Edit 화면용 [AfternoteEditorReceiver] 목록으로 변환합니다. */ -internal fun Iterable.toAfternoteEditorReceivers(): List = - map { it.toAfternoteEditorReceiver() } diff --git a/feature/afternote/presentation/src/main/kotlin/com/afternote/feature/afternote/presentation/author/editor/mapper/EditorReceiverMapping.kt b/feature/afternote/presentation/src/main/kotlin/com/afternote/feature/afternote/presentation/author/editor/mapper/EditorReceiverMapping.kt new file mode 100644 index 0000000000..fa5b93c717 --- /dev/null +++ b/feature/afternote/presentation/src/main/kotlin/com/afternote/feature/afternote/presentation/author/editor/mapper/EditorReceiverMapping.kt @@ -0,0 +1,14 @@ +package com.afternote.feature.afternote.presentation.author.editor.mapper + +import com.afternote.core.model.user.Receiver +import com.afternote.feature.afternote.presentation.author.editor.receiver.model.AfternoteEditorReceiver + +internal fun Receiver.toAfternoteEditorReceiver(): AfternoteEditorReceiver = + AfternoteEditorReceiver( + id = receiverId.toString(), + name = name, + label = relation, + ) + +/** 사용자가 등록한 수신자([Receiver]) 목록을 에디터 표시 모델로 변환합니다. */ +internal fun List.toAfternoteEditorReceivers(): List = map { it.toAfternoteEditorReceiver() } diff --git a/feature/afternote/presentation/src/main/kotlin/com/afternote/feature/afternote/presentation/author/navigation/AfternoteNavGraphEditor.kt b/feature/afternote/presentation/src/main/kotlin/com/afternote/feature/afternote/presentation/author/navigation/AfternoteNavGraphEditor.kt index 7caa8ff649..724c6b7544 100644 --- a/feature/afternote/presentation/src/main/kotlin/com/afternote/feature/afternote/presentation/author/navigation/AfternoteNavGraphEditor.kt +++ b/feature/afternote/presentation/src/main/kotlin/com/afternote/feature/afternote/presentation/author/navigation/AfternoteNavGraphEditor.kt @@ -33,7 +33,7 @@ internal fun tryApplyReceiverSelectionFromSavedState( val id = backStackEntry.savedStateHandle[SELECTED_RECEIVER_ID_KEY] as? Long ?: return backStackEntry.savedStateHandle.remove(SELECTED_RECEIVER_ID_KEY) val receiver = viewModel.getReceiverById(id) ?: return - state.addReceiverById(receiver.receiverId, receiver.name, receiver.relation) + state.addReceiverById(id, receiver.name, receiver.label) } internal fun buildOnRegisterClick( diff --git a/feature/afternote/presentation/src/main/kotlin/com/afternote/feature/afternote/presentation/reporting/AfternoteFailureReporting.kt b/feature/afternote/presentation/src/main/kotlin/com/afternote/feature/afternote/presentation/reporting/AfternoteFailureReporting.kt index 0a46a4dde8..439c24c91a 100644 --- a/feature/afternote/presentation/src/main/kotlin/com/afternote/feature/afternote/presentation/reporting/AfternoteFailureReporting.kt +++ b/feature/afternote/presentation/src/main/kotlin/com/afternote/feature/afternote/presentation/reporting/AfternoteFailureReporting.kt @@ -21,6 +21,15 @@ enum class AfternoteFailureStage( /** 수정 진입 시 기존 애프터노트 로드 — 여기서 깨지면 빈 폼으로 덮어쓸 위험이 있다. */ PREFILL_LOAD("prefill_load"), + /** + * 신규 작성 진입 시 작성자가 등록한 수신자 목록 조회. + * + * 실패해도 화면에는 수신자 자리가 비어 보일 뿐이라 계측하지 않으면 흔적이 남지 않는다. + * 수신자는 저장 필수값(서버 코드 475)이므로, 여기서 조용히 실패하면 사용자는 원인을 알 수 없는 + * 검증 오류만 만나게 된다. + */ + AUTHOR_RECEIVER_LOAD("author_receiver_load"), + MEMORIAL_THUMBNAIL_UPLOAD("memorial_thumbnail_upload"), /**