From 35f1960a69ae04eb04a1f792fdc392a1d871cb7f Mon Sep 17 00:00:00 2001 From: kwakjoohyeong Date: Wed, 20 Mar 2024 19:04:55 +0900 Subject: [PATCH 01/35] =?UTF-8?q?feat:=20MyPage=20=EB=B7=B0=20=EA=B5=AC?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/withpeace/withpeace/WithpeaceApp.kt | 5 +- .../withpeace/withpeace/navigation/NavHost.kt | 18 +- .../core/designsystem/ui/TitleBar.kt | 29 +++ feature/mypage/build.gradle.kts | 2 + .../withpeace/feature/mypage/MyPageScreen.kt | 199 +++++++++++++++++- .../mypage/navigation/MyPageNavigation.kt | 10 +- .../main/res/drawable/ic_default_profile.xml | 24 +++ .../mypage/src/main/res/values/strings.xml | 10 + 8 files changed, 282 insertions(+), 15 deletions(-) create mode 100644 core/designsystem/src/main/java/com/withpeace/withpeace/core/designsystem/ui/TitleBar.kt create mode 100644 feature/mypage/src/main/res/drawable/ic_default_profile.xml create mode 100644 feature/mypage/src/main/res/values/strings.xml diff --git a/app/src/main/java/com/withpeace/withpeace/WithpeaceApp.kt b/app/src/main/java/com/withpeace/withpeace/WithpeaceApp.kt index 9299dd80..8fab21da 100644 --- a/app/src/main/java/com/withpeace/withpeace/WithpeaceApp.kt +++ b/app/src/main/java/com/withpeace/withpeace/WithpeaceApp.kt @@ -1,10 +1,8 @@ package com.withpeace.withpeace -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.safeDrawingPadding import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState @@ -46,7 +44,8 @@ fun WithpeaceApp( } } }, - modifier = Modifier.fillMaxSize().safeDrawingPadding(), + modifier = Modifier + .fillMaxSize(), snackbarHost = { SnackbarHost(snackBarHostState) }, ) { innerPadding -> WithpeaceNavHost( diff --git a/app/src/main/java/com/withpeace/withpeace/navigation/NavHost.kt b/app/src/main/java/com/withpeace/withpeace/navigation/NavHost.kt index 9d8bb973..a54668c9 100644 --- a/app/src/main/java/com/withpeace/withpeace/navigation/NavHost.kt +++ b/app/src/main/java/com/withpeace/withpeace/navigation/NavHost.kt @@ -1,21 +1,16 @@ package com.withpeace.withpeace.navigation -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost -import androidx.navigation.compose.rememberNavController -import androidx.navigation.navOptions -import com.withpeace.withpeace.feature.home.navigation.homeNavGraph -import com.withpeace.withpeace.feature.mypage.navigation.myPageNavGraph -import com.withpeace.withpeace.feature.post.navigation.postNavGraph import com.withpeace.withpeace.feature.gallery.navigation.galleryNavGraph import com.withpeace.withpeace.feature.gallery.navigation.navigateToGallery +import com.withpeace.withpeace.feature.home.navigation.homeNavGraph import com.withpeace.withpeace.feature.login.navigation.LOGIN_ROUTE import com.withpeace.withpeace.feature.login.navigation.loginNavGraph +import com.withpeace.withpeace.feature.mypage.navigation.myPageNavGraph +import com.withpeace.withpeace.feature.post.navigation.postNavGraph import com.withpeace.withpeace.feature.registerpost.navigation.IMAGE_LIST_ARGUMENT import com.withpeace.withpeace.feature.registerpost.navigation.registerPostNavGraph @@ -60,6 +55,11 @@ fun WithpeaceNavHost( ) homeNavGraph(onShowSnackBar) postNavGraph(onShowSnackBar) - myPageNavGraph(onShowSnackBar) + myPageNavGraph( + onShowSnackBar = onShowSnackBar, + onEditProfile = {}, + onLogoutClick = {}, + onWithdrawClick = {}, + ) } } diff --git a/core/designsystem/src/main/java/com/withpeace/withpeace/core/designsystem/ui/TitleBar.kt b/core/designsystem/src/main/java/com/withpeace/withpeace/core/designsystem/ui/TitleBar.kt new file mode 100644 index 00000000..9cadc15b --- /dev/null +++ b/core/designsystem/src/main/java/com/withpeace/withpeace/core/designsystem/ui/TitleBar.kt @@ -0,0 +1,29 @@ +package com.withpeace.withpeace.core.designsystem.ui + +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +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.unit.dp +import com.withpeace.withpeace.core.designsystem.theme.WithpeaceTheme + +@Composable +fun TitleBar(title: String, modifier: Modifier = Modifier) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = modifier + .fillMaxWidth() + .height(56.dp) + .padding(start = 24.dp), + ) { + Text( + text = title, + style = WithpeaceTheme.typography.title1, + color = WithpeaceTheme.colors.SystemBlack, + ) + } +} \ No newline at end of file diff --git a/feature/mypage/build.gradle.kts b/feature/mypage/build.gradle.kts index 6957f060..61f160ca 100644 --- a/feature/mypage/build.gradle.kts +++ b/feature/mypage/build.gradle.kts @@ -7,4 +7,6 @@ android { } dependencies { + implementation(libs.skydoves.landscapist.glide) + implementation(libs.skydoves.landscapist.bom) } \ No newline at end of file diff --git a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageScreen.kt b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageScreen.kt index f593e23f..71600f6e 100644 --- a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageScreen.kt +++ b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageScreen.kt @@ -1,14 +1,209 @@ package com.withpeace.withpeace.feature.mypage +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.Image +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.Divider +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +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 com.skydoves.landscapist.glide.GlideImage +import com.withpeace.withpeace.core.designsystem.theme.WithpeaceTheme +import com.withpeace.withpeace.core.designsystem.ui.TitleBar @Composable fun MyPageRoute( onShowSnackBar: (message: String) -> Unit = {}, + onEditProfile: () -> Unit, + onLogoutClick: () -> Unit, + onWithdrawClick: () -> Unit, ) { - MyPageScreen() + MyPageScreen( + onEditProfile = onEditProfile, + onLogoutClick = onLogoutClick, + onWithdrawClick = onWithdrawClick, + ) } @Composable -fun MyPageScreen() { +fun MyPageScreen( + modifier: Modifier = Modifier, + onEditProfile: () -> Unit, + onLogoutClick: () -> Unit, + onWithdrawClick: () -> Unit, +) { + Column( + modifier, + ) { + TitleBar(title = stringResource(R.string.my_page)) + Column(modifier = modifier.padding(horizontal = WithpeaceTheme.padding.BasicHorizontalPadding)) { + Row( + modifier = modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + val imageModifier = modifier + .size(54.dp) + .border( + BorderStroke(0.dp, Color.Transparent), + shape = CircleShape, + ) + GlideImage( + modifier = imageModifier, + imageModel = { "" }, + failure = { + Image( + painterResource(id = R.drawable.ic_default_profile), + modifier = imageModifier, + contentDescription = "", + ) + }, + ) + Text( + style = WithpeaceTheme.typography.body, + text = "닉네임닉네임", + modifier = modifier.padding(start = 8.dp), + ) + } + TextButton( + onClick = { onEditProfile() }, + ) { + Text( + color = WithpeaceTheme.colors.MainPink, + text = stringResource(R.string.edit_profile), + style = WithpeaceTheme.typography.caption, + ) + } + } + } + Spacer(modifier = modifier.height(16.dp)) + Divider( + color = WithpeaceTheme.colors.SystemGray3, + modifier = modifier + .fillMaxWidth() + .height(4.dp), + ) + MyPageSections( + modifier = modifier, + onLogoutClick = onLogoutClick, + onWithdrawClick = onWithdrawClick, + ) + } +} + +@Composable +fun MyPageSections( + modifier: Modifier, + onLogoutClick: () -> Unit, + onWithdrawClick: () -> Unit, +) { + Column(modifier = modifier.padding(horizontal = WithpeaceTheme.padding.BasicHorizontalPadding)) { + AccountSection(modifier) + Divider( + modifier = modifier + .fillMaxWidth() + .height(1.dp), + color = WithpeaceTheme.colors.SystemGray3, + ) + EtcSection(modifier, onLogoutClick = onLogoutClick, onWithdrawClick = onWithdrawClick) + } +} + +@Composable +private fun AccountSection(modifier: Modifier) { + Section(title = stringResource(R.string.account)) { + Spacer(modifier = modifier.height(16.dp)) + Row(modifier = modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + Text( + text = stringResource(R.string.connected_account), + style = WithpeaceTheme.typography.body, + color = WithpeaceTheme.colors.SystemBlack, + ) + Text( + text = "abc@gmail.com", + style = WithpeaceTheme.typography.caption, + color = WithpeaceTheme.colors.SystemGray2, + ) + } + } +} + +@Composable +private fun EtcSection( + modifier: Modifier, + onLogoutClick: () -> Unit, + onWithdrawClick: () -> Unit, +) { + Section(title = stringResource(R.string.etc)) { + Spacer(modifier = modifier.height(8.dp)) + Text( + text = stringResource(R.string.logout), + style = WithpeaceTheme.typography.body, + color = WithpeaceTheme.colors.SystemBlack, + modifier = modifier + .fillMaxWidth() + .clickable { + onLogoutClick() + } + .padding(vertical = 8.dp), + ) + Text( + text = stringResource(R.string.withdraw), + style = WithpeaceTheme.typography.body, + color = WithpeaceTheme.colors.SystemBlack, + modifier = modifier + .fillMaxWidth() + .clickable { + onWithdrawClick() + } + .padding(vertical = 8.dp), + ) + } +} + +@Composable +private fun Section( + title: String, + modifier: Modifier = Modifier, + content: @Composable () -> Unit = {}, +) { + Column { + Spacer(modifier = modifier.height(16.dp)) + Text(text = title, style = WithpeaceTheme.typography.caption, color = Color(0xFF858585)) + content() + Spacer(modifier = modifier.height(16.dp)) + } + +} + +@Preview +@Composable +fun MyPagePreview() { + WithpeaceTheme { + MyPageScreen( + onEditProfile = { + }, + modifier = Modifier, + onLogoutClick = {}, + onWithdrawClick = {}, + ) + } } \ No newline at end of file diff --git a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/navigation/MyPageNavigation.kt b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/navigation/MyPageNavigation.kt index 8b7854b2..bbef4cce 100644 --- a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/navigation/MyPageNavigation.kt +++ b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/navigation/MyPageNavigation.kt @@ -14,8 +14,16 @@ fun NavController.navigateMyPage(navOptions: NavOptions? = null) { fun NavGraphBuilder.myPageNavGraph( onShowSnackBar: (message: String) -> Unit, + onEditProfile: () -> Unit, + onLogoutClick: () -> Unit, + onWithdrawClick: () -> Unit, ) { composable(route = MY_PAGE_ROUTE) { - MyPageRoute(onShowSnackBar = onShowSnackBar) + MyPageRoute( + onShowSnackBar = onShowSnackBar, + onEditProfile = onEditProfile, + onLogoutClick = onLogoutClick, + onWithdrawClick = onWithdrawClick, + ) } } \ No newline at end of file diff --git a/feature/mypage/src/main/res/drawable/ic_default_profile.xml b/feature/mypage/src/main/res/drawable/ic_default_profile.xml new file mode 100644 index 00000000..da35818c --- /dev/null +++ b/feature/mypage/src/main/res/drawable/ic_default_profile.xml @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/feature/mypage/src/main/res/values/strings.xml b/feature/mypage/src/main/res/values/strings.xml new file mode 100644 index 00000000..71e1d449 --- /dev/null +++ b/feature/mypage/src/main/res/values/strings.xml @@ -0,0 +1,10 @@ + + + 마이페이지 + 프로필 수정 + 계정 + 연결된 계정 + 기타 + 로그아웃 + 탈퇴하기 + \ No newline at end of file From 6416db1934d0c467c0796bd06d776b366886a107 Mon Sep 17 00:00:00 2001 From: kwakjoohyeong Date: Wed, 20 Mar 2024 19:17:10 +0900 Subject: [PATCH 02/35] =?UTF-8?q?feat:=20profile=20editor=20=EB=AA=A8?= =?UTF-8?q?=EB=93=88=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 1 + feature/profileeditor/.gitignore | 1 + feature/profileeditor/build.gradle.kts | 12 ++++++++++ feature/profileeditor/consumer-rules.pro | 0 feature/profileeditor/proguard-rules.pro | 21 ++++++++++++++++ .../profileeditor/ExampleInstrumentedTest.kt | 24 +++++++++++++++++++ .../src/main/AndroidManifest.xml | 4 ++++ .../app/profileeditor/ProfileEditorScreen.kt | 15 ++++++++++++ .../navigation/ProfileEditorNavigation.kt | 23 ++++++++++++++++++ .../com/app/profileeditor/ExampleUnitTest.kt | 17 +++++++++++++ settings.gradle.kts | 1 + 11 files changed, 119 insertions(+) create mode 100644 feature/profileeditor/.gitignore create mode 100644 feature/profileeditor/build.gradle.kts create mode 100644 feature/profileeditor/consumer-rules.pro create mode 100644 feature/profileeditor/proguard-rules.pro create mode 100644 feature/profileeditor/src/androidTest/java/com/app/profileeditor/ExampleInstrumentedTest.kt create mode 100644 feature/profileeditor/src/main/AndroidManifest.xml create mode 100644 feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt create mode 100644 feature/profileeditor/src/main/java/com/app/profileeditor/navigation/ProfileEditorNavigation.kt create mode 100644 feature/profileeditor/src/test/java/com/app/profileeditor/ExampleUnitTest.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c83a3794..f5cc77fc 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -45,6 +45,7 @@ dependencies { implementation(project(":feature:mypage")) implementation(project(":feature:registerpost")) implementation(project(":feature:gallery")) + implementation(project(":feature:profileeditor")) implementation(project(":core:interceptor")) implementation(project(":core:data")) implementation(project(":core:network")) diff --git a/feature/profileeditor/.gitignore b/feature/profileeditor/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/feature/profileeditor/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/profileeditor/build.gradle.kts b/feature/profileeditor/build.gradle.kts new file mode 100644 index 00000000..bf4f7371 --- /dev/null +++ b/feature/profileeditor/build.gradle.kts @@ -0,0 +1,12 @@ +plugins { + id("convention.feature") +} + +android { + namespace = "com.withpeace.withpeace.feature.profileeditor" +} + +dependencies { + implementation(libs.skydoves.landscapist.glide) + implementation(libs.skydoves.landscapist.bom) +} \ No newline at end of file diff --git a/feature/profileeditor/consumer-rules.pro b/feature/profileeditor/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/feature/profileeditor/proguard-rules.pro b/feature/profileeditor/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/feature/profileeditor/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/feature/profileeditor/src/androidTest/java/com/app/profileeditor/ExampleInstrumentedTest.kt b/feature/profileeditor/src/androidTest/java/com/app/profileeditor/ExampleInstrumentedTest.kt new file mode 100644 index 00000000..01c6fd57 --- /dev/null +++ b/feature/profileeditor/src/androidTest/java/com/app/profileeditor/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.app.profileeditor + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.app.profileeditor.test", appContext.packageName) + } +} \ No newline at end of file diff --git a/feature/profileeditor/src/main/AndroidManifest.xml b/feature/profileeditor/src/main/AndroidManifest.xml new file mode 100644 index 00000000..a5918e68 --- /dev/null +++ b/feature/profileeditor/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt new file mode 100644 index 00000000..7cc929fe --- /dev/null +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt @@ -0,0 +1,15 @@ +package com.app.profileeditor + +import androidx.compose.runtime.Composable + +@Composable +fun ProfileEditorRoute( + onShowSnackBar: (message: String) -> Unit, +) { + +} + +@Composable +fun ProfileEditorScreen() { + +} \ No newline at end of file diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/navigation/ProfileEditorNavigation.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/navigation/ProfileEditorNavigation.kt new file mode 100644 index 00000000..cb8c4c7c --- /dev/null +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/navigation/ProfileEditorNavigation.kt @@ -0,0 +1,23 @@ +package com.app.profileeditor.navigation + +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavOptions +import androidx.navigation.compose.composable +import com.app.profileeditor.ProfileEditorRoute + +const val PROFILE_EDITOR_ROUTE = "profileEditorRoute" + +fun NavController.navigateProfileEditor(navOptions: NavOptions? = null) { + navigate(PROFILE_EDITOR_ROUTE, navOptions) +} + +fun NavGraphBuilder.profileEditorNavGraph( + onShowSnackBar: (message: String) -> Unit, +) { + composable(route = PROFILE_EDITOR_ROUTE) { + ProfileEditorRoute( + onShowSnackBar = onShowSnackBar, + ) + } +} \ No newline at end of file diff --git a/feature/profileeditor/src/test/java/com/app/profileeditor/ExampleUnitTest.kt b/feature/profileeditor/src/test/java/com/app/profileeditor/ExampleUnitTest.kt new file mode 100644 index 00000000..b723f776 --- /dev/null +++ b/feature/profileeditor/src/test/java/com/app/profileeditor/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.app.profileeditor + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 06af9647..53dab9e3 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -32,3 +32,4 @@ include(":feature:registerpost") include(":core:imagestorage") include(":feature:gallery") include(":core:permission") +include(":feature:profileeditor") From 7cd610defb0d6cbe30c0390090b9869a85b665b4 Mon Sep 17 00:00:00 2001 From: kwakjoohyeong Date: Thu, 21 Mar 2024 14:52:33 +0900 Subject: [PATCH 03/35] =?UTF-8?q?feat:=20profile=20editor=20UI=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../withpeace/withpeace/navigation/NavHost.kt | 12 +- .../app/profileeditor/ProfileEditorScreen.kt | 214 +++++++++++++++++- .../navigation/ProfileEditorNavigation.kt | 2 + .../main/res/drawable/ic_default_profile.xml | 24 ++ .../main/res/drawable/ic_editor_pencil.xml | 14 ++ .../src/main/res/values/strings.xml | 5 + 6 files changed, 268 insertions(+), 3 deletions(-) create mode 100644 feature/profileeditor/src/main/res/drawable/ic_default_profile.xml create mode 100644 feature/profileeditor/src/main/res/drawable/ic_editor_pencil.xml create mode 100644 feature/profileeditor/src/main/res/values/strings.xml diff --git a/app/src/main/java/com/withpeace/withpeace/navigation/NavHost.kt b/app/src/main/java/com/withpeace/withpeace/navigation/NavHost.kt index a54668c9..4cd632e5 100644 --- a/app/src/main/java/com/withpeace/withpeace/navigation/NavHost.kt +++ b/app/src/main/java/com/withpeace/withpeace/navigation/NavHost.kt @@ -4,6 +4,8 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost +import com.app.profileeditor.navigation.navigateProfileEditor +import com.app.profileeditor.navigation.profileEditorNavGraph import com.withpeace.withpeace.feature.gallery.navigation.galleryNavGraph import com.withpeace.withpeace.feature.gallery.navigation.navigateToGallery import com.withpeace.withpeace.feature.home.navigation.homeNavGraph @@ -57,9 +59,17 @@ fun WithpeaceNavHost( postNavGraph(onShowSnackBar) myPageNavGraph( onShowSnackBar = onShowSnackBar, - onEditProfile = {}, + onEditProfile = { + navController.navigateProfileEditor() + }, onLogoutClick = {}, onWithdrawClick = {}, ) + profileEditorNavGraph( + onShowSnackBar = onShowSnackBar, + onClickBackButton = { + navController.popBackStack() + }, + ) } } diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt index 7cc929fe..07b655ad 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt @@ -1,15 +1,225 @@ package com.app.profileeditor +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.Image +import androidx.compose.foundation.border +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +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.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Divider +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Text +import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.skydoves.landscapist.glide.GlideImage +import com.withpeace.withpeace.core.designsystem.theme.WithpeaceTheme +import com.withpeace.withpeace.core.designsystem.ui.WithPeaceBackButtonTopAppBar +import com.withpeace.withpeace.feature.profileeditor.R @Composable fun ProfileEditorRoute( onShowSnackBar: (message: String) -> Unit, + onClickBackButton: () -> Unit, ) { + ProfileEditorScreen( + onClickBackButton = onClickBackButton, + ) +} + +@Composable +fun ProfileEditorScreen( + modifier: Modifier = Modifier, + onClickBackButton: () -> Unit, +) { + Column( + verticalArrangement = Arrangement.SpaceBetween, + modifier = modifier + .fillMaxSize() + .padding(top = 0.dp), + ) { + Column( + modifier = modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + WithPeaceBackButtonTopAppBar( + modifier = modifier, + onClickBackButton = { onClickBackButton() }, + title = { + Text( + text = stringResource(R.string.edit_profile), + style = WithpeaceTheme.typography.title1, + color = WithpeaceTheme.colors.SystemBlack, + ) + }, + ) + Spacer(modifier = modifier.height(16.dp)) + val imageModifier = modifier + .size(120.dp) + .border( + BorderStroke(0.dp, Color.Transparent), + shape = CircleShape, + ) + Row( + modifier = modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center, + ) { + Box( + ) { + GlideImage( + modifier = imageModifier, + imageModel = { "" }, + failure = { + Image( + painterResource(id = R.drawable.ic_default_profile), + modifier = imageModifier, + contentDescription = "", + ) + }, + ) + Image( + modifier = modifier + .align(Alignment.BottomEnd) + .padding(bottom = 6.dp, end = 6.dp), + painter = painterResource(id = R.drawable.ic_editor_pencil), + contentDescription = "프로필 수정", + ) + } + } + Spacer(modifier = modifier.height(24.dp)) + Text( + text = stringResource(R.string.nickname_policy), + style = WithpeaceTheme.typography.caption, + color = WithpeaceTheme.colors.SystemGray1, + ) + Spacer(modifier = modifier.height(16.dp)) + NickNameTextField(nickname = "", onNickNameChanged = { }) + } + EditCompletedButton(onClick = { /*TODO*/ }) + } } +@OptIn(ExperimentalMaterial3Api::class) @Composable -fun ProfileEditorScreen() { +private fun NickNameTextField( + modifier: Modifier = Modifier, + nickname: String, + onNickNameChanged: (String) -> Unit, +) { + val interactionSource = remember { MutableInteractionSource() } + Column( + modifier = modifier + .width(140.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + BasicTextField( + value = nickname, onValueChange = onNickNameChanged, + modifier = modifier.fillMaxWidth(), + enabled = true, + textStyle = WithpeaceTheme.typography.body.copy(textAlign = TextAlign.Center), + singleLine = true, + maxLines = 1, + ) { + TextFieldDefaults.DecorationBox( + value = nickname, + innerTextField = it, + enabled = true, + singleLine = false, + visualTransformation = VisualTransformation.None, + placeholder = { + Text( + modifier = modifier.fillMaxWidth(), + textAlign = TextAlign.Center, + text = "닉네임을 입력하세요", + style = WithpeaceTheme.typography.body, + color = WithpeaceTheme.colors.SystemGray2, + ) + }, + interactionSource = interactionSource, + contentPadding = PaddingValues(0.dp), + colors = TextFieldDefaults.colors( + disabledTextColor = Color.Transparent, + focusedIndicatorColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent, + disabledIndicatorColor = Color.Transparent, + unfocusedContainerColor = Color.Transparent, + focusedContainerColor = Color.Transparent, + ), + ) + } + Divider( + color = WithpeaceTheme.colors.SystemBlack, + modifier = modifier + .width(140.dp) + .height(1.dp), + ) + Text(text = "", modifier = modifier.padding(top = 4.dp)) + } +} + + +@Composable +@Preview +fun ProfileEditorPreview() { + WithpeaceTheme { + ProfileEditorScreen { + + } + } +} + +@Composable +private fun EditCompletedButton( + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { + Button( + onClick = { + onClick() + }, + contentPadding = PaddingValues(0.dp), + modifier = modifier + .padding( + bottom = 40.dp, + end = WithpeaceTheme.padding.BasicHorizontalPadding, + start = WithpeaceTheme.padding.BasicHorizontalPadding, + ) + .fillMaxWidth(), + colors = ButtonDefaults.buttonColors(containerColor = WithpeaceTheme.colors.SystemGray2), + shape = RoundedCornerShape(9.dp), + ) { + Text( + style = WithpeaceTheme.typography.body, + text = "변경 완료", + modifier = Modifier.padding(vertical = 18.dp), + color = WithpeaceTheme.colors.SystemWhite, + ) + } +} -} \ No newline at end of file +// 화면짜고 갤러리 이동 추가 \ No newline at end of file diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/navigation/ProfileEditorNavigation.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/navigation/ProfileEditorNavigation.kt index cb8c4c7c..c61fc0b7 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/navigation/ProfileEditorNavigation.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/navigation/ProfileEditorNavigation.kt @@ -14,10 +14,12 @@ fun NavController.navigateProfileEditor(navOptions: NavOptions? = null) { fun NavGraphBuilder.profileEditorNavGraph( onShowSnackBar: (message: String) -> Unit, + onClickBackButton: () -> Unit, ) { composable(route = PROFILE_EDITOR_ROUTE) { ProfileEditorRoute( onShowSnackBar = onShowSnackBar, + onClickBackButton = onClickBackButton ) } } \ No newline at end of file diff --git a/feature/profileeditor/src/main/res/drawable/ic_default_profile.xml b/feature/profileeditor/src/main/res/drawable/ic_default_profile.xml new file mode 100644 index 00000000..da35818c --- /dev/null +++ b/feature/profileeditor/src/main/res/drawable/ic_default_profile.xml @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/feature/profileeditor/src/main/res/drawable/ic_editor_pencil.xml b/feature/profileeditor/src/main/res/drawable/ic_editor_pencil.xml new file mode 100644 index 00000000..8fa03dc4 --- /dev/null +++ b/feature/profileeditor/src/main/res/drawable/ic_editor_pencil.xml @@ -0,0 +1,14 @@ + + + + diff --git a/feature/profileeditor/src/main/res/values/strings.xml b/feature/profileeditor/src/main/res/values/strings.xml new file mode 100644 index 00000000..cbd6a379 --- /dev/null +++ b/feature/profileeditor/src/main/res/values/strings.xml @@ -0,0 +1,5 @@ + + + 프로필 편집 + 닉네임은 2~10자의 한글, 영문만 가능합니다. + \ No newline at end of file From 5a252a85a7bbe77bcb0ec12f4300c832175fd42b Mon Sep 17 00:00:00 2001 From: kwakjoohyeong Date: Thu, 21 Mar 2024 19:11:37 +0900 Subject: [PATCH 04/35] =?UTF-8?q?feat:=20=EA=B0=A4=EB=9F=AC=EB=A6=AC=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=EB=B0=8F=20ProfileEditor=20=EB=8F=84=EB=A9=94=EC=9D=B8=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../withpeace/withpeace/navigation/NavHost.kt | 3 + .../core/domain/model/profile/ProfileInfo.kt | 15 ++ .../domain/usecase/VerifyNicknameUseCase.kt | 9 ++ feature/profileeditor/build.gradle.kts | 1 + .../app/profileeditor/ProfileEditorScreen.kt | 145 ++++++++++++------ .../profileeditor/ProfileEditorViewModel.kt | 30 ++++ .../navigation/ProfileEditorNavigation.kt | 14 +- 7 files changed, 170 insertions(+), 47 deletions(-) create mode 100644 core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ProfileInfo.kt create mode 100644 core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/VerifyNicknameUseCase.kt create mode 100644 feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt diff --git a/app/src/main/java/com/withpeace/withpeace/navigation/NavHost.kt b/app/src/main/java/com/withpeace/withpeace/navigation/NavHost.kt index 4cd632e5..4de8abf1 100644 --- a/app/src/main/java/com/withpeace/withpeace/navigation/NavHost.kt +++ b/app/src/main/java/com/withpeace/withpeace/navigation/NavHost.kt @@ -70,6 +70,9 @@ fun WithpeaceNavHost( onClickBackButton = { navController.popBackStack() }, + onNavigateToGallery = { + navController.navigateToGallery(imageLimit = 1) + }, ) } } diff --git a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ProfileInfo.kt b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ProfileInfo.kt new file mode 100644 index 00000000..4b5bec85 --- /dev/null +++ b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ProfileInfo.kt @@ -0,0 +1,15 @@ +package com.withpeace.withpeace.core.domain.model.profile + +data class ProfileInfo( + val nickname: String, + val profileImageUrl: String?, +) { + companion object { + private const val NICKNAME_REGEX_PATTERN = "[가-힣a-zA-Z]{2,10}" + + fun validateNickname(nickname: String): Boolean { + val regex = Regex(NICKNAME_REGEX_PATTERN) + return regex.matches(nickname) + } + } +} diff --git a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/VerifyNicknameUseCase.kt b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/VerifyNicknameUseCase.kt new file mode 100644 index 00000000..1bbdeb6c --- /dev/null +++ b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/VerifyNicknameUseCase.kt @@ -0,0 +1,9 @@ +package com.withpeace.withpeace.core.domain.usecase + +import com.withpeace.withpeace.core.domain.model.profile.ProfileInfo + +class VerifyNicknameUseCase { + operator fun invoke(nickname: String): Boolean { + return ProfileInfo.validateNickname(nickname) + } +} diff --git a/feature/profileeditor/build.gradle.kts b/feature/profileeditor/build.gradle.kts index bf4f7371..81822e00 100644 --- a/feature/profileeditor/build.gradle.kts +++ b/feature/profileeditor/build.gradle.kts @@ -7,6 +7,7 @@ android { } dependencies { + implementation(project(":core:permission")) implementation(libs.skydoves.landscapist.glide) implementation(libs.skydoves.landscapist.bom) } \ No newline at end of file diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt index 07b655ad..91ae4835 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt @@ -3,6 +3,7 @@ package com.app.profileeditor import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Image import androidx.compose.foundation.border +import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -16,6 +17,7 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.BasicTextField @@ -26,35 +28,55 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Text import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.skydoves.landscapist.glide.GlideImage import com.withpeace.withpeace.core.designsystem.theme.WithpeaceTheme import com.withpeace.withpeace.core.designsystem.ui.WithPeaceBackButtonTopAppBar +import com.withpeace.withpeace.core.domain.model.profile.ProfileInfo +import com.withpeace.withpeace.core.permission.ImagePermissionHelper import com.withpeace.withpeace.feature.profileeditor.R @Composable fun ProfileEditorRoute( onShowSnackBar: (message: String) -> Unit, onClickBackButton: () -> Unit, + onNavigateToGallery: () -> Unit, + viewModel: ProfileEditorViewModel, ) { ProfileEditorScreen( + profileInfo = viewModel.profileInfo.collectAsStateWithLifecycle().value, onClickBackButton = onClickBackButton, + onNavigateToGallery = onNavigateToGallery, + onEditCompleted = {}, + onNickNameChanged = viewModel::onNickNameChanged, ) + } @Composable fun ProfileEditorScreen( + profileInfo: ProfileInfo, modifier: Modifier = Modifier, onClickBackButton: () -> Unit, + onNavigateToGallery: () -> Unit, + onEditCompleted: () -> Unit, + onNickNameChanged: (String) -> Unit, ) { Column( verticalArrangement = Arrangement.SpaceBetween, @@ -78,38 +100,11 @@ fun ProfileEditorScreen( }, ) Spacer(modifier = modifier.height(16.dp)) - val imageModifier = modifier - .size(120.dp) - .border( - BorderStroke(0.dp, Color.Transparent), - shape = CircleShape, - ) - Row( - modifier = modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.Center, - ) { - Box( - ) { - GlideImage( - modifier = imageModifier, - imageModel = { "" }, - failure = { - Image( - painterResource(id = R.drawable.ic_default_profile), - modifier = imageModifier, - contentDescription = "", - ) - }, - ) - Image( - modifier = modifier - .align(Alignment.BottomEnd) - .padding(bottom = 6.dp, end = 6.dp), - painter = painterResource(id = R.drawable.ic_editor_pencil), - contentDescription = "프로필 수정", - ) - } - } + ProfileImage( + profileImage = profileInfo.profileImageUrl, + modifier = modifier, + onNavigateToGallery = onNavigateToGallery, + ) Spacer(modifier = modifier.height(24.dp)) Text( text = stringResource(R.string.nickname_policy), @@ -117,10 +112,68 @@ fun ProfileEditorScreen( color = WithpeaceTheme.colors.SystemGray1, ) Spacer(modifier = modifier.height(16.dp)) - NickNameTextField(nickname = "", onNickNameChanged = { }) + NickNameTextField( + nickname = profileInfo.nickname, + onNickNameChanged = { + onNickNameChanged(it) + }, + ) } - EditCompletedButton(onClick = { /*TODO*/ }) + EditCompletedButton(onClick = { onEditCompleted() }) + } +} + +@Composable +private fun ProfileImage( + profileImage: String?, + modifier: Modifier, + onNavigateToGallery: () -> Unit, +) { + var showDialog by rememberSaveable { mutableStateOf(false) } + val context = LocalContext.current + val imagePermissionHelper = remember { ImagePermissionHelper(context) } + val launcher = imagePermissionHelper.getImageLauncher( + onPermissionGranted = onNavigateToGallery, + onPermissionDenied = { showDialog = true }, + ) + if (showDialog) { + imagePermissionHelper.ImagePermissionDialog { showDialog = false } + } + val imageModifier = modifier.size(120.dp).clip(CircleShape) + Row( + modifier = modifier.wrapContentSize(Alignment.Center), + horizontalArrangement = Arrangement.Center, + ) { + Box( + modifier.clickable { + imagePermissionHelper.onCheckSelfImagePermission( + onPermissionGranted = onNavigateToGallery, + onPermissionDenied = { + imagePermissionHelper.requestPermissionDialog(launcher) + }, + ) + }, + ) { + GlideImage( + modifier = imageModifier, + imageModel = { profileImage }, + failure = { + Image( + painterResource(id = R.drawable.ic_default_profile), + modifier = imageModifier, + contentDescription = "", + ) + }, + ) + Image( + modifier = modifier + .align(Alignment.BottomEnd) + .padding(bottom = 6.dp, end = 6.dp), + painter = painterResource(id = R.drawable.ic_editor_pencil), + contentDescription = "프로필 수정", + ) + } } } @@ -183,16 +236,6 @@ private fun NickNameTextField( } -@Composable -@Preview -fun ProfileEditorPreview() { - WithpeaceTheme { - ProfileEditorScreen { - - } - } -} - @Composable private fun EditCompletedButton( onClick: () -> Unit, @@ -222,4 +265,16 @@ private fun EditCompletedButton( } } -// 화면짜고 갤러리 이동 추가 \ No newline at end of file +@Composable +@Preview +fun ProfileEditorPreview() { + WithpeaceTheme { + ProfileEditorScreen( + profileInfo = ProfileInfo("nickname", null), + onClickBackButton = {}, + onNavigateToGallery = {}, + onEditCompleted = {}, + onNickNameChanged = {}, + ) + } +} diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt new file mode 100644 index 00000000..6c88a5b2 --- /dev/null +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt @@ -0,0 +1,30 @@ +package com.app.profileeditor + +import androidx.lifecycle.ViewModel +import com.withpeace.withpeace.core.domain.model.profile.ProfileInfo +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import javax.inject.Inject + +@HiltViewModel +class ProfileEditorViewModel @Inject constructor() : ViewModel() { + // 변경이 되었는지 알 수 있어야한다. + // validation status + private val _profileInfo = MutableStateFlow( + ProfileInfo( + nickname = "", + profileImageUrl = null, + ), + ) + val profileInfo = _profileInfo.asStateFlow() + + fun onImageUriAdded(imageUri: String) { + _profileInfo.update { it.copy(profileImageUrl = imageUri) } + } + + fun onNickNameChanged(nickname: String) { + _profileInfo.update { it.copy(nickname = nickname) } + } +} \ No newline at end of file diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/navigation/ProfileEditorNavigation.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/navigation/ProfileEditorNavigation.kt index c61fc0b7..080a223d 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/navigation/ProfileEditorNavigation.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/navigation/ProfileEditorNavigation.kt @@ -1,12 +1,15 @@ package com.app.profileeditor.navigation +import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptions import androidx.navigation.compose.composable import com.app.profileeditor.ProfileEditorRoute +import com.app.profileeditor.ProfileEditorViewModel const val PROFILE_EDITOR_ROUTE = "profileEditorRoute" +const val IMAGE_LIST_ARGUMENT = "image_list_argument" fun NavController.navigateProfileEditor(navOptions: NavOptions? = null) { navigate(PROFILE_EDITOR_ROUTE, navOptions) @@ -15,11 +18,18 @@ fun NavController.navigateProfileEditor(navOptions: NavOptions? = null) { fun NavGraphBuilder.profileEditorNavGraph( onShowSnackBar: (message: String) -> Unit, onClickBackButton: () -> Unit, + onNavigateToGallery: () -> Unit, ) { - composable(route = PROFILE_EDITOR_ROUTE) { + composable(route = PROFILE_EDITOR_ROUTE) { entry -> + val selectedImageUri = + entry.savedStateHandle.get>(IMAGE_LIST_ARGUMENT) ?: emptyList() + val viewModel: ProfileEditorViewModel = hiltViewModel() + viewModel.onImageUriAdded(imageUri = selectedImageUri.firstOrNull() ?: "") ProfileEditorRoute( onShowSnackBar = onShowSnackBar, - onClickBackButton = onClickBackButton + onClickBackButton = onClickBackButton, + onNavigateToGallery = onNavigateToGallery, + viewModel = viewModel, ) } } \ No newline at end of file From 33473c94acb056df5bf0fbcb6d1324c3a2f95eb1 Mon Sep 17 00:00:00 2001 From: kwakjoohyeong Date: Sun, 24 Mar 2024 15:26:37 +0900 Subject: [PATCH 05/35] =?UTF-8?q?feat:topappbar=20window=20inset=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 --- .../com/withpeace/withpeace/core/designsystem/ui/TopAppBar.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/designsystem/src/main/java/com/withpeace/withpeace/core/designsystem/ui/TopAppBar.kt b/core/designsystem/src/main/java/com/withpeace/withpeace/core/designsystem/ui/TopAppBar.kt index 6146f0e8..e3b05e20 100644 --- a/core/designsystem/src/main/java/com/withpeace/withpeace/core/designsystem/ui/TopAppBar.kt +++ b/core/designsystem/src/main/java/com/withpeace/withpeace/core/designsystem/ui/TopAppBar.kt @@ -2,6 +2,7 @@ package com.withpeace.withpeace.core.designsystem.ui import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.padding import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon @@ -41,6 +42,7 @@ fun WithPeaceBackButtonTopAppBar( }, actions = actions, colors = TopAppBarDefaults.topAppBarColors(containerColor = WithpeaceTheme.colors.SystemWhite), + windowInsets = WindowInsets(0, 0, 0, 0), ) } From 6b56f9b1cd911d1d8301e7252bf5b80219333b83 Mon Sep 17 00:00:00 2001 From: kwakjoohyeong Date: Mon, 25 Mar 2024 15:32:03 +0900 Subject: [PATCH 06/35] =?UTF-8?q?feat:=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=ED=99=94=EB=A9=B4=20API=20=EC=97=B0?= =?UTF-8?q?=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/data/di/RepositoryModule.kt | 6 ++ .../core/data/mapper/ProfileInfoMapper.kt | 12 ++++ .../data/repository/DefaultTokenRepository.kt | 3 +- .../data/repository/DefaultUserRepository.kt | 65 +++++++++++++++++++ .../core/domain/model/profile/ProfileInfo.kt | 1 + .../core/domain/repository/UserRepository.kt | 33 ++++++++++ .../domain/usecase/GetProfileInfoUseCase.kt | 17 +++++ .../core/network/di/di/ServiceModule.kt | 10 +-- .../network/di/response/ProfileResponse.kt | 10 +++ .../core/network/di/service/AuthService.kt | 7 ++ .../core/network/di/service/UserService.kt | 22 ++++--- .../withpeace/feature/mypage/MyPageScreen.kt | 24 ++++++- .../withpeace/feature/mypage/MyPageUiState.kt | 9 +++ .../feature/mypage/MyPageViewModel.kt | 39 +++++++++++ .../mypage/src/main/res/values/strings.xml | 1 + .../app/profileeditor/ProfileEditorScreen.kt | 3 +- .../profileeditor/ProfileEditorViewModel.kt | 1 + 17 files changed, 244 insertions(+), 19 deletions(-) create mode 100644 core/data/src/main/kotlin/com/withpeace/withpeace/core/data/mapper/ProfileInfoMapper.kt create mode 100644 core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultUserRepository.kt create mode 100644 core/domain/src/main/java/com/withpeace/withpeace/core/domain/repository/UserRepository.kt create mode 100644 core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/GetProfileInfoUseCase.kt create mode 100644 core/network/src/main/java/com/withpeace/withpeace/core/network/di/response/ProfileResponse.kt create mode 100644 feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageUiState.kt create mode 100644 feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageViewModel.kt diff --git a/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/di/RepositoryModule.kt b/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/di/RepositoryModule.kt index dd12259e..f0d9fd2f 100644 --- a/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/di/RepositoryModule.kt +++ b/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/di/RepositoryModule.kt @@ -3,9 +3,11 @@ package com.withpeace.withpeace.core.data.di import com.withpeace.withpeace.core.data.repository.DefaultImageRepository import com.withpeace.withpeace.core.data.repository.DefaultPostRepository import com.withpeace.withpeace.core.data.repository.DefaultTokenRepository +import com.withpeace.withpeace.core.data.repository.DefaultUserRepository import com.withpeace.withpeace.core.domain.repository.ImageRepository import com.withpeace.withpeace.core.domain.repository.PostRepository import com.withpeace.withpeace.core.domain.repository.TokenRepository +import com.withpeace.withpeace.core.domain.repository.UserRepository import dagger.Binds import dagger.Module import dagger.hilt.InstallIn @@ -27,4 +29,8 @@ interface RepositoryModule { @Binds @Singleton fun bindsPostRepository(defaultPostRepository: DefaultPostRepository): PostRepository + + @Binds + @Singleton + fun bindsUserRepository(defaultUserRepository: DefaultUserRepository): UserRepository } diff --git a/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/mapper/ProfileInfoMapper.kt b/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/mapper/ProfileInfoMapper.kt new file mode 100644 index 00000000..dc659d88 --- /dev/null +++ b/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/mapper/ProfileInfoMapper.kt @@ -0,0 +1,12 @@ +package com.withpeace.withpeace.core.data.mapper + +import com.withpeace.withpeace.core.domain.model.profile.ProfileInfo +import com.withpeace.withpeace.core.network.di.response.ProfileResponse + +fun ProfileResponse.toDomain(): ProfileInfo { + return ProfileInfo( + nickname = nickname, + profileImageUrl = profileImageUrl, + email = email, + ) +} \ No newline at end of file diff --git a/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultTokenRepository.kt b/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultTokenRepository.kt index 488e1b91..23f56bd7 100644 --- a/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultTokenRepository.kt +++ b/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultTokenRepository.kt @@ -18,7 +18,6 @@ import javax.inject.Inject class DefaultTokenRepository @Inject constructor( private val tokenPreferenceDataSource: TokenPreferenceDataSource, private val authService: AuthService, - private val userService: UserService, ) : TokenRepository { override suspend fun isLogin(): Boolean { val token = tokenPreferenceDataSource.accessToken.firstOrNull() @@ -30,7 +29,7 @@ class DefaultTokenRepository @Inject constructor( nickname: String, onError: (String) -> Unit, ): Flow = flow { - userService.signUp( + authService.signUp( SignUpRequest(email = email, nickname = nickname, deviceToken = null), ).suspendMapSuccess { val data = this.data diff --git a/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultUserRepository.kt b/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultUserRepository.kt new file mode 100644 index 00000000..075b2fc4 --- /dev/null +++ b/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultUserRepository.kt @@ -0,0 +1,65 @@ +package com.withpeace.withpeace.core.data.repository + +import com.skydoves.sandwich.messageOrNull +import com.skydoves.sandwich.suspendMapSuccess +import com.skydoves.sandwich.suspendOnError +import com.skydoves.sandwich.suspendOnException +import com.withpeace.withpeace.core.data.mapper.toDomain +import com.withpeace.withpeace.core.domain.model.WithPeaceError +import com.withpeace.withpeace.core.domain.model.profile.ProfileInfo +import com.withpeace.withpeace.core.domain.repository.UserRepository +import com.withpeace.withpeace.core.network.di.service.UserService +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import javax.inject.Inject + +class DefaultUserRepository @Inject constructor( + private val userService: UserService, +) : UserRepository { + override fun getProfile( + onError: (WithPeaceError) -> Unit, + ): Flow = flow { + userService.getProfile().suspendMapSuccess { + emit(this.data.toDomain()) + }.suspendOnError { + if (statusCode.code == 401) onError(WithPeaceError.UnAuthorized()) + else onError(WithPeaceError.GeneralError(statusCode.code, messageOrNull)) + }.suspendOnException { + onError(WithPeaceError.GeneralError(message = messageOrNull)) + } + } + + override fun registerProfile( + nickname: String, + profileImage: String, + onError: (WithPeaceError) -> Unit, + ): Flow { + TODO("Not yet implemented") + } + + override fun updateProfileImage( + profileImage: String, + onError: (WithPeaceError) -> Unit, + ): Flow { + TODO("Not yet implemented") + } + + override fun updateNickname(nickname: String, onError: (WithPeaceError) -> Unit): Flow { + TODO("Not yet implemented") + } + + override fun updateProfile( + nickname: String, + profileImage: String, + onError: (WithPeaceError) -> Unit, + ): Flow { + TODO("Not yet implemented") + } + + override fun isNicknameDuplicated( + nickname: String, + onError: (WithPeaceError) -> Unit, + ): Flow { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ProfileInfo.kt b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ProfileInfo.kt index 4b5bec85..e400e6a3 100644 --- a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ProfileInfo.kt +++ b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ProfileInfo.kt @@ -3,6 +3,7 @@ package com.withpeace.withpeace.core.domain.model.profile data class ProfileInfo( val nickname: String, val profileImageUrl: String?, + val email: String ) { companion object { private const val NICKNAME_REGEX_PATTERN = "[가-힣a-zA-Z]{2,10}" diff --git a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/repository/UserRepository.kt b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/repository/UserRepository.kt new file mode 100644 index 00000000..160984ba --- /dev/null +++ b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/repository/UserRepository.kt @@ -0,0 +1,33 @@ +package com.withpeace.withpeace.core.domain.repository + +import com.withpeace.withpeace.core.domain.model.WithPeaceError +import com.withpeace.withpeace.core.domain.model.profile.ProfileInfo +import kotlinx.coroutines.flow.Flow + +interface UserRepository { + fun getProfile(onError: (WithPeaceError) -> Unit): Flow + fun registerProfile( + nickname: String, profileImage: String, + onError: (WithPeaceError) -> Unit, + ): Flow + + fun updateProfileImage( + profileImage: String, + onError: (WithPeaceError) -> Unit, + ): Flow + + fun updateNickname( + nickname: String, + onError: (WithPeaceError) -> Unit, + ): Flow + + fun updateProfile( + nickname: String, profileImage: String, + onError: (WithPeaceError) -> Unit, + ): Flow + + fun isNicknameDuplicated( + nickname: String, + onError: (WithPeaceError) -> Unit, + ): Flow +} diff --git a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/GetProfileInfoUseCase.kt b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/GetProfileInfoUseCase.kt new file mode 100644 index 00000000..886686fe --- /dev/null +++ b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/GetProfileInfoUseCase.kt @@ -0,0 +1,17 @@ +package com.withpeace.withpeace.core.domain.usecase + +import com.withpeace.withpeace.core.domain.model.WithPeaceError +import com.withpeace.withpeace.core.domain.model.profile.ProfileInfo +import com.withpeace.withpeace.core.domain.repository.UserRepository +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject + +class GetProfileInfoUseCase @Inject constructor( + private val userRepository: UserRepository, +) { + operator fun invoke(onError: (WithPeaceError) -> Unit): Flow { + return userRepository.getProfile( + onError = onError, + ) + } +} diff --git a/core/network/src/main/java/com/withpeace/withpeace/core/network/di/di/ServiceModule.kt b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/di/ServiceModule.kt index b0ea6f2a..a40228c0 100644 --- a/core/network/src/main/java/com/withpeace/withpeace/core/network/di/di/ServiceModule.kt +++ b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/di/ServiceModule.kt @@ -15,11 +15,6 @@ import javax.inject.Singleton @InstallIn(SingletonComponent::class) object ServiceModule { - @Provides - @Singleton - fun providesAuthService(@Named("general") retrofit: Retrofit): UserService = - retrofit.create(UserService::class.java) - @Provides @Singleton fun providesLoginService(@Named("auth") retrofit: Retrofit): AuthService = @@ -29,4 +24,9 @@ object ServiceModule { @Singleton fun providesPostService(@Named("general") retrofit: Retrofit): PostService = retrofit.create(PostService::class.java) + + @Provides + @Singleton + fun providesUserService(@Named("general") retrofit: Retrofit): UserService = + retrofit.create(UserService::class.java) } diff --git a/core/network/src/main/java/com/withpeace/withpeace/core/network/di/response/ProfileResponse.kt b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/response/ProfileResponse.kt new file mode 100644 index 00000000..d892b3c6 --- /dev/null +++ b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/response/ProfileResponse.kt @@ -0,0 +1,10 @@ +package com.withpeace.withpeace.core.network.di.response + +import kotlinx.serialization.Serializable + +@Serializable +data class ProfileResponse( + val email: String, + val profileImageUrl: String, + val nickname: String, +) diff --git a/core/network/src/main/java/com/withpeace/withpeace/core/network/di/service/AuthService.kt b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/service/AuthService.kt index a1ef9cf4..c1400765 100644 --- a/core/network/src/main/java/com/withpeace/withpeace/core/network/di/service/AuthService.kt +++ b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/service/AuthService.kt @@ -1,9 +1,11 @@ package com.withpeace.withpeace.core.network.di.service import com.skydoves.sandwich.ApiResponse +import com.withpeace.withpeace.core.network.di.request.SignUpRequest import com.withpeace.withpeace.core.network.di.response.BaseResponse import com.withpeace.withpeace.core.network.di.response.LoginResponse import com.withpeace.withpeace.core.network.di.response.TokenResponse +import retrofit2.http.Body import retrofit2.http.Header import retrofit2.http.POST @@ -20,4 +22,9 @@ interface AuthService { @POST("/api/v1/auth/logout") suspend fun logout(): ApiResponse> + + @POST("/api/v1/auth/register") + suspend fun signUp( + @Body signUpRequest: SignUpRequest, + ): ApiResponse> } diff --git a/core/network/src/main/java/com/withpeace/withpeace/core/network/di/service/UserService.kt b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/service/UserService.kt index 1a8ecf09..2496852b 100644 --- a/core/network/src/main/java/com/withpeace/withpeace/core/network/di/service/UserService.kt +++ b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/service/UserService.kt @@ -1,15 +1,21 @@ package com.withpeace.withpeace.core.network.di.service import com.skydoves.sandwich.ApiResponse -import com.withpeace.withpeace.core.network.di.request.SignUpRequest import com.withpeace.withpeace.core.network.di.response.BaseResponse -import com.withpeace.withpeace.core.network.di.response.TokenResponse -import retrofit2.http.Body -import retrofit2.http.POST +import com.withpeace.withpeace.core.network.di.response.ProfileResponse +import okhttp3.MultipartBody +import retrofit2.http.GET +import retrofit2.http.PATCH +import retrofit2.http.PUT +import retrofit2.http.Part interface UserService { - @POST("/api/v1/auth/register") - suspend fun signUp( - @Body signUpRequest: SignUpRequest, - ): ApiResponse> + @GET("/api/v1/users/profile") + suspend fun getProfile(): ApiResponse> + + @PATCH("/api/v1/users/profile/nickname") + suspend fun updateNickname(nickname: String): ApiResponse> + + @PATCH("/api/v1/users/profile/image") + suspend fun updateImage(@Part imageFile: MultipartBody.Part): ApiResponse> } diff --git a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageScreen.kt b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageScreen.kt index 71600f6e..7105ae7b 100644 --- a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageScreen.kt +++ b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageScreen.kt @@ -17,6 +17,7 @@ import androidx.compose.material3.Divider import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -24,21 +25,38 @@ 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.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.skydoves.landscapist.glide.GlideImage import com.withpeace.withpeace.core.designsystem.theme.WithpeaceTheme import com.withpeace.withpeace.core.designsystem.ui.TitleBar +import com.withpeace.withpeace.core.domain.model.profile.ProfileInfo @Composable fun MyPageRoute( + viewModel: MyPageViewModel = hiltViewModel(), onShowSnackBar: (message: String) -> Unit = {}, onEditProfile: () -> Unit, onLogoutClick: () -> Unit, onWithdrawClick: () -> Unit, ) { + val mypageUiState by viewModel.postUiState.collectAsStateWithLifecycle() + when (mypageUiState) { + MyPageUiState.Loading -> {} + is MyPageUiState.Success -> {} + MyPageUiState.Fail -> { + onShowSnackBar(stringResource(R.string.network_failure_message)) + } + } MyPageScreen( onEditProfile = onEditProfile, onLogoutClick = onLogoutClick, onWithdrawClick = onWithdrawClick, + profileInfo = (mypageUiState as? MyPageUiState.Success)?.profileInfo ?: ProfileInfo( + "", + "", + "", + ), // TODO("없을 시 로컬에서 가지고 온다.") ) } @@ -48,6 +66,7 @@ fun MyPageScreen( onEditProfile: () -> Unit, onLogoutClick: () -> Unit, onWithdrawClick: () -> Unit, + profileInfo: ProfileInfo, ) { Column( modifier, @@ -68,7 +87,7 @@ fun MyPageScreen( ) GlideImage( modifier = imageModifier, - imageModel = { "" }, + imageModel = { profileInfo.profileImageUrl }, failure = { Image( painterResource(id = R.drawable.ic_default_profile), @@ -79,7 +98,7 @@ fun MyPageScreen( ) Text( style = WithpeaceTheme.typography.body, - text = "닉네임닉네임", + text = profileInfo.nickname, modifier = modifier.padding(start = 8.dp), ) } @@ -204,6 +223,7 @@ fun MyPagePreview() { modifier = Modifier, onLogoutClick = {}, onWithdrawClick = {}, + profileInfo = ProfileInfo("닉네임닉네임", "", "abc@gmail.com"), ) } } \ No newline at end of file diff --git a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageUiState.kt b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageUiState.kt new file mode 100644 index 00000000..823c3641 --- /dev/null +++ b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageUiState.kt @@ -0,0 +1,9 @@ +package com.withpeace.withpeace.feature.mypage + +import com.withpeace.withpeace.core.domain.model.profile.ProfileInfo + +sealed interface MyPageUiState { + data object Loading : MyPageUiState + data class Success(val profileInfo: ProfileInfo) : MyPageUiState + data object Fail : MyPageUiState +} \ No newline at end of file diff --git a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageViewModel.kt b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageViewModel.kt new file mode 100644 index 00000000..c462630c --- /dev/null +++ b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageViewModel.kt @@ -0,0 +1,39 @@ +package com.withpeace.withpeace.feature.mypage + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.withpeace.withpeace.core.domain.model.profile.ProfileInfo +import com.withpeace.withpeace.core.domain.repository.UserRepository +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class MyPageViewModel @Inject constructor( + private val userRepository: UserRepository, +) : ViewModel() { + + private val _myPageUiState = MutableStateFlow(MyPageUiState.Loading) + val postUiState = _myPageUiState.asStateFlow() + + init { + getProfile() + } + + private fun getProfile() { + viewModelScope.launch { + userRepository.getProfile { error -> + }.collect { profileInfo -> + _myPageUiState.update { + MyPageUiState.Success( + profileInfo, + ) + } + } + } + + } +} \ No newline at end of file diff --git a/feature/mypage/src/main/res/values/strings.xml b/feature/mypage/src/main/res/values/strings.xml index 71e1d449..b5bc428c 100644 --- a/feature/mypage/src/main/res/values/strings.xml +++ b/feature/mypage/src/main/res/values/strings.xml @@ -7,4 +7,5 @@ 기타 로그아웃 탈퇴하기 + 서버와의 오류가 발생했습니다. 다시 시도해주세요. \ No newline at end of file diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt index 91ae4835..8ec14f63 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt @@ -66,7 +66,6 @@ fun ProfileEditorRoute( onEditCompleted = {}, onNickNameChanged = viewModel::onNickNameChanged, ) - } @Composable @@ -270,7 +269,7 @@ private fun EditCompletedButton( fun ProfileEditorPreview() { WithpeaceTheme { ProfileEditorScreen( - profileInfo = ProfileInfo("nickname", null), + profileInfo = ProfileInfo("nickname", null,""), onClickBackButton = {}, onNavigateToGallery = {}, onEditCompleted = {}, diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt index 6c88a5b2..f9311c2a 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt @@ -16,6 +16,7 @@ class ProfileEditorViewModel @Inject constructor() : ViewModel() { ProfileInfo( nickname = "", profileImageUrl = null, + "" ), ) val profileInfo = _profileInfo.asStateFlow() From fac4011c472a425e74dc68e72c7c43cbf8a05c9e Mon Sep 17 00:00:00 2001 From: kwakjoohyeong Date: Mon, 25 Mar 2024 16:59:36 +0900 Subject: [PATCH 07/35] =?UTF-8?q?feat:=20=EB=8B=89=EB=84=A4=EC=9E=84=20?= =?UTF-8?q?=EB=84=A4=EB=B9=84=EA=B2=8C=EC=9D=B4=EC=85=98=20=EC=A0=84?= =?UTF-8?q?=EB=8B=AC=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 --- .../withpeace/withpeace/navigation/NavHost.kt | 4 ++-- .../withpeace/feature/mypage/MyPageScreen.kt | 23 +++++++++++-------- .../mypage/navigation/MyPageNavigation.kt | 2 +- .../app/profileeditor/ProfileEditorScreen.kt | 8 ++++--- .../profileeditor/ProfileEditorViewModel.kt | 15 ++++++++---- .../navigation/ProfileEditorNavigation.kt | 22 +++++++++++++++--- .../src/main/res/values/strings.xml | 1 + 7 files changed, 53 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/com/withpeace/withpeace/navigation/NavHost.kt b/app/src/main/java/com/withpeace/withpeace/navigation/NavHost.kt index 4de8abf1..caa83781 100644 --- a/app/src/main/java/com/withpeace/withpeace/navigation/NavHost.kt +++ b/app/src/main/java/com/withpeace/withpeace/navigation/NavHost.kt @@ -59,8 +59,8 @@ fun WithpeaceNavHost( postNavGraph(onShowSnackBar) myPageNavGraph( onShowSnackBar = onShowSnackBar, - onEditProfile = { - navController.navigateProfileEditor() + onEditProfile = { nickname, profileImageUrl -> + navController.navigateProfileEditor(nickname = nickname, profileImageUrl = profileImageUrl) }, onLogoutClick = {}, onWithdrawClick = {}, diff --git a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageScreen.kt b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageScreen.kt index 7105ae7b..2986ee45 100644 --- a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageScreen.kt +++ b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageScreen.kt @@ -1,5 +1,6 @@ package com.withpeace.withpeace.feature.mypage +import android.util.Log import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Image import androidx.compose.foundation.border @@ -17,6 +18,7 @@ import androidx.compose.material3.Divider import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -36,7 +38,7 @@ import com.withpeace.withpeace.core.domain.model.profile.ProfileInfo fun MyPageRoute( viewModel: MyPageViewModel = hiltViewModel(), onShowSnackBar: (message: String) -> Unit = {}, - onEditProfile: () -> Unit, + onEditProfile: (nickname: String, profileImageUrl: String) -> Unit, onLogoutClick: () -> Unit, onWithdrawClick: () -> Unit, ) { @@ -48,22 +50,25 @@ fun MyPageRoute( onShowSnackBar(stringResource(R.string.network_failure_message)) } } + val profileInfo = (mypageUiState as? MyPageUiState.Success)?.profileInfo ?: ProfileInfo( + "", + "", + "", + ) // TODO("없을 시 로컬에서 가지고 온다.") MyPageScreen( - onEditProfile = onEditProfile, + onEditProfile = { + onEditProfile(it.nickname, it.profileImageUrl ?: "") + }, onLogoutClick = onLogoutClick, onWithdrawClick = onWithdrawClick, - profileInfo = (mypageUiState as? MyPageUiState.Success)?.profileInfo ?: ProfileInfo( - "", - "", - "", - ), // TODO("없을 시 로컬에서 가지고 온다.") + profileInfo = profileInfo, ) } @Composable fun MyPageScreen( modifier: Modifier = Modifier, - onEditProfile: () -> Unit, + onEditProfile: (ProfileInfo) -> Unit, onLogoutClick: () -> Unit, onWithdrawClick: () -> Unit, profileInfo: ProfileInfo, @@ -103,7 +108,7 @@ fun MyPageScreen( ) } TextButton( - onClick = { onEditProfile() }, + onClick = { onEditProfile(profileInfo) }, ) { Text( color = WithpeaceTheme.colors.MainPink, diff --git a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/navigation/MyPageNavigation.kt b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/navigation/MyPageNavigation.kt index bbef4cce..8832ae62 100644 --- a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/navigation/MyPageNavigation.kt +++ b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/navigation/MyPageNavigation.kt @@ -14,7 +14,7 @@ fun NavController.navigateMyPage(navOptions: NavOptions? = null) { fun NavGraphBuilder.myPageNavGraph( onShowSnackBar: (message: String) -> Unit, - onEditProfile: () -> Unit, + onEditProfile: (nickname: String, profileImageUrl: String) -> Unit, onLogoutClick: () -> Unit, onWithdrawClick: () -> Unit, ) { diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt index 8ec14f63..16590d39 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt @@ -139,7 +139,9 @@ private fun ProfileImage( if (showDialog) { imagePermissionHelper.ImagePermissionDialog { showDialog = false } } - val imageModifier = modifier.size(120.dp).clip(CircleShape) + val imageModifier = modifier + .size(120.dp) + .clip(CircleShape) Row( modifier = modifier.wrapContentSize(Alignment.Center), horizontalArrangement = Arrangement.Center, @@ -252,12 +254,12 @@ private fun EditCompletedButton( start = WithpeaceTheme.padding.BasicHorizontalPadding, ) .fillMaxWidth(), - colors = ButtonDefaults.buttonColors(containerColor = WithpeaceTheme.colors.SystemGray2), + colors = ButtonDefaults.buttonColors(containerColor = WithpeaceTheme.colors.MainPink), shape = RoundedCornerShape(9.dp), ) { Text( style = WithpeaceTheme.typography.body, - text = "변경 완료", + text = stringResource(R.string.edit_completed), modifier = Modifier.padding(vertical = 18.dp), color = WithpeaceTheme.colors.SystemWhite, ) diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt index f9311c2a..64a2eef8 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt @@ -1,7 +1,11 @@ package com.app.profileeditor +import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel +import com.app.profileeditor.navigation.PROFILE_IMAGE_URL_ARGUMENT +import com.app.profileeditor.navigation.PROFILE_NICKNAME_ARGUMENT import com.withpeace.withpeace.core.domain.model.profile.ProfileInfo +import com.withpeace.withpeace.core.domain.repository.UserRepository import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow @@ -9,14 +13,17 @@ import kotlinx.coroutines.flow.update import javax.inject.Inject @HiltViewModel -class ProfileEditorViewModel @Inject constructor() : ViewModel() { +class ProfileEditorViewModel @Inject constructor( + savedStateHandle: SavedStateHandle, + private val userRepository: UserRepository, +) : ViewModel() { // 변경이 되었는지 알 수 있어야한다. // validation status private val _profileInfo = MutableStateFlow( ProfileInfo( - nickname = "", - profileImageUrl = null, - "" + nickname = savedStateHandle.get(PROFILE_NICKNAME_ARGUMENT) ?: "", + profileImageUrl = savedStateHandle.get(PROFILE_IMAGE_URL_ARGUMENT) ?: "", + "", ), ) val profileInfo = _profileInfo.asStateFlow() diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/navigation/ProfileEditorNavigation.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/navigation/ProfileEditorNavigation.kt index 080a223d..6edd3b62 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/navigation/ProfileEditorNavigation.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/navigation/ProfileEditorNavigation.kt @@ -4,15 +4,25 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptions +import androidx.navigation.NavType import androidx.navigation.compose.composable +import androidx.navigation.navArgument import com.app.profileeditor.ProfileEditorRoute import com.app.profileeditor.ProfileEditorViewModel const val PROFILE_EDITOR_ROUTE = "profileEditorRoute" const val IMAGE_LIST_ARGUMENT = "image_list_argument" +const val PROFILE_NICKNAME_ARGUMENT = "nickname_argument" +const val PROFILE_IMAGE_URL_ARGUMENT = "profile_image_url_argument" +const val PROFILE_EDITOR_ROUTE_WITH_ARGUMENT = + "$PROFILE_EDITOR_ROUTE/{$PROFILE_NICKNAME_ARGUMENT}/{$PROFILE_IMAGE_URL_ARGUMENT}" -fun NavController.navigateProfileEditor(navOptions: NavOptions? = null) { - navigate(PROFILE_EDITOR_ROUTE, navOptions) +fun NavController.navigateProfileEditor( + navOptions: NavOptions? = null, + nickname: String, + profileImageUrl: String, +) { + navigate("$PROFILE_EDITOR_ROUTE/$nickname/$profileImageUrl", navOptions) } fun NavGraphBuilder.profileEditorNavGraph( @@ -20,7 +30,13 @@ fun NavGraphBuilder.profileEditorNavGraph( onClickBackButton: () -> Unit, onNavigateToGallery: () -> Unit, ) { - composable(route = PROFILE_EDITOR_ROUTE) { entry -> + composable( + route = PROFILE_EDITOR_ROUTE_WITH_ARGUMENT, + arguments = listOf( + navArgument(PROFILE_NICKNAME_ARGUMENT) { type = NavType.StringType }, + navArgument(PROFILE_IMAGE_URL_ARGUMENT) { type = NavType.StringType }, + ), + ) { entry -> val selectedImageUri = entry.savedStateHandle.get>(IMAGE_LIST_ARGUMENT) ?: emptyList() val viewModel: ProfileEditorViewModel = hiltViewModel() diff --git a/feature/profileeditor/src/main/res/values/strings.xml b/feature/profileeditor/src/main/res/values/strings.xml index cbd6a379..f072d827 100644 --- a/feature/profileeditor/src/main/res/values/strings.xml +++ b/feature/profileeditor/src/main/res/values/strings.xml @@ -2,4 +2,5 @@ 프로필 편집 닉네임은 2~10자의 한글, 영문만 가능합니다. + 수정 완료 \ No newline at end of file From 4533b350be7f6456faa9ad6bd15cf14925242add Mon Sep 17 00:00:00 2001 From: kwakjoohyeong Date: Tue, 26 Mar 2024 00:41:54 +0900 Subject: [PATCH 08/35] =?UTF-8?q?feat:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20=EC=97=AC=EB=B6=80=20=ED=99=95=EC=9D=B8=20?= =?UTF-8?q?=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 --- .../model/profile/ChangingProfileInfo.kt | 7 ++ .../core/domain/model/profile/ProfileInfo.kt | 2 +- .../withpeace/feature/mypage/MyPageScreen.kt | 18 ++--- .../feature/mypage/MyPageViewModel.kt | 3 +- .../app/profileeditor/ProfileEditUiEvent.kt | 9 +++ .../app/profileeditor/ProfileEditUiState.kt | 11 ++++ .../app/profileeditor/ProfileEditorScreen.kt | 65 ++++++++++++++++--- .../profileeditor/ProfileEditorViewModel.kt | 63 +++++++++++++----- .../navigation/ProfileEditorNavigation.kt | 8 +-- .../src/main/res/values/strings.xml | 1 + 10 files changed, 148 insertions(+), 39 deletions(-) create mode 100644 core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ChangingProfileInfo.kt create mode 100644 feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditUiEvent.kt create mode 100644 feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditUiState.kt diff --git a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ChangingProfileInfo.kt b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ChangingProfileInfo.kt new file mode 100644 index 00000000..ff5cdf84 --- /dev/null +++ b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ChangingProfileInfo.kt @@ -0,0 +1,7 @@ +package com.withpeace.withpeace.core.domain.model.profile + +data class ChangingProfileInfo(val nickname: String, val profileImage: String?) { + fun isSameTo(nickname: String, profileImage: String?): Boolean { + return nickname == this.nickname && profileImage == this.profileImage + } +} \ No newline at end of file diff --git a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ProfileInfo.kt b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ProfileInfo.kt index e400e6a3..86d87fdb 100644 --- a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ProfileInfo.kt +++ b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ProfileInfo.kt @@ -2,7 +2,7 @@ package com.withpeace.withpeace.core.domain.model.profile data class ProfileInfo( val nickname: String, - val profileImageUrl: String?, + val profileImageUrl: String, val email: String ) { companion object { diff --git a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageScreen.kt b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageScreen.kt index 2986ee45..8b1216ee 100644 --- a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageScreen.kt +++ b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageScreen.kt @@ -1,6 +1,5 @@ package com.withpeace.withpeace.feature.mypage -import android.util.Log import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Image import androidx.compose.foundation.border @@ -18,7 +17,6 @@ import androidx.compose.material3.Divider import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -42,7 +40,7 @@ fun MyPageRoute( onLogoutClick: () -> Unit, onWithdrawClick: () -> Unit, ) { - val mypageUiState by viewModel.postUiState.collectAsStateWithLifecycle() + val mypageUiState by viewModel.myPageUiState.collectAsStateWithLifecycle() when (mypageUiState) { MyPageUiState.Loading -> {} is MyPageUiState.Success -> {} @@ -51,13 +49,13 @@ fun MyPageRoute( } } val profileInfo = (mypageUiState as? MyPageUiState.Success)?.profileInfo ?: ProfileInfo( - "", - "", + "nickname", + "profile", "", ) // TODO("없을 시 로컬에서 가지고 온다.") MyPageScreen( onEditProfile = { - onEditProfile(it.nickname, it.profileImageUrl ?: "") + onEditProfile(it.nickname, it.profileImageUrl) }, onLogoutClick = onLogoutClick, onWithdrawClick = onWithdrawClick, @@ -129,6 +127,7 @@ fun MyPageScreen( modifier = modifier, onLogoutClick = onLogoutClick, onWithdrawClick = onWithdrawClick, + email = profileInfo.email, ) } } @@ -138,9 +137,10 @@ fun MyPageSections( modifier: Modifier, onLogoutClick: () -> Unit, onWithdrawClick: () -> Unit, + email: String, ) { Column(modifier = modifier.padding(horizontal = WithpeaceTheme.padding.BasicHorizontalPadding)) { - AccountSection(modifier) + AccountSection(modifier, email = email) Divider( modifier = modifier .fillMaxWidth() @@ -152,7 +152,7 @@ fun MyPageSections( } @Composable -private fun AccountSection(modifier: Modifier) { +private fun AccountSection(modifier: Modifier, email: String) { Section(title = stringResource(R.string.account)) { Spacer(modifier = modifier.height(16.dp)) Row(modifier = modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { @@ -162,7 +162,7 @@ private fun AccountSection(modifier: Modifier) { color = WithpeaceTheme.colors.SystemBlack, ) Text( - text = "abc@gmail.com", + text = email, style = WithpeaceTheme.typography.caption, color = WithpeaceTheme.colors.SystemGray2, ) diff --git a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageViewModel.kt b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageViewModel.kt index c462630c..f6371715 100644 --- a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageViewModel.kt +++ b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageViewModel.kt @@ -2,7 +2,6 @@ package com.withpeace.withpeace.feature.mypage import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.withpeace.withpeace.core.domain.model.profile.ProfileInfo import com.withpeace.withpeace.core.domain.repository.UserRepository import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow @@ -17,7 +16,7 @@ class MyPageViewModel @Inject constructor( ) : ViewModel() { private val _myPageUiState = MutableStateFlow(MyPageUiState.Loading) - val postUiState = _myPageUiState.asStateFlow() + val myPageUiState = _myPageUiState.asStateFlow() init { getProfile() diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditUiEvent.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditUiEvent.kt new file mode 100644 index 00000000..20ef5f11 --- /dev/null +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditUiEvent.kt @@ -0,0 +1,9 @@ +package com.app.profileeditor + +sealed interface ProfileEditUiEvent { + data object ShowDuplicate : ProfileEditUiEvent + data object ShowInvalidFormat : ProfileEditUiEvent + data object ShowSuccess : ProfileEditUiEvent + data object ShowFailure : ProfileEditUiEvent + data object ShowUnchanged : ProfileEditUiEvent +} \ No newline at end of file diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditUiState.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditUiState.kt new file mode 100644 index 00000000..b09a2efa --- /dev/null +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditUiState.kt @@ -0,0 +1,11 @@ +package com.app.profileeditor + +sealed interface ProfileEditUiState { + data class Editing( + val nickname: String, + val profileImage: String, + val isBasicTextValid: Boolean + ) : ProfileEditUiState + + data object NoChanges : ProfileEditUiState +} \ No newline at end of file diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt index 16590d39..7faa8811 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt @@ -1,8 +1,6 @@ package com.app.profileeditor -import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Image -import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement @@ -25,6 +23,7 @@ import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Divider import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable @@ -44,11 +43,12 @@ import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.skydoves.landscapist.glide.GlideImage import com.withpeace.withpeace.core.designsystem.theme.WithpeaceTheme import com.withpeace.withpeace.core.designsystem.ui.WithPeaceBackButtonTopAppBar -import com.withpeace.withpeace.core.domain.model.profile.ProfileInfo +import com.withpeace.withpeace.core.domain.model.profile.ChangingProfileInfo import com.withpeace.withpeace.core.permission.ImagePermissionHelper import com.withpeace.withpeace.feature.profileeditor.R @@ -59,18 +59,45 @@ fun ProfileEditorRoute( onNavigateToGallery: () -> Unit, viewModel: ProfileEditorViewModel, ) { + var showAlertDialog by remember { mutableStateOf(false) } + val profileUiState: ProfileEditUiState by viewModel.profileEditUiState.collectAsStateWithLifecycle() + val profileInfo = when (profileUiState) { + is ProfileEditUiState.Editing -> { + val editing = profileUiState as ProfileEditUiState.Editing + ChangingProfileInfo(nickname = editing.nickname, profileImage = editing.profileImage) + } + + is ProfileEditUiState.NoChanges -> { + ChangingProfileInfo( + viewModel.baseProfileInfo.nickname, + viewModel.baseProfileInfo.profileImage, + ) + } + } + if (showAlertDialog) { + ModifySaveDialog { + showAlertDialog = false + } + } ProfileEditorScreen( - profileInfo = viewModel.profileInfo.collectAsStateWithLifecycle().value, - onClickBackButton = onClickBackButton, + profileInfo = ChangingProfileInfo(profileInfo.nickname, profileInfo.profileImage), + onClickBackButton = { + if (profileUiState is ProfileEditUiState.Editing) { + showAlertDialog = true + } else { + onClickBackButton() + } + }, onNavigateToGallery = onNavigateToGallery, onEditCompleted = {}, onNickNameChanged = viewModel::onNickNameChanged, ) + } @Composable fun ProfileEditorScreen( - profileInfo: ProfileInfo, + profileInfo: ChangingProfileInfo, modifier: Modifier = Modifier, onClickBackButton: () -> Unit, onNavigateToGallery: () -> Unit, @@ -100,7 +127,7 @@ fun ProfileEditorScreen( ) Spacer(modifier = modifier.height(16.dp)) ProfileImage( - profileImage = profileInfo.profileImageUrl, + profileImage = profileInfo.profileImage, modifier = modifier, onNavigateToGallery = onNavigateToGallery, ) @@ -266,12 +293,34 @@ private fun EditCompletedButton( } } +//TODO("common-ui로 이동") +@Composable +fun ModifySaveDialog( + modifier: Modifier = Modifier, + onModifyDismissRequest: () -> Unit, +) { + Dialog(onDismissRequest = { onModifyDismissRequest() }) { + Surface(modifier = modifier.width(327.dp)) { + ModifySaveDialogContent(modifier) + } + } +} + +@Composable +fun ModifySaveDialogContent(modifier: Modifier) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Spacer(modifier = modifier.height(24.dp)) + Text(text = stringResource(R.string.modify_save_request)) + + } +} + @Composable @Preview fun ProfileEditorPreview() { WithpeaceTheme { ProfileEditorScreen( - profileInfo = ProfileInfo("nickname", null,""), + profileInfo = ChangingProfileInfo("nickname", ""), onClickBackButton = {}, onNavigateToGallery = {}, onEditCompleted = {}, diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt index 64a2eef8..00447b56 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt @@ -4,7 +4,7 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import com.app.profileeditor.navigation.PROFILE_IMAGE_URL_ARGUMENT import com.app.profileeditor.navigation.PROFILE_NICKNAME_ARGUMENT -import com.withpeace.withpeace.core.domain.model.profile.ProfileInfo +import com.withpeace.withpeace.core.domain.model.profile.ChangingProfileInfo import com.withpeace.withpeace.core.domain.repository.UserRepository import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow @@ -17,22 +17,55 @@ class ProfileEditorViewModel @Inject constructor( savedStateHandle: SavedStateHandle, private val userRepository: UserRepository, ) : ViewModel() { - // 변경이 되었는지 알 수 있어야한다. - // validation status - private val _profileInfo = MutableStateFlow( - ProfileInfo( - nickname = savedStateHandle.get(PROFILE_NICKNAME_ARGUMENT) ?: "", - profileImageUrl = savedStateHandle.get(PROFILE_IMAGE_URL_ARGUMENT) ?: "", - "", - ), - ) - val profileInfo = _profileInfo.asStateFlow() + val baseProfileInfo = ChangingProfileInfo( + nickname = savedStateHandle.get(PROFILE_NICKNAME_ARGUMENT) ?: "", + profileImage = savedStateHandle.get(PROFILE_IMAGE_URL_ARGUMENT) ?: "default.png", + ) // 최초 정보에서 변경사항이 있는지 비교를 위한 필드 - fun onImageUriAdded(imageUri: String) { - _profileInfo.update { it.copy(profileImageUrl = imageUri) } + private val _profileEditUiState = + MutableStateFlow( + ProfileEditUiState.NoChanges, + ) + val profileEditUiState = _profileEditUiState.asStateFlow() + + fun onImageChanged(imageUri: String) { + _profileEditUiState.update { + val updateData = ProfileEditUiState.Editing( + (it as? ProfileEditUiState.Editing)?.nickname + ?: baseProfileInfo.nickname, + profileImage = imageUri, + isBasicTextValid = false, + ) + if (baseProfileInfo.isSameTo(updateData.nickname, updateData.profileImage)) { + return@update ProfileEditUiState.NoChanges + } + updateData + } } fun onNickNameChanged(nickname: String) { - _profileInfo.update { it.copy(nickname = nickname) } + _profileEditUiState.update { + val updateData = ProfileEditUiState.Editing( + nickname = nickname, + profileImage = (it as? ProfileEditUiState.Editing)?.profileImage + ?: baseProfileInfo.profileImage ?: "default.png", + isBasicTextValid = false, + ) + if (baseProfileInfo.isSameTo(updateData.nickname, updateData.profileImage)) { + return@update ProfileEditUiState.NoChanges + } + updateData + } + } + + fun updateProfile() { + if (_profileEditUiState.value is ProfileEditUiState.NoChanges) { + + } } -} \ No newline at end of file +} +// 1. 다이어로그 +// 2. 하단 문구 및 오류 표시 +// 3. updateProfileUsecase() 3개 있어야겠다. ㅋㅋ +// 4. 오류 표시 +// 5, 이전 화면 갱신 \ No newline at end of file diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/navigation/ProfileEditorNavigation.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/navigation/ProfileEditorNavigation.kt index 6edd3b62..2fee48a3 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/navigation/ProfileEditorNavigation.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/navigation/ProfileEditorNavigation.kt @@ -19,10 +19,10 @@ const val PROFILE_EDITOR_ROUTE_WITH_ARGUMENT = fun NavController.navigateProfileEditor( navOptions: NavOptions? = null, - nickname: String, - profileImageUrl: String, + nickname: String?, + profileImageUrl: String?, ) { - navigate("$PROFILE_EDITOR_ROUTE/$nickname/$profileImageUrl", navOptions) + navigate("$PROFILE_EDITOR_ROUTE/${nickname ?: ""}/${profileImageUrl ?: ""}", navOptions) } fun NavGraphBuilder.profileEditorNavGraph( @@ -40,7 +40,7 @@ fun NavGraphBuilder.profileEditorNavGraph( val selectedImageUri = entry.savedStateHandle.get>(IMAGE_LIST_ARGUMENT) ?: emptyList() val viewModel: ProfileEditorViewModel = hiltViewModel() - viewModel.onImageUriAdded(imageUri = selectedImageUri.firstOrNull() ?: "") + viewModel.onImageChanged(imageUri = selectedImageUri.firstOrNull() ?: "default.png") ProfileEditorRoute( onShowSnackBar = onShowSnackBar, onClickBackButton = onClickBackButton, diff --git a/feature/profileeditor/src/main/res/values/strings.xml b/feature/profileeditor/src/main/res/values/strings.xml index f072d827..e36f1121 100644 --- a/feature/profileeditor/src/main/res/values/strings.xml +++ b/feature/profileeditor/src/main/res/values/strings.xml @@ -3,4 +3,5 @@ 프로필 편집 닉네임은 2~10자의 한글, 영문만 가능합니다. 수정 완료 + 수정사항이 있습니다.\n저장하시겠습니까? \ No newline at end of file From d9ab4a265d166788f7d0f012fb47987c2db00de4 Mon Sep 17 00:00:00 2001 From: kwakjoohyeong Date: Tue, 26 Mar 2024 01:35:27 +0900 Subject: [PATCH 09/35] =?UTF-8?q?feat:=20=EC=A0=80=EC=9E=A5=EC=97=AC?= =?UTF-8?q?=EB=B6=80=20dialog=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/profileeditor/ProfileEditorScreen.kt | 90 ++++++++++++++++--- .../profileeditor/ProfileEditorViewModel.kt | 1 + .../src/main/res/values/strings.xml | 5 +- 3 files changed, 84 insertions(+), 12 deletions(-) diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt index 7faa8811..6a2df7c3 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt @@ -1,6 +1,8 @@ package com.app.profileeditor import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement @@ -25,6 +27,7 @@ import androidx.compose.material3.Divider import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Surface import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -75,9 +78,18 @@ fun ProfileEditorRoute( } } if (showAlertDialog) { - ModifySaveDialog { - showAlertDialog = false - } + ModifySaveDialog( + onClickSave = { + showAlertDialog = false + }, + onClickExit = { + showAlertDialog = false + onClickBackButton() + }, + onModifyDismissRequest = { + showAlertDialog = false + }, + ) } ProfileEditorScreen( profileInfo = ChangingProfileInfo(profileInfo.nickname, profileInfo.profileImage), @@ -199,7 +211,7 @@ private fun ProfileImage( .align(Alignment.BottomEnd) .padding(bottom = 6.dp, end = 6.dp), painter = painterResource(id = R.drawable.ic_editor_pencil), - contentDescription = "프로필 수정", + contentDescription = stringResource(id = R.string.edit_profile), ) } } @@ -236,7 +248,7 @@ private fun NickNameTextField( Text( modifier = modifier.fillMaxWidth(), textAlign = TextAlign.Center, - text = "닉네임을 입력하세요", + text = stringResource(R.string.enter_nickname), style = WithpeaceTheme.typography.body, color = WithpeaceTheme.colors.SystemGray2, ) @@ -293,25 +305,81 @@ private fun EditCompletedButton( } } -//TODO("common-ui로 이동") @Composable fun ModifySaveDialog( modifier: Modifier = Modifier, onModifyDismissRequest: () -> Unit, + onClickExit: () -> Unit, + onClickSave: () -> Unit, ) { Dialog(onDismissRequest = { onModifyDismissRequest() }) { - Surface(modifier = modifier.width(327.dp)) { - ModifySaveDialogContent(modifier) + Surface( + modifier = modifier + .width(327.dp) + .clip(RoundedCornerShape(10.dp)), + ) { + ModifySaveDialogContent( + modifier = modifier, + onClickExit = onClickExit, + onClickSave = onClickSave, + ) } } } @Composable -fun ModifySaveDialogContent(modifier: Modifier) { +fun ModifySaveDialogContent( + modifier: Modifier, + onClickExit: () -> Unit, + onClickSave: () -> Unit, +) { Column(horizontalAlignment = Alignment.CenterHorizontally) { Spacer(modifier = modifier.height(24.dp)) - Text(text = stringResource(R.string.modify_save_request)) - + Text( + text = stringResource(R.string.modify_save_request), + style = WithpeaceTheme.typography.body, + color = WithpeaceTheme.colors.SystemGray1, + textAlign = TextAlign.Center, + ) + Spacer(modifier = modifier.height(16.dp)) + Row( + modifier = modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center, + ) { + TextButton( + modifier = modifier + .width(136.dp) + .border( + width = 1.dp, + shape = RoundedCornerShape(10.dp), + color = WithpeaceTheme.colors.MainPink, + ) + .background(WithpeaceTheme.colors.SystemWhite), + onClick = { onClickExit() }, + content = { + Text( + text = stringResource(R.string.dialog_exit), + color = WithpeaceTheme.colors.MainPink, + style = WithpeaceTheme.typography.caption, + ) + }, + ) + Spacer(modifier = Modifier.width(8.dp)) + TextButton( + modifier = modifier + .width(136.dp) + .background(WithpeaceTheme.colors.MainPink, shape = RoundedCornerShape(10.dp)), + onClick = { onClickSave() }, + content = { + Text( + text = stringResource(R.string.dialog_save), + color = WithpeaceTheme.colors.SystemWhite, + style = WithpeaceTheme.typography.caption, + ) + }, + ) + } + Spacer(modifier = modifier.height(24.dp)) } } diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt index 00447b56..1fb0fa1a 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt @@ -56,6 +56,7 @@ class ProfileEditorViewModel @Inject constructor( } updateData } + // verify } fun updateProfile() { diff --git a/feature/profileeditor/src/main/res/values/strings.xml b/feature/profileeditor/src/main/res/values/strings.xml index e36f1121..6ec18c1b 100644 --- a/feature/profileeditor/src/main/res/values/strings.xml +++ b/feature/profileeditor/src/main/res/values/strings.xml @@ -1,7 +1,10 @@ - 프로필 편집 + 프로필 수정 닉네임은 2~10자의 한글, 영문만 가능합니다. 수정 완료 수정사항이 있습니다.\n저장하시겠습니까? + 나가기 + 저장하기 + 닉네임을 입력하세요 \ No newline at end of file From 92cb9f81fe6011d8862b2948e1216ee709c15c87 Mon Sep 17 00:00:00 2001 From: kwakjoohyeong Date: Tue, 26 Mar 2024 20:06:34 +0900 Subject: [PATCH 10/35] =?UTF-8?q?feat:=20=EB=8B=89=EB=84=A4=EC=9E=84=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=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 --- .../data/repository/DefaultUserRepository.kt | 18 +++-- .../core/domain/model/WithPeaceErrorCode.kt | 28 ++++++++ .../model/profile/ChangingProfileInfo.kt | 11 +++- .../core/domain/model/profile/Nickname.kt | 17 +++++ .../core/domain/model/profile/ProfileInfo.kt | 13 +--- .../core/domain/repository/UserRepository.kt | 5 +- .../domain/usecase/VerifyNicknameUseCase.kt | 19 ++++-- .../core/network/di/service/UserService.kt | 4 ++ .../app/profileeditor/ProfileEditUiEvent.kt | 3 +- .../app/profileeditor/ProfileEditorScreen.kt | 56 ++++++++++++++-- .../profileeditor/ProfileEditorViewModel.kt | 66 +++++++++++++++---- 11 files changed, 197 insertions(+), 43 deletions(-) create mode 100644 core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/WithPeaceErrorCode.kt create mode 100644 core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/Nickname.kt diff --git a/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultUserRepository.kt b/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultUserRepository.kt index 075b2fc4..f2921d61 100644 --- a/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultUserRepository.kt +++ b/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultUserRepository.kt @@ -6,6 +6,7 @@ import com.skydoves.sandwich.suspendOnError import com.skydoves.sandwich.suspendOnException import com.withpeace.withpeace.core.data.mapper.toDomain import com.withpeace.withpeace.core.domain.model.WithPeaceError +import com.withpeace.withpeace.core.domain.model.profile.Nickname import com.withpeace.withpeace.core.domain.model.profile.ProfileInfo import com.withpeace.withpeace.core.domain.repository.UserRepository import com.withpeace.withpeace.core.network.di.service.UserService @@ -56,10 +57,19 @@ class DefaultUserRepository @Inject constructor( TODO("Not yet implemented") } - override fun isNicknameDuplicated( - nickname: String, + override fun verifyNicknameDuplicated( + nickname: Nickname, onError: (WithPeaceError) -> Unit, - ): Flow { - TODO("Not yet implemented") + ): Flow = flow { + userService.isNicknameDuplicate(nickname.value).suspendMapSuccess { + if (this.data == "true") { + onError(WithPeaceError.GeneralError(code = 2)) + } + emit(true) + }.suspendOnError { + onError(WithPeaceError.GeneralError(statusCode.code, messageOrNull)) + }.suspendOnException { + onError(WithPeaceError.GeneralError(message = messageOrNull)) + } } } \ No newline at end of file diff --git a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/WithPeaceErrorCode.kt b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/WithPeaceErrorCode.kt new file mode 100644 index 00000000..ae171bc7 --- /dev/null +++ b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/WithPeaceErrorCode.kt @@ -0,0 +1,28 @@ +package com.withpeace.withpeace.core.domain.model + +enum class WithPeaceErrorCode(val code: Int) { + /* Server 401 */ + EXPIRED_TOKEN_ERROR(40100), + INVALID_TOKEN_ERROR(40101), + TOKEN_MALFORMED_ERROR(40102), + TOKEN_TYPE_ERROR(40103), + TOKEN_UNSUPPORTED_ERROR(40104), + TOKEN_GENERATION_ERROR(40105), + FAILURE_LOGIN(40106), + FAILURE_LOGOUT(40107), + TOKEN_UNKNOWN_ERROR(40106), + + /* Server 403 */ + ACCESS_DENIED_ERROR(40300), + + /* Server 404 */ + NOT_FOUND_USER(40401), + NOT_FOUND_POST(40402), + + /* Server 500 */ + SERVER_ERROR(50000), + AUTH_SERVER_USER_INFO_ERROR(50001), + + /* Client Code (1 ~ 10000) */ + NICKNAME_VALIDATION_ERROR(1) +} \ No newline at end of file diff --git a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ChangingProfileInfo.kt b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ChangingProfileInfo.kt index ff5cdf84..55d10587 100644 --- a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ChangingProfileInfo.kt +++ b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ChangingProfileInfo.kt @@ -1,7 +1,14 @@ package com.withpeace.withpeace.core.domain.model.profile -data class ChangingProfileInfo(val nickname: String, val profileImage: String?) { +data class ChangingProfileInfo(val nickname: Nickname, val profileImage: String?) { + fun isSameTo(nickname: String, profileImage: String?): Boolean { - return nickname == this.nickname && profileImage == this.profileImage + return Nickname.create(nickname) == this.nickname && profileImage == this.profileImage + } + + companion object { + operator fun invoke(nickname: String, profileImage: String?): ChangingProfileInfo { + return ChangingProfileInfo(Nickname.create(nickname), profileImage) + } } } \ No newline at end of file diff --git a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/Nickname.kt b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/Nickname.kt new file mode 100644 index 00000000..c926c19c --- /dev/null +++ b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/Nickname.kt @@ -0,0 +1,17 @@ +package com.withpeace.withpeace.core.domain.model.profile + +/* + Data와 UI에서 모두 쓰일 수 있는 도메인입니다. + */ +@JvmInline +value class Nickname private constructor(val value: String) { + + companion object { + private const val NICKNAME_REGEX_PATTERN = "[a-zA-Z0-9가-힣]{2,10}" + fun create(value: String): Nickname = Nickname(value) + fun verifyNickname(nickname: String): Boolean { + val regex = Regex(NICKNAME_REGEX_PATTERN) + return regex.matches(nickname) + } + } +} diff --git a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ProfileInfo.kt b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ProfileInfo.kt index 86d87fdb..78ec01bb 100644 --- a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ProfileInfo.kt +++ b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ProfileInfo.kt @@ -3,14 +3,5 @@ package com.withpeace.withpeace.core.domain.model.profile data class ProfileInfo( val nickname: String, val profileImageUrl: String, - val email: String -) { - companion object { - private const val NICKNAME_REGEX_PATTERN = "[가-힣a-zA-Z]{2,10}" - - fun validateNickname(nickname: String): Boolean { - val regex = Regex(NICKNAME_REGEX_PATTERN) - return regex.matches(nickname) - } - } -} + val email: String, +) diff --git a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/repository/UserRepository.kt b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/repository/UserRepository.kt index 160984ba..e4694ebe 100644 --- a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/repository/UserRepository.kt +++ b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/repository/UserRepository.kt @@ -1,6 +1,7 @@ package com.withpeace.withpeace.core.domain.repository import com.withpeace.withpeace.core.domain.model.WithPeaceError +import com.withpeace.withpeace.core.domain.model.profile.Nickname import com.withpeace.withpeace.core.domain.model.profile.ProfileInfo import kotlinx.coroutines.flow.Flow @@ -26,8 +27,8 @@ interface UserRepository { onError: (WithPeaceError) -> Unit, ): Flow - fun isNicknameDuplicated( - nickname: String, + fun verifyNicknameDuplicated( + nickname: Nickname, onError: (WithPeaceError) -> Unit, ): Flow } diff --git a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/VerifyNicknameUseCase.kt b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/VerifyNicknameUseCase.kt index 1bbdeb6c..f06f13d9 100644 --- a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/VerifyNicknameUseCase.kt +++ b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/VerifyNicknameUseCase.kt @@ -1,9 +1,20 @@ package com.withpeace.withpeace.core.domain.usecase -import com.withpeace.withpeace.core.domain.model.profile.ProfileInfo +import com.withpeace.withpeace.core.domain.model.WithPeaceError +import com.withpeace.withpeace.core.domain.model.profile.Nickname +import com.withpeace.withpeace.core.domain.repository.UserRepository +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import javax.inject.Inject -class VerifyNicknameUseCase { - operator fun invoke(nickname: String): Boolean { - return ProfileInfo.validateNickname(nickname) +class VerifyNicknameUseCase @Inject constructor( + private val userRepository: UserRepository, +) { + operator fun invoke(nickname: String, onError: (WithPeaceError) -> Unit): Flow { + if (!Nickname.verifyNickname(nickname)) { + onError(WithPeaceError.GeneralError(code = 1)) + return flow { emit(false) } + } + return userRepository.verifyNicknameDuplicated(Nickname.create(nickname), onError = onError) } } diff --git a/core/network/src/main/java/com/withpeace/withpeace/core/network/di/service/UserService.kt b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/service/UserService.kt index 2496852b..995bdef5 100644 --- a/core/network/src/main/java/com/withpeace/withpeace/core/network/di/service/UserService.kt +++ b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/service/UserService.kt @@ -8,6 +8,7 @@ import retrofit2.http.GET import retrofit2.http.PATCH import retrofit2.http.PUT import retrofit2.http.Part +import retrofit2.http.Query interface UserService { @GET("/api/v1/users/profile") @@ -18,4 +19,7 @@ interface UserService { @PATCH("/api/v1/users/profile/image") suspend fun updateImage(@Part imageFile: MultipartBody.Part): ApiResponse> + + @GET("/api/v1/users/profile/nickname/check") + suspend fun isNicknameDuplicate(@Query("nickname") nickname: String): ApiResponse> } diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditUiEvent.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditUiEvent.kt index 20ef5f11..48bc16f6 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditUiEvent.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditUiEvent.kt @@ -2,8 +2,9 @@ package com.app.profileeditor sealed interface ProfileEditUiEvent { data object ShowDuplicate : ProfileEditUiEvent + data object ShowNicknameVerified : ProfileEditUiEvent data object ShowInvalidFormat : ProfileEditUiEvent - data object ShowSuccess : ProfileEditUiEvent + data object ShowUpdateSuccess : ProfileEditUiEvent data object ShowFailure : ProfileEditUiEvent data object ShowUnchanged : ProfileEditUiEvent } \ No newline at end of file diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt index 6a2df7c3..97e41586 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt @@ -30,6 +30,7 @@ import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -54,6 +55,8 @@ import com.withpeace.withpeace.core.designsystem.ui.WithPeaceBackButtonTopAppBar import com.withpeace.withpeace.core.domain.model.profile.ChangingProfileInfo import com.withpeace.withpeace.core.permission.ImagePermissionHelper import com.withpeace.withpeace.feature.profileeditor.R +import kotlinx.coroutines.delay +import kotlin.time.Duration.Companion.seconds @Composable fun ProfileEditorRoute( @@ -64,23 +67,22 @@ fun ProfileEditorRoute( ) { var showAlertDialog by remember { mutableStateOf(false) } val profileUiState: ProfileEditUiState by viewModel.profileEditUiState.collectAsStateWithLifecycle() + val profileInfo = when (profileUiState) { is ProfileEditUiState.Editing -> { val editing = profileUiState as ProfileEditUiState.Editing ChangingProfileInfo(nickname = editing.nickname, profileImage = editing.profileImage) } - is ProfileEditUiState.NoChanges -> { - ChangingProfileInfo( - viewModel.baseProfileInfo.nickname, - viewModel.baseProfileInfo.profileImage, - ) + ProfileEditUiState.NoChanges -> { + viewModel.baseProfileInfo } } if (showAlertDialog) { ModifySaveDialog( onClickSave = { showAlertDialog = false + viewModel.updateProfile() }, onClickExit = { showAlertDialog = false @@ -91,6 +93,26 @@ fun ProfileEditorRoute( }, ) } + + LaunchedEffect(viewModel.profileEditUiEvent) { + viewModel.profileEditUiEvent.collect { + when (it) { + ProfileEditUiEvent.ShowDuplicate -> { + onShowSnackBar("중복") + } + + ProfileEditUiEvent.ShowFailure -> {} + ProfileEditUiEvent.ShowInvalidFormat -> {} + ProfileEditUiEvent.ShowUnchanged -> {} + ProfileEditUiEvent.ShowUpdateSuccess -> {} + ProfileEditUiEvent.ShowNicknameVerified -> { + onShowSnackBar("맞음") + } + } + } + } + + ProfileEditorScreen( profileInfo = ChangingProfileInfo(profileInfo.nickname, profileInfo.profileImage), onClickBackButton = { @@ -103,6 +125,10 @@ fun ProfileEditorRoute( onNavigateToGallery = onNavigateToGallery, onEditCompleted = {}, onNickNameChanged = viewModel::onNickNameChanged, + onKeyBoardTimerEnd = { + viewModel.verifyNickname() + }, + isNicknameError = false, ) } @@ -115,6 +141,8 @@ fun ProfileEditorScreen( onNavigateToGallery: () -> Unit, onEditCompleted: () -> Unit, onNickNameChanged: (String) -> Unit, + onKeyBoardTimerEnd: () -> Unit, + isNicknameError: Boolean, ) { Column( verticalArrangement = Arrangement.SpaceBetween, @@ -151,10 +179,12 @@ fun ProfileEditorScreen( ) Spacer(modifier = modifier.height(16.dp)) NickNameTextField( - nickname = profileInfo.nickname, + nickname = profileInfo.nickname.value, onNickNameChanged = { onNickNameChanged(it) }, + onKeyBoardTimerEnd = onKeyBoardTimerEnd, + isNicknameVerified = isNicknameError, ) } @@ -222,16 +252,26 @@ private fun ProfileImage( private fun NickNameTextField( modifier: Modifier = Modifier, nickname: String, + isNicknameVerified: Boolean, onNickNameChanged: (String) -> Unit, + onKeyBoardTimerEnd: () -> Unit, ) { val interactionSource = remember { MutableInteractionSource() } + LaunchedEffect(nickname) { + delay(1.seconds) + onKeyBoardTimerEnd() + } + Column( modifier = modifier .width(140.dp), horizontalAlignment = Alignment.CenterHorizontally, ) { BasicTextField( - value = nickname, onValueChange = onNickNameChanged, + value = nickname, + onValueChange = { + onNickNameChanged(it) + }, modifier = modifier.fillMaxWidth(), enabled = true, textStyle = WithpeaceTheme.typography.body.copy(textAlign = TextAlign.Center), @@ -393,6 +433,8 @@ fun ProfileEditorPreview() { onNavigateToGallery = {}, onEditCompleted = {}, onNickNameChanged = {}, + onKeyBoardTimerEnd = {}, + isNicknameError = false, ) } } diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt index 1fb0fa1a..766d997f 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt @@ -2,20 +2,25 @@ package com.app.profileeditor import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import com.app.profileeditor.navigation.PROFILE_IMAGE_URL_ARGUMENT import com.app.profileeditor.navigation.PROFILE_NICKNAME_ARGUMENT +import com.withpeace.withpeace.core.domain.model.WithPeaceError import com.withpeace.withpeace.core.domain.model.profile.ChangingProfileInfo -import com.withpeace.withpeace.core.domain.repository.UserRepository +import com.withpeace.withpeace.core.domain.usecase.VerifyNicknameUseCase import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel class ProfileEditorViewModel @Inject constructor( savedStateHandle: SavedStateHandle, - private val userRepository: UserRepository, + private val verifyNicknameUseCase: VerifyNicknameUseCase, ) : ViewModel() { val baseProfileInfo = ChangingProfileInfo( nickname = savedStateHandle.get(PROFILE_NICKNAME_ARGUMENT) ?: "", @@ -28,11 +33,14 @@ class ProfileEditorViewModel @Inject constructor( ) val profileEditUiState = _profileEditUiState.asStateFlow() + private val _profileEditUiEvent = Channel() + val profileEditUiEvent = _profileEditUiEvent.receiveAsFlow() + fun onImageChanged(imageUri: String) { _profileEditUiState.update { val updateData = ProfileEditUiState.Editing( (it as? ProfileEditUiState.Editing)?.nickname - ?: baseProfileInfo.nickname, + ?: baseProfileInfo.nickname.value, profileImage = imageUri, isBasicTextValid = false, ) @@ -50,23 +58,57 @@ class ProfileEditorViewModel @Inject constructor( profileImage = (it as? ProfileEditUiState.Editing)?.profileImage ?: baseProfileInfo.profileImage ?: "default.png", isBasicTextValid = false, - ) + ) // Editing 중이면 값을 갱신, 아닐 경우 기본 값에 nickname만 값을 추가 if (baseProfileInfo.isSameTo(updateData.nickname, updateData.profileImage)) { return@update ProfileEditUiState.NoChanges - } + } // 변경 값이 기본 값일 경우 noChanges 상태 updateData } - // verify + } + + fun verifyNickname() { + if (profileEditUiState.value is ProfileEditUiState.NoChanges) { + return + } + viewModelScope.launch { + verifyNicknameUseCase( + onError = { error -> + this.launch { + _profileEditUiEvent.send( + when (error) { + is WithPeaceError.GeneralError -> { + when (error.code) { + 1 -> ProfileEditUiEvent.ShowInvalidFormat + 2 -> ProfileEditUiEvent.ShowDuplicate + else -> ProfileEditUiEvent.ShowFailure + } + } + + else -> ProfileEditUiEvent.ShowFailure + }, + ) + } + }, + nickname = (profileEditUiState.value as ProfileEditUiState.Editing).nickname, + ).collect { verified -> + if (verified) { + _profileEditUiEvent.send( + ProfileEditUiEvent.ShowNicknameVerified, + ) + } + } + } } fun updateProfile() { if (_profileEditUiState.value is ProfileEditUiState.NoChanges) { - + viewModelScope.launch { + _profileEditUiEvent.send(ProfileEditUiEvent.ShowUnchanged) + } } } } -// 1. 다이어로그 -// 2. 하단 문구 및 오류 표시 -// 3. updateProfileUsecase() 3개 있어야겠다. ㅋㅋ -// 4. 오류 표시 -// 5, 이전 화면 갱신 \ No newline at end of file +// 1. 하단 문구 및 오류 표시 +// 2. updateProfileUsecase() 3개 있어야겠다. ㅋㅋ +// 3. 오류 표시 +// 4, 이전 화면 갱신 \ No newline at end of file From 9cb9deee870c4ab28529c0de38acc6922eafc873 Mon Sep 17 00:00:00 2001 From: kwakjoohyeong Date: Wed, 27 Mar 2024 01:39:23 +0900 Subject: [PATCH 11/35] =?UTF-8?q?feat:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20API=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../withpeace/withpeace/navigation/NavHost.kt | 3 + .../data/repository/DefaultUserRepository.kt | 71 ++++++++++++++++--- .../core/domain/repository/UserRepository.kt | 7 +- .../domain/usecase/UpdateProfileUseCase.kt | 35 +++++++++ .../network/di/request/NicknameRequest.kt | 8 +++ .../core/network/di/service/UserService.kt | 14 +++- .../withpeace/feature/mypage/MyPageScreen.kt | 11 ++- .../feature/mypage/MyPageViewModel.kt | 6 +- .../app/profileeditor/ProfileEditUiEvent.kt | 1 + .../app/profileeditor/ProfileEditUiState.kt | 1 - .../app/profileeditor/ProfileEditorScreen.kt | 61 ++++++++++++---- .../profileeditor/ProfileEditorViewModel.kt | 28 +++++++- .../ProfileNicknameValidUiState.kt | 7 ++ .../navigation/ProfileEditorNavigation.kt | 11 ++- .../src/main/res/values/strings.xml | 1 + 15 files changed, 222 insertions(+), 43 deletions(-) create mode 100644 core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/UpdateProfileUseCase.kt create mode 100644 core/network/src/main/java/com/withpeace/withpeace/core/network/di/request/NicknameRequest.kt create mode 100644 feature/profileeditor/src/main/java/com/app/profileeditor/ProfileNicknameValidUiState.kt diff --git a/app/src/main/java/com/withpeace/withpeace/navigation/NavHost.kt b/app/src/main/java/com/withpeace/withpeace/navigation/NavHost.kt index caa83781..a8d1e039 100644 --- a/app/src/main/java/com/withpeace/withpeace/navigation/NavHost.kt +++ b/app/src/main/java/com/withpeace/withpeace/navigation/NavHost.kt @@ -73,6 +73,9 @@ fun WithpeaceNavHost( onNavigateToGallery = { navController.navigateToGallery(imageLimit = 1) }, + onUpdateSuccess = { + navController.popBackStack() + } ) } } diff --git a/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultUserRepository.kt b/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultUserRepository.kt index f2921d61..8bea7246 100644 --- a/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultUserRepository.kt +++ b/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultUserRepository.kt @@ -1,20 +1,32 @@ package com.withpeace.withpeace.core.data.repository +import android.content.Context +import android.net.Uri +import android.util.Log import com.skydoves.sandwich.messageOrNull import com.skydoves.sandwich.suspendMapSuccess import com.skydoves.sandwich.suspendOnError import com.skydoves.sandwich.suspendOnException import com.withpeace.withpeace.core.data.mapper.toDomain +import com.withpeace.withpeace.core.data.util.convertToFile import com.withpeace.withpeace.core.domain.model.WithPeaceError import com.withpeace.withpeace.core.domain.model.profile.Nickname import com.withpeace.withpeace.core.domain.model.profile.ProfileInfo import com.withpeace.withpeace.core.domain.repository.UserRepository +import com.withpeace.withpeace.core.network.di.request.NicknameRequest import com.withpeace.withpeace.core.network.di.service.UserService +import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.MultipartBody +import okhttp3.RequestBody.Companion.asRequestBody +import okhttp3.RequestBody.Companion.toRequestBody +import java.io.File import javax.inject.Inject class DefaultUserRepository @Inject constructor( + @ApplicationContext private val context: Context, private val userService: UserService, ) : UserRepository { override fun getProfile( @@ -38,23 +50,62 @@ class DefaultUserRepository @Inject constructor( TODO("Not yet implemented") } - override fun updateProfileImage( + override fun updateProfile( + nickname: String, profileImage: String, onError: (WithPeaceError) -> Unit, - ): Flow { - TODO("Not yet implemented") + ): Flow = flow { + val requestFile: File = Uri.parse(profileImage).convertToFile(context) + val imageRequestBody = requestFile.asRequestBody("image/*".toMediaTypeOrNull()) + val imagePart = MultipartBody.Part.createFormData( + "imageFile", + requestFile.name, + imageRequestBody, + ) + userService.updateProfile( + nickname.toRequestBody("text/plain".toMediaTypeOrNull()), imagePart, + ).suspendMapSuccess { + emit(Unit) + }.suspendOnError { + if (statusCode.code == 401) onError(WithPeaceError.UnAuthorized()) + else onError(WithPeaceError.GeneralError(statusCode.code, messageOrNull)) + }.suspendOnException { + onError(WithPeaceError.GeneralError(message = messageOrNull)) + } } - override fun updateNickname(nickname: String, onError: (WithPeaceError) -> Unit): Flow { - TODO("Not yet implemented") - } + override fun updateNickname(nickname: String, onError: (WithPeaceError) -> Unit): Flow = + flow { + userService.updateNickname(NicknameRequest(nickname)).suspendMapSuccess { + emit(Unit) + }.suspendOnError { + if (statusCode.code == 401) onError(WithPeaceError.UnAuthorized()) + else onError(WithPeaceError.GeneralError(statusCode.code, messageOrNull)) + }.suspendOnException { + onError(WithPeaceError.GeneralError(message = messageOrNull)) + } + } - override fun updateProfile( - nickname: String, + override fun updateProfileImage( profileImage: String, onError: (WithPeaceError) -> Unit, - ): Flow { - TODO("Not yet implemented") + ): Flow = flow { + val requestFile: File = Uri.parse(profileImage).convertToFile(context) + val imageRequestBody = requestFile.asRequestBody("image/*".toMediaTypeOrNull()) + val imagePart = MultipartBody.Part.createFormData( + "imageFile", + requestFile.name, + imageRequestBody, + ) + userService.updateImage(imagePart).suspendMapSuccess { + + emit(Unit) + }.suspendOnError { + if (statusCode.code == 401) onError(WithPeaceError.UnAuthorized()) + else onError(WithPeaceError.GeneralError(statusCode.code, messageOrNull)) + }.suspendOnException { + onError(WithPeaceError.GeneralError(message = messageOrNull)) + } } override fun verifyNicknameDuplicated( diff --git a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/repository/UserRepository.kt b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/repository/UserRepository.kt index e4694ebe..fa7dd398 100644 --- a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/repository/UserRepository.kt +++ b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/repository/UserRepository.kt @@ -1,6 +1,7 @@ package com.withpeace.withpeace.core.domain.repository import com.withpeace.withpeace.core.domain.model.WithPeaceError +import com.withpeace.withpeace.core.domain.model.profile.ChangingProfileInfo import com.withpeace.withpeace.core.domain.model.profile.Nickname import com.withpeace.withpeace.core.domain.model.profile.ProfileInfo import kotlinx.coroutines.flow.Flow @@ -15,17 +16,17 @@ interface UserRepository { fun updateProfileImage( profileImage: String, onError: (WithPeaceError) -> Unit, - ): Flow + ): Flow fun updateNickname( nickname: String, onError: (WithPeaceError) -> Unit, - ): Flow + ): Flow fun updateProfile( nickname: String, profileImage: String, onError: (WithPeaceError) -> Unit, - ): Flow + ): Flow fun verifyNicknameDuplicated( nickname: Nickname, diff --git a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/UpdateProfileUseCase.kt b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/UpdateProfileUseCase.kt new file mode 100644 index 00000000..b3af66ed --- /dev/null +++ b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/UpdateProfileUseCase.kt @@ -0,0 +1,35 @@ +package com.withpeace.withpeace.core.domain.usecase + +import com.withpeace.withpeace.core.domain.model.WithPeaceError +import com.withpeace.withpeace.core.domain.model.profile.ChangingProfileInfo +import com.withpeace.withpeace.core.domain.repository.UserRepository +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject + +class UpdateProfileUseCase @Inject constructor( + private val userRepository: UserRepository, +) { + operator fun invoke( + beforeProfile: ChangingProfileInfo, + afterProfile: ChangingProfileInfo, + onError: (WithPeaceError) -> Unit, + ): Flow { + println("test test test test") + return if (beforeProfile.profileImage != afterProfile.profileImage + && afterProfile.nickname != beforeProfile.nickname && afterProfile.profileImage != null + ) { // 이미지 닉네임 둘 다 변경 되었을 때 + userRepository.updateProfile( + afterProfile.nickname.value, afterProfile.profileImage, + onError = onError, + ) + } else if (afterProfile.profileImage != null && beforeProfile.profileImage != afterProfile.profileImage + ) { // 이미지만 변경되었을 때 + userRepository.updateProfileImage( + profileImage = afterProfile.profileImage, + onError = onError, + ) + } else { // 닉네임만 변경 되었을 때 + userRepository.updateNickname(afterProfile.nickname.value, onError = onError) + } + } +} \ No newline at end of file diff --git a/core/network/src/main/java/com/withpeace/withpeace/core/network/di/request/NicknameRequest.kt b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/request/NicknameRequest.kt new file mode 100644 index 00000000..db523e55 --- /dev/null +++ b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/request/NicknameRequest.kt @@ -0,0 +1,8 @@ +package com.withpeace.withpeace.core.network.di.request + +import kotlinx.serialization.Serializable + +@Serializable +data class NicknameRequest( + val nickname: String, +) \ No newline at end of file diff --git a/core/network/src/main/java/com/withpeace/withpeace/core/network/di/service/UserService.kt b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/service/UserService.kt index 995bdef5..21b2bff8 100644 --- a/core/network/src/main/java/com/withpeace/withpeace/core/network/di/service/UserService.kt +++ b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/service/UserService.kt @@ -1,10 +1,14 @@ package com.withpeace.withpeace.core.network.di.service import com.skydoves.sandwich.ApiResponse +import com.withpeace.withpeace.core.network.di.request.NicknameRequest import com.withpeace.withpeace.core.network.di.response.BaseResponse import com.withpeace.withpeace.core.network.di.response.ProfileResponse import okhttp3.MultipartBody +import okhttp3.RequestBody +import retrofit2.http.Body import retrofit2.http.GET +import retrofit2.http.Multipart import retrofit2.http.PATCH import retrofit2.http.PUT import retrofit2.http.Part @@ -15,11 +19,19 @@ interface UserService { suspend fun getProfile(): ApiResponse> @PATCH("/api/v1/users/profile/nickname") - suspend fun updateNickname(nickname: String): ApiResponse> + suspend fun updateNickname(@Body nicknameRequest: NicknameRequest): ApiResponse> + @Multipart @PATCH("/api/v1/users/profile/image") suspend fun updateImage(@Part imageFile: MultipartBody.Part): ApiResponse> + @Multipart + @PUT("/api/v1/users/profile") + suspend fun updateProfile( + @Part nickname: RequestBody, + @Part imageFile: MultipartBody.Part, + ): ApiResponse> + @GET("/api/v1/users/profile/nickname/check") suspend fun isNicknameDuplicate(@Query("nickname") nickname: String): ApiResponse> } diff --git a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageScreen.kt b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageScreen.kt index 8b1216ee..f7f335c5 100644 --- a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageScreen.kt +++ b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageScreen.kt @@ -17,9 +17,11 @@ import androidx.compose.material3.Divider import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -48,9 +50,12 @@ fun MyPageRoute( onShowSnackBar(stringResource(R.string.network_failure_message)) } } + LaunchedEffect(Unit){ + viewModel.getProfile() + } val profileInfo = (mypageUiState as? MyPageUiState.Success)?.profileInfo ?: ProfileInfo( - "nickname", - "profile", + "", + "default.png", "", ) // TODO("없을 시 로컬에서 가지고 온다.") MyPageScreen( @@ -89,7 +94,7 @@ fun MyPageScreen( shape = CircleShape, ) GlideImage( - modifier = imageModifier, + modifier = imageModifier.clip(CircleShape), imageModel = { profileInfo.profileImageUrl }, failure = { Image( diff --git a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageViewModel.kt b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageViewModel.kt index f6371715..3fc65b9a 100644 --- a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageViewModel.kt +++ b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageViewModel.kt @@ -18,11 +18,7 @@ class MyPageViewModel @Inject constructor( private val _myPageUiState = MutableStateFlow(MyPageUiState.Loading) val myPageUiState = _myPageUiState.asStateFlow() - init { - getProfile() - } - - private fun getProfile() { + fun getProfile() { viewModelScope.launch { userRepository.getProfile { error -> }.collect { profileInfo -> diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditUiEvent.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditUiEvent.kt index 48bc16f6..4d0665ff 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditUiEvent.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditUiEvent.kt @@ -2,6 +2,7 @@ package com.app.profileeditor sealed interface ProfileEditUiEvent { data object ShowDuplicate : ProfileEditUiEvent + data object ShowDuplicateSnackBar : ProfileEditUiEvent data object ShowNicknameVerified : ProfileEditUiEvent data object ShowInvalidFormat : ProfileEditUiEvent data object ShowUpdateSuccess : ProfileEditUiEvent diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditUiState.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditUiState.kt index b09a2efa..9e26752d 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditUiState.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditUiState.kt @@ -4,7 +4,6 @@ sealed interface ProfileEditUiState { data class Editing( val nickname: String, val profileImage: String, - val isBasicTextValid: Boolean ) : ProfileEditUiState data object NoChanges : ProfileEditUiState diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt index 97e41586..001d1fe9 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt @@ -64,6 +64,7 @@ fun ProfileEditorRoute( onClickBackButton: () -> Unit, onNavigateToGallery: () -> Unit, viewModel: ProfileEditorViewModel, + onUpdateSuccess: () -> Unit, ) { var showAlertDialog by remember { mutableStateOf(false) } val profileUiState: ProfileEditUiState by viewModel.profileEditUiState.collectAsStateWithLifecycle() @@ -74,7 +75,8 @@ fun ProfileEditorRoute( ChangingProfileInfo(nickname = editing.nickname, profileImage = editing.profileImage) } - ProfileEditUiState.NoChanges -> { + is ProfileEditUiState.NoChanges -> { + viewModel.updateIsNicknameValidStatus(ProfileNicknameValidUiState.Valid) viewModel.baseProfileInfo } } @@ -98,15 +100,32 @@ fun ProfileEditorRoute( viewModel.profileEditUiEvent.collect { when (it) { ProfileEditUiEvent.ShowDuplicate -> { - onShowSnackBar("중복") + viewModel.updateIsNicknameValidStatus(ProfileNicknameValidUiState.InValidDuplicated) + } + + ProfileEditUiEvent.ShowDuplicateSnackBar -> { + onShowSnackBar("중복된 닉네임입니다.") + } + + ProfileEditUiEvent.ShowFailure -> { + onShowSnackBar("서버와 통신 중 오류가 발생했습니다.") + } + + ProfileEditUiEvent.ShowInvalidFormat -> { + viewModel.updateIsNicknameValidStatus(ProfileNicknameValidUiState.InValidFormat) + } + + ProfileEditUiEvent.ShowUnchanged -> { + onShowSnackBar("수정사항이 없습니다.") + } + + ProfileEditUiEvent.ShowUpdateSuccess -> { + onShowSnackBar("변경되었습니다.") + onUpdateSuccess() } - ProfileEditUiEvent.ShowFailure -> {} - ProfileEditUiEvent.ShowInvalidFormat -> {} - ProfileEditUiEvent.ShowUnchanged -> {} - ProfileEditUiEvent.ShowUpdateSuccess -> {} ProfileEditUiEvent.ShowNicknameVerified -> { - onShowSnackBar("맞음") + viewModel.updateIsNicknameValidStatus(ProfileNicknameValidUiState.Valid) } } } @@ -123,12 +142,14 @@ fun ProfileEditorRoute( } }, onNavigateToGallery = onNavigateToGallery, - onEditCompleted = {}, + onEditCompleted = { + viewModel.updateProfile() + }, onNickNameChanged = viewModel::onNickNameChanged, onKeyBoardTimerEnd = { viewModel.verifyNickname() }, - isNicknameError = false, + nicknameValidStatus = viewModel.profileNicknameValidUiState.collectAsStateWithLifecycle().value, ) } @@ -142,7 +163,7 @@ fun ProfileEditorScreen( onEditCompleted: () -> Unit, onNickNameChanged: (String) -> Unit, onKeyBoardTimerEnd: () -> Unit, - isNicknameError: Boolean, + nicknameValidStatus: ProfileNicknameValidUiState, ) { Column( verticalArrangement = Arrangement.SpaceBetween, @@ -184,7 +205,7 @@ fun ProfileEditorScreen( onNickNameChanged(it) }, onKeyBoardTimerEnd = onKeyBoardTimerEnd, - isNicknameVerified = isNicknameError, + nicknameValidStatus = nicknameValidStatus, ) } @@ -252,7 +273,7 @@ private fun ProfileImage( private fun NickNameTextField( modifier: Modifier = Modifier, nickname: String, - isNicknameVerified: Boolean, + nicknameValidStatus: ProfileNicknameValidUiState, onNickNameChanged: (String) -> Unit, onKeyBoardTimerEnd: () -> Unit, ) { @@ -306,12 +327,22 @@ private fun NickNameTextField( ) } Divider( - color = WithpeaceTheme.colors.SystemBlack, + color = if (nicknameValidStatus is ProfileNicknameValidUiState.Valid) WithpeaceTheme.colors.SystemBlack + else WithpeaceTheme.colors.SystemError, modifier = modifier .width(140.dp) .height(1.dp), ) - Text(text = "", modifier = modifier.padding(top = 4.dp)) + } + if (nicknameValidStatus !is ProfileNicknameValidUiState.Valid) { + Text( + text = if (nicknameValidStatus is ProfileNicknameValidUiState.InValidDuplicated) stringResource( + R.string.nickname_duplicated, + ) else stringResource(id = R.string.nickname_policy), + style = WithpeaceTheme.typography.caption, + color = WithpeaceTheme.colors.SystemError, + modifier = modifier.padding(top = 4.dp), + ) } } @@ -434,7 +465,7 @@ fun ProfileEditorPreview() { onEditCompleted = {}, onNickNameChanged = {}, onKeyBoardTimerEnd = {}, - isNicknameError = false, + nicknameValidStatus = ProfileNicknameValidUiState.Valid, ) } } diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt index 766d997f..7570313d 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt @@ -7,6 +7,7 @@ import com.app.profileeditor.navigation.PROFILE_IMAGE_URL_ARGUMENT import com.app.profileeditor.navigation.PROFILE_NICKNAME_ARGUMENT import com.withpeace.withpeace.core.domain.model.WithPeaceError import com.withpeace.withpeace.core.domain.model.profile.ChangingProfileInfo +import com.withpeace.withpeace.core.domain.usecase.UpdateProfileUseCase import com.withpeace.withpeace.core.domain.usecase.VerifyNicknameUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.channels.Channel @@ -21,6 +22,7 @@ import javax.inject.Inject class ProfileEditorViewModel @Inject constructor( savedStateHandle: SavedStateHandle, private val verifyNicknameUseCase: VerifyNicknameUseCase, + private val updateProfileUseCase: UpdateProfileUseCase, ) : ViewModel() { val baseProfileInfo = ChangingProfileInfo( nickname = savedStateHandle.get(PROFILE_NICKNAME_ARGUMENT) ?: "", @@ -36,13 +38,16 @@ class ProfileEditorViewModel @Inject constructor( private val _profileEditUiEvent = Channel() val profileEditUiEvent = _profileEditUiEvent.receiveAsFlow() + private val _profileNicknameValidUiState = + MutableStateFlow(ProfileNicknameValidUiState.Valid) + val profileNicknameValidUiState = _profileNicknameValidUiState.asStateFlow() + fun onImageChanged(imageUri: String) { _profileEditUiState.update { val updateData = ProfileEditUiState.Editing( (it as? ProfileEditUiState.Editing)?.nickname ?: baseProfileInfo.nickname.value, profileImage = imageUri, - isBasicTextValid = false, ) if (baseProfileInfo.isSameTo(updateData.nickname, updateData.profileImage)) { return@update ProfileEditUiState.NoChanges @@ -57,7 +62,6 @@ class ProfileEditorViewModel @Inject constructor( nickname = nickname, profileImage = (it as? ProfileEditUiState.Editing)?.profileImage ?: baseProfileInfo.profileImage ?: "default.png", - isBasicTextValid = false, ) // Editing 중이면 값을 갱신, 아닐 경우 기본 값에 nickname만 값을 추가 if (baseProfileInfo.isSameTo(updateData.nickname, updateData.profileImage)) { return@update ProfileEditUiState.NoChanges @@ -100,15 +104,33 @@ class ProfileEditorViewModel @Inject constructor( } } + fun updateIsNicknameValidStatus(status: ProfileNicknameValidUiState) { + _profileNicknameValidUiState.update { status } + } + fun updateProfile() { if (_profileEditUiState.value is ProfileEditUiState.NoChanges) { viewModelScope.launch { _profileEditUiEvent.send(ProfileEditUiEvent.ShowUnchanged) } + } else if (_profileEditUiState.value is ProfileEditUiState.Editing) { + val editing = _profileEditUiState.value as ProfileEditUiState.Editing + viewModelScope.launch { + updateProfileUseCase( + beforeProfile = baseProfileInfo, + afterProfile = ChangingProfileInfo(editing.nickname, editing.profileImage), + onError = { + this.launch { + _profileEditUiEvent.send(ProfileEditUiEvent.ShowFailure) + } + }, + ).collect { + _profileEditUiEvent.send(ProfileEditUiEvent.ShowUpdateSuccess) + } + } } } } -// 1. 하단 문구 및 오류 표시 // 2. updateProfileUsecase() 3개 있어야겠다. ㅋㅋ // 3. 오류 표시 // 4, 이전 화면 갱신 \ No newline at end of file diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileNicknameValidUiState.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileNicknameValidUiState.kt new file mode 100644 index 00000000..90860530 --- /dev/null +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileNicknameValidUiState.kt @@ -0,0 +1,7 @@ +package com.app.profileeditor + +sealed interface ProfileNicknameValidUiState { + data object Valid : ProfileNicknameValidUiState + data object InValidFormat : ProfileNicknameValidUiState + data object InValidDuplicated : ProfileNicknameValidUiState +} \ No newline at end of file diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/navigation/ProfileEditorNavigation.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/navigation/ProfileEditorNavigation.kt index 2fee48a3..15c92669 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/navigation/ProfileEditorNavigation.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/navigation/ProfileEditorNavigation.kt @@ -9,6 +9,8 @@ import androidx.navigation.compose.composable import androidx.navigation.navArgument import com.app.profileeditor.ProfileEditorRoute import com.app.profileeditor.ProfileEditorViewModel +import java.net.URLEncoder +import java.nio.charset.StandardCharsets const val PROFILE_EDITOR_ROUTE = "profileEditorRoute" const val IMAGE_LIST_ARGUMENT = "image_list_argument" @@ -22,13 +24,15 @@ fun NavController.navigateProfileEditor( nickname: String?, profileImageUrl: String?, ) { - navigate("$PROFILE_EDITOR_ROUTE/${nickname ?: ""}/${profileImageUrl ?: ""}", navOptions) + val encodedUrl = URLEncoder.encode(profileImageUrl, StandardCharsets.UTF_8.toString()) + navigate("$PROFILE_EDITOR_ROUTE/${nickname ?: ""}/${encodedUrl ?: ""}", navOptions) } fun NavGraphBuilder.profileEditorNavGraph( onShowSnackBar: (message: String) -> Unit, onClickBackButton: () -> Unit, onNavigateToGallery: () -> Unit, + onUpdateSuccess: () -> Unit, ) { composable( route = PROFILE_EDITOR_ROUTE_WITH_ARGUMENT, @@ -40,12 +44,15 @@ fun NavGraphBuilder.profileEditorNavGraph( val selectedImageUri = entry.savedStateHandle.get>(IMAGE_LIST_ARGUMENT) ?: emptyList() val viewModel: ProfileEditorViewModel = hiltViewModel() - viewModel.onImageChanged(imageUri = selectedImageUri.firstOrNull() ?: "default.png") + if (selectedImageUri.isNotEmpty()) { + viewModel.onImageChanged(imageUri = selectedImageUri.firstOrNull() ?: "default.png") + } ProfileEditorRoute( onShowSnackBar = onShowSnackBar, onClickBackButton = onClickBackButton, onNavigateToGallery = onNavigateToGallery, viewModel = viewModel, + onUpdateSuccess = onUpdateSuccess, ) } } \ No newline at end of file diff --git a/feature/profileeditor/src/main/res/values/strings.xml b/feature/profileeditor/src/main/res/values/strings.xml index 6ec18c1b..357c2965 100644 --- a/feature/profileeditor/src/main/res/values/strings.xml +++ b/feature/profileeditor/src/main/res/values/strings.xml @@ -7,4 +7,5 @@ 나가기 저장하기 닉네임을 입력하세요 + 중복된 닉네임입니다. \ No newline at end of file From 103b89e8323e5cda4c70591cc7e2486c9b7d64cc Mon Sep 17 00:00:00 2001 From: kwakjoohyeong Date: Wed, 27 Mar 2024 15:39:19 +0900 Subject: [PATCH 12/35] =?UTF-8?q?fix:=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=EC=8B=9C=20=EA=B8=B0=EB=B3=B8=20=EB=8B=89?= =?UTF-8?q?=EB=84=A4=EC=9E=84=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/withpeace/withpeace/feature/mypage/MyPageScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageScreen.kt b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageScreen.kt index f7f335c5..b4671c6d 100644 --- a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageScreen.kt +++ b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageScreen.kt @@ -54,7 +54,7 @@ fun MyPageRoute( viewModel.getProfile() } val profileInfo = (mypageUiState as? MyPageUiState.Success)?.profileInfo ?: ProfileInfo( - "", + "nickname", "default.png", "", ) // TODO("없을 시 로컬에서 가지고 온다.") From 59094ea60c6a66032238ee8b655d76d0418bd0fb Mon Sep 17 00:00:00 2001 From: kwakjoohyeong Date: Wed, 27 Mar 2024 21:36:54 +0900 Subject: [PATCH 13/35] =?UTF-8?q?fix:=20=ED=94=84=EB=A1=9C=ED=95=84=20Put?= =?UTF-8?q?=20=EC=9A=94=EC=B2=AD=20=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../withpeace/withpeace/core/network/di/service/UserService.kt | 2 +- .../main/java/com/app/profileeditor/ProfileEditorViewModel.kt | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/core/network/src/main/java/com/withpeace/withpeace/core/network/di/service/UserService.kt b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/service/UserService.kt index 21b2bff8..85252e49 100644 --- a/core/network/src/main/java/com/withpeace/withpeace/core/network/di/service/UserService.kt +++ b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/service/UserService.kt @@ -28,7 +28,7 @@ interface UserService { @Multipart @PUT("/api/v1/users/profile") suspend fun updateProfile( - @Part nickname: RequestBody, + @Part("nickname") nickname: RequestBody, @Part imageFile: MultipartBody.Part, ): ApiResponse> diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt index 7570313d..9495d179 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt @@ -131,6 +131,3 @@ class ProfileEditorViewModel @Inject constructor( } } } -// 2. updateProfileUsecase() 3개 있어야겠다. ㅋㅋ -// 3. 오류 표시 -// 4, 이전 화면 갱신 \ No newline at end of file From ba3cd48f6c6db1fc2b56ee6cd373356af641b210 Mon Sep 17 00:00:00 2001 From: kwakjoohyeong Date: Thu, 28 Mar 2024 01:32:06 +0900 Subject: [PATCH 14/35] =?UTF-8?q?feat:=20changedProfile=20domain=20?= =?UTF-8?q?=EB=AA=A8=EB=8D=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/data/mapper/ChangedProfileMapper.kt | 11 +++++++++++ .../core/domain/model/profile/ChangedProfile.kt | 6 ++++++ .../network/di/response/ChangedProfileResponse.kt | 6 ++++++ 3 files changed, 23 insertions(+) create mode 100644 core/data/src/main/kotlin/com/withpeace/withpeace/core/data/mapper/ChangedProfileMapper.kt create mode 100644 core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ChangedProfile.kt create mode 100644 core/network/src/main/java/com/withpeace/withpeace/core/network/di/response/ChangedProfileResponse.kt diff --git a/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/mapper/ChangedProfileMapper.kt b/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/mapper/ChangedProfileMapper.kt new file mode 100644 index 00000000..0c1b07d9 --- /dev/null +++ b/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/mapper/ChangedProfileMapper.kt @@ -0,0 +1,11 @@ +package com.withpeace.withpeace.core.data.mapper + +import com.withpeace.withpeace.core.domain.model.profile.ChangedProfile +import com.withpeace.withpeace.core.network.di.response.ChangedProfileResponse + +fun ChangedProfileResponse.toDomain(): ChangedProfile { + return ChangedProfile( + nickname = this.nickname, + profileImageUrl = profileImageUrl, + ) +} diff --git a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ChangedProfile.kt b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ChangedProfile.kt new file mode 100644 index 00000000..72ca746d --- /dev/null +++ b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ChangedProfile.kt @@ -0,0 +1,6 @@ +package com.withpeace.withpeace.core.domain.model.profile + +data class ChangedProfile( + val nickname: String, + val profileImageUrl: String, +) diff --git a/core/network/src/main/java/com/withpeace/withpeace/core/network/di/response/ChangedProfileResponse.kt b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/response/ChangedProfileResponse.kt new file mode 100644 index 00000000..6e7f1d5f --- /dev/null +++ b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/response/ChangedProfileResponse.kt @@ -0,0 +1,6 @@ +package com.withpeace.withpeace.core.network.di.response + +data class ChangedProfileResponse( + val nickname: String, + val profileImageUrl: String, +) \ No newline at end of file From 0517e424dff29e1b62673098778752907ebc7e83 Mon Sep 17 00:00:00 2001 From: kwakjoohyeong Date: Fri, 29 Mar 2024 22:55:34 +0900 Subject: [PATCH 15/35] =?UTF-8?q?feat:=20ProfileEdit=20UiModel=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/repository/DefaultUserRepository.kt | 5 +-- .../model/profile/ChangingProfileInfo.kt | 4 --- .../domain/usecase/UpdateProfileUseCase.kt | 1 - .../app/profileeditor/ProfileEditorScreen.kt | 17 ++++++---- .../profileeditor/ProfileEditorViewModel.kt | 33 +++++++++++-------- .../app/profileeditor/ProfileModelMapper.kt | 8 +++++ .../{ => uistate}/ProfileEditUiEvent.kt | 2 +- .../{ => uistate}/ProfileEditUiState.kt | 5 ++- .../ProfileNicknameValidUiState.kt | 2 +- .../profileeditor/uistate/ProfileUiModel.kt | 6 ++++ 10 files changed, 52 insertions(+), 31 deletions(-) create mode 100644 feature/profileeditor/src/main/java/com/app/profileeditor/ProfileModelMapper.kt rename feature/profileeditor/src/main/java/com/app/profileeditor/{ => uistate}/ProfileEditUiEvent.kt (91%) rename feature/profileeditor/src/main/java/com/app/profileeditor/{ => uistate}/ProfileEditUiState.kt (59%) rename feature/profileeditor/src/main/java/com/app/profileeditor/{ => uistate}/ProfileNicknameValidUiState.kt (85%) create mode 100644 feature/profileeditor/src/main/java/com/app/profileeditor/uistate/ProfileUiModel.kt diff --git a/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultUserRepository.kt b/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultUserRepository.kt index 8bea7246..b040d506 100644 --- a/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultUserRepository.kt +++ b/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultUserRepository.kt @@ -2,7 +2,6 @@ package com.withpeace.withpeace.core.data.repository import android.content.Context import android.net.Uri -import android.util.Log import com.skydoves.sandwich.messageOrNull import com.skydoves.sandwich.suspendMapSuccess import com.skydoves.sandwich.suspendOnError @@ -115,8 +114,10 @@ class DefaultUserRepository @Inject constructor( userService.isNicknameDuplicate(nickname.value).suspendMapSuccess { if (this.data == "true") { onError(WithPeaceError.GeneralError(code = 2)) + return@suspendMapSuccess emit(false) + } else { + emit(true) } - emit(true) }.suspendOnError { onError(WithPeaceError.GeneralError(statusCode.code, messageOrNull)) }.suspendOnException { diff --git a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ChangingProfileInfo.kt b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ChangingProfileInfo.kt index 55d10587..1db90ed8 100644 --- a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ChangingProfileInfo.kt +++ b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ChangingProfileInfo.kt @@ -2,10 +2,6 @@ package com.withpeace.withpeace.core.domain.model.profile data class ChangingProfileInfo(val nickname: Nickname, val profileImage: String?) { - fun isSameTo(nickname: String, profileImage: String?): Boolean { - return Nickname.create(nickname) == this.nickname && profileImage == this.profileImage - } - companion object { operator fun invoke(nickname: String, profileImage: String?): ChangingProfileInfo { return ChangingProfileInfo(Nickname.create(nickname), profileImage) diff --git a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/UpdateProfileUseCase.kt b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/UpdateProfileUseCase.kt index b3af66ed..42b95247 100644 --- a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/UpdateProfileUseCase.kt +++ b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/UpdateProfileUseCase.kt @@ -14,7 +14,6 @@ class UpdateProfileUseCase @Inject constructor( afterProfile: ChangingProfileInfo, onError: (WithPeaceError) -> Unit, ): Flow { - println("test test test test") return if (beforeProfile.profileImage != afterProfile.profileImage && afterProfile.nickname != beforeProfile.nickname && afterProfile.profileImage != null ) { // 이미지 닉네임 둘 다 변경 되었을 때 diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt index 001d1fe9..ec0d7a2a 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt @@ -1,5 +1,6 @@ package com.app.profileeditor +import android.util.Log import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.border @@ -49,10 +50,13 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.app.profileeditor.uistate.ProfileEditUiEvent +import com.app.profileeditor.uistate.ProfileEditUiState +import com.app.profileeditor.uistate.ProfileNicknameValidUiState +import com.app.profileeditor.uistate.ProfileUiModel import com.skydoves.landscapist.glide.GlideImage import com.withpeace.withpeace.core.designsystem.theme.WithpeaceTheme import com.withpeace.withpeace.core.designsystem.ui.WithPeaceBackButtonTopAppBar -import com.withpeace.withpeace.core.domain.model.profile.ChangingProfileInfo import com.withpeace.withpeace.core.permission.ImagePermissionHelper import com.withpeace.withpeace.feature.profileeditor.R import kotlinx.coroutines.delay @@ -72,7 +76,7 @@ fun ProfileEditorRoute( val profileInfo = when (profileUiState) { is ProfileEditUiState.Editing -> { val editing = profileUiState as ProfileEditUiState.Editing - ChangingProfileInfo(nickname = editing.nickname, profileImage = editing.profileImage) + ProfileUiModel(nickname = editing.profileInfo.nickname, profileImage = editing.profileInfo.profileImage) } is ProfileEditUiState.NoChanges -> { @@ -133,7 +137,7 @@ fun ProfileEditorRoute( ProfileEditorScreen( - profileInfo = ChangingProfileInfo(profileInfo.nickname, profileInfo.profileImage), + profileInfo = ProfileUiModel(profileInfo.nickname, profileInfo.profileImage), onClickBackButton = { if (profileUiState is ProfileEditUiState.Editing) { showAlertDialog = true @@ -156,7 +160,7 @@ fun ProfileEditorRoute( @Composable fun ProfileEditorScreen( - profileInfo: ChangingProfileInfo, + profileInfo: ProfileUiModel, modifier: Modifier = Modifier, onClickBackButton: () -> Unit, onNavigateToGallery: () -> Unit, @@ -200,7 +204,7 @@ fun ProfileEditorScreen( ) Spacer(modifier = modifier.height(16.dp)) NickNameTextField( - nickname = profileInfo.nickname.value, + nickname = profileInfo.nickname, onNickNameChanged = { onNickNameChanged(it) }, @@ -459,7 +463,7 @@ fun ModifySaveDialogContent( fun ProfileEditorPreview() { WithpeaceTheme { ProfileEditorScreen( - profileInfo = ChangingProfileInfo("nickname", ""), + profileInfo = ProfileUiModel("nickname", ""), onClickBackButton = {}, onNavigateToGallery = {}, onEditCompleted = {}, @@ -469,3 +473,4 @@ fun ProfileEditorPreview() { ) } } +// uiModel 적용, glide 수정, response 값 전달 \ No newline at end of file diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt index 9495d179..2889c38d 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt @@ -5,8 +5,11 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.app.profileeditor.navigation.PROFILE_IMAGE_URL_ARGUMENT import com.app.profileeditor.navigation.PROFILE_NICKNAME_ARGUMENT +import com.app.profileeditor.uistate.ProfileEditUiEvent +import com.app.profileeditor.uistate.ProfileEditUiState +import com.app.profileeditor.uistate.ProfileNicknameValidUiState +import com.app.profileeditor.uistate.ProfileUiModel import com.withpeace.withpeace.core.domain.model.WithPeaceError -import com.withpeace.withpeace.core.domain.model.profile.ChangingProfileInfo import com.withpeace.withpeace.core.domain.usecase.UpdateProfileUseCase import com.withpeace.withpeace.core.domain.usecase.VerifyNicknameUseCase import dagger.hilt.android.lifecycle.HiltViewModel @@ -24,7 +27,7 @@ class ProfileEditorViewModel @Inject constructor( private val verifyNicknameUseCase: VerifyNicknameUseCase, private val updateProfileUseCase: UpdateProfileUseCase, ) : ViewModel() { - val baseProfileInfo = ChangingProfileInfo( + val baseProfileInfo = ProfileUiModel( nickname = savedStateHandle.get(PROFILE_NICKNAME_ARGUMENT) ?: "", profileImage = savedStateHandle.get(PROFILE_IMAGE_URL_ARGUMENT) ?: "default.png", ) // 최초 정보에서 변경사항이 있는지 비교를 위한 필드 @@ -45,11 +48,13 @@ class ProfileEditorViewModel @Inject constructor( fun onImageChanged(imageUri: String) { _profileEditUiState.update { val updateData = ProfileEditUiState.Editing( - (it as? ProfileEditUiState.Editing)?.nickname - ?: baseProfileInfo.nickname.value, - profileImage = imageUri, + ProfileUiModel( + (it as? ProfileEditUiState.Editing)?.profileInfo?.nickname + ?: baseProfileInfo.nickname, + profileImage = imageUri, + ), ) - if (baseProfileInfo.isSameTo(updateData.nickname, updateData.profileImage)) { + if (baseProfileInfo == updateData.profileInfo) { return@update ProfileEditUiState.NoChanges } updateData @@ -59,11 +64,13 @@ class ProfileEditorViewModel @Inject constructor( fun onNickNameChanged(nickname: String) { _profileEditUiState.update { val updateData = ProfileEditUiState.Editing( - nickname = nickname, - profileImage = (it as? ProfileEditUiState.Editing)?.profileImage - ?: baseProfileInfo.profileImage ?: "default.png", + ProfileUiModel( + nickname = nickname, + profileImage = (it as? ProfileEditUiState.Editing)?.profileInfo?.profileImage + ?: baseProfileInfo.profileImage, + ), ) // Editing 중이면 값을 갱신, 아닐 경우 기본 값에 nickname만 값을 추가 - if (baseProfileInfo.isSameTo(updateData.nickname, updateData.profileImage)) { + if (baseProfileInfo == updateData.profileInfo) { return@update ProfileEditUiState.NoChanges } // 변경 값이 기본 값일 경우 noChanges 상태 updateData @@ -93,7 +100,7 @@ class ProfileEditorViewModel @Inject constructor( ) } }, - nickname = (profileEditUiState.value as ProfileEditUiState.Editing).nickname, + nickname = (profileEditUiState.value as ProfileEditUiState.Editing).profileInfo.nickname, ).collect { verified -> if (verified) { _profileEditUiEvent.send( @@ -117,8 +124,8 @@ class ProfileEditorViewModel @Inject constructor( val editing = _profileEditUiState.value as ProfileEditUiState.Editing viewModelScope.launch { updateProfileUseCase( - beforeProfile = baseProfileInfo, - afterProfile = ChangingProfileInfo(editing.nickname, editing.profileImage), + beforeProfile = baseProfileInfo.toDomain(), + afterProfile = editing.profileInfo.toDomain(), onError = { this.launch { _profileEditUiEvent.send(ProfileEditUiEvent.ShowFailure) diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileModelMapper.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileModelMapper.kt new file mode 100644 index 00000000..81a1ef22 --- /dev/null +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileModelMapper.kt @@ -0,0 +1,8 @@ +package com.app.profileeditor + +import com.app.profileeditor.uistate.ProfileUiModel +import com.withpeace.withpeace.core.domain.model.profile.ChangingProfileInfo + +internal fun ProfileUiModel.toDomain(): ChangingProfileInfo { + return ChangingProfileInfo(nickname, profileImage) +} \ No newline at end of file diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditUiEvent.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/uistate/ProfileEditUiEvent.kt similarity index 91% rename from feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditUiEvent.kt rename to feature/profileeditor/src/main/java/com/app/profileeditor/uistate/ProfileEditUiEvent.kt index 4d0665ff..3b9aa465 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditUiEvent.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/uistate/ProfileEditUiEvent.kt @@ -1,4 +1,4 @@ -package com.app.profileeditor +package com.app.profileeditor.uistate sealed interface ProfileEditUiEvent { data object ShowDuplicate : ProfileEditUiEvent diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditUiState.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/uistate/ProfileEditUiState.kt similarity index 59% rename from feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditUiState.kt rename to feature/profileeditor/src/main/java/com/app/profileeditor/uistate/ProfileEditUiState.kt index 9e26752d..0c05ff92 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditUiState.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/uistate/ProfileEditUiState.kt @@ -1,9 +1,8 @@ -package com.app.profileeditor +package com.app.profileeditor.uistate sealed interface ProfileEditUiState { data class Editing( - val nickname: String, - val profileImage: String, + val profileInfo: ProfileUiModel ) : ProfileEditUiState data object NoChanges : ProfileEditUiState diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileNicknameValidUiState.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/uistate/ProfileNicknameValidUiState.kt similarity index 85% rename from feature/profileeditor/src/main/java/com/app/profileeditor/ProfileNicknameValidUiState.kt rename to feature/profileeditor/src/main/java/com/app/profileeditor/uistate/ProfileNicknameValidUiState.kt index 90860530..c594e384 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileNicknameValidUiState.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/uistate/ProfileNicknameValidUiState.kt @@ -1,4 +1,4 @@ -package com.app.profileeditor +package com.app.profileeditor.uistate sealed interface ProfileNicknameValidUiState { data object Valid : ProfileNicknameValidUiState diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/uistate/ProfileUiModel.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/uistate/ProfileUiModel.kt new file mode 100644 index 00000000..fda4f440 --- /dev/null +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/uistate/ProfileUiModel.kt @@ -0,0 +1,6 @@ +package com.app.profileeditor.uistate + +data class ProfileUiModel( + val nickname: String, + val profileImage: String, +) \ No newline at end of file From af3658ad18c2367ecfc30f36deedc9f37475851e Mon Sep 17 00:00:00 2001 From: kwakjoohyeong Date: Fri, 29 Mar 2024 23:11:40 +0900 Subject: [PATCH 16/35] =?UTF-8?q?feat:=20ProfileInfo=20UiModel=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../withpeace/feature/mypage/MyPageScreen.kt | 15 ++++++++------- .../withpeace/feature/mypage/MyPageViewModel.kt | 5 +++-- .../feature/mypage/{ => uistate}/MyPageUiState.kt | 4 ++-- .../feature/mypage/uistate/ProfileInfoMapper.kt | 9 +++++++++ .../feature/mypage/uistate/ProfileInfoUiModel.kt | 7 +++++++ .../com/app/profileeditor/ProfileEditorScreen.kt | 1 - 6 files changed, 29 insertions(+), 12 deletions(-) rename feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/{ => uistate}/MyPageUiState.kt (58%) create mode 100644 feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/uistate/ProfileInfoMapper.kt create mode 100644 feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/uistate/ProfileInfoUiModel.kt diff --git a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageScreen.kt b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageScreen.kt index b4671c6d..bf9b49d5 100644 --- a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageScreen.kt +++ b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageScreen.kt @@ -32,7 +32,8 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.skydoves.landscapist.glide.GlideImage import com.withpeace.withpeace.core.designsystem.theme.WithpeaceTheme import com.withpeace.withpeace.core.designsystem.ui.TitleBar -import com.withpeace.withpeace.core.domain.model.profile.ProfileInfo +import com.withpeace.withpeace.feature.mypage.uistate.MyPageUiState +import com.withpeace.withpeace.feature.mypage.uistate.ProfileInfoUiModel @Composable fun MyPageRoute( @@ -53,14 +54,14 @@ fun MyPageRoute( LaunchedEffect(Unit){ viewModel.getProfile() } - val profileInfo = (mypageUiState as? MyPageUiState.Success)?.profileInfo ?: ProfileInfo( + val profileInfo = (mypageUiState as? MyPageUiState.Success)?.profileInfo ?: ProfileInfoUiModel( "nickname", "default.png", "", ) // TODO("없을 시 로컬에서 가지고 온다.") MyPageScreen( onEditProfile = { - onEditProfile(it.nickname, it.profileImageUrl) + onEditProfile(it.nickname, it.profileImage) }, onLogoutClick = onLogoutClick, onWithdrawClick = onWithdrawClick, @@ -71,10 +72,10 @@ fun MyPageRoute( @Composable fun MyPageScreen( modifier: Modifier = Modifier, - onEditProfile: (ProfileInfo) -> Unit, + onEditProfile: (ProfileInfoUiModel) -> Unit, onLogoutClick: () -> Unit, onWithdrawClick: () -> Unit, - profileInfo: ProfileInfo, + profileInfo: ProfileInfoUiModel, ) { Column( modifier, @@ -95,7 +96,7 @@ fun MyPageScreen( ) GlideImage( modifier = imageModifier.clip(CircleShape), - imageModel = { profileInfo.profileImageUrl }, + imageModel = { profileInfo.profileImage }, failure = { Image( painterResource(id = R.drawable.ic_default_profile), @@ -233,7 +234,7 @@ fun MyPagePreview() { modifier = Modifier, onLogoutClick = {}, onWithdrawClick = {}, - profileInfo = ProfileInfo("닉네임닉네임", "", "abc@gmail.com"), + profileInfo = ProfileInfoUiModel("닉네임닉네임", "", "abc@gmail.com"), ) } } \ No newline at end of file diff --git a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageViewModel.kt b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageViewModel.kt index 3fc65b9a..bba5a6bb 100644 --- a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageViewModel.kt +++ b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageViewModel.kt @@ -3,6 +3,8 @@ package com.withpeace.withpeace.feature.mypage import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.withpeace.withpeace.core.domain.repository.UserRepository +import com.withpeace.withpeace.feature.mypage.uistate.MyPageUiState +import com.withpeace.withpeace.feature.mypage.uistate.toUiModel import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow @@ -24,11 +26,10 @@ class MyPageViewModel @Inject constructor( }.collect { profileInfo -> _myPageUiState.update { MyPageUiState.Success( - profileInfo, + profileInfo.toUiModel(), ) } } } - } } \ No newline at end of file diff --git a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageUiState.kt b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/uistate/MyPageUiState.kt similarity index 58% rename from feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageUiState.kt rename to feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/uistate/MyPageUiState.kt index 823c3641..6c259af1 100644 --- a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageUiState.kt +++ b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/uistate/MyPageUiState.kt @@ -1,9 +1,9 @@ -package com.withpeace.withpeace.feature.mypage +package com.withpeace.withpeace.feature.mypage.uistate import com.withpeace.withpeace.core.domain.model.profile.ProfileInfo sealed interface MyPageUiState { data object Loading : MyPageUiState - data class Success(val profileInfo: ProfileInfo) : MyPageUiState + data class Success(val profileInfo: ProfileInfoUiModel) : MyPageUiState data object Fail : MyPageUiState } \ No newline at end of file diff --git a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/uistate/ProfileInfoMapper.kt b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/uistate/ProfileInfoMapper.kt new file mode 100644 index 00000000..8119791c --- /dev/null +++ b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/uistate/ProfileInfoMapper.kt @@ -0,0 +1,9 @@ +package com.withpeace.withpeace.feature.mypage.uistate + +import com.withpeace.withpeace.core.domain.model.profile.ProfileInfo + +internal fun ProfileInfo.toUiModel(): ProfileInfoUiModel { + return ProfileInfoUiModel( + nickname, profileImageUrl, email, + ) +} \ No newline at end of file diff --git a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/uistate/ProfileInfoUiModel.kt b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/uistate/ProfileInfoUiModel.kt new file mode 100644 index 00000000..ebefae5f --- /dev/null +++ b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/uistate/ProfileInfoUiModel.kt @@ -0,0 +1,7 @@ +package com.withpeace.withpeace.feature.mypage.uistate + +data class ProfileInfoUiModel( + val nickname: String, + val profileImage: String, + val email: String, +) \ No newline at end of file diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt index ec0d7a2a..d2b1ffe2 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt @@ -1,6 +1,5 @@ package com.app.profileeditor -import android.util.Log import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.border From 4d1c73c24068908ced9a179c8a2c0a00010b7ca4 Mon Sep 17 00:00:00 2001 From: kwakjoohyeong Date: Fri, 29 Mar 2024 23:22:31 +0900 Subject: [PATCH 17/35] =?UTF-8?q?faet:=20domain=20nickname=20format=20exce?= =?UTF-8?q?ption=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../withpeace/core/domain/model/profile/Nickname.kt | 6 +++++- .../withpeace/feature/mypage/uistate/MyPageUiState.kt | 2 -- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/Nickname.kt b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/Nickname.kt index c926c19c..f176c00e 100644 --- a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/Nickname.kt +++ b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/Nickname.kt @@ -1,13 +1,17 @@ package com.withpeace.withpeace.core.domain.model.profile /* - Data와 UI에서 모두 쓰일 수 있는 도메인입니다. + Data와 Presentation Layer에서 모두 쓰일 수 있는 도메인입니다. */ @JvmInline value class Nickname private constructor(val value: String) { + init { + check(verifyNickname(value)) { NICKNAME_FORMAT_ERROR } + } companion object { private const val NICKNAME_REGEX_PATTERN = "[a-zA-Z0-9가-힣]{2,10}" + private const val NICKNAME_FORMAT_ERROR = "닉네임 형식이 잘못 되었습니다." fun create(value: String): Nickname = Nickname(value) fun verifyNickname(nickname: String): Boolean { val regex = Regex(NICKNAME_REGEX_PATTERN) diff --git a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/uistate/MyPageUiState.kt b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/uistate/MyPageUiState.kt index 6c259af1..970be4c9 100644 --- a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/uistate/MyPageUiState.kt +++ b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/uistate/MyPageUiState.kt @@ -1,7 +1,5 @@ package com.withpeace.withpeace.feature.mypage.uistate -import com.withpeace.withpeace.core.domain.model.profile.ProfileInfo - sealed interface MyPageUiState { data object Loading : MyPageUiState data class Success(val profileInfo: ProfileInfoUiModel) : MyPageUiState From e314e634dd6f9365e094ed902494c384ff0bdb49 Mon Sep 17 00:00:00 2001 From: kwakjoohyeong Date: Sat, 30 Mar 2024 02:02:40 +0900 Subject: [PATCH 18/35] =?UTF-8?q?refactor:=20image=20part=20=EC=A4=91?= =?UTF-8?q?=EB=B3=B5=EB=A1=9C=EC=A7=81=20=ED=95=A8=EC=88=98=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/repository/DefaultUserRepository.kt | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultUserRepository.kt b/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultUserRepository.kt index b040d506..64cff187 100644 --- a/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultUserRepository.kt +++ b/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultUserRepository.kt @@ -54,13 +54,7 @@ class DefaultUserRepository @Inject constructor( profileImage: String, onError: (WithPeaceError) -> Unit, ): Flow = flow { - val requestFile: File = Uri.parse(profileImage).convertToFile(context) - val imageRequestBody = requestFile.asRequestBody("image/*".toMediaTypeOrNull()) - val imagePart = MultipartBody.Part.createFormData( - "imageFile", - requestFile.name, - imageRequestBody, - ) + val imagePart = getImagePart(profileImage) userService.updateProfile( nickname.toRequestBody("text/plain".toMediaTypeOrNull()), imagePart, ).suspendMapSuccess { @@ -89,15 +83,8 @@ class DefaultUserRepository @Inject constructor( profileImage: String, onError: (WithPeaceError) -> Unit, ): Flow = flow { - val requestFile: File = Uri.parse(profileImage).convertToFile(context) - val imageRequestBody = requestFile.asRequestBody("image/*".toMediaTypeOrNull()) - val imagePart = MultipartBody.Part.createFormData( - "imageFile", - requestFile.name, - imageRequestBody, - ) + val imagePart = getImagePart(profileImage) userService.updateImage(imagePart).suspendMapSuccess { - emit(Unit) }.suspendOnError { if (statusCode.code == 401) onError(WithPeaceError.UnAuthorized()) @@ -124,4 +111,14 @@ class DefaultUserRepository @Inject constructor( onError(WithPeaceError.GeneralError(message = messageOrNull)) } } + + private fun getImagePart(profileImage: String): MultipartBody.Part { + val requestFile: File = Uri.parse(profileImage).convertToFile(context) + val imageRequestBody = requestFile.asRequestBody("image/*".toMediaTypeOrNull()) + return MultipartBody.Part.createFormData( + "imageFile", + requestFile.name, + imageRequestBody, + ) + } } \ No newline at end of file From a519f547da9275e369eabf3ddca91b7c7158a774 Mon Sep 17 00:00:00 2001 From: kwakjoohyeong Date: Sat, 30 Mar 2024 21:40:10 +0900 Subject: [PATCH 19/35] =?UTF-8?q?refactor:=20verifyNickname=20response=20b?= =?UTF-8?q?oolean=EC=9C=BC=EB=A1=9C=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20?= =?UTF-8?q?=ED=95=84=EC=9A=94=EC=97=86=EB=8A=94=20emit=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/data/repository/DefaultUserRepository.kt | 7 +++---- .../withpeace/core/domain/repository/UserRepository.kt | 2 +- .../core/domain/usecase/VerifyNicknameUseCase.kt | 4 ++-- .../withpeace/core/network/di/service/UserService.kt | 2 +- .../com/app/profileeditor/ProfileEditorViewModel.kt | 10 ++++------ 5 files changed, 11 insertions(+), 14 deletions(-) diff --git a/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultUserRepository.kt b/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultUserRepository.kt index 64cff187..f0a14582 100644 --- a/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultUserRepository.kt +++ b/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultUserRepository.kt @@ -97,13 +97,12 @@ class DefaultUserRepository @Inject constructor( override fun verifyNicknameDuplicated( nickname: Nickname, onError: (WithPeaceError) -> Unit, - ): Flow = flow { + ): Flow = flow { userService.isNicknameDuplicate(nickname.value).suspendMapSuccess { - if (this.data == "true") { + if (this.data) { onError(WithPeaceError.GeneralError(code = 2)) - return@suspendMapSuccess emit(false) } else { - emit(true) + emit(Unit) } }.suspendOnError { onError(WithPeaceError.GeneralError(statusCode.code, messageOrNull)) diff --git a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/repository/UserRepository.kt b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/repository/UserRepository.kt index fa7dd398..76601ac0 100644 --- a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/repository/UserRepository.kt +++ b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/repository/UserRepository.kt @@ -31,5 +31,5 @@ interface UserRepository { fun verifyNicknameDuplicated( nickname: Nickname, onError: (WithPeaceError) -> Unit, - ): Flow + ): Flow } diff --git a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/VerifyNicknameUseCase.kt b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/VerifyNicknameUseCase.kt index f06f13d9..e4719dbc 100644 --- a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/VerifyNicknameUseCase.kt +++ b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/VerifyNicknameUseCase.kt @@ -10,10 +10,10 @@ import javax.inject.Inject class VerifyNicknameUseCase @Inject constructor( private val userRepository: UserRepository, ) { - operator fun invoke(nickname: String, onError: (WithPeaceError) -> Unit): Flow { + operator fun invoke(nickname: String, onError: (WithPeaceError) -> Unit): Flow { if (!Nickname.verifyNickname(nickname)) { onError(WithPeaceError.GeneralError(code = 1)) - return flow { emit(false) } + return flow { } } return userRepository.verifyNicknameDuplicated(Nickname.create(nickname), onError = onError) } diff --git a/core/network/src/main/java/com/withpeace/withpeace/core/network/di/service/UserService.kt b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/service/UserService.kt index 85252e49..b227c7be 100644 --- a/core/network/src/main/java/com/withpeace/withpeace/core/network/di/service/UserService.kt +++ b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/service/UserService.kt @@ -33,5 +33,5 @@ interface UserService { ): ApiResponse> @GET("/api/v1/users/profile/nickname/check") - suspend fun isNicknameDuplicate(@Query("nickname") nickname: String): ApiResponse> + suspend fun isNicknameDuplicate(@Query("nickname") nickname: String): ApiResponse> } diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt index 2889c38d..6a346813 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt @@ -101,12 +101,10 @@ class ProfileEditorViewModel @Inject constructor( } }, nickname = (profileEditUiState.value as ProfileEditUiState.Editing).profileInfo.nickname, - ).collect { verified -> - if (verified) { - _profileEditUiEvent.send( - ProfileEditUiEvent.ShowNicknameVerified, - ) - } + ).collect { + _profileEditUiEvent.send( + ProfileEditUiEvent.ShowNicknameVerified, + ) } } } From c0562d30207243b255790247ecd346dadbe18229 Mon Sep 17 00:00:00 2001 From: kwakjoohyeong Date: Sun, 31 Mar 2024 00:05:41 +0900 Subject: [PATCH 20/35] =?UTF-8?q?refactor:=20changingProfileStatus?= =?UTF-8?q?=EB=A5=BC=20=ED=86=B5=ED=95=B4=20=EA=B0=92=EC=9D=84=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=EC=83=81=ED=83=9C=EB=A5=BC=20=ED=99=95=EC=9D=B8?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../model/profile/ProfileChangingStatus.kt | 25 +++++++++++++++ .../domain/usecase/UpdateProfileUseCase.kt | 32 ++++++++++--------- 2 files changed, 42 insertions(+), 15 deletions(-) create mode 100644 core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ProfileChangingStatus.kt diff --git a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ProfileChangingStatus.kt b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ProfileChangingStatus.kt new file mode 100644 index 00000000..f48c3bfb --- /dev/null +++ b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ProfileChangingStatus.kt @@ -0,0 +1,25 @@ +package com.withpeace.withpeace.core.domain.model.profile + +sealed interface ProfileChangingStatus { + data object OnlyNicknameChanging : ProfileChangingStatus + data object OnlyImageChanging : ProfileChangingStatus + data object AllChanging : ProfileChangingStatus + + companion object { + fun getStatus( + beforeProfile: ChangingProfileInfo, + afterProfile: ChangingProfileInfo, + ): ProfileChangingStatus { + return if (beforeProfile.profileImage != afterProfile.profileImage + && afterProfile.nickname != beforeProfile.nickname && afterProfile.profileImage != null + ) { + AllChanging + } else if (afterProfile.profileImage != null && beforeProfile.profileImage != afterProfile.profileImage + ) { + OnlyImageChanging + } else { + OnlyNicknameChanging + } + } + } +} \ No newline at end of file diff --git a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/UpdateProfileUseCase.kt b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/UpdateProfileUseCase.kt index 42b95247..7cb0d1ef 100644 --- a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/UpdateProfileUseCase.kt +++ b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/UpdateProfileUseCase.kt @@ -2,6 +2,7 @@ package com.withpeace.withpeace.core.domain.usecase import com.withpeace.withpeace.core.domain.model.WithPeaceError import com.withpeace.withpeace.core.domain.model.profile.ChangingProfileInfo +import com.withpeace.withpeace.core.domain.model.profile.ProfileChangingStatus import com.withpeace.withpeace.core.domain.repository.UserRepository import kotlinx.coroutines.flow.Flow import javax.inject.Inject @@ -14,21 +15,22 @@ class UpdateProfileUseCase @Inject constructor( afterProfile: ChangingProfileInfo, onError: (WithPeaceError) -> Unit, ): Flow { - return if (beforeProfile.profileImage != afterProfile.profileImage - && afterProfile.nickname != beforeProfile.nickname && afterProfile.profileImage != null - ) { // 이미지 닉네임 둘 다 변경 되었을 때 - userRepository.updateProfile( - afterProfile.nickname.value, afterProfile.profileImage, - onError = onError, - ) - } else if (afterProfile.profileImage != null && beforeProfile.profileImage != afterProfile.profileImage - ) { // 이미지만 변경되었을 때 - userRepository.updateProfileImage( - profileImage = afterProfile.profileImage, - onError = onError, - ) - } else { // 닉네임만 변경 되었을 때 - userRepository.updateNickname(afterProfile.nickname.value, onError = onError) + return when (ProfileChangingStatus.getStatus(beforeProfile, afterProfile)) { + ProfileChangingStatus.AllChanging -> { + userRepository.updateProfile( + afterProfile.nickname.value, afterProfile.profileImage!!, + onError = onError, + ) + } + ProfileChangingStatus.OnlyImageChanging -> { + userRepository.updateProfileImage( + profileImage = afterProfile.profileImage!!, + onError = onError, + ) + } + ProfileChangingStatus.OnlyNicknameChanging -> { + userRepository.updateNickname(afterProfile.nickname.value, onError = onError) + } } } } \ No newline at end of file From b6cb17fa5c21b017c4866b2e067d11346cdfa257 Mon Sep 17 00:00:00 2001 From: kwakjoohyeong Date: Sun, 31 Mar 2024 02:16:21 +0900 Subject: [PATCH 21/35] =?UTF-8?q?refactor:=20getProfile=EC=97=90=EC=84=9C?= =?UTF-8?q?=20onError=20suspend=20=EC=A0=81=EC=9A=A9=20=EB=B0=8F=20?= =?UTF-8?q?=EB=B6=80=EC=88=98=ED=9A=A8=EA=B3=BC=EC=97=90=20=EB=8C=80?= =?UTF-8?q?=ED=95=B4=20channel=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/repository/DefaultUserRepository.kt | 2 +- .../core/domain/repository/UserRepository.kt | 2 +- .../withpeace/feature/mypage/MyPageScreen.kt | 17 ++++++++++++++--- .../withpeace/feature/mypage/MyPageViewModel.kt | 15 +++++++++++++++ .../feature/mypage/uistate/MyPageUiEvent.kt | 6 ++++++ .../feature/mypage/uistate/MyPageUiState.kt | 1 - 6 files changed, 37 insertions(+), 6 deletions(-) create mode 100644 feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/uistate/MyPageUiEvent.kt diff --git a/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultUserRepository.kt b/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultUserRepository.kt index f0a14582..36a02460 100644 --- a/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultUserRepository.kt +++ b/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultUserRepository.kt @@ -29,7 +29,7 @@ class DefaultUserRepository @Inject constructor( private val userService: UserService, ) : UserRepository { override fun getProfile( - onError: (WithPeaceError) -> Unit, + onError: suspend (WithPeaceError) -> Unit, ): Flow = flow { userService.getProfile().suspendMapSuccess { emit(this.data.toDomain()) diff --git a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/repository/UserRepository.kt b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/repository/UserRepository.kt index 76601ac0..f1fcea13 100644 --- a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/repository/UserRepository.kt +++ b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/repository/UserRepository.kt @@ -7,7 +7,7 @@ import com.withpeace.withpeace.core.domain.model.profile.ProfileInfo import kotlinx.coroutines.flow.Flow interface UserRepository { - fun getProfile(onError: (WithPeaceError) -> Unit): Flow + fun getProfile(onError: suspend (WithPeaceError) -> Unit): Flow fun registerProfile( nickname: String, profileImage: String, onError: (WithPeaceError) -> Unit, diff --git a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageScreen.kt b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageScreen.kt index bf9b49d5..f6d8d19b 100644 --- a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageScreen.kt +++ b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageScreen.kt @@ -32,6 +32,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.skydoves.landscapist.glide.GlideImage import com.withpeace.withpeace.core.designsystem.theme.WithpeaceTheme import com.withpeace.withpeace.core.designsystem.ui.TitleBar +import com.withpeace.withpeace.feature.mypage.uistate.MyPageUiEvent import com.withpeace.withpeace.feature.mypage.uistate.MyPageUiState import com.withpeace.withpeace.feature.mypage.uistate.ProfileInfoUiModel @@ -47,11 +48,21 @@ fun MyPageRoute( when (mypageUiState) { MyPageUiState.Loading -> {} is MyPageUiState.Success -> {} - MyPageUiState.Fail -> { - onShowSnackBar(stringResource(R.string.network_failure_message)) + + } + LaunchedEffect(viewModel.myPageUiEvent) { + viewModel.myPageUiEvent.collect { + when(it) { + MyPageUiEvent.UnAuthorizedError -> { + onShowSnackBar("인증에 실패했습니다") + } + MyPageUiEvent.GeneralError -> { + onShowSnackBar("서버와의 오류가 발생했습니다. 다시 시도해주세요.") + } + } } } - LaunchedEffect(Unit){ + LaunchedEffect(Unit) { viewModel.getProfile() } val profileInfo = (mypageUiState as? MyPageUiState.Success)?.profileInfo ?: ProfileInfoUiModel( diff --git a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageViewModel.kt b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageViewModel.kt index bba5a6bb..bd560f01 100644 --- a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageViewModel.kt +++ b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageViewModel.kt @@ -2,12 +2,16 @@ package com.withpeace.withpeace.feature.mypage import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.withpeace.withpeace.core.domain.model.WithPeaceError import com.withpeace.withpeace.core.domain.repository.UserRepository +import com.withpeace.withpeace.feature.mypage.uistate.MyPageUiEvent import com.withpeace.withpeace.feature.mypage.uistate.MyPageUiState import com.withpeace.withpeace.feature.mypage.uistate.toUiModel import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import javax.inject.Inject @@ -16,6 +20,8 @@ import javax.inject.Inject class MyPageViewModel @Inject constructor( private val userRepository: UserRepository, ) : ViewModel() { + private val _myPageUiEvent = Channel() + val myPageUiEvent = _myPageUiEvent.receiveAsFlow() private val _myPageUiState = MutableStateFlow(MyPageUiState.Loading) val myPageUiState = _myPageUiState.asStateFlow() @@ -23,6 +29,15 @@ class MyPageViewModel @Inject constructor( fun getProfile() { viewModelScope.launch { userRepository.getProfile { error -> + when (error) { + is WithPeaceError.GeneralError -> { + _myPageUiEvent.send(MyPageUiEvent.GeneralError) + } + + is WithPeaceError.UnAuthorized -> { + _myPageUiEvent.send(MyPageUiEvent.UnAuthorizedError) + } + } }.collect { profileInfo -> _myPageUiState.update { MyPageUiState.Success( diff --git a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/uistate/MyPageUiEvent.kt b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/uistate/MyPageUiEvent.kt new file mode 100644 index 00000000..b0b323de --- /dev/null +++ b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/uistate/MyPageUiEvent.kt @@ -0,0 +1,6 @@ +package com.withpeace.withpeace.feature.mypage.uistate + +sealed interface MyPageUiEvent { + data object UnAuthorizedError: MyPageUiEvent + data object GeneralError: MyPageUiEvent +} \ No newline at end of file diff --git a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/uistate/MyPageUiState.kt b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/uistate/MyPageUiState.kt index 970be4c9..22e358f8 100644 --- a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/uistate/MyPageUiState.kt +++ b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/uistate/MyPageUiState.kt @@ -3,5 +3,4 @@ package com.withpeace.withpeace.feature.mypage.uistate sealed interface MyPageUiState { data object Loading : MyPageUiState data class Success(val profileInfo: ProfileInfoUiModel) : MyPageUiState - data object Fail : MyPageUiState } \ No newline at end of file From 2a7411f73ce0c04eeaf2716c87cc3d0b0cfab329 Mon Sep 17 00:00:00 2001 From: kwakjoohyeong Date: Sun, 31 Mar 2024 02:34:08 +0900 Subject: [PATCH 22/35] =?UTF-8?q?refactor:=20viewModelScope=20=EC=A4=91?= =?UTF-8?q?=EB=B3=B5=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/app/profileeditor/ProfileEditorViewModel.kt | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt index 6a346813..f8f04b72 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt @@ -114,18 +114,16 @@ class ProfileEditorViewModel @Inject constructor( } fun updateProfile() { - if (_profileEditUiState.value is ProfileEditUiState.NoChanges) { - viewModelScope.launch { + viewModelScope.launch { + if (_profileEditUiState.value is ProfileEditUiState.NoChanges) { _profileEditUiEvent.send(ProfileEditUiEvent.ShowUnchanged) - } - } else if (_profileEditUiState.value is ProfileEditUiState.Editing) { - val editing = _profileEditUiState.value as ProfileEditUiState.Editing - viewModelScope.launch { + } else if (_profileEditUiState.value is ProfileEditUiState.Editing) { + val editing = _profileEditUiState.value as ProfileEditUiState.Editing updateProfileUseCase( beforeProfile = baseProfileInfo.toDomain(), afterProfile = editing.profileInfo.toDomain(), onError = { - this.launch { + viewModelScope.launch { _profileEditUiEvent.send(ProfileEditUiEvent.ShowFailure) } }, From ef5c4c122a18c317e53165ab70a361cad5ead770 Mon Sep 17 00:00:00 2001 From: kwakjoohyeong Date: Sun, 31 Mar 2024 02:39:49 +0900 Subject: [PATCH 23/35] =?UTF-8?q?refactor:=20onError()=20suspend=EB=A1=9C?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/data/repository/DefaultUserRepository.kt | 8 ++++---- .../withpeace/core/domain/repository/UserRepository.kt | 9 ++++----- .../core/domain/usecase/UpdateProfileUseCase.kt | 2 +- .../core/domain/usecase/VerifyNicknameUseCase.kt | 5 ++--- .../java/com/app/profileeditor/ProfileEditorViewModel.kt | 3 --- 5 files changed, 11 insertions(+), 16 deletions(-) diff --git a/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultUserRepository.kt b/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultUserRepository.kt index 36a02460..1f54830c 100644 --- a/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultUserRepository.kt +++ b/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultUserRepository.kt @@ -52,7 +52,7 @@ class DefaultUserRepository @Inject constructor( override fun updateProfile( nickname: String, profileImage: String, - onError: (WithPeaceError) -> Unit, + onError: suspend (WithPeaceError) -> Unit, ): Flow = flow { val imagePart = getImagePart(profileImage) userService.updateProfile( @@ -67,7 +67,7 @@ class DefaultUserRepository @Inject constructor( } } - override fun updateNickname(nickname: String, onError: (WithPeaceError) -> Unit): Flow = + override fun updateNickname(nickname: String, onError: suspend (WithPeaceError) -> Unit): Flow = flow { userService.updateNickname(NicknameRequest(nickname)).suspendMapSuccess { emit(Unit) @@ -81,7 +81,7 @@ class DefaultUserRepository @Inject constructor( override fun updateProfileImage( profileImage: String, - onError: (WithPeaceError) -> Unit, + onError: suspend (WithPeaceError) -> Unit, ): Flow = flow { val imagePart = getImagePart(profileImage) userService.updateImage(imagePart).suspendMapSuccess { @@ -96,7 +96,7 @@ class DefaultUserRepository @Inject constructor( override fun verifyNicknameDuplicated( nickname: Nickname, - onError: (WithPeaceError) -> Unit, + onError: suspend (WithPeaceError) -> Unit, ): Flow = flow { userService.isNicknameDuplicate(nickname.value).suspendMapSuccess { if (this.data) { diff --git a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/repository/UserRepository.kt b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/repository/UserRepository.kt index f1fcea13..724992bf 100644 --- a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/repository/UserRepository.kt +++ b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/repository/UserRepository.kt @@ -1,7 +1,6 @@ package com.withpeace.withpeace.core.domain.repository import com.withpeace.withpeace.core.domain.model.WithPeaceError -import com.withpeace.withpeace.core.domain.model.profile.ChangingProfileInfo import com.withpeace.withpeace.core.domain.model.profile.Nickname import com.withpeace.withpeace.core.domain.model.profile.ProfileInfo import kotlinx.coroutines.flow.Flow @@ -15,21 +14,21 @@ interface UserRepository { fun updateProfileImage( profileImage: String, - onError: (WithPeaceError) -> Unit, + onError: suspend (WithPeaceError) -> Unit, ): Flow fun updateNickname( nickname: String, - onError: (WithPeaceError) -> Unit, + onError: suspend (WithPeaceError) -> Unit, ): Flow fun updateProfile( nickname: String, profileImage: String, - onError: (WithPeaceError) -> Unit, + onError: suspend (WithPeaceError) -> Unit, ): Flow fun verifyNicknameDuplicated( nickname: Nickname, - onError: (WithPeaceError) -> Unit, + onError: suspend (WithPeaceError) -> Unit, ): Flow } diff --git a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/UpdateProfileUseCase.kt b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/UpdateProfileUseCase.kt index 7cb0d1ef..8831bdcc 100644 --- a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/UpdateProfileUseCase.kt +++ b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/UpdateProfileUseCase.kt @@ -13,7 +13,7 @@ class UpdateProfileUseCase @Inject constructor( operator fun invoke( beforeProfile: ChangingProfileInfo, afterProfile: ChangingProfileInfo, - onError: (WithPeaceError) -> Unit, + onError: suspend (WithPeaceError) -> Unit, ): Flow { return when (ProfileChangingStatus.getStatus(beforeProfile, afterProfile)) { ProfileChangingStatus.AllChanging -> { diff --git a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/VerifyNicknameUseCase.kt b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/VerifyNicknameUseCase.kt index e4719dbc..d821b97d 100644 --- a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/VerifyNicknameUseCase.kt +++ b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/VerifyNicknameUseCase.kt @@ -10,10 +10,9 @@ import javax.inject.Inject class VerifyNicknameUseCase @Inject constructor( private val userRepository: UserRepository, ) { - operator fun invoke(nickname: String, onError: (WithPeaceError) -> Unit): Flow { + operator fun invoke(nickname: String, onError: suspend (WithPeaceError) -> Unit): Flow { if (!Nickname.verifyNickname(nickname)) { - onError(WithPeaceError.GeneralError(code = 1)) - return flow { } + return flow { onError(WithPeaceError.GeneralError(code = 1)) } } return userRepository.verifyNicknameDuplicated(Nickname.create(nickname), onError = onError) } diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt index f8f04b72..f132854c 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt @@ -84,7 +84,6 @@ class ProfileEditorViewModel @Inject constructor( viewModelScope.launch { verifyNicknameUseCase( onError = { error -> - this.launch { _profileEditUiEvent.send( when (error) { is WithPeaceError.GeneralError -> { @@ -94,11 +93,9 @@ class ProfileEditorViewModel @Inject constructor( else -> ProfileEditUiEvent.ShowFailure } } - else -> ProfileEditUiEvent.ShowFailure }, ) - } }, nickname = (profileEditUiState.value as ProfileEditUiState.Editing).profileInfo.nickname, ).collect { From e1afaa92c466d00c672548760602cf1e69cea707 Mon Sep 17 00:00:00 2001 From: kwakjoohyeong Date: Sun, 31 Mar 2024 15:35:57 +0900 Subject: [PATCH 24/35] =?UTF-8?q?refactor:=20=EB=B6=84=EA=B8=B0=EA=B0=80?= =?UTF-8?q?=203=EA=B0=9C=EC=9D=B4=EC=83=81=EC=9D=BC=EC=8B=9C=EC=97=90=20if?= =?UTF-8?q?->=20when=EC=9D=84=20=EC=82=AC=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/data/mapper/ChangedProfileMapper.kt | 3 ++- .../domain/model/profile/ChangedProfile.kt | 4 ++-- .../model/profile/ProfileChangingStatus.kt | 18 +++++++++--------- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/mapper/ChangedProfileMapper.kt b/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/mapper/ChangedProfileMapper.kt index 0c1b07d9..dcfc9090 100644 --- a/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/mapper/ChangedProfileMapper.kt +++ b/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/mapper/ChangedProfileMapper.kt @@ -1,11 +1,12 @@ package com.withpeace.withpeace.core.data.mapper import com.withpeace.withpeace.core.domain.model.profile.ChangedProfile +import com.withpeace.withpeace.core.domain.model.profile.Nickname import com.withpeace.withpeace.core.network.di.response.ChangedProfileResponse fun ChangedProfileResponse.toDomain(): ChangedProfile { return ChangedProfile( - nickname = this.nickname, + nickname = Nickname.create(this.nickname), profileImageUrl = profileImageUrl, ) } diff --git a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ChangedProfile.kt b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ChangedProfile.kt index 72ca746d..af2cdcf7 100644 --- a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ChangedProfile.kt +++ b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ChangedProfile.kt @@ -1,6 +1,6 @@ package com.withpeace.withpeace.core.domain.model.profile data class ChangedProfile( - val nickname: String, - val profileImageUrl: String, + val nickname: Nickname?, + val profileImageUrl: String?, ) diff --git a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ProfileChangingStatus.kt b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ProfileChangingStatus.kt index f48c3bfb..a229763c 100644 --- a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ProfileChangingStatus.kt +++ b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ProfileChangingStatus.kt @@ -10,15 +10,15 @@ sealed interface ProfileChangingStatus { beforeProfile: ChangingProfileInfo, afterProfile: ChangingProfileInfo, ): ProfileChangingStatus { - return if (beforeProfile.profileImage != afterProfile.profileImage - && afterProfile.nickname != beforeProfile.nickname && afterProfile.profileImage != null - ) { - AllChanging - } else if (afterProfile.profileImage != null && beforeProfile.profileImage != afterProfile.profileImage - ) { - OnlyImageChanging - } else { - OnlyNicknameChanging + return when { + beforeProfile.profileImage != afterProfile.profileImage && + afterProfile.nickname != beforeProfile.nickname && + afterProfile.profileImage != null -> AllChanging + + afterProfile.profileImage != null && + beforeProfile.profileImage != afterProfile.profileImage -> OnlyImageChanging + + else -> OnlyNicknameChanging } } } From 4a27116869d5a19e56fd3d0fae3d7f91dbcf1f9f Mon Sep 17 00:00:00 2001 From: kwakjoohyeong Date: Sun, 31 Mar 2024 15:56:28 +0900 Subject: [PATCH 25/35] =?UTF-8?q?refactor:=20suspend=20onError=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/app/profileeditor/ProfileEditorViewModel.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt index f132854c..5b895c4d 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt @@ -120,9 +120,8 @@ class ProfileEditorViewModel @Inject constructor( beforeProfile = baseProfileInfo.toDomain(), afterProfile = editing.profileInfo.toDomain(), onError = { - viewModelScope.launch { - _profileEditUiEvent.send(ProfileEditUiEvent.ShowFailure) - } + _profileEditUiEvent.send(ProfileEditUiEvent.ShowFailure) + }, ).collect { _profileEditUiEvent.send(ProfileEditUiEvent.ShowUpdateSuccess) From 1958090db6cf6b238c7b206b0ee70b8f3e2f1326 Mon Sep 17 00:00:00 2001 From: kwakjoohyeong Date: Sun, 31 Mar 2024 16:14:53 +0900 Subject: [PATCH 26/35] =?UTF-8?q?refactor:=20nickname=20check(require)=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../withpeace/withpeace/core/domain/model/profile/Nickname.kt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/Nickname.kt b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/Nickname.kt index f176c00e..307ede34 100644 --- a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/Nickname.kt +++ b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/Nickname.kt @@ -5,13 +5,9 @@ package com.withpeace.withpeace.core.domain.model.profile */ @JvmInline value class Nickname private constructor(val value: String) { - init { - check(verifyNickname(value)) { NICKNAME_FORMAT_ERROR } - } companion object { private const val NICKNAME_REGEX_PATTERN = "[a-zA-Z0-9가-힣]{2,10}" - private const val NICKNAME_FORMAT_ERROR = "닉네임 형식이 잘못 되었습니다." fun create(value: String): Nickname = Nickname(value) fun verifyNickname(nickname: String): Boolean { val regex = Regex(NICKNAME_REGEX_PATTERN) From 3ba2d2e623e149fc297878e5c8461cd167e3fc99 Mon Sep 17 00:00:00 2001 From: kwakjoohyeong Date: Sun, 31 Mar 2024 17:34:53 +0900 Subject: [PATCH 27/35] =?UTF-8?q?refactor:=20uiState=EC=97=90=20isChanged?= =?UTF-8?q?=20=EC=BB=AC=EB=9F=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/profileeditor/ProfileEditorScreen.kt | 13 +--- .../profileeditor/ProfileEditorViewModel.kt | 68 +++++++++++-------- .../uistate/ProfileEditUiState.kt | 17 +++-- 3 files changed, 52 insertions(+), 46 deletions(-) diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt index d2b1ffe2..9b635b18 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt @@ -72,17 +72,8 @@ fun ProfileEditorRoute( var showAlertDialog by remember { mutableStateOf(false) } val profileUiState: ProfileEditUiState by viewModel.profileEditUiState.collectAsStateWithLifecycle() - val profileInfo = when (profileUiState) { - is ProfileEditUiState.Editing -> { - val editing = profileUiState as ProfileEditUiState.Editing - ProfileUiModel(nickname = editing.profileInfo.nickname, profileImage = editing.profileInfo.profileImage) - } + val profileInfo = profileUiState.currentProfileInfo - is ProfileEditUiState.NoChanges -> { - viewModel.updateIsNicknameValidStatus(ProfileNicknameValidUiState.Valid) - viewModel.baseProfileInfo - } - } if (showAlertDialog) { ModifySaveDialog( onClickSave = { @@ -138,7 +129,7 @@ fun ProfileEditorRoute( ProfileEditorScreen( profileInfo = ProfileUiModel(profileInfo.nickname, profileInfo.profileImage), onClickBackButton = { - if (profileUiState is ProfileEditUiState.Editing) { + if (profileUiState.isChanged) { showAlertDialog = true } else { onClickBackButton() diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt index 5b895c4d..9bd3048f 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt @@ -34,7 +34,7 @@ class ProfileEditorViewModel @Inject constructor( private val _profileEditUiState = MutableStateFlow( - ProfileEditUiState.NoChanges, + ProfileEditUiState(baseProfileInfo, false), ) val profileEditUiState = _profileEditUiState.asStateFlow() @@ -46,39 +46,49 @@ class ProfileEditorViewModel @Inject constructor( val profileNicknameValidUiState = _profileNicknameValidUiState.asStateFlow() fun onImageChanged(imageUri: String) { + val changingUiState = _profileEditUiState.value.currentProfileInfo.copy( + nickname = _profileEditUiState.value.currentProfileInfo.nickname, + profileImage = imageUri, + ) _profileEditUiState.update { - val updateData = ProfileEditUiState.Editing( - ProfileUiModel( - (it as? ProfileEditUiState.Editing)?.profileInfo?.nickname - ?: baseProfileInfo.nickname, - profileImage = imageUri, - ), - ) - if (baseProfileInfo == updateData.profileInfo) { - return@update ProfileEditUiState.NoChanges + return@update if (baseProfileInfo == changingUiState + ) { + _profileEditUiState.value.copy( + currentProfileInfo = baseProfileInfo, + isChanged = false, + ) + } else { + _profileEditUiState.value.copy( + currentProfileInfo = changingUiState, + isChanged = true, + ) } - updateData } } - fun onNickNameChanged(nickname: String) { + val changingUiState = _profileEditUiState.value.currentProfileInfo.copy( + nickname = nickname, + profileImage = _profileEditUiState.value.currentProfileInfo.profileImage, + ) _profileEditUiState.update { - val updateData = ProfileEditUiState.Editing( - ProfileUiModel( - nickname = nickname, - profileImage = (it as? ProfileEditUiState.Editing)?.profileInfo?.profileImage - ?: baseProfileInfo.profileImage, - ), - ) // Editing 중이면 값을 갱신, 아닐 경우 기본 값에 nickname만 값을 추가 - if (baseProfileInfo == updateData.profileInfo) { - return@update ProfileEditUiState.NoChanges - } // 변경 값이 기본 값일 경우 noChanges 상태 - updateData + // changingUiState.toDomain().getChangingState(baseProfileInfo.toDomain()).toUiModel() + return@update if (baseProfileInfo == changingUiState + ) { + _profileEditUiState.value.copy( + currentProfileInfo = baseProfileInfo, + isChanged = false, + ) + } else { + _profileEditUiState.value.copy( + currentProfileInfo = changingUiState, + isChanged = true, + ) + } } } fun verifyNickname() { - if (profileEditUiState.value is ProfileEditUiState.NoChanges) { + if (profileEditUiState.value.isChanged.not()) { return } viewModelScope.launch { @@ -93,11 +103,12 @@ class ProfileEditorViewModel @Inject constructor( else -> ProfileEditUiEvent.ShowFailure } } + else -> ProfileEditUiEvent.ShowFailure }, ) }, - nickname = (profileEditUiState.value as ProfileEditUiState.Editing).profileInfo.nickname, + nickname = _profileEditUiState.value.currentProfileInfo.nickname, ).collect { _profileEditUiEvent.send( ProfileEditUiEvent.ShowNicknameVerified, @@ -112,13 +123,12 @@ class ProfileEditorViewModel @Inject constructor( fun updateProfile() { viewModelScope.launch { - if (_profileEditUiState.value is ProfileEditUiState.NoChanges) { + if (_profileEditUiState.value.isChanged.not()) { _profileEditUiEvent.send(ProfileEditUiEvent.ShowUnchanged) - } else if (_profileEditUiState.value is ProfileEditUiState.Editing) { - val editing = _profileEditUiState.value as ProfileEditUiState.Editing + } else if (_profileEditUiState.value.isChanged) { updateProfileUseCase( beforeProfile = baseProfileInfo.toDomain(), - afterProfile = editing.profileInfo.toDomain(), + afterProfile = _profileEditUiState.value.currentProfileInfo.toDomain(), onError = { _profileEditUiEvent.send(ProfileEditUiEvent.ShowFailure) diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/uistate/ProfileEditUiState.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/uistate/ProfileEditUiState.kt index 0c05ff92..ebe090f7 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/uistate/ProfileEditUiState.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/uistate/ProfileEditUiState.kt @@ -1,9 +1,14 @@ package com.app.profileeditor.uistate -sealed interface ProfileEditUiState { - data class Editing( - val profileInfo: ProfileUiModel - ) : ProfileEditUiState +// sealed interface ProfileEditUiState { +// data class Editing( +// val profileInfo: ProfileUiModel, +// ) : ProfileEditUiState +// +// data object NoChanges : ProfileEditUiState +// } - data object NoChanges : ProfileEditUiState -} \ No newline at end of file +data class ProfileEditUiState( + val currentProfileInfo: ProfileUiModel, + val isChanged: Boolean, +) \ No newline at end of file From a77266d269df528e083120fe8a38d83a472e28b7 Mon Sep 17 00:00:00 2001 From: kwakjoohyeong Date: Sun, 31 Mar 2024 17:45:12 +0900 Subject: [PATCH 28/35] =?UTF-8?q?refactor:=20nickname=20validate=EB=A5=BC?= =?UTF-8?q?=20state=EB=A5=BC=20=EB=B0=94=EA=BE=B8=EA=B2=8C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/profileeditor/ProfileEditorScreen.kt | 12 --------- .../profileeditor/ProfileEditorViewModel.kt | 27 ++++++++----------- .../uistate/ProfileEditUiEvent.kt | 3 --- 3 files changed, 11 insertions(+), 31 deletions(-) diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt index 9b635b18..2d8df083 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt @@ -93,10 +93,6 @@ fun ProfileEditorRoute( LaunchedEffect(viewModel.profileEditUiEvent) { viewModel.profileEditUiEvent.collect { when (it) { - ProfileEditUiEvent.ShowDuplicate -> { - viewModel.updateIsNicknameValidStatus(ProfileNicknameValidUiState.InValidDuplicated) - } - ProfileEditUiEvent.ShowDuplicateSnackBar -> { onShowSnackBar("중복된 닉네임입니다.") } @@ -105,10 +101,6 @@ fun ProfileEditorRoute( onShowSnackBar("서버와 통신 중 오류가 발생했습니다.") } - ProfileEditUiEvent.ShowInvalidFormat -> { - viewModel.updateIsNicknameValidStatus(ProfileNicknameValidUiState.InValidFormat) - } - ProfileEditUiEvent.ShowUnchanged -> { onShowSnackBar("수정사항이 없습니다.") } @@ -117,10 +109,6 @@ fun ProfileEditorRoute( onShowSnackBar("변경되었습니다.") onUpdateSuccess() } - - ProfileEditUiEvent.ShowNicknameVerified -> { - viewModel.updateIsNicknameValidStatus(ProfileNicknameValidUiState.Valid) - } } } } diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt index 9bd3048f..baf12f79 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt @@ -94,30 +94,25 @@ class ProfileEditorViewModel @Inject constructor( viewModelScope.launch { verifyNicknameUseCase( onError = { error -> - _profileEditUiEvent.send( - when (error) { - is WithPeaceError.GeneralError -> { - when (error.code) { - 1 -> ProfileEditUiEvent.ShowInvalidFormat - 2 -> ProfileEditUiEvent.ShowDuplicate - else -> ProfileEditUiEvent.ShowFailure - } - } + when (error) { + is WithPeaceError.GeneralError -> { + when (error.code) { + 1 -> updateIsNicknameValidStatus(ProfileNicknameValidUiState.InValidFormat) + 2 -> updateIsNicknameValidStatus(ProfileNicknameValidUiState.InValidDuplicated) + } + } - else -> ProfileEditUiEvent.ShowFailure - }, - ) + else -> _profileEditUiEvent.send(ProfileEditUiEvent.ShowFailure) + } }, nickname = _profileEditUiState.value.currentProfileInfo.nickname, ).collect { - _profileEditUiEvent.send( - ProfileEditUiEvent.ShowNicknameVerified, - ) + updateIsNicknameValidStatus(ProfileNicknameValidUiState.Valid) } } } - fun updateIsNicknameValidStatus(status: ProfileNicknameValidUiState) { + private fun updateIsNicknameValidStatus(status: ProfileNicknameValidUiState) { _profileNicknameValidUiState.update { status } } diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/uistate/ProfileEditUiEvent.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/uistate/ProfileEditUiEvent.kt index 3b9aa465..db583e47 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/uistate/ProfileEditUiEvent.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/uistate/ProfileEditUiEvent.kt @@ -1,10 +1,7 @@ package com.app.profileeditor.uistate sealed interface ProfileEditUiEvent { - data object ShowDuplicate : ProfileEditUiEvent data object ShowDuplicateSnackBar : ProfileEditUiEvent - data object ShowNicknameVerified : ProfileEditUiEvent - data object ShowInvalidFormat : ProfileEditUiEvent data object ShowUpdateSuccess : ProfileEditUiEvent data object ShowFailure : ProfileEditUiEvent data object ShowUnchanged : ProfileEditUiEvent From 257f63cdaf7bb8c052898007df0d30f736f956cd Mon Sep 17 00:00:00 2001 From: kwakjoohyeong Date: Sun, 31 Mar 2024 18:40:01 +0900 Subject: [PATCH 29/35] =?UTF-8?q?refactor:=20=EC=84=9C=EB=B2=84=20errorbod?= =?UTF-8?q?y=EB=A5=BC=20=EC=B2=98=EB=A6=AC=ED=95=98=EB=8A=94=20=EB=A1=9C?= =?UTF-8?q?=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 --- .../data/repository/DefaultUserRepository.kt | 25 ++++++++++++++----- .../core/network/di/common/OkHttpUtil.kt | 12 +++++++++ .../network/di/common/WithPeaceErrorBody.kt | 6 +++++ .../app/profileeditor/ProfileEditorScreen.kt | 17 ++++++------- .../profileeditor/ProfileEditorViewModel.kt | 13 +++++++++- .../uistate/ProfileEditUiEvent.kt | 2 ++ 6 files changed, 59 insertions(+), 16 deletions(-) create mode 100644 core/network/src/main/java/com/withpeace/withpeace/core/network/di/common/OkHttpUtil.kt create mode 100644 core/network/src/main/java/com/withpeace/withpeace/core/network/di/common/WithPeaceErrorBody.kt diff --git a/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultUserRepository.kt b/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultUserRepository.kt index 1f54830c..fc11da8f 100644 --- a/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultUserRepository.kt +++ b/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultUserRepository.kt @@ -12,6 +12,7 @@ import com.withpeace.withpeace.core.domain.model.WithPeaceError import com.withpeace.withpeace.core.domain.model.profile.Nickname import com.withpeace.withpeace.core.domain.model.profile.ProfileInfo import com.withpeace.withpeace.core.domain.repository.UserRepository +import com.withpeace.withpeace.core.network.di.common.getErrorBody import com.withpeace.withpeace.core.network.di.request.NicknameRequest import com.withpeace.withpeace.core.network.di.service.UserService import dagger.hilt.android.qualifiers.ApplicationContext @@ -60,8 +61,12 @@ class DefaultUserRepository @Inject constructor( ).suspendMapSuccess { emit(Unit) }.suspendOnError { - if (statusCode.code == 401) onError(WithPeaceError.UnAuthorized()) - else onError(WithPeaceError.GeneralError(statusCode.code, messageOrNull)) + if (statusCode.code == 401) { + onError(WithPeaceError.UnAuthorized()) + } else { + val errorBody = errorBody?.getErrorBody() + onError(WithPeaceError.GeneralError(errorBody?.code, errorBody?.message)) + } }.suspendOnException { onError(WithPeaceError.GeneralError(message = messageOrNull)) } @@ -72,8 +77,12 @@ class DefaultUserRepository @Inject constructor( userService.updateNickname(NicknameRequest(nickname)).suspendMapSuccess { emit(Unit) }.suspendOnError { - if (statusCode.code == 401) onError(WithPeaceError.UnAuthorized()) - else onError(WithPeaceError.GeneralError(statusCode.code, messageOrNull)) + if (statusCode.code == 401) { + onError(WithPeaceError.UnAuthorized()) + } else { + val errorBody = errorBody?.getErrorBody() + onError(WithPeaceError.GeneralError(errorBody?.code, errorBody?.message)) + } }.suspendOnException { onError(WithPeaceError.GeneralError(message = messageOrNull)) } @@ -87,8 +96,12 @@ class DefaultUserRepository @Inject constructor( userService.updateImage(imagePart).suspendMapSuccess { emit(Unit) }.suspendOnError { - if (statusCode.code == 401) onError(WithPeaceError.UnAuthorized()) - else onError(WithPeaceError.GeneralError(statusCode.code, messageOrNull)) + if (statusCode.code == 401) { + onError(WithPeaceError.UnAuthorized()) + } else { + val errorBody = errorBody?.getErrorBody() + onError(WithPeaceError.GeneralError(errorBody?.code, errorBody?.message)) + } }.suspendOnException { onError(WithPeaceError.GeneralError(message = messageOrNull)) } diff --git a/core/network/src/main/java/com/withpeace/withpeace/core/network/di/common/OkHttpUtil.kt b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/common/OkHttpUtil.kt new file mode 100644 index 00000000..a8c996c6 --- /dev/null +++ b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/common/OkHttpUtil.kt @@ -0,0 +1,12 @@ +package com.withpeace.withpeace.core.network.di.common + +import okhttp3.ResponseBody +import org.json.JSONObject + +fun ResponseBody.getErrorBody(): WithPeaceErrorBody { + val json = JSONObject(string()) + val errorBody = JSONObject(json.getString("error") ?: "") + val errorMessage = errorBody.getString("code") + val errorCode = errorBody.getInt("code") + return WithPeaceErrorBody(errorCode, errorMessage) +} \ No newline at end of file diff --git a/core/network/src/main/java/com/withpeace/withpeace/core/network/di/common/WithPeaceErrorBody.kt b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/common/WithPeaceErrorBody.kt new file mode 100644 index 00000000..792b89b1 --- /dev/null +++ b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/common/WithPeaceErrorBody.kt @@ -0,0 +1,6 @@ +package com.withpeace.withpeace.core.network.di.common + +data class WithPeaceErrorBody( + val code: Int?, + val message: String?, +) diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt index 2d8df083..b16e2f0a 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt @@ -93,22 +93,21 @@ fun ProfileEditorRoute( LaunchedEffect(viewModel.profileEditUiEvent) { viewModel.profileEditUiEvent.collect { when (it) { - ProfileEditUiEvent.ShowDuplicateSnackBar -> { - onShowSnackBar("중복된 닉네임입니다.") - } + ProfileEditUiEvent.ShowDuplicateSnackBar -> onShowSnackBar("중복된 닉네임입니다.") - ProfileEditUiEvent.ShowFailure -> { - onShowSnackBar("서버와 통신 중 오류가 발생했습니다.") - } + ProfileEditUiEvent.ShowInvalidFormatSnackBar -> onShowSnackBar("닉네임 형식이 올바르지 않습니다.") - ProfileEditUiEvent.ShowUnchanged -> { - onShowSnackBar("수정사항이 없습니다.") - } + ProfileEditUiEvent.ShowFailure -> onShowSnackBar("서버와 통신 중 오류가 발생했습니다.") + + ProfileEditUiEvent.ShowUnchanged -> onShowSnackBar("수정사항이 없습니다.") ProfileEditUiEvent.ShowUpdateSuccess -> { onShowSnackBar("변경되었습니다.") onUpdateSuccess() } + + ProfileEditUiEvent.UnAuthorized -> onShowSnackBar("인가 되지 않은 게정이에요") + } } } diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt index baf12f79..d568ea33 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt @@ -125,8 +125,19 @@ class ProfileEditorViewModel @Inject constructor( beforeProfile = baseProfileInfo.toDomain(), afterProfile = _profileEditUiState.value.currentProfileInfo.toDomain(), onError = { - _profileEditUiEvent.send(ProfileEditUiEvent.ShowFailure) + _profileEditUiEvent.send( + when (it) { + is WithPeaceError.GeneralError -> { + when (it.code) { + 40001 -> ProfileEditUiEvent.ShowInvalidFormatSnackBar + 40007 -> ProfileEditUiEvent.ShowDuplicateSnackBar + else -> ProfileEditUiEvent.ShowFailure + } + } + is WithPeaceError.UnAuthorized -> ProfileEditUiEvent.UnAuthorized + }, + ) }, ).collect { _profileEditUiEvent.send(ProfileEditUiEvent.ShowUpdateSuccess) diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/uistate/ProfileEditUiEvent.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/uistate/ProfileEditUiEvent.kt index db583e47..ea85a54b 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/uistate/ProfileEditUiEvent.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/uistate/ProfileEditUiEvent.kt @@ -2,7 +2,9 @@ package com.app.profileeditor.uistate sealed interface ProfileEditUiEvent { data object ShowDuplicateSnackBar : ProfileEditUiEvent + data object ShowInvalidFormatSnackBar : ProfileEditUiEvent data object ShowUpdateSuccess : ProfileEditUiEvent data object ShowFailure : ProfileEditUiEvent data object ShowUnchanged : ProfileEditUiEvent + data object UnAuthorized : ProfileEditUiEvent } \ No newline at end of file From 2e797eccb56f204c2450e385f817a5856efb42ee Mon Sep 17 00:00:00 2001 From: kwakjoohyeong Date: Sun, 31 Mar 2024 19:30:24 +0900 Subject: [PATCH 30/35] =?UTF-8?q?refactor:=20=EB=B3=80=EA=B2=BD=EC=97=AC?= =?UTF-8?q?=EB=B6=80=EC=97=90=20=EB=94=B0=EB=9D=BC=20=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?=ED=91=9C=EC=8B=9C=20=EB=B6=84=EA=B8=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/app/profileeditor/ProfileEditorScreen.kt | 9 +++++++-- .../java/com/app/profileeditor/ProfileEditorViewModel.kt | 4 ++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt index b16e2f0a..2dfcfc89 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt @@ -131,6 +131,7 @@ fun ProfileEditorRoute( viewModel.verifyNickname() }, nicknameValidStatus = viewModel.profileNicknameValidUiState.collectAsStateWithLifecycle().value, + isChanged = profileUiState.isChanged, ) } @@ -138,6 +139,7 @@ fun ProfileEditorRoute( @Composable fun ProfileEditorScreen( profileInfo: ProfileUiModel, + isChanged: Boolean, modifier: Modifier = Modifier, onClickBackButton: () -> Unit, onNavigateToGallery: () -> Unit, @@ -187,6 +189,7 @@ fun ProfileEditorScreen( }, onKeyBoardTimerEnd = onKeyBoardTimerEnd, nicknameValidStatus = nicknameValidStatus, + isChanged = isChanged, ) } @@ -254,6 +257,7 @@ private fun ProfileImage( private fun NickNameTextField( modifier: Modifier = Modifier, nickname: String, + isChanged: Boolean, nicknameValidStatus: ProfileNicknameValidUiState, onNickNameChanged: (String) -> Unit, onKeyBoardTimerEnd: () -> Unit, @@ -308,14 +312,14 @@ private fun NickNameTextField( ) } Divider( - color = if (nicknameValidStatus is ProfileNicknameValidUiState.Valid) WithpeaceTheme.colors.SystemBlack + color = if (nicknameValidStatus is ProfileNicknameValidUiState.Valid || isChanged.not()) WithpeaceTheme.colors.SystemBlack else WithpeaceTheme.colors.SystemError, modifier = modifier .width(140.dp) .height(1.dp), ) } - if (nicknameValidStatus !is ProfileNicknameValidUiState.Valid) { + if (nicknameValidStatus !is ProfileNicknameValidUiState.Valid && isChanged.not()) { Text( text = if (nicknameValidStatus is ProfileNicknameValidUiState.InValidDuplicated) stringResource( R.string.nickname_duplicated, @@ -447,6 +451,7 @@ fun ProfileEditorPreview() { onNickNameChanged = {}, onKeyBoardTimerEnd = {}, nicknameValidStatus = ProfileNicknameValidUiState.Valid, + isChanged = false ) } } diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt index d568ea33..55a09d22 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt @@ -88,7 +88,8 @@ class ProfileEditorViewModel @Inject constructor( } fun verifyNickname() { - if (profileEditUiState.value.isChanged.not()) { + if (profileEditUiState.value.isChanged.not() + ) { return } viewModelScope.launch { @@ -101,7 +102,6 @@ class ProfileEditorViewModel @Inject constructor( 2 -> updateIsNicknameValidStatus(ProfileNicknameValidUiState.InValidDuplicated) } } - else -> _profileEditUiEvent.send(ProfileEditUiEvent.ShowFailure) } }, From 6947606412dcacf3157f9e6cb7c87b149cb8e8d1 Mon Sep 17 00:00:00 2001 From: kwakjoohyeong Date: Sun, 31 Mar 2024 21:30:27 +0900 Subject: [PATCH 31/35] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=95=84?= =?UTF-8?q?=EC=9B=83=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../withpeace/withpeace/navigation/NavHost.kt | 17 ++++++++++-- .../core/data/di/RepositoryModule.kt | 4 ++- .../data/repository/DefaultUserRepository.kt | 26 +++++++++++++++++-- .../DefaultTokenPreferenceDataSource.kt | 6 +++++ .../dataStore/TokenPreferenceDataSource.kt | 1 + .../core/domain/repository/UserRepository.kt | 2 ++ .../domain/usecase/GetProfileInfoUseCase.kt | 2 +- .../core/domain/usecase/LogoutUseCase.kt | 13 ++++++++++ .../core/network/di/service/UserService.kt | 4 +++ .../withpeace/feature/mypage/MyPageScreen.kt | 11 +++++--- .../feature/mypage/MyPageViewModel.kt | 23 +++++++++++++--- .../mypage/navigation/MyPageNavigation.kt | 4 +-- .../feature/mypage/uistate/MyPageUiEvent.kt | 3 ++- 13 files changed, 101 insertions(+), 15 deletions(-) create mode 100644 core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/LogoutUseCase.kt diff --git a/app/src/main/java/com/withpeace/withpeace/navigation/NavHost.kt b/app/src/main/java/com/withpeace/withpeace/navigation/NavHost.kt index a8d1e039..9aacce17 100644 --- a/app/src/main/java/com/withpeace/withpeace/navigation/NavHost.kt +++ b/app/src/main/java/com/withpeace/withpeace/navigation/NavHost.kt @@ -4,6 +4,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost +import androidx.navigation.navOptions import com.app.profileeditor.navigation.navigateProfileEditor import com.app.profileeditor.navigation.profileEditorNavGraph import com.withpeace.withpeace.feature.gallery.navigation.galleryNavGraph @@ -11,6 +12,7 @@ import com.withpeace.withpeace.feature.gallery.navigation.navigateToGallery import com.withpeace.withpeace.feature.home.navigation.homeNavGraph import com.withpeace.withpeace.feature.login.navigation.LOGIN_ROUTE import com.withpeace.withpeace.feature.login.navigation.loginNavGraph +import com.withpeace.withpeace.feature.login.navigation.navigateLogin import com.withpeace.withpeace.feature.mypage.navigation.myPageNavGraph import com.withpeace.withpeace.feature.post.navigation.postNavGraph import com.withpeace.withpeace.feature.registerpost.navigation.IMAGE_LIST_ARGUMENT @@ -60,9 +62,20 @@ fun WithpeaceNavHost( myPageNavGraph( onShowSnackBar = onShowSnackBar, onEditProfile = { nickname, profileImageUrl -> - navController.navigateProfileEditor(nickname = nickname, profileImageUrl = profileImageUrl) + navController.navigateProfileEditor( + nickname = nickname, + profileImageUrl = profileImageUrl, + ) + }, + onLogoutSuccess = { + navController.navigateLogin( + navOptions = navOptions { + popUpTo(navController.graph.id) { + inclusive = true + } + }, + ) }, - onLogoutClick = {}, onWithdrawClick = {}, ) profileEditorNavGraph( diff --git a/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/di/RepositoryModule.kt b/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/di/RepositoryModule.kt index f0d9fd2f..53b6ac50 100644 --- a/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/di/RepositoryModule.kt +++ b/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/di/RepositoryModule.kt @@ -32,5 +32,7 @@ interface RepositoryModule { @Binds @Singleton - fun bindsUserRepository(defaultUserRepository: DefaultUserRepository): UserRepository + fun bindsUserRepository( + defaultUserRepository: DefaultUserRepository, + ): UserRepository } diff --git a/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultUserRepository.kt b/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultUserRepository.kt index fc11da8f..956a22de 100644 --- a/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultUserRepository.kt +++ b/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultUserRepository.kt @@ -8,6 +8,7 @@ import com.skydoves.sandwich.suspendOnError import com.skydoves.sandwich.suspendOnException import com.withpeace.withpeace.core.data.mapper.toDomain import com.withpeace.withpeace.core.data.util.convertToFile +import com.withpeace.withpeace.core.datastore.dataStore.TokenPreferenceDataSource import com.withpeace.withpeace.core.domain.model.WithPeaceError import com.withpeace.withpeace.core.domain.model.profile.Nickname import com.withpeace.withpeace.core.domain.model.profile.ProfileInfo @@ -28,6 +29,7 @@ import javax.inject.Inject class DefaultUserRepository @Inject constructor( @ApplicationContext private val context: Context, private val userService: UserService, + private val tokenPreferenceDataSource: TokenPreferenceDataSource, ) : UserRepository { override fun getProfile( onError: suspend (WithPeaceError) -> Unit, @@ -35,8 +37,12 @@ class DefaultUserRepository @Inject constructor( userService.getProfile().suspendMapSuccess { emit(this.data.toDomain()) }.suspendOnError { - if (statusCode.code == 401) onError(WithPeaceError.UnAuthorized()) - else onError(WithPeaceError.GeneralError(statusCode.code, messageOrNull)) + if (statusCode.code == 401) { + onError(WithPeaceError.UnAuthorized()) + } else { + val errorBody = errorBody?.getErrorBody() + onError(WithPeaceError.GeneralError(errorBody?.code, errorBody?.message)) + } }.suspendOnException { onError(WithPeaceError.GeneralError(message = messageOrNull)) } @@ -124,6 +130,22 @@ class DefaultUserRepository @Inject constructor( } } + override fun logout(onError: suspend (WithPeaceError) -> Unit): Flow = flow { + userService.logout().suspendMapSuccess { + tokenPreferenceDataSource.removeAll() + emit(Unit) + }.suspendOnError { + if (statusCode.code == 401) { + onError(WithPeaceError.UnAuthorized()) + } else { + val errorBody = errorBody?.getErrorBody() + onError(WithPeaceError.GeneralError(errorBody?.code, errorBody?.message)) + } + }.suspendOnException { + onError(WithPeaceError.GeneralError(message = messageOrNull)) + } + } + private fun getImagePart(profileImage: String): MultipartBody.Part { val requestFile: File = Uri.parse(profileImage).convertToFile(context) val imageRequestBody = requestFile.asRequestBody("image/*".toMediaTypeOrNull()) diff --git a/core/datastore/src/main/java/com/withpeace/withpeace/core/datastore/dataStore/DefaultTokenPreferenceDataSource.kt b/core/datastore/src/main/java/com/withpeace/withpeace/core/datastore/dataStore/DefaultTokenPreferenceDataSource.kt index 6b5cca17..5461fdd4 100644 --- a/core/datastore/src/main/java/com/withpeace/withpeace/core/datastore/dataStore/DefaultTokenPreferenceDataSource.kt +++ b/core/datastore/src/main/java/com/withpeace/withpeace/core/datastore/dataStore/DefaultTokenPreferenceDataSource.kt @@ -33,6 +33,12 @@ class DefaultTokenPreferenceDataSource @Inject constructor( } } + override suspend fun removeAll() { + dataStore.edit { preferences -> + preferences.clear() + } + } + companion object { private val ACCESS_TOKEN = stringPreferencesKey("ACCESS_TOKEN") private val REFRESH_TOKEN = stringPreferencesKey("REFRESH_TOKEN") diff --git a/core/datastore/src/main/java/com/withpeace/withpeace/core/datastore/dataStore/TokenPreferenceDataSource.kt b/core/datastore/src/main/java/com/withpeace/withpeace/core/datastore/dataStore/TokenPreferenceDataSource.kt index 7d5df2be..1bda640a 100644 --- a/core/datastore/src/main/java/com/withpeace/withpeace/core/datastore/dataStore/TokenPreferenceDataSource.kt +++ b/core/datastore/src/main/java/com/withpeace/withpeace/core/datastore/dataStore/TokenPreferenceDataSource.kt @@ -11,4 +11,5 @@ interface TokenPreferenceDataSource { suspend fun updateRefreshToken(refreshToken: String) suspend fun updateAccessToken(accessToken: String) + suspend fun removeAll() } \ No newline at end of file diff --git a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/repository/UserRepository.kt b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/repository/UserRepository.kt index 724992bf..82846137 100644 --- a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/repository/UserRepository.kt +++ b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/repository/UserRepository.kt @@ -31,4 +31,6 @@ interface UserRepository { nickname: Nickname, onError: suspend (WithPeaceError) -> Unit, ): Flow + + fun logout(onError: suspend (WithPeaceError) -> Unit): Flow } diff --git a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/GetProfileInfoUseCase.kt b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/GetProfileInfoUseCase.kt index 886686fe..0b49379e 100644 --- a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/GetProfileInfoUseCase.kt +++ b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/GetProfileInfoUseCase.kt @@ -9,7 +9,7 @@ import javax.inject.Inject class GetProfileInfoUseCase @Inject constructor( private val userRepository: UserRepository, ) { - operator fun invoke(onError: (WithPeaceError) -> Unit): Flow { + operator fun invoke(onError: suspend (WithPeaceError) -> Unit): Flow { return userRepository.getProfile( onError = onError, ) diff --git a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/LogoutUseCase.kt b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/LogoutUseCase.kt new file mode 100644 index 00000000..2091a8c6 --- /dev/null +++ b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/LogoutUseCase.kt @@ -0,0 +1,13 @@ +package com.withpeace.withpeace.core.domain.usecase + +import com.withpeace.withpeace.core.domain.model.WithPeaceError +import com.withpeace.withpeace.core.domain.repository.UserRepository +import javax.inject.Inject + +class LogoutUseCase @Inject constructor( + private val userRepository: UserRepository, +) { + operator fun invoke(onError: suspend (WithPeaceError) -> Unit) = userRepository.logout( + onError = onError, + ) +} \ No newline at end of file diff --git a/core/network/src/main/java/com/withpeace/withpeace/core/network/di/service/UserService.kt b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/service/UserService.kt index b227c7be..14cedd66 100644 --- a/core/network/src/main/java/com/withpeace/withpeace/core/network/di/service/UserService.kt +++ b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/service/UserService.kt @@ -10,6 +10,7 @@ import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.Multipart import retrofit2.http.PATCH +import retrofit2.http.POST import retrofit2.http.PUT import retrofit2.http.Part import retrofit2.http.Query @@ -34,4 +35,7 @@ interface UserService { @GET("/api/v1/users/profile/nickname/check") suspend fun isNicknameDuplicate(@Query("nickname") nickname: String): ApiResponse> + + @POST("/api/v1/auth/logout") + suspend fun logout(): ApiResponse> } diff --git a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageScreen.kt b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageScreen.kt index f6d8d19b..87b3838a 100644 --- a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageScreen.kt +++ b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageScreen.kt @@ -41,7 +41,7 @@ fun MyPageRoute( viewModel: MyPageViewModel = hiltViewModel(), onShowSnackBar: (message: String) -> Unit = {}, onEditProfile: (nickname: String, profileImageUrl: String) -> Unit, - onLogoutClick: () -> Unit, + onLogoutSuccess: () -> Unit, onWithdrawClick: () -> Unit, ) { val mypageUiState by viewModel.myPageUiState.collectAsStateWithLifecycle() @@ -52,13 +52,16 @@ fun MyPageRoute( } LaunchedEffect(viewModel.myPageUiEvent) { viewModel.myPageUiEvent.collect { - when(it) { + when (it) { MyPageUiEvent.UnAuthorizedError -> { onShowSnackBar("인증에 실패했습니다") } + MyPageUiEvent.GeneralError -> { onShowSnackBar("서버와의 오류가 발생했습니다. 다시 시도해주세요.") } + + MyPageUiEvent.Logout -> onLogoutSuccess() } } } @@ -74,7 +77,9 @@ fun MyPageRoute( onEditProfile = { onEditProfile(it.nickname, it.profileImage) }, - onLogoutClick = onLogoutClick, + onLogoutClick = { + viewModel.logout() + }, onWithdrawClick = onWithdrawClick, profileInfo = profileInfo, ) diff --git a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageViewModel.kt b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageViewModel.kt index bd560f01..0775f7fe 100644 --- a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageViewModel.kt +++ b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageViewModel.kt @@ -3,7 +3,8 @@ package com.withpeace.withpeace.feature.mypage import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.withpeace.withpeace.core.domain.model.WithPeaceError -import com.withpeace.withpeace.core.domain.repository.UserRepository +import com.withpeace.withpeace.core.domain.usecase.GetProfileInfoUseCase +import com.withpeace.withpeace.core.domain.usecase.LogoutUseCase import com.withpeace.withpeace.feature.mypage.uistate.MyPageUiEvent import com.withpeace.withpeace.feature.mypage.uistate.MyPageUiState import com.withpeace.withpeace.feature.mypage.uistate.toUiModel @@ -18,7 +19,8 @@ import javax.inject.Inject @HiltViewModel class MyPageViewModel @Inject constructor( - private val userRepository: UserRepository, + private val getUserInfoUseCase: GetProfileInfoUseCase, + private val logoutUseCase: LogoutUseCase, ) : ViewModel() { private val _myPageUiEvent = Channel() val myPageUiEvent = _myPageUiEvent.receiveAsFlow() @@ -28,7 +30,7 @@ class MyPageViewModel @Inject constructor( fun getProfile() { viewModelScope.launch { - userRepository.getProfile { error -> + getUserInfoUseCase { error -> when (error) { is WithPeaceError.GeneralError -> { _myPageUiEvent.send(MyPageUiEvent.GeneralError) @@ -47,4 +49,19 @@ class MyPageViewModel @Inject constructor( } } } + + fun logout() { + viewModelScope.launch { + logoutUseCase { + _myPageUiEvent.send( + when (it) { + is WithPeaceError.GeneralError -> MyPageUiEvent.GeneralError + is WithPeaceError.UnAuthorized -> MyPageUiEvent.UnAuthorizedError + }, + ) + }.collect { + _myPageUiEvent.send(MyPageUiEvent.Logout) + } + } + } } \ No newline at end of file diff --git a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/navigation/MyPageNavigation.kt b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/navigation/MyPageNavigation.kt index 8832ae62..a4c6c24b 100644 --- a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/navigation/MyPageNavigation.kt +++ b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/navigation/MyPageNavigation.kt @@ -15,14 +15,14 @@ fun NavController.navigateMyPage(navOptions: NavOptions? = null) { fun NavGraphBuilder.myPageNavGraph( onShowSnackBar: (message: String) -> Unit, onEditProfile: (nickname: String, profileImageUrl: String) -> Unit, - onLogoutClick: () -> Unit, + onLogoutSuccess: () -> Unit, onWithdrawClick: () -> Unit, ) { composable(route = MY_PAGE_ROUTE) { MyPageRoute( onShowSnackBar = onShowSnackBar, onEditProfile = onEditProfile, - onLogoutClick = onLogoutClick, + onLogoutSuccess = onLogoutSuccess, onWithdrawClick = onWithdrawClick, ) } diff --git a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/uistate/MyPageUiEvent.kt b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/uistate/MyPageUiEvent.kt index b0b323de..baefb607 100644 --- a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/uistate/MyPageUiEvent.kt +++ b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/uistate/MyPageUiEvent.kt @@ -2,5 +2,6 @@ package com.withpeace.withpeace.feature.mypage.uistate sealed interface MyPageUiEvent { data object UnAuthorizedError: MyPageUiEvent - data object GeneralError: MyPageUiEvent + data object GeneralError : MyPageUiEvent + data object Logout : MyPageUiEvent } \ No newline at end of file From 8210a818757b8fc626fbbfb90c825bc1636b3a16 Mon Sep 17 00:00:00 2001 From: kwakjoohyeong Date: Sun, 31 Mar 2024 23:33:30 +0900 Subject: [PATCH 32/35] =?UTF-8?q?fix:=20=EB=B6=84=EA=B8=B0=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/app/profileeditor/ProfileEditorScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt index 2dfcfc89..9f0d0962 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt @@ -319,7 +319,7 @@ private fun NickNameTextField( .height(1.dp), ) } - if (nicknameValidStatus !is ProfileNicknameValidUiState.Valid && isChanged.not()) { + if (nicknameValidStatus !is ProfileNicknameValidUiState.Valid && isChanged) { Text( text = if (nicknameValidStatus is ProfileNicknameValidUiState.InValidDuplicated) stringResource( R.string.nickname_duplicated, From 0a8ad9471ba059dae8ad04921acf94e18aae3107 Mon Sep 17 00:00:00 2001 From: kwakjoohyeong Date: Mon, 1 Apr 2024 20:04:44 +0900 Subject: [PATCH 33/35] =?UTF-8?q?fix:=20=ED=94=BC=EB=93=9C=EB=B0=B1=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/data/mapper/ChangedProfileMapper.kt | 2 +- .../core/data/mapper/ProfileInfoMapper.kt | 3 +- .../model/profile/ChangingProfileInfo.kt | 9 +- .../core/domain/model/profile/Nickname.kt | 14 +-- .../model/profile/ProfileChangingStatus.kt | 13 ++- .../core/domain/model/profile/ProfileInfo.kt | 2 +- .../domain/usecase/UpdateProfileUseCase.kt | 10 +- .../domain/usecase/VerifyNicknameUseCase.kt | 11 ++- .../mypage/uistate/ProfileInfoMapper.kt | 2 +- .../app/profileeditor/ProfileEditorScreen.kt | 13 +-- .../profileeditor/ProfileEditorViewModel.kt | 97 +++++++------------ .../app/profileeditor/ProfileModelMapper.kt | 14 ++- .../uistate/ProfileEditUiState.kt | 7 +- .../profileeditor/uistate/ProfileUiModel.kt | 1 + 14 files changed, 91 insertions(+), 107 deletions(-) diff --git a/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/mapper/ChangedProfileMapper.kt b/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/mapper/ChangedProfileMapper.kt index dcfc9090..3122c492 100644 --- a/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/mapper/ChangedProfileMapper.kt +++ b/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/mapper/ChangedProfileMapper.kt @@ -6,7 +6,7 @@ import com.withpeace.withpeace.core.network.di.response.ChangedProfileResponse fun ChangedProfileResponse.toDomain(): ChangedProfile { return ChangedProfile( - nickname = Nickname.create(this.nickname), + nickname = Nickname(this.nickname), profileImageUrl = profileImageUrl, ) } diff --git a/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/mapper/ProfileInfoMapper.kt b/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/mapper/ProfileInfoMapper.kt index dc659d88..de9a2080 100644 --- a/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/mapper/ProfileInfoMapper.kt +++ b/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/mapper/ProfileInfoMapper.kt @@ -1,11 +1,12 @@ package com.withpeace.withpeace.core.data.mapper +import com.withpeace.withpeace.core.domain.model.profile.Nickname import com.withpeace.withpeace.core.domain.model.profile.ProfileInfo import com.withpeace.withpeace.core.network.di.response.ProfileResponse fun ProfileResponse.toDomain(): ProfileInfo { return ProfileInfo( - nickname = nickname, + nickname = Nickname(nickname), profileImageUrl = profileImageUrl, email = email, ) diff --git a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ChangingProfileInfo.kt b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ChangingProfileInfo.kt index 1db90ed8..9fbd7f0d 100644 --- a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ChangingProfileInfo.kt +++ b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ChangingProfileInfo.kt @@ -1,10 +1,7 @@ package com.withpeace.withpeace.core.domain.model.profile -data class ChangingProfileInfo(val nickname: Nickname, val profileImage: String?) { - - companion object { - operator fun invoke(nickname: String, profileImage: String?): ChangingProfileInfo { - return ChangingProfileInfo(Nickname.create(nickname), profileImage) - } +data class ChangingProfileInfo(val nickname: String, val profileImage: String) { + fun getChangingStatus(baseProfileInfo: ChangingProfileInfo): ProfileChangingStatus { + return ProfileChangingStatus.getStatus(baseProfileInfo, this) } } \ No newline at end of file diff --git a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/Nickname.kt b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/Nickname.kt index 307ede34..d4d9a9dc 100644 --- a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/Nickname.kt +++ b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/Nickname.kt @@ -1,17 +1,17 @@ package com.withpeace.withpeace.core.domain.model.profile -/* - Data와 Presentation Layer에서 모두 쓰일 수 있는 도메인입니다. - */ @JvmInline -value class Nickname private constructor(val value: String) { +value class Nickname(val value: String) { + init { + require(verifyFormat(value)) { "잘못된 닉네임 형식입니다" } + } companion object { private const val NICKNAME_REGEX_PATTERN = "[a-zA-Z0-9가-힣]{2,10}" - fun create(value: String): Nickname = Nickname(value) - fun verifyNickname(nickname: String): Boolean { + + fun verifyFormat(nickname: String): Boolean { val regex = Regex(NICKNAME_REGEX_PATTERN) return regex.matches(nickname) } } -} +} \ No newline at end of file diff --git a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ProfileChangingStatus.kt b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ProfileChangingStatus.kt index a229763c..d125d1c5 100644 --- a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ProfileChangingStatus.kt +++ b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ProfileChangingStatus.kt @@ -2,9 +2,13 @@ package com.withpeace.withpeace.core.domain.model.profile sealed interface ProfileChangingStatus { data object OnlyNicknameChanging : ProfileChangingStatus + data object OnlyImageChanging : ProfileChangingStatus + data object AllChanging : ProfileChangingStatus + data object Same : ProfileChangingStatus + companion object { fun getStatus( beforeProfile: ChangingProfileInfo, @@ -12,13 +16,14 @@ sealed interface ProfileChangingStatus { ): ProfileChangingStatus { return when { beforeProfile.profileImage != afterProfile.profileImage && - afterProfile.nickname != beforeProfile.nickname && - afterProfile.profileImage != null -> AllChanging + afterProfile.nickname != beforeProfile.nickname -> AllChanging - afterProfile.profileImage != null && + beforeProfile.nickname == afterProfile.nickname && beforeProfile.profileImage != afterProfile.profileImage -> OnlyImageChanging - else -> OnlyNicknameChanging + beforeProfile.nickname != afterProfile.nickname -> OnlyNicknameChanging + + else -> Same } } } diff --git a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ProfileInfo.kt b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ProfileInfo.kt index 78ec01bb..ccee3468 100644 --- a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ProfileInfo.kt +++ b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ProfileInfo.kt @@ -1,7 +1,7 @@ package com.withpeace.withpeace.core.domain.model.profile data class ProfileInfo( - val nickname: String, + val nickname: Nickname, val profileImageUrl: String, val email: String, ) diff --git a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/UpdateProfileUseCase.kt b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/UpdateProfileUseCase.kt index 8831bdcc..dd99d503 100644 --- a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/UpdateProfileUseCase.kt +++ b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/UpdateProfileUseCase.kt @@ -5,6 +5,8 @@ import com.withpeace.withpeace.core.domain.model.profile.ChangingProfileInfo import com.withpeace.withpeace.core.domain.model.profile.ProfileChangingStatus import com.withpeace.withpeace.core.domain.repository.UserRepository import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOf import javax.inject.Inject class UpdateProfileUseCase @Inject constructor( @@ -18,19 +20,21 @@ class UpdateProfileUseCase @Inject constructor( return when (ProfileChangingStatus.getStatus(beforeProfile, afterProfile)) { ProfileChangingStatus.AllChanging -> { userRepository.updateProfile( - afterProfile.nickname.value, afterProfile.profileImage!!, + afterProfile.nickname, afterProfile.profileImage, onError = onError, ) } ProfileChangingStatus.OnlyImageChanging -> { userRepository.updateProfileImage( - profileImage = afterProfile.profileImage!!, + profileImage = afterProfile.profileImage, onError = onError, ) } ProfileChangingStatus.OnlyNicknameChanging -> { - userRepository.updateNickname(afterProfile.nickname.value, onError = onError) + userRepository.updateNickname(afterProfile.nickname, onError = onError) } + + ProfileChangingStatus.Same -> flow { onError(WithPeaceError.GeneralError(3)) } } } } \ No newline at end of file diff --git a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/VerifyNicknameUseCase.kt b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/VerifyNicknameUseCase.kt index d821b97d..060cdee7 100644 --- a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/VerifyNicknameUseCase.kt +++ b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/VerifyNicknameUseCase.kt @@ -4,16 +4,21 @@ import com.withpeace.withpeace.core.domain.model.WithPeaceError import com.withpeace.withpeace.core.domain.model.profile.Nickname import com.withpeace.withpeace.core.domain.repository.UserRepository import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.map import javax.inject.Inject class VerifyNicknameUseCase @Inject constructor( private val userRepository: UserRepository, ) { - operator fun invoke(nickname: String, onError: suspend (WithPeaceError) -> Unit): Flow { - if (!Nickname.verifyNickname(nickname)) { + operator fun invoke( + nickname: String, + onError: suspend (WithPeaceError) -> Unit, + ): Flow { + if (!Nickname.verifyFormat(nickname)) { return flow { onError(WithPeaceError.GeneralError(code = 1)) } } - return userRepository.verifyNicknameDuplicated(Nickname.create(nickname), onError = onError) + return userRepository.verifyNicknameDuplicated(Nickname(nickname), onError = onError) } } diff --git a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/uistate/ProfileInfoMapper.kt b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/uistate/ProfileInfoMapper.kt index 8119791c..5fbcb3ec 100644 --- a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/uistate/ProfileInfoMapper.kt +++ b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/uistate/ProfileInfoMapper.kt @@ -4,6 +4,6 @@ import com.withpeace.withpeace.core.domain.model.profile.ProfileInfo internal fun ProfileInfo.toUiModel(): ProfileInfoUiModel { return ProfileInfoUiModel( - nickname, profileImageUrl, email, + nickname.value, profileImageUrl, email, ) } \ No newline at end of file diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt index 9f0d0962..c8c79f41 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt @@ -50,7 +50,6 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.app.profileeditor.uistate.ProfileEditUiEvent -import com.app.profileeditor.uistate.ProfileEditUiState import com.app.profileeditor.uistate.ProfileNicknameValidUiState import com.app.profileeditor.uistate.ProfileUiModel import com.skydoves.landscapist.glide.GlideImage @@ -70,9 +69,7 @@ fun ProfileEditorRoute( onUpdateSuccess: () -> Unit, ) { var showAlertDialog by remember { mutableStateOf(false) } - val profileUiState: ProfileEditUiState by viewModel.profileEditUiState.collectAsStateWithLifecycle() - - val profileInfo = profileUiState.currentProfileInfo + val profileInfo: ProfileUiModel by viewModel.profileEditUiState.collectAsStateWithLifecycle() if (showAlertDialog) { ModifySaveDialog( @@ -114,9 +111,9 @@ fun ProfileEditorRoute( ProfileEditorScreen( - profileInfo = ProfileUiModel(profileInfo.nickname, profileInfo.profileImage), + profileInfo = profileInfo, onClickBackButton = { - if (profileUiState.isChanged) { + if (profileInfo.isChanged) { showAlertDialog = true } else { onClickBackButton() @@ -131,7 +128,7 @@ fun ProfileEditorRoute( viewModel.verifyNickname() }, nicknameValidStatus = viewModel.profileNicknameValidUiState.collectAsStateWithLifecycle().value, - isChanged = profileUiState.isChanged, + isChanged = profileInfo.isChanged, ) } @@ -444,7 +441,7 @@ fun ModifySaveDialogContent( fun ProfileEditorPreview() { WithpeaceTheme { ProfileEditorScreen( - profileInfo = ProfileUiModel("nickname", ""), + profileInfo = ProfileUiModel("nickname", "", false), onClickBackButton = {}, onNavigateToGallery = {}, onEditCompleted = {}, diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt index 55a09d22..4e6d9aa0 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt @@ -6,7 +6,6 @@ import androidx.lifecycle.viewModelScope import com.app.profileeditor.navigation.PROFILE_IMAGE_URL_ARGUMENT import com.app.profileeditor.navigation.PROFILE_NICKNAME_ARGUMENT import com.app.profileeditor.uistate.ProfileEditUiEvent -import com.app.profileeditor.uistate.ProfileEditUiState import com.app.profileeditor.uistate.ProfileNicknameValidUiState import com.app.profileeditor.uistate.ProfileUiModel import com.withpeace.withpeace.core.domain.model.WithPeaceError @@ -19,6 +18,8 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import toDomain +import toUiModel import javax.inject.Inject @HiltViewModel @@ -27,14 +28,15 @@ class ProfileEditorViewModel @Inject constructor( private val verifyNicknameUseCase: VerifyNicknameUseCase, private val updateProfileUseCase: UpdateProfileUseCase, ) : ViewModel() { - val baseProfileInfo = ProfileUiModel( + private val baseProfileInfo = ProfileUiModel( nickname = savedStateHandle.get(PROFILE_NICKNAME_ARGUMENT) ?: "", profileImage = savedStateHandle.get(PROFILE_IMAGE_URL_ARGUMENT) ?: "default.png", + isChanged = false, ) // 최초 정보에서 변경사항이 있는지 비교를 위한 필드 private val _profileEditUiState = - MutableStateFlow( - ProfileEditUiState(baseProfileInfo, false), + MutableStateFlow( + ProfileUiModel(baseProfileInfo.nickname, baseProfileInfo.profileImage, false), ) val profileEditUiState = _profileEditUiState.asStateFlow() @@ -46,51 +48,20 @@ class ProfileEditorViewModel @Inject constructor( val profileNicknameValidUiState = _profileNicknameValidUiState.asStateFlow() fun onImageChanged(imageUri: String) { - val changingUiState = _profileEditUiState.value.currentProfileInfo.copy( - nickname = _profileEditUiState.value.currentProfileInfo.nickname, - profileImage = imageUri, - ) _profileEditUiState.update { - return@update if (baseProfileInfo == changingUiState - ) { - _profileEditUiState.value.copy( - currentProfileInfo = baseProfileInfo, - isChanged = false, - ) - } else { - _profileEditUiState.value.copy( - currentProfileInfo = changingUiState, - isChanged = true, - ) - } + return@update it.toDomain().copy(profileImage = imageUri).toUiModel(baseProfileInfo) } } + fun onNickNameChanged(nickname: String) { - val changingUiState = _profileEditUiState.value.currentProfileInfo.copy( - nickname = nickname, - profileImage = _profileEditUiState.value.currentProfileInfo.profileImage, - ) _profileEditUiState.update { - // changingUiState.toDomain().getChangingState(baseProfileInfo.toDomain()).toUiModel() - return@update if (baseProfileInfo == changingUiState - ) { - _profileEditUiState.value.copy( - currentProfileInfo = baseProfileInfo, - isChanged = false, - ) - } else { - _profileEditUiState.value.copy( - currentProfileInfo = changingUiState, - isChanged = true, - ) - } + return@update it.toDomain().copy(nickname = nickname).toUiModel(baseProfileInfo) } } - fun verifyNickname() { - if (profileEditUiState.value.isChanged.not() - ) { - return + fun verifyNickname() { // 닉네임만 바뀐 경우, 기본 값이 아닌 경우 + if (_profileEditUiState.value.nickname == baseProfileInfo.nickname) { + return updateIsNicknameValidStatus(ProfileNicknameValidUiState.Valid) } viewModelScope.launch { verifyNicknameUseCase( @@ -102,10 +73,11 @@ class ProfileEditorViewModel @Inject constructor( 2 -> updateIsNicknameValidStatus(ProfileNicknameValidUiState.InValidDuplicated) } } + else -> _profileEditUiEvent.send(ProfileEditUiEvent.ShowFailure) } }, - nickname = _profileEditUiState.value.currentProfileInfo.nickname, + nickname = _profileEditUiState.value.nickname, ).collect { updateIsNicknameValidStatus(ProfileNicknameValidUiState.Valid) } @@ -118,30 +90,27 @@ class ProfileEditorViewModel @Inject constructor( fun updateProfile() { viewModelScope.launch { - if (_profileEditUiState.value.isChanged.not()) { - _profileEditUiEvent.send(ProfileEditUiEvent.ShowUnchanged) - } else if (_profileEditUiState.value.isChanged) { - updateProfileUseCase( - beforeProfile = baseProfileInfo.toDomain(), - afterProfile = _profileEditUiState.value.currentProfileInfo.toDomain(), - onError = { - _profileEditUiEvent.send( - when (it) { - is WithPeaceError.GeneralError -> { - when (it.code) { - 40001 -> ProfileEditUiEvent.ShowInvalidFormatSnackBar - 40007 -> ProfileEditUiEvent.ShowDuplicateSnackBar - else -> ProfileEditUiEvent.ShowFailure - } + updateProfileUseCase( + beforeProfile = baseProfileInfo.toDomain(), + afterProfile = _profileEditUiState.value.toDomain(), + onError = { + _profileEditUiEvent.send( + when (it) { + is WithPeaceError.GeneralError -> { + when (it.code) { + 3 -> ProfileEditUiEvent.ShowUnchanged + 40001 -> ProfileEditUiEvent.ShowInvalidFormatSnackBar + 40007 -> ProfileEditUiEvent.ShowDuplicateSnackBar + else -> ProfileEditUiEvent.ShowFailure } + } - is WithPeaceError.UnAuthorized -> ProfileEditUiEvent.UnAuthorized - }, - ) - }, - ).collect { - _profileEditUiEvent.send(ProfileEditUiEvent.ShowUpdateSuccess) - } + is WithPeaceError.UnAuthorized -> ProfileEditUiEvent.UnAuthorized + }, + ) + }, + ).collect { + _profileEditUiEvent.send(ProfileEditUiEvent.ShowUpdateSuccess) } } } diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileModelMapper.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileModelMapper.kt index 81a1ef22..b647ed7c 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileModelMapper.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileModelMapper.kt @@ -1,8 +1,18 @@ -package com.app.profileeditor - import com.app.profileeditor.uistate.ProfileUiModel import com.withpeace.withpeace.core.domain.model.profile.ChangingProfileInfo +import com.withpeace.withpeace.core.domain.model.profile.ProfileChangingStatus internal fun ProfileUiModel.toDomain(): ChangingProfileInfo { return ChangingProfileInfo(nickname, profileImage) +} + +internal fun ChangingProfileInfo.toUiModel(baseProfile: ProfileUiModel): ProfileUiModel { + return ProfileUiModel( + nickname, + profileImage, + isChanged = when (getChangingStatus(baseProfile.toDomain())) { + ProfileChangingStatus.Same -> false + else -> true + }, + ) } \ No newline at end of file diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/uistate/ProfileEditUiState.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/uistate/ProfileEditUiState.kt index ebe090f7..c70b7c11 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/uistate/ProfileEditUiState.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/uistate/ProfileEditUiState.kt @@ -6,9 +6,4 @@ package com.app.profileeditor.uistate // ) : ProfileEditUiState // // data object NoChanges : ProfileEditUiState -// } - -data class ProfileEditUiState( - val currentProfileInfo: ProfileUiModel, - val isChanged: Boolean, -) \ No newline at end of file +// } \ No newline at end of file diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/uistate/ProfileUiModel.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/uistate/ProfileUiModel.kt index fda4f440..ec65414b 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/uistate/ProfileUiModel.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/uistate/ProfileUiModel.kt @@ -3,4 +3,5 @@ package com.app.profileeditor.uistate data class ProfileUiModel( val nickname: String, val profileImage: String, + val isChanged: Boolean, ) \ No newline at end of file From c4cfcfc2bf5d0f20f3de77ef67853c68ef1ab125 Mon Sep 17 00:00:00 2001 From: kwakjoohyeong Date: Mon, 1 Apr 2024 20:15:40 +0900 Subject: [PATCH 34/35] =?UTF-8?q?fix:=20=EC=95=88=EC=93=B0=EB=8A=94=20?= =?UTF-8?q?=ED=8C=A8=ED=82=A4=EC=A7=80=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/com/withpeace/withpeace/navigation/NavHost.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/main/java/com/withpeace/withpeace/navigation/NavHost.kt b/app/src/main/java/com/withpeace/withpeace/navigation/NavHost.kt index c2e7b6dd..d8c07dbf 100644 --- a/app/src/main/java/com/withpeace/withpeace/navigation/NavHost.kt +++ b/app/src/main/java/com/withpeace/withpeace/navigation/NavHost.kt @@ -14,7 +14,6 @@ import com.withpeace.withpeace.feature.login.navigation.LOGIN_ROUTE import com.withpeace.withpeace.feature.login.navigation.loginNavGraph import com.withpeace.withpeace.feature.login.navigation.navigateLogin import com.withpeace.withpeace.feature.mypage.navigation.myPageNavGraph -import com.withpeace.withpeace.feature.post.navigation.postNavGraph import com.withpeace.withpeace.feature.postlist.navigation.postListGraph import com.withpeace.withpeace.feature.registerpost.navigation.IMAGE_LIST_ARGUMENT import com.withpeace.withpeace.feature.registerpost.navigation.registerPostNavGraph @@ -59,7 +58,6 @@ fun WithpeaceNavHost( onShowSnackBar = onShowSnackBar, ) homeNavGraph(onShowSnackBar) - postNavGraph(onShowSnackBar) myPageNavGraph( onShowSnackBar = onShowSnackBar, onEditProfile = { nickname, profileImageUrl -> From 2d8a150c52b6b3405d602c316a1cb208843e8336 Mon Sep 17 00:00:00 2001 From: kwakjoohyeong Date: Mon, 1 Apr 2024 22:19:00 +0900 Subject: [PATCH 35/35] =?UTF-8?q?refactor:=20=EA=B2=B0=EA=B3=BC=EA=B0=92?= =?UTF-8?q?=EC=9D=84=20=ED=86=B5=ED=95=9C=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EA=B0=B1=EC=8B=A0=20=EB=B0=8F=20=EB=A6=AC=EB=B7=B0=20=EB=B0=98?= =?UTF-8?q?=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../withpeace/withpeace/navigation/NavHost.kt | 10 ++++-- .../data/repository/DefaultUserRepository.kt | 16 +++++---- .../domain/model/profile/ChangedProfile.kt | 4 +-- .../core/domain/repository/UserRepository.kt | 7 ++-- .../domain/usecase/UpdateProfileUseCase.kt | 3 +- .../di/response/ChangedProfileResponse.kt | 3 ++ .../core/network/di/service/UserService.kt | 3 +- .../withpeace/feature/mypage/MyPageScreen.kt | 16 +-------- .../feature/mypage/MyPageViewModel.kt | 33 ++++++++++++++----- .../mypage/navigation/MyPageNavigation.kt | 10 ++++++ .../feature/mypage/uistate/MyPageUiState.kt | 6 ---- .../app/profileeditor/ProfileEditorScreen.kt | 14 ++++---- .../profileeditor/ProfileEditorViewModel.kt | 29 ++++++++-------- .../navigation/ProfileEditorNavigation.kt | 2 +- .../uistate/ProfileEditUiEvent.kt | 10 +++--- .../uistate/ProfileEditUiState.kt | 9 ----- 16 files changed, 95 insertions(+), 80 deletions(-) delete mode 100644 feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/uistate/MyPageUiState.kt delete mode 100644 feature/profileeditor/src/main/java/com/app/profileeditor/uistate/ProfileEditUiState.kt diff --git a/app/src/main/java/com/withpeace/withpeace/navigation/NavHost.kt b/app/src/main/java/com/withpeace/withpeace/navigation/NavHost.kt index d8c07dbf..11b6a7a1 100644 --- a/app/src/main/java/com/withpeace/withpeace/navigation/NavHost.kt +++ b/app/src/main/java/com/withpeace/withpeace/navigation/NavHost.kt @@ -13,6 +13,8 @@ import com.withpeace.withpeace.feature.home.navigation.homeNavGraph import com.withpeace.withpeace.feature.login.navigation.LOGIN_ROUTE import com.withpeace.withpeace.feature.login.navigation.loginNavGraph import com.withpeace.withpeace.feature.login.navigation.navigateLogin +import com.withpeace.withpeace.feature.mypage.navigation.MY_PAGE_CHANGED_IMAGE_ARGUMENT +import com.withpeace.withpeace.feature.mypage.navigation.MY_PAGE_CHANGED_NICKNAME_ARGUMENT import com.withpeace.withpeace.feature.mypage.navigation.myPageNavGraph import com.withpeace.withpeace.feature.postlist.navigation.postListGraph import com.withpeace.withpeace.feature.registerpost.navigation.IMAGE_LIST_ARGUMENT @@ -85,9 +87,13 @@ fun WithpeaceNavHost( onNavigateToGallery = { navController.navigateToGallery(imageLimit = 1) }, - onUpdateSuccess = { + onUpdateSuccess = { nickname, imageUrl -> + navController.previousBackStackEntry?.savedStateHandle?.apply { + set(MY_PAGE_CHANGED_NICKNAME_ARGUMENT, nickname) + set(MY_PAGE_CHANGED_IMAGE_ARGUMENT, imageUrl) + } navController.popBackStack() - } + }, ) postListGraph(onShowSnackBar) } diff --git a/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultUserRepository.kt b/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultUserRepository.kt index 956a22de..a1f67b27 100644 --- a/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultUserRepository.kt +++ b/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultUserRepository.kt @@ -10,6 +10,7 @@ import com.withpeace.withpeace.core.data.mapper.toDomain import com.withpeace.withpeace.core.data.util.convertToFile import com.withpeace.withpeace.core.datastore.dataStore.TokenPreferenceDataSource import com.withpeace.withpeace.core.domain.model.WithPeaceError +import com.withpeace.withpeace.core.domain.model.profile.ChangedProfile import com.withpeace.withpeace.core.domain.model.profile.Nickname import com.withpeace.withpeace.core.domain.model.profile.ProfileInfo import com.withpeace.withpeace.core.domain.repository.UserRepository @@ -60,12 +61,12 @@ class DefaultUserRepository @Inject constructor( nickname: String, profileImage: String, onError: suspend (WithPeaceError) -> Unit, - ): Flow = flow { + ): Flow = flow { val imagePart = getImagePart(profileImage) userService.updateProfile( nickname.toRequestBody("text/plain".toMediaTypeOrNull()), imagePart, ).suspendMapSuccess { - emit(Unit) + emit(this.data.toDomain()) }.suspendOnError { if (statusCode.code == 401) { onError(WithPeaceError.UnAuthorized()) @@ -78,10 +79,13 @@ class DefaultUserRepository @Inject constructor( } } - override fun updateNickname(nickname: String, onError: suspend (WithPeaceError) -> Unit): Flow = + override fun updateNickname( + nickname: String, + onError: suspend (WithPeaceError) -> Unit, + ): Flow = flow { userService.updateNickname(NicknameRequest(nickname)).suspendMapSuccess { - emit(Unit) + emit(ChangedProfile(nickname = Nickname(this.data))) }.suspendOnError { if (statusCode.code == 401) { onError(WithPeaceError.UnAuthorized()) @@ -97,10 +101,10 @@ class DefaultUserRepository @Inject constructor( override fun updateProfileImage( profileImage: String, onError: suspend (WithPeaceError) -> Unit, - ): Flow = flow { + ): Flow = flow { val imagePart = getImagePart(profileImage) userService.updateImage(imagePart).suspendMapSuccess { - emit(Unit) + emit(ChangedProfile(profileImageUrl = this.data)) }.suspendOnError { if (statusCode.code == 401) { onError(WithPeaceError.UnAuthorized()) diff --git a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ChangedProfile.kt b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ChangedProfile.kt index af2cdcf7..ba3e9da3 100644 --- a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ChangedProfile.kt +++ b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/profile/ChangedProfile.kt @@ -1,6 +1,6 @@ package com.withpeace.withpeace.core.domain.model.profile data class ChangedProfile( - val nickname: Nickname?, - val profileImageUrl: String?, + val nickname: Nickname? = null, + val profileImageUrl: String? = null, ) diff --git a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/repository/UserRepository.kt b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/repository/UserRepository.kt index 82846137..692ce52c 100644 --- a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/repository/UserRepository.kt +++ b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/repository/UserRepository.kt @@ -1,6 +1,7 @@ package com.withpeace.withpeace.core.domain.repository import com.withpeace.withpeace.core.domain.model.WithPeaceError +import com.withpeace.withpeace.core.domain.model.profile.ChangedProfile import com.withpeace.withpeace.core.domain.model.profile.Nickname import com.withpeace.withpeace.core.domain.model.profile.ProfileInfo import kotlinx.coroutines.flow.Flow @@ -15,17 +16,17 @@ interface UserRepository { fun updateProfileImage( profileImage: String, onError: suspend (WithPeaceError) -> Unit, - ): Flow + ): Flow fun updateNickname( nickname: String, onError: suspend (WithPeaceError) -> Unit, - ): Flow + ): Flow fun updateProfile( nickname: String, profileImage: String, onError: suspend (WithPeaceError) -> Unit, - ): Flow + ): Flow fun verifyNicknameDuplicated( nickname: Nickname, diff --git a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/UpdateProfileUseCase.kt b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/UpdateProfileUseCase.kt index dd99d503..c93fe381 100644 --- a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/UpdateProfileUseCase.kt +++ b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/usecase/UpdateProfileUseCase.kt @@ -1,6 +1,7 @@ package com.withpeace.withpeace.core.domain.usecase import com.withpeace.withpeace.core.domain.model.WithPeaceError +import com.withpeace.withpeace.core.domain.model.profile.ChangedProfile import com.withpeace.withpeace.core.domain.model.profile.ChangingProfileInfo import com.withpeace.withpeace.core.domain.model.profile.ProfileChangingStatus import com.withpeace.withpeace.core.domain.repository.UserRepository @@ -16,7 +17,7 @@ class UpdateProfileUseCase @Inject constructor( beforeProfile: ChangingProfileInfo, afterProfile: ChangingProfileInfo, onError: suspend (WithPeaceError) -> Unit, - ): Flow { + ): Flow { return when (ProfileChangingStatus.getStatus(beforeProfile, afterProfile)) { ProfileChangingStatus.AllChanging -> { userRepository.updateProfile( diff --git a/core/network/src/main/java/com/withpeace/withpeace/core/network/di/response/ChangedProfileResponse.kt b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/response/ChangedProfileResponse.kt index 6e7f1d5f..1bb80cec 100644 --- a/core/network/src/main/java/com/withpeace/withpeace/core/network/di/response/ChangedProfileResponse.kt +++ b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/response/ChangedProfileResponse.kt @@ -1,5 +1,8 @@ package com.withpeace.withpeace.core.network.di.response +import kotlinx.serialization.Serializable + +@Serializable data class ChangedProfileResponse( val nickname: String, val profileImageUrl: String, diff --git a/core/network/src/main/java/com/withpeace/withpeace/core/network/di/service/UserService.kt b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/service/UserService.kt index 14cedd66..74a716c0 100644 --- a/core/network/src/main/java/com/withpeace/withpeace/core/network/di/service/UserService.kt +++ b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/service/UserService.kt @@ -3,6 +3,7 @@ package com.withpeace.withpeace.core.network.di.service import com.skydoves.sandwich.ApiResponse import com.withpeace.withpeace.core.network.di.request.NicknameRequest import com.withpeace.withpeace.core.network.di.response.BaseResponse +import com.withpeace.withpeace.core.network.di.response.ChangedProfileResponse import com.withpeace.withpeace.core.network.di.response.ProfileResponse import okhttp3.MultipartBody import okhttp3.RequestBody @@ -31,7 +32,7 @@ interface UserService { suspend fun updateProfile( @Part("nickname") nickname: RequestBody, @Part imageFile: MultipartBody.Part, - ): ApiResponse> + ): ApiResponse> @GET("/api/v1/users/profile/nickname/check") suspend fun isNicknameDuplicate(@Query("nickname") nickname: String): ApiResponse> diff --git a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageScreen.kt b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageScreen.kt index 87b3838a..d951ce16 100644 --- a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageScreen.kt +++ b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageScreen.kt @@ -33,7 +33,6 @@ import com.skydoves.landscapist.glide.GlideImage import com.withpeace.withpeace.core.designsystem.theme.WithpeaceTheme import com.withpeace.withpeace.core.designsystem.ui.TitleBar import com.withpeace.withpeace.feature.mypage.uistate.MyPageUiEvent -import com.withpeace.withpeace.feature.mypage.uistate.MyPageUiState import com.withpeace.withpeace.feature.mypage.uistate.ProfileInfoUiModel @Composable @@ -44,12 +43,7 @@ fun MyPageRoute( onLogoutSuccess: () -> Unit, onWithdrawClick: () -> Unit, ) { - val mypageUiState by viewModel.myPageUiState.collectAsStateWithLifecycle() - when (mypageUiState) { - MyPageUiState.Loading -> {} - is MyPageUiState.Success -> {} - - } + val profileInfo by viewModel.myPageUiState.collectAsStateWithLifecycle() LaunchedEffect(viewModel.myPageUiEvent) { viewModel.myPageUiEvent.collect { when (it) { @@ -65,14 +59,6 @@ fun MyPageRoute( } } } - LaunchedEffect(Unit) { - viewModel.getProfile() - } - val profileInfo = (mypageUiState as? MyPageUiState.Success)?.profileInfo ?: ProfileInfoUiModel( - "nickname", - "default.png", - "", - ) // TODO("없을 시 로컬에서 가지고 온다.") MyPageScreen( onEditProfile = { onEditProfile(it.nickname, it.profileImage) diff --git a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageViewModel.kt b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageViewModel.kt index 0775f7fe..a392702f 100644 --- a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageViewModel.kt +++ b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/MyPageViewModel.kt @@ -6,7 +6,7 @@ import com.withpeace.withpeace.core.domain.model.WithPeaceError import com.withpeace.withpeace.core.domain.usecase.GetProfileInfoUseCase import com.withpeace.withpeace.core.domain.usecase.LogoutUseCase import com.withpeace.withpeace.feature.mypage.uistate.MyPageUiEvent -import com.withpeace.withpeace.feature.mypage.uistate.MyPageUiState +import com.withpeace.withpeace.feature.mypage.uistate.ProfileInfoUiModel import com.withpeace.withpeace.feature.mypage.uistate.toUiModel import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.channels.Channel @@ -25,10 +25,20 @@ class MyPageViewModel @Inject constructor( private val _myPageUiEvent = Channel() val myPageUiEvent = _myPageUiEvent.receiveAsFlow() - private val _myPageUiState = MutableStateFlow(MyPageUiState.Loading) - val myPageUiState = _myPageUiState.asStateFlow() + private val _profileUiModel = MutableStateFlow( + ProfileInfoUiModel( + "nickname", + "default.png", + "", + ), + ) + val myPageUiState = _profileUiModel.asStateFlow() - fun getProfile() { + init { + getProfile() + } + + private fun getProfile() { viewModelScope.launch { getUserInfoUseCase { error -> when (error) { @@ -41,15 +51,22 @@ class MyPageViewModel @Inject constructor( } } }.collect { profileInfo -> - _myPageUiState.update { - MyPageUiState.Success( - profileInfo.toUiModel(), - ) + _profileUiModel.update { + profileInfo.toUiModel() } } } } + fun updateProfile(nickname: String?, profileUrl: String?) { + _profileUiModel.update { + it.copy( + nickname = nickname ?: it.nickname, + profileImage = profileUrl ?: it.profileImage, + ) + } + } + fun logout() { viewModelScope.launch { logoutUseCase { diff --git a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/navigation/MyPageNavigation.kt b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/navigation/MyPageNavigation.kt index a4c6c24b..f6038f80 100644 --- a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/navigation/MyPageNavigation.kt +++ b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/navigation/MyPageNavigation.kt @@ -1,12 +1,16 @@ package com.withpeace.withpeace.feature.mypage.navigation +import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptions import androidx.navigation.compose.composable import com.withpeace.withpeace.feature.mypage.MyPageRoute +import com.withpeace.withpeace.feature.mypage.MyPageViewModel const val MY_PAGE_ROUTE = "myPageRoute" +const val MY_PAGE_CHANGED_NICKNAME_ARGUMENT = "myPageChangedNicknameArgument" +const val MY_PAGE_CHANGED_IMAGE_ARGUMENT = "myPageChangedImageArgument" fun NavController.navigateMyPage(navOptions: NavOptions? = null) { navigate(MY_PAGE_ROUTE, navOptions) @@ -17,13 +21,19 @@ fun NavGraphBuilder.myPageNavGraph( onEditProfile: (nickname: String, profileImageUrl: String) -> Unit, onLogoutSuccess: () -> Unit, onWithdrawClick: () -> Unit, + ) { composable(route = MY_PAGE_ROUTE) { + val nickname = it.savedStateHandle.get(MY_PAGE_CHANGED_NICKNAME_ARGUMENT) + val profile = it.savedStateHandle.get(MY_PAGE_CHANGED_IMAGE_ARGUMENT) + val viewModel: MyPageViewModel = hiltViewModel() + viewModel.updateProfile(nickname, profile) MyPageRoute( onShowSnackBar = onShowSnackBar, onEditProfile = onEditProfile, onLogoutSuccess = onLogoutSuccess, onWithdrawClick = onWithdrawClick, + viewModel = viewModel, ) } } \ No newline at end of file diff --git a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/uistate/MyPageUiState.kt b/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/uistate/MyPageUiState.kt deleted file mode 100644 index 22e358f8..00000000 --- a/feature/mypage/src/main/java/com/withpeace/withpeace/feature/mypage/uistate/MyPageUiState.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.withpeace.withpeace.feature.mypage.uistate - -sealed interface MyPageUiState { - data object Loading : MyPageUiState - data class Success(val profileInfo: ProfileInfoUiModel) : MyPageUiState -} \ No newline at end of file diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt index c8c79f41..53b150df 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorScreen.kt @@ -66,7 +66,7 @@ fun ProfileEditorRoute( onClickBackButton: () -> Unit, onNavigateToGallery: () -> Unit, viewModel: ProfileEditorViewModel, - onUpdateSuccess: () -> Unit, + onUpdateSuccess: (nickname: String, imageUrl: String) -> Unit, ) { var showAlertDialog by remember { mutableStateOf(false) } val profileInfo: ProfileUiModel by viewModel.profileEditUiState.collectAsStateWithLifecycle() @@ -90,17 +90,17 @@ fun ProfileEditorRoute( LaunchedEffect(viewModel.profileEditUiEvent) { viewModel.profileEditUiEvent.collect { when (it) { - ProfileEditUiEvent.ShowDuplicateSnackBar -> onShowSnackBar("중복된 닉네임입니다.") + ProfileEditUiEvent.NicknameDuplicated -> onShowSnackBar("중복된 닉네임입니다.") - ProfileEditUiEvent.ShowInvalidFormatSnackBar -> onShowSnackBar("닉네임 형식이 올바르지 않습니다.") + ProfileEditUiEvent.NicknameInvalidFormat -> onShowSnackBar("닉네임 형식이 올바르지 않습니다.") - ProfileEditUiEvent.ShowFailure -> onShowSnackBar("서버와 통신 중 오류가 발생했습니다.") + ProfileEditUiEvent.UpdateFailure -> onShowSnackBar("서버와 통신 중 오류가 발생했습니다.") - ProfileEditUiEvent.ShowUnchanged -> onShowSnackBar("수정사항이 없습니다.") + ProfileEditUiEvent.ProfileUnchanged -> onShowSnackBar("수정사항이 없습니다.") - ProfileEditUiEvent.ShowUpdateSuccess -> { + is ProfileEditUiEvent.UpdateSuccess -> { onShowSnackBar("변경되었습니다.") - onUpdateSuccess() + onUpdateSuccess(it.nickname, it.imageUrl) } ProfileEditUiEvent.UnAuthorized -> onShowSnackBar("인가 되지 않은 게정이에요") diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt index 4e6d9aa0..070f1415 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/ProfileEditorViewModel.kt @@ -61,7 +61,7 @@ class ProfileEditorViewModel @Inject constructor( fun verifyNickname() { // 닉네임만 바뀐 경우, 기본 값이 아닌 경우 if (_profileEditUiState.value.nickname == baseProfileInfo.nickname) { - return updateIsNicknameValidStatus(ProfileNicknameValidUiState.Valid) + return _profileNicknameValidUiState.update { ProfileNicknameValidUiState.Valid } } viewModelScope.launch { verifyNicknameUseCase( @@ -69,25 +69,21 @@ class ProfileEditorViewModel @Inject constructor( when (error) { is WithPeaceError.GeneralError -> { when (error.code) { - 1 -> updateIsNicknameValidStatus(ProfileNicknameValidUiState.InValidFormat) - 2 -> updateIsNicknameValidStatus(ProfileNicknameValidUiState.InValidDuplicated) + 1 -> _profileNicknameValidUiState.update { ProfileNicknameValidUiState.InValidFormat } + 2 -> _profileNicknameValidUiState.update { ProfileNicknameValidUiState.InValidDuplicated } } } - else -> _profileEditUiEvent.send(ProfileEditUiEvent.ShowFailure) + else -> _profileEditUiEvent.send(ProfileEditUiEvent.UpdateFailure) } }, nickname = _profileEditUiState.value.nickname, ).collect { - updateIsNicknameValidStatus(ProfileNicknameValidUiState.Valid) + _profileNicknameValidUiState.update { ProfileNicknameValidUiState.Valid } } } } - private fun updateIsNicknameValidStatus(status: ProfileNicknameValidUiState) { - _profileNicknameValidUiState.update { status } - } - fun updateProfile() { viewModelScope.launch { updateProfileUseCase( @@ -98,10 +94,10 @@ class ProfileEditorViewModel @Inject constructor( when (it) { is WithPeaceError.GeneralError -> { when (it.code) { - 3 -> ProfileEditUiEvent.ShowUnchanged - 40001 -> ProfileEditUiEvent.ShowInvalidFormatSnackBar - 40007 -> ProfileEditUiEvent.ShowDuplicateSnackBar - else -> ProfileEditUiEvent.ShowFailure + 3 -> ProfileEditUiEvent.ProfileUnchanged + 40001 -> ProfileEditUiEvent.NicknameInvalidFormat + 40007 -> ProfileEditUiEvent.NicknameDuplicated + else -> ProfileEditUiEvent.UpdateFailure } } @@ -110,7 +106,12 @@ class ProfileEditorViewModel @Inject constructor( ) }, ).collect { - _profileEditUiEvent.send(ProfileEditUiEvent.ShowUpdateSuccess) + _profileEditUiEvent.send( + ProfileEditUiEvent.UpdateSuccess( + it.nickname?.value ?: _profileEditUiState.value.nickname, + it.profileImageUrl ?: _profileEditUiState.value.profileImage, + ), + ) } } } diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/navigation/ProfileEditorNavigation.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/navigation/ProfileEditorNavigation.kt index 15c92669..0bbc0d1f 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/navigation/ProfileEditorNavigation.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/navigation/ProfileEditorNavigation.kt @@ -32,7 +32,7 @@ fun NavGraphBuilder.profileEditorNavGraph( onShowSnackBar: (message: String) -> Unit, onClickBackButton: () -> Unit, onNavigateToGallery: () -> Unit, - onUpdateSuccess: () -> Unit, + onUpdateSuccess: (nickname: String, imageUrl: String) -> Unit, ) { composable( route = PROFILE_EDITOR_ROUTE_WITH_ARGUMENT, diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/uistate/ProfileEditUiEvent.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/uistate/ProfileEditUiEvent.kt index ea85a54b..e9b3cbad 100644 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/uistate/ProfileEditUiEvent.kt +++ b/feature/profileeditor/src/main/java/com/app/profileeditor/uistate/ProfileEditUiEvent.kt @@ -1,10 +1,10 @@ package com.app.profileeditor.uistate sealed interface ProfileEditUiEvent { - data object ShowDuplicateSnackBar : ProfileEditUiEvent - data object ShowInvalidFormatSnackBar : ProfileEditUiEvent - data object ShowUpdateSuccess : ProfileEditUiEvent - data object ShowFailure : ProfileEditUiEvent - data object ShowUnchanged : ProfileEditUiEvent + data object NicknameDuplicated : ProfileEditUiEvent + data object NicknameInvalidFormat : ProfileEditUiEvent + data class UpdateSuccess(val nickname: String, val imageUrl: String) : ProfileEditUiEvent + data object UpdateFailure : ProfileEditUiEvent + data object ProfileUnchanged : ProfileEditUiEvent data object UnAuthorized : ProfileEditUiEvent } \ No newline at end of file diff --git a/feature/profileeditor/src/main/java/com/app/profileeditor/uistate/ProfileEditUiState.kt b/feature/profileeditor/src/main/java/com/app/profileeditor/uistate/ProfileEditUiState.kt deleted file mode 100644 index c70b7c11..00000000 --- a/feature/profileeditor/src/main/java/com/app/profileeditor/uistate/ProfileEditUiState.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.app.profileeditor.uistate - -// sealed interface ProfileEditUiState { -// data class Editing( -// val profileInfo: ProfileUiModel, -// ) : ProfileEditUiState -// -// data object NoChanges : ProfileEditUiState -// } \ No newline at end of file