From 8357aed6d8ec7e250f6e402a1891b57ea6635d42 Mon Sep 17 00:00:00 2001 From: Hyunkuk Date: Mon, 2 Oct 2023 16:14:57 +0900 Subject: [PATCH 1/7] =?UTF-8?q?[chore]=20capturable=20libs=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gradle/libs.versions.toml | 5 +++++ presenter/build.gradle.kts | 1 + 2 files changed, 6 insertions(+) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9ad32353..97e6f68b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -38,6 +38,8 @@ coroutine_test = "1.7.2" bottomsheetdialog = "1.2.1" extensions-compose-keyboard-state = "1.4.3" lottie = "6.1.0" +capture = "1.0.3" + [libraries] core-ktx = { module = "androidx.core:core-ktx", version.ref = "core_ktx" } @@ -123,6 +125,9 @@ compose-keyboard-state = { module = "tech.thdev:extensions-compose-keyboard-stat #lottie lottie = { module = "com.airbnb.android:lottie-compose", version.ref = "lottie" } +#capturable +capture = { module ="dev.shreyaspatil:capturable", version.ref = "capture" } + [plugins] android-application = { id = "com.android.application", version.ref = "android_gradle_plugin" } android-library = { id = "com.android.library", version.ref = "android_gradle_plugin" } diff --git a/presenter/build.gradle.kts b/presenter/build.gradle.kts index 8b9b2d70..a0238d3c 100644 --- a/presenter/build.gradle.kts +++ b/presenter/build.gradle.kts @@ -88,6 +88,7 @@ dependencies { implementation(libs.bottomsheetdialog) implementation(libs.compose.keyboard.state) implementation(libs.lottie) + implementation(libs.capture) } fun getApiKey(propertyKey: String): String { From bb297eac1a00cd7364790e3e8978b25582d77e96 Mon Sep 17 00:00:00 2001 From: Hyunkuk Date: Mon, 2 Oct 2023 16:31:13 +0900 Subject: [PATCH 2/7] =?UTF-8?q?[fix]=20toolbar=20=EB=82=B4=EB=B6=80?= =?UTF-8?q?=EC=9D=98=20=EC=83=89=EC=83=81=EC=9D=84=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=ED=95=98=EA=B8=B0=20=EC=9C=84=ED=95=9C=20param=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presenter/designsystem/component/toast/TwoTooSnackbar.kt | 2 +- .../designsystem/component/toolbar/TwoTooBackToolbar.kt | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/presenter/src/main/java/com/mashup/twotoo/presenter/designsystem/component/toast/TwoTooSnackbar.kt b/presenter/src/main/java/com/mashup/twotoo/presenter/designsystem/component/toast/TwoTooSnackbar.kt index e9f92b5d..405b2a90 100644 --- a/presenter/src/main/java/com/mashup/twotoo/presenter/designsystem/component/toast/TwoTooSnackbar.kt +++ b/presenter/src/main/java/com/mashup/twotoo/presenter/designsystem/component/toast/TwoTooSnackbar.kt @@ -35,7 +35,7 @@ fun SnackBarHost(modifier: Modifier, snackState: SnackbarHostState) { } @Composable -fun TwoTooSnackBarView( +private fun TwoTooSnackBarView( message: String, ) { Card( diff --git a/presenter/src/main/java/com/mashup/twotoo/presenter/designsystem/component/toolbar/TwoTooBackToolbar.kt b/presenter/src/main/java/com/mashup/twotoo/presenter/designsystem/component/toolbar/TwoTooBackToolbar.kt index 9b4112d2..3e29f23f 100644 --- a/presenter/src/main/java/com/mashup/twotoo/presenter/designsystem/component/toolbar/TwoTooBackToolbar.kt +++ b/presenter/src/main/java/com/mashup/twotoo/presenter/designsystem/component/toolbar/TwoTooBackToolbar.kt @@ -23,6 +23,7 @@ fun TwoTooBackToolbar( titleModifier: Modifier = Modifier, @DrawableRes backIconId: Int = R.drawable.ic_back, color: Color = Color.Transparent, + contentColor: Color? = null, actionIconButton: @Composable () -> Unit = {}, ) { TopAppBar( @@ -34,7 +35,7 @@ fun TwoTooBackToolbar( Text( text = title, modifier = titleModifier, - color = TwoTooTheme.color.mainBrown, + color = contentColor ?: TwoTooTheme.color.mainBrown, style = TwoTooTheme.typography.headLineNormal24, textAlign = TextAlign.Center, ) @@ -48,6 +49,7 @@ fun TwoTooBackToolbar( Icon( painter = painterResource(id = backIconId), contentDescription = null, + tint = contentColor ?: LocalContentColor.current, ) } } From 06cbe62f595f7a12a482739dc5afe104e5a15f7d Mon Sep 17 00:00:00 2001 From: Hyunkuk Date: Mon, 2 Oct 2023 16:31:54 +0900 Subject: [PATCH 3/7] =?UTF-8?q?[feat]=20image=20detail=20screen=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 --- .../history/detailImage/DetailImageScreen.kt | 220 ++++++++++++++++++ .../history/navigation/HistoryNavigation.kt | 28 ++- .../navigation/TopLevelDestination.kt | 1 + 3 files changed, 248 insertions(+), 1 deletion(-) create mode 100644 presenter/src/main/java/com/mashup/twotoo/presenter/history/detailImage/DetailImageScreen.kt diff --git a/presenter/src/main/java/com/mashup/twotoo/presenter/history/detailImage/DetailImageScreen.kt b/presenter/src/main/java/com/mashup/twotoo/presenter/history/detailImage/DetailImageScreen.kt new file mode 100644 index 00000000..37885a08 --- /dev/null +++ b/presenter/src/main/java/com/mashup/twotoo/presenter/history/detailImage/DetailImageScreen.kt @@ -0,0 +1,220 @@ +package com.mashup.twotoo.presenter.history.detailImage + +import android.graphics.Bitmap +import androidx.compose.foundation.background +import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.foundation.gestures.detectTransformGestures +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.material.SnackbarHostState +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.asAndroidBitmap +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.constraintlayout.compose.ConstraintLayout +import androidx.constraintlayout.compose.Dimension +import com.mashup.twotoo.presenter.R +import com.mashup.twotoo.presenter.designsystem.component.TwoTooImageView +import com.mashup.twotoo.presenter.designsystem.component.toast.SnackBarHost +import com.mashup.twotoo.presenter.designsystem.component.toolbar.TwoTooBackToolbar +import com.mashup.twotoo.presenter.designsystem.theme.TwoTooTheme +import com.mashup.twotoo.presenter.util.saveBitmapToStorage +import dev.shreyaspatil.capturable.Capturable +import dev.shreyaspatil.capturable.controller.rememberCaptureController +import kotlinx.coroutines.launch + +@Composable +fun DetailImageRoute( + url: String, + onClickBackButton: () -> Unit = {}, +) { + DetailImageScreen( + modifier = Modifier.fillMaxSize(), + url = url, + onClickBackButton = onClickBackButton, + ) +} + +@Composable +fun DetailImageScreen( + url: String, + modifier: Modifier = Modifier, + onClickBackButton: () -> Unit = {}, +) { + var scale by remember { mutableFloatStateOf(1f) } + var offsetX by remember { mutableFloatStateOf(0f) } + var offsetY by remember { mutableFloatStateOf(0f) } + var settingMenuVisibility by remember { mutableStateOf(false) } + val captureController = rememberCaptureController() + val context = LocalContext.current + + val screenWidth = LocalConfiguration.current.screenWidthDp.toFloat() + val snackbarHostState: SnackbarHostState = remember { SnackbarHostState() } + val successToastText = stringResource(id = R.string.saveImageSuccessToastText) + val errorToastText = stringResource(id = R.string.saveImageErrorToastText) + val coroutineScope = rememberCoroutineScope() + + ConstraintLayout( + modifier = modifier + .background(color = Color.Black) + .pointerInput(Unit) { + detectTransformGestures( + onGesture = { _, pan, gestureZoom, _ -> + scale = (scale * gestureZoom).coerceIn(1f, 5f) + if (scale > 1) { + offsetX += pan.x * scale + offsetY += pan.y * scale + offsetX = offsetX.coerceIn(-screenWidth, screenWidth) + offsetY = offsetY.coerceIn(-screenWidth, screenWidth) + } else { + offsetX = 0f + offsetY = 0f + } + }, + ) + } + .pointerInput(Unit) { + detectTapGestures( + onDoubleTap = { + scale = 1f + offsetX = 0f + offsetY = 0f + }, + ) + }, + ) { + val (topBar, image, captureWrapper, dropDownMenu, snackBar) = createRefs() + TwoTooBackToolbar( + modifier = Modifier.constrainAs(topBar) { + top.linkTo(parent.top) + start.linkTo(parent.start) + end.linkTo(parent.end) + }, + onClickBackIcon = { + onClickBackButton() + }, + contentColor = Color.White, + ) { + IconButton( + onClick = { + settingMenuVisibility = true + }, + ) { + Icon( + painter = painterResource(id = R.drawable.ic_more), + contentDescription = null, + tint = Color.White, + ) + } + DropdownMenu( + modifier = Modifier + .wrapContentHeight() + .constrainAs(dropDownMenu) { + top.linkTo(topBar.bottom) + end.linkTo(parent.end) + }, + expanded = settingMenuVisibility, + onDismissRequest = { settingMenuVisibility = false }, + ) { + DropdownMenuItem(text = { + Text(text = stringResource(id = R.string.saveImage)) + }, onClick = { + captureController.capture(Bitmap.Config.ARGB_8888) + }) + } + } + + Capturable( + modifier = Modifier.constrainAs(captureWrapper) { + centerHorizontallyTo(parent) + centerVerticallyTo(parent) + height = Dimension.ratio("1:1") + }, + controller = captureController, + onCaptured = { bitmap, error -> + if (bitmap != null) { + saveBitmapToStorage( + context = context, + bitmap = bitmap.asAndroidBitmap(), + title = url, + onSuccessToSave = { + coroutineScope.launch { + snackbarHostState.showSnackbar(successToastText) + } + settingMenuVisibility = false + }, + onFailToSave = { + coroutineScope.launch { + snackbarHostState.showSnackbar(errorToastText) + } + settingMenuVisibility = false + }, + ) + } + if (error != null) { + coroutineScope.launch { + snackbarHostState.showSnackbar(errorToastText) + } + settingMenuVisibility = false + } + }, + ) { + TwoTooImageView( + modifier = Modifier + .constrainAs(image) { + linkTo( + start = parent.start, + end = parent.end, + top = parent.top, + bottom = parent.bottom, + ) + } + .graphicsLayer { + scaleX = scale + scaleY = scale + translationX = offsetX + translationY = offsetY + }, + model = url, + contentScale = ContentScale.Fit, + previewPlaceholder = R.drawable.empty_image_color_placeholder, + ) + } + SnackBarHost( + modifier = Modifier.constrainAs(snackBar) { + start.linkTo(parent.start) + end.linkTo(parent.end) + bottom.linkTo(parent.bottom, margin = 30.dp) + }, + snackState = snackbarHostState, + ) + } +} + +@Preview +@Composable +private fun PreviewDetailImageScreen() { + TwoTooTheme { + DetailImageRoute(url = "") + } +} diff --git a/presenter/src/main/java/com/mashup/twotoo/presenter/history/navigation/HistoryNavigation.kt b/presenter/src/main/java/com/mashup/twotoo/presenter/history/navigation/HistoryNavigation.kt index b997d8ff..88ee0dd4 100644 --- a/presenter/src/main/java/com/mashup/twotoo/presenter/history/navigation/HistoryNavigation.kt +++ b/presenter/src/main/java/com/mashup/twotoo/presenter/history/navigation/HistoryNavigation.kt @@ -1,6 +1,8 @@ package com.mashup.twotoo.presenter.history.navigation import android.annotation.SuppressLint +import android.util.Log +import androidx.compose.runtime.SideEffect import androidx.compose.runtime.remember import androidx.lifecycle.ViewModelStoreOwner import androidx.navigation.* @@ -8,11 +10,13 @@ import androidx.navigation.compose.composable import com.mashup.twotoo.presenter.di.daggerViewModel import com.mashup.twotoo.presenter.history.HistoryRoute import com.mashup.twotoo.presenter.history.datail.HistoryDetailRoute +import com.mashup.twotoo.presenter.history.detailImage.DetailImageRoute import com.mashup.twotoo.presenter.history.di.HistoryComponentProvider import com.mashup.twotoo.presenter.home.model.HomeGoalAchievePartnerAndMeUiModel import com.mashup.twotoo.presenter.navigation.NavigationRoute import com.mashup.twotoo.presenter.util.MoshiUtils import com.mashup.twotoo.presenter.util.componentProvider +import com.mashup.twotoo.presenter.util.toIncodeUrl fun NavController.navigateToHistory(challengeNo: Int, homeGoalAchievePartnerAndMeUiModel: HomeGoalAchievePartnerAndMeUiModel? = null) { if (homeGoalAchievePartnerAndMeUiModel != null) { @@ -27,6 +31,10 @@ private fun NavController.navigateToHistoryDetail(commitNo: Int) { this.navigate(route = "${NavigationRoute.HistoryGraph.HistoryDetailScreen.route}/$commitNo") } +private fun NavController.navigateToDetailImageScreen(url: String) { + this.navigate(route = "${NavigationRoute.HistoryGraph.DetailImageScreen.route}/${url.toIncodeUrl()}") +} + @SuppressLint("UnrememberedGetBackStackEntry") fun NavGraphBuilder.historyGraph(navController: NavController) { navigation(startDestination = "${NavigationRoute.HistoryGraph.HistoryScreen.route}/{challengeNo}?progress={progress}", route = NavigationRoute.HistoryGraph.route) { @@ -84,7 +92,25 @@ fun NavGraphBuilder.historyGraph(navController: NavController) { HistoryDetailRoute( commitNo = commitNo, historyViewModel = historyViewModel, - onClickBackButton = { navController.popBackStack() }, + onClickBackButton = navController::popBackStack, + onClickImage = navController::navigateToDetailImageScreen, + ) + } + + composable( + route = "${NavigationRoute.HistoryGraph.DetailImageScreen.route}/{url}", + arguments = listOf( + navArgument("url") { type = NavType.StringType }, + ), + ) { navBackStackEntry: NavBackStackEntry -> + + val url = navBackStackEntry.arguments?.getString("url") ?: "" + SideEffect { + Log.d("Exception", "historyGraph: $url") + } + DetailImageRoute( + url = url, + onClickBackButton = navController::popBackStack, ) } } diff --git a/presenter/src/main/java/com/mashup/twotoo/presenter/navigation/TopLevelDestination.kt b/presenter/src/main/java/com/mashup/twotoo/presenter/navigation/TopLevelDestination.kt index 5f0b45bb..b99e42cd 100644 --- a/presenter/src/main/java/com/mashup/twotoo/presenter/navigation/TopLevelDestination.kt +++ b/presenter/src/main/java/com/mashup/twotoo/presenter/navigation/TopLevelDestination.kt @@ -71,6 +71,7 @@ sealed class NavigationRoute(val route: String) { object HistoryGraph : NavigationRoute("history") { object HistoryScreen : NavigationRoute("history/screen") object HistoryDetailScreen : NavigationRoute("detail/screen") + object DetailImageScreen : NavigationRoute("detail/image/screen") } object GuideGraph : NavigationRoute("guide") { From c2584c806a5e62f229649553f27f107dfae5c2d4 Mon Sep 17 00:00:00 2001 From: Hyunkuk Date: Mon, 2 Oct 2023 16:32:30 +0900 Subject: [PATCH 4/7] =?UTF-8?q?[feat]=20navigation=20string=20route?= =?UTF-8?q?=EC=9D=98=20url=EB=A5=BC=20=EC=9D=B8=EC=BD=94=EB=94=A9=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=ED=95=A8=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/mashup/twotoo/presenter/util/IncodingImageUrl.kt | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 presenter/src/main/java/com/mashup/twotoo/presenter/util/IncodingImageUrl.kt diff --git a/presenter/src/main/java/com/mashup/twotoo/presenter/util/IncodingImageUrl.kt b/presenter/src/main/java/com/mashup/twotoo/presenter/util/IncodingImageUrl.kt new file mode 100644 index 00000000..107199a3 --- /dev/null +++ b/presenter/src/main/java/com/mashup/twotoo/presenter/util/IncodingImageUrl.kt @@ -0,0 +1,8 @@ +package com.mashup.twotoo.presenter.util + +import java.net.URLEncoder +import java.nio.charset.StandardCharsets + +fun String.toIncodeUrl(): String { + return URLEncoder.encode(this, StandardCharsets.UTF_8.toString()) +} From 5b203394f74b0978bf1836f0f8a7488da9350d12 Mon Sep 17 00:00:00 2001 From: Hyunkuk Date: Mon, 2 Oct 2023 16:32:44 +0900 Subject: [PATCH 5/7] =?UTF-8?q?[feat]=20=EB=82=B4=EB=B6=80=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=EC=86=8C=EC=97=90=20=EC=A0=80=EC=9E=A5=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mashup/twotoo/presenter/util/SaveImage.kt | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 presenter/src/main/java/com/mashup/twotoo/presenter/util/SaveImage.kt diff --git a/presenter/src/main/java/com/mashup/twotoo/presenter/util/SaveImage.kt b/presenter/src/main/java/com/mashup/twotoo/presenter/util/SaveImage.kt new file mode 100644 index 00000000..c9bd09b0 --- /dev/null +++ b/presenter/src/main/java/com/mashup/twotoo/presenter/util/SaveImage.kt @@ -0,0 +1,50 @@ +package com.mashup.twotoo.presenter.util + +import android.content.ContentValues +import android.content.Context +import android.graphics.Bitmap +import android.net.Uri +import android.os.Build +import android.os.Environment +import android.provider.MediaStore +import java.io.File +import java.io.FileOutputStream +import java.io.OutputStream + +fun saveBitmapToStorage( + context: Context, + bitmap: Bitmap, + title: String, + onSuccessToSave: () -> Unit, + onFailToSave: () -> Unit, + mimeType: String = "image/jpeg", + directory: String = Environment.DIRECTORY_PICTURES, + mediaContentUri: Uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI, +) { + val imageOutStream: OutputStream? + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + val values = ContentValues().apply { + put(MediaStore.Images.Media.DISPLAY_NAME, title) + put(MediaStore.Images.Media.MIME_TYPE, mimeType) + put(MediaStore.Images.Media.RELATIVE_PATH, directory) + } + + context.contentResolver.run { + val uri = this.insert(mediaContentUri, values) ?: run { + onFailToSave() + return + } + imageOutStream = openOutputStream(uri) ?: run { + onFailToSave() + return + } + } + } else { + val imagePath = Environment.getExternalStoragePublicDirectory(directory).absolutePath + val image = File(imagePath, title) + imageOutStream = FileOutputStream(image) + } + imageOutStream.use { bitmap.compress(Bitmap.CompressFormat.JPEG, 100, it) }.run { + onSuccessToSave() + } +} From 1f7a07d9f5e7dce104279700a04960598c4c36de Mon Sep 17 00:00:00 2001 From: Hyunkuk Date: Mon, 2 Oct 2023 16:32:59 +0900 Subject: [PATCH 6/7] =?UTF-8?q?[feat]=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=ED=81=B4=EB=A6=AD=20=EC=95=A1=EC=85=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presenter/history/datail/HistoryDetailScreen.kt | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/presenter/src/main/java/com/mashup/twotoo/presenter/history/datail/HistoryDetailScreen.kt b/presenter/src/main/java/com/mashup/twotoo/presenter/history/datail/HistoryDetailScreen.kt index fb73ecbf..f7dedc40 100644 --- a/presenter/src/main/java/com/mashup/twotoo/presenter/history/datail/HistoryDetailScreen.kt +++ b/presenter/src/main/java/com/mashup/twotoo/presenter/history/datail/HistoryDetailScreen.kt @@ -36,6 +36,7 @@ fun HistoryDetailRoute( commitNo: Int, historyViewModel: HistoryViewModel, onClickBackButton: () -> Unit, + onClickImage: (String) -> Unit, ) { Log.i("HistoryDetailRoute", "commitNo = $commitNo") val lifecycleOwner = LocalLifecycleOwner.current @@ -46,15 +47,17 @@ fun HistoryDetailRoute( } val state by historyViewModel.collectAsState() HistoryDetailScreen( - onClickBackButton = onClickBackButton, historyDetailInfoUiModel = state.historyDetailInfoUiModel, + onClickBackButton = onClickBackButton, + onClickImage = onClickImage, ) } @Composable fun HistoryDetailScreen( - onClickBackButton: () -> Unit, historyDetailInfoUiModel: HistoryDetailInfoUiModel, + onClickBackButton: () -> Unit = {}, + onClickImage: (String) -> Unit = {}, ) { val scrollableState = rememberScrollState() @@ -98,7 +101,10 @@ fun HistoryDetailScreen( .fillMaxWidth() .padding(vertical = 24.dp) .aspectRatio(1f) - .clip(TwoTooTheme.shape.extraSmall), + .clip(TwoTooTheme.shape.extraSmall).clickable { + onClickImage(historyDetailInfoUiModel.infoUiModel.photoUrl) + }, + ) Text( text = historyDetailInfoUiModel.challengeName, From 89865ff659003ca0cafcacd1a955c1bef9f0d119 Mon Sep 17 00:00:00 2001 From: Hyunkuk Date: Mon, 2 Oct 2023 16:33:28 +0900 Subject: [PATCH 7/7] =?UTF-8?q?[feat]=20imageDetail=20=ED=86=A0=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EB=AC=B8=EA=B5=AC=20=EB=B0=8F=20=EC=98=B5=EC=85=98?= =?UTF-8?q?=20=EB=A9=94=EB=89=B4=20=ED=85=8D=EC=8A=A4=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- presenter/src/main/res/values/string.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/presenter/src/main/res/values/string.xml b/presenter/src/main/res/values/string.xml index ebb3d55c..775b58e9 100644 --- a/presenter/src/main/res/values/string.xml +++ b/presenter/src/main/res/values/string.xml @@ -144,6 +144,9 @@ %s의 기록 입력 시간 : %s %s의 칭찬 한마디 + 저장하기 + 사진 저장에 성공했습니다 + 사진 저장에 실패했습니다 확인