Skip to content

Commit 1803cce

Browse files
authored
Merge pull request #67 from Nexters/feature/image-upload-navigation-FD-261
[FD-261] 이미지 업로드 후 화면 이동
2 parents c611cfc + 1399bee commit 1803cce

7 files changed

Lines changed: 63 additions & 67 deletions

File tree

app/src/main/java/com/nexters/fooddiary/navigation/FoodDiaryNavHost.kt

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -313,10 +313,24 @@ fun FoodDiaryNavHost(
313313
)
314314

315315
imageScreen(
316-
onClose = {
317-
if (!navController.popBackStack()) {
316+
onClose = { selectedDateString ->
317+
val previousIsDetail =
318+
navController.previousBackStackEntry?.destination?.hasRoute(DetailRoute::class) == true
319+
if (previousIsDetail && !selectedDateString.isNullOrBlank()) {
320+
navController.popBackStack()
321+
navController.popBackStack()
322+
navController.navigate(DetailRoute(dateString = selectedDateString)) {
323+
launchSingleTop = true
324+
}
325+
} else if (!navController.popBackStack()) {
318326
onFinish()
319327
}
328+
},
329+
onUploadSuccess = { uploadedDate ->
330+
navController.popBackStack()
331+
navController.navigate(DetailRoute(dateString = uploadedDate.toString())) {
332+
launchSingleTop = true
333+
}
320334
}
321335
)
322336

data/src/main/java/com/nexters/fooddiary/data/remote/photo/model/response/BatchUploadResponse.kt

Lines changed: 8 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,46 +5,18 @@ import kotlinx.serialization.Serializable
55

66
@Serializable
77
data class BatchUploadResponse(
8-
@SerialName("results")
9-
val results: List<BatchUploadResultItem>
8+
@SerialName("diary_date")
9+
val diaryDate: String? = null,
10+
@SerialName("diaries")
11+
val diaries: List<BatchUploadDiaryItem> = emptyList(),
1012
)
1113

1214
@Serializable
13-
data class BatchUploadResultItem(
14-
@SerialName("analysis")
15-
val analysis: BatchUploadAnalysis?,
15+
data class BatchUploadDiaryItem(
1616
@SerialName("diary_id")
1717
val diaryId: Long,
18-
@SerialName("image_url")
19-
val imageUrl: String,
20-
@SerialName("photo_id")
21-
val photoId: Long,
18+
@SerialName("diary_status")
19+
val diaryStatus: String,
2220
@SerialName("time_type")
23-
val timeType: String
24-
)
25-
26-
@Serializable
27-
data class BatchUploadAnalysis(
28-
@SerialName("food_category")
29-
val foodCategory: String? = null,
30-
@SerialName("keywords")
31-
val keywords: List<String>? = null,
32-
@SerialName("menu_candidates")
33-
val menuCandidates: List<BatchUploadMenuCandidate>? = null,
34-
@SerialName("restaurant_candidates")
35-
val restaurantCandidates: List<BatchUploadRestaurantCandidate>? = null
36-
)
37-
38-
@Serializable
39-
data class BatchUploadMenuCandidate(
40-
@SerialName("name")
41-
val name: String
42-
)
43-
44-
@Serializable
45-
data class BatchUploadRestaurantCandidate(
46-
@SerialName("confidence")
47-
val confidence: Double,
48-
@SerialName("name")
49-
val name: String
21+
val timeType: String,
5022
)

data/src/main/java/com/nexters/fooddiary/data/repository/PhotoRepositoryImpl.kt

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import com.nexters.fooddiary.data.remote.photo.PhotoApi
99
import com.nexters.fooddiary.core.common.network.defaultMessage
1010
import com.nexters.fooddiary.core.common.resource.ResourceProvider
1111
import com.nexters.fooddiary.data.network.toNetworkError
12-
import com.nexters.fooddiary.data.remote.photo.model.response.BatchUploadResultItem
12+
import com.nexters.fooddiary.data.remote.photo.model.response.BatchUploadDiaryItem
1313
import com.nexters.fooddiary.domain.repository.PhotoRepository
1414
import dagger.hilt.android.qualifiers.ApplicationContext
1515
import kotlinx.coroutines.Dispatchers
@@ -66,7 +66,7 @@ internal class PhotoRepositoryImpl @Inject constructor(
6666
),
6767
photos = parts
6868
)
69-
recordPendingUploads(response.results, uploadDateStr)
69+
recordPendingUploads(response.diaries, uploadDateStr)
7070
Result.success(Unit)
7171
} catch (e: Exception) {
7272
recordUploadFailure(uploadDateStr, e)
@@ -94,14 +94,12 @@ internal class PhotoRepositoryImpl @Inject constructor(
9494
}
9595

9696
private suspend fun recordPendingUploads(
97-
results: List<BatchUploadResultItem>,
97+
diaries: List<BatchUploadDiaryItem>,
9898
uploadDateStr: String
9999
) {
100-
val entities = results.map { item ->
100+
val entities = diaries.map { item ->
101101
PhotoUploadEntity(
102-
photoId = item.photoId,
103102
diaryId = item.diaryId,
104-
imageUrl = item.imageUrl,
105103
timeType = item.timeType,
106104
uploadDate = uploadDateStr,
107105
status = UploadStatus.PENDING

presentation/image/src/main/java/com/nexters/fooddiary/presentation/image/ImagePickerScreen.kt

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.nexters.fooddiary.presentation.image
22

33
import android.net.Uri
4+
import androidx.activity.compose.BackHandler
45
import androidx.activity.compose.rememberLauncherForActivityResult
56
import androidx.activity.result.contract.ActivityResultContracts
67
import androidx.compose.foundation.Image
@@ -32,6 +33,7 @@ import androidx.compose.runtime.LaunchedEffect
3233
import androidx.compose.runtime.getValue
3334
import androidx.compose.runtime.mutableStateOf
3435
import androidx.compose.runtime.remember
36+
import androidx.compose.runtime.rememberUpdatedState
3537
import androidx.compose.runtime.setValue
3638
import com.airbnb.mvrx.compose.collectAsState
3739
import com.airbnb.mvrx.compose.mavericksViewModel
@@ -57,6 +59,7 @@ import com.nexters.fooddiary.core.ui.theme.Gray050
5759
import com.nexters.fooddiary.core.ui.theme.Gray400
5860
import com.nexters.fooddiary.core.ui.theme.PrimBase
5961
import com.nexters.fooddiary.core.ui.theme.SdBase
62+
import java.time.LocalDate
6063

6164
private const val PREVIEW_IMAGE_URL = "https://picsum.photos/200/200?random="
6265

@@ -93,9 +96,13 @@ fun ImagePickerScreen(
9396
modifier: Modifier = Modifier,
9497
selectedDateString: String? = null,
9598
onClose: () -> Unit,
99+
onUploadSuccess: (LocalDate) -> Unit = {},
96100
viewModel: ImagePickerViewModel = mavericksViewModel()
97101
) {
98102
val state by viewModel.collectAsState()
103+
val currentOnUploadSuccess by rememberUpdatedState(onUploadSuccess)
104+
105+
BackHandler(onBack = onClose)
99106

100107
LaunchedEffect(selectedDateString) {
101108
viewModel.loadPhotos(selectedDateString)
@@ -116,6 +123,13 @@ fun ImagePickerScreen(
116123
}
117124
}
118125

126+
LaunchedEffect(state.uploadSucceededDate) {
127+
state.uploadSucceededDate?.let { uploadedDate ->
128+
currentOnUploadSuccess(uploadedDate)
129+
viewModel.consumeUploadSuccess()
130+
}
131+
}
132+
119133
ImagePickerContent(
120134
modifier = modifier,
121135
foodImageUris = state.foodImageUris,
@@ -126,12 +140,7 @@ fun ImagePickerScreen(
126140
onImageClick = { uri -> viewModel.toggleImageSelection(uri) },
127141
onDeselectAll = { viewModel.clearSelection() },
128142
onDone = {
129-
viewModel.uploadImage { result ->
130-
when (result) {
131-
is UploadResult.Success -> onClose()
132-
is UploadResult.Failure -> { /* 실패 시 처리 없음 */ }
133-
}
134-
}
143+
viewModel.uploadImage()
135144
},
136145
onClose = onClose,
137146
onRequestPermission = { permissionLauncher.launch(requiredPermission) }

presentation/image/src/main/java/com/nexters/fooddiary/presentation/image/ImagePickerState.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ data class ImagePickerState(
1111
val hasPermission: Boolean = false,
1212
val selectedUris: Set<Uri> = emptySet(),
1313
val filterDate: LocalDate? = null,
14+
val uploadSucceededDate: LocalDate? = null,
1415
) : MavericksState

presentation/image/src/main/java/com/nexters/fooddiary/presentation/image/ImagePickerViewModel.kt

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,6 @@ import dagger.hilt.android.qualifiers.ApplicationContext
1616
import kotlinx.coroutines.launch
1717
import java.time.LocalDate
1818

19-
sealed class UploadResult {
20-
data object Success : UploadResult()
21-
data class Failure(val error: Throwable? = null) : UploadResult()
22-
}
23-
2419
internal fun nextSelectionAfterToggle(current: Set<Uri>, uri: Uri, maxCount: Int): Set<Uri> =
2520
if (current.contains(uri)) current - uri
2621
else if (current.size < maxCount) current + uri
@@ -52,6 +47,7 @@ class ImagePickerViewModel @AssistedInject constructor(
5247
allImageUris = emptyList(),
5348
foodImageUris = emptyList(),
5449
isLoading = true,
50+
uploadSucceededDate = null,
5551
)
5652
}
5753
withState { state ->
@@ -94,17 +90,20 @@ class ImagePickerViewModel @AssistedInject constructor(
9490
setState { copy(selectedUris = emptySet()) }
9591
}
9692

97-
fun uploadImage(onResult: (UploadResult) -> Unit) {
93+
fun uploadImage() {
9894
viewModelScope.launch {
9995
val urisToUpload = selectedUrisAsStrings()
10096
if (urisToUpload.isEmpty()) {
101-
onResult(UploadResult.Failure())
10297
return@launch
10398
}
104-
performUpload(urisToUpload, onResult)
99+
performUpload(urisToUpload)
105100
}
106101
}
107102

103+
fun consumeUploadSuccess() {
104+
setState { copy(uploadSucceededDate = null) }
105+
}
106+
108107
@AssistedFactory
109108
interface Factory : AssistedViewModelFactory<ImagePickerViewModel, ImagePickerState> {
110109
override fun create(state: ImagePickerState): ImagePickerViewModel
@@ -137,13 +136,14 @@ class ImagePickerViewModel @AssistedInject constructor(
137136
private suspend fun selectedUrisAsStrings(): List<String> =
138137
awaitState().selectedUris.map { it.toString() }
139138

140-
private suspend fun performUpload(
141-
urisToUpload: List<String>,
142-
onResult: (UploadResult) -> Unit
143-
) {
139+
private suspend fun performUpload(urisToUpload: List<String>) {
144140
val targetDate = awaitState().filterDate ?: LocalDate.now()
145141
batchUploadPhotosUseCase(targetDate, urisToUpload)
146-
.onSuccess { onResult(UploadResult.Success) }
147-
.onFailure { e -> onResult(UploadResult.Failure(e)) }
142+
.onSuccess {
143+
setState { copy(uploadSucceededDate = targetDate) }
144+
}
145+
.onFailure {
146+
// 실패 시 현재 화면 유지 (기존 동작)
147+
}
148148
}
149149
}

presentation/image/src/main/java/com/nexters/fooddiary/presentation/image/navigation/ImageNavigation.kt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,23 @@ import androidx.navigation.compose.composable
55
import androidx.navigation.toRoute
66
import com.nexters.fooddiary.presentation.image.ImagePickerScreen
77
import kotlinx.serialization.Serializable
8+
import java.time.LocalDate
89

910
@Serializable
1011
data class ImagePickerRoute(
1112
val dateString: String? = null
1213
)
1314

1415
fun NavGraphBuilder.imageScreen(
15-
onClose: () -> Unit
16+
onClose: (String?) -> Unit,
17+
onUploadSuccess: (LocalDate) -> Unit,
1618
) {
1719
composable<ImagePickerRoute> { backStackEntry ->
1820
val route = backStackEntry.toRoute<ImagePickerRoute>()
1921
ImagePickerScreen(
2022
selectedDateString = route.dateString,
21-
onClose = onClose,
23+
onClose = { onClose(route.dateString) },
24+
onUploadSuccess = onUploadSuccess,
2225
)
2326
}
2427
}
25-

0 commit comments

Comments
 (0)