From e6cd394243bfde09fd929b724ae0cfee5f51f236 Mon Sep 17 00:00:00 2001 From: hyeonseo Date: Sun, 14 Jun 2026 19:07:09 +0900 Subject: [PATCH 01/10] =?UTF-8?q?:sparkles:=20::=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=ED=95=84=20=ED=99=94=EB=A9=B4=20v2=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/res/drawable/ic_setting.png | Bin 0 -> 655 bytes .../main/res/drawable/img_empty_profile.png | Bin 4148 -> 1775 bytes .../profile/component/ProfileCard.kt | 66 +++++++--------- .../profile/component/ProfileHeader.kt | 14 ++-- .../profile/component/ProfileTicketCard.kt | 74 +++++++++++------- .../profile/profile/ProfileScreen.kt | 53 +++++++++++-- 6 files changed, 130 insertions(+), 77 deletions(-) create mode 100644 common/resource/src/main/res/drawable/ic_setting.png diff --git a/common/resource/src/main/res/drawable/ic_setting.png b/common/resource/src/main/res/drawable/ic_setting.png new file mode 100644 index 0000000000000000000000000000000000000000..fd5069cc5104fc66fdb3882da68309e096861f00 GIT binary patch literal 655 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 Unit, ) { - Row( - modifier = modifier - .background( - color = MSTheme.color.white, - shape = RoundedCornerShape(10.dp) - ) - .padding(horizontal = 16.dp, vertical = 20.dp), - verticalAlignment = Alignment.CenterVertically + Column( + modifier = modifier.background(color = MSTheme.color.white), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center ) { imageUrl?.let { GlideImage( modifier = Modifier - .size(54.dp) + .size(80.dp) .clip(CircleShape), imageModel = { it } ) } ?: run { Image( - modifier = Modifier.size(54.dp), - painter = painterResource(R.drawable.img_profile_54), + modifier = Modifier.size(width = 78.dp, height = 80.dp), + painter = painterResource(R.drawable.img_empty_profile), contentDescription = "empty_profile" ) } - - Spacer(modifier = Modifier.width(10.dp)) + Spacer(modifier = Modifier.height(8.dp)) MSText( text = nickname, fontWeight = FontWeight.Bold, - fontSize = 16.dp, + fontSize = 24.dp, color = MSTheme.color.greyG5 ) - Spacer(modifier = Modifier.weight(1f)) - Row( - modifier = Modifier - .background( - color = MSTheme.color.greyG1, - shape = RoundedCornerShape(29.dp) - ) - .padding( - top = 8.dp, bottom = 8.dp, - start = 8.dp, end = 10.dp - ) - .noRippleClickable(onClick = onEditClick), - verticalAlignment = Alignment.CenterVertically + Spacer(modifier = Modifier.height(20.dp)) + MSButton( + modifier = Modifier.height(32.dp), + contentPadding = PaddingValues(horizontal = 8.dp), + colors = ButtonDefaults.buttonColors( + containerColor = MSTheme.color.greyG5, + disabledContainerColor = MSTheme.color.greyG5 + ), + pressColors = ButtonDefaults.buttonColors( + containerColor = MSTheme.color.greyG5, + disabledContainerColor = MSTheme.color.greyG5 + ), + onClick = onEditClick ) { - Icon( - modifier = Modifier.size(16.dp), - painter = painterResource(R.drawable.ic_edit), - contentDescription = "edit", - tint = MSTheme.color.greyG3 - ) - Spacer(modifier = Modifier.width(2.dp)) MSText( text = "프로필 수정", - fontWeight = FontWeight.Medium, + fontWeight = FontWeight.Bold, fontSize = 12.dp, - color = MSTheme.color.greyG3 + color = MSTheme.color.white ) } } diff --git a/feature/profile/src/main/java/com/idiotfrogs/profile/component/ProfileHeader.kt b/feature/profile/src/main/java/com/idiotfrogs/profile/component/ProfileHeader.kt index b8f133a4..8b2c81b2 100644 --- a/feature/profile/src/main/java/com/idiotfrogs/profile/component/ProfileHeader.kt +++ b/feature/profile/src/main/java/com/idiotfrogs/profile/component/ProfileHeader.kt @@ -43,15 +43,15 @@ fun ProfileHeader( MSText( text = "프로필", fontWeight = FontWeight.Bold, - fontSize = 14.dp, + fontSize = 20.dp, color = MSTheme.color.greyG5 ) - MSText( - modifier = Modifier.noRippleClickable(onClick = onSetting), - text = "설정", - fontWeight = FontWeight.Medium, - fontSize = 14.dp, - color = MSTheme.color.greyG3 + Image( + modifier = Modifier + .size(24.dp) + .noRippleClickable(onClick = onSetting), + painter = painterResource(R.drawable.ic_setting), + contentDescription = "설정" ) } } diff --git a/feature/profile/src/main/java/com/idiotfrogs/profile/component/ProfileTicketCard.kt b/feature/profile/src/main/java/com/idiotfrogs/profile/component/ProfileTicketCard.kt index 18b0c210..87054ee1 100644 --- a/feature/profile/src/main/java/com/idiotfrogs/profile/component/ProfileTicketCard.kt +++ b/feature/profile/src/main/java/com/idiotfrogs/profile/component/ProfileTicketCard.kt @@ -1,10 +1,13 @@ package com.idiotfrogs.profile.component import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable @@ -16,6 +19,7 @@ import androidx.compose.ui.unit.dp import com.idiotfrogs.designsystem.component.MSText import com.idiotfrogs.designsystem.theme.MSTheme import com.idiotfrogs.designsystem.util.noRippleClickable +import com.idiotfrogs.designsystem.util.wavyStroke import com.skydoves.landscapist.glide.GlideImage @Composable @@ -25,39 +29,51 @@ fun ProfileTicketCard( date: String, onClick: () -> Unit, ) { - Column( - modifier = Modifier - .background( - color = MSTheme.color.white, - shape = RoundedCornerShape(16.dp) - ) - .padding(horizontal = 12.dp, vertical = 16.dp) - .noRippleClickable(onClick = onClick) - ) { - GlideImage( - modifier = Modifier.aspectRatio(1f), - imageModel = { imageUrl }, - ) - Spacer(modifier = Modifier.height(12.dp)) - MSText( - text = title, - fontWeight = FontWeight.Bold, - fontSize = 16.dp, - color = MSTheme.color.greyG5, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - Spacer(modifier = Modifier.height(6.dp)) - MSText( - text = date, - fontWeight = FontWeight.Normal, - fontSize = 12.dp, - color = MSTheme.color.greyG3 + Column(modifier = Modifier.noRippleClickable(onClick = onClick)) { + Box( + modifier = Modifier + .fillMaxWidth() + .height(55.dp) + .wavyStroke( + amplitude = 1.dp, + spacing = 2.dp, + color = MSTheme.color.greyG5, + fillColor = MSTheme.color.primaryNormal + ) ) + Column( + modifier = Modifier + .offset(y = (-5).dp) + .fillMaxWidth() + .height(133.dp) + .wavyStroke( + amplitude = 1.dp, + spacing = 2.dp, + color = MSTheme.color.greyG5, + fillColor = MSTheme.color.white + ) + .padding(12.dp) + ) { + MSText( + text = title, + fontWeight = FontWeight.Bold, + fontSize = 16.dp, + color = MSTheme.color.greyG5, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + Spacer(modifier = Modifier.height(6.dp)) + MSText( + text = date, + fontWeight = FontWeight.Normal, + fontSize = 14.dp, + color = MSTheme.color.greyG3 + ) + } } } -@Preview +@Preview(showBackground = true) @Composable private fun ProfileTicketCardPreview() { ProfileTicketCard( diff --git a/feature/profile/src/main/java/com/idiotfrogs/profile/profile/ProfileScreen.kt b/feature/profile/src/main/java/com/idiotfrogs/profile/profile/ProfileScreen.kt index 409180c7..f0246e4d 100644 --- a/feature/profile/src/main/java/com/idiotfrogs/profile/profile/ProfileScreen.kt +++ b/feature/profile/src/main/java/com/idiotfrogs/profile/profile/ProfileScreen.kt @@ -14,24 +14,37 @@ import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.items 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 import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel +import com.idiotfrogs.designsystem.component.MSDashHorizontalDivider import com.idiotfrogs.designsystem.component.MSText import com.idiotfrogs.designsystem.theme.MSTheme import com.idiotfrogs.extension.toYearMonthDay +import com.idiotfrogs.model.timecapsule.MyTimeCapsuleResponse +import com.idiotfrogs.model.timecapsule.TimeCapsuleRole +import com.idiotfrogs.model.timecapsule.TimeCapsuleStatus +import com.idiotfrogs.model.user.ProfileResponse import com.idiotfrogs.navigation.LocalComposeMSNavigator import com.idiotfrogs.navigation.Routes import com.idiotfrogs.profile.component.ProfileCard import com.idiotfrogs.profile.component.ProfileHeader import com.idiotfrogs.profile.component.ProfileTicketCard import com.idiotfrogs.util.UiState +import kotlinx.datetime.LocalDate +import kotlinx.datetime.LocalDateTime +import kotlinx.datetime.TimeZone +import kotlinx.datetime.atTime +import kotlinx.datetime.todayIn import org.orbitmvi.orbit.compose.collectAsState import org.orbitmvi.orbit.compose.collectSideEffect +import kotlin.time.Clock const val HeaderHeight = 56 @@ -58,6 +71,7 @@ fun ProfileRoute( onAction = viewModel::onAction ) } + is UiState.Error -> Unit } } @@ -80,7 +94,7 @@ fun ProfileScreen( LazyVerticalGrid( modifier = Modifier .fillMaxSize() - .background(Color(0xFFF6F6F6)) + .background(MSTheme.color.white) .padding(horizontal = 20.dp), columns = GridCells.Fixed(2), horizontalArrangement = Arrangement.spacedBy(12.dp), @@ -90,7 +104,7 @@ fun ProfileScreen( ProfileCard( modifier = Modifier.padding(top = (HeaderHeight + 24).dp), nickname = data.user?.nickname ?: "", - imageUrl = data.user?.profileImageUrl, + imageUrl = data.user?.profileImageUrl?.ifEmpty { null }, onEditClick = { onAction(ProfileAction.NavigateToEditProfile) } ) } @@ -99,8 +113,16 @@ fun ProfileScreen( modifier = Modifier.padding(top = 16.dp), text = "오픈된 티켓", fontWeight = FontWeight.Bold, - fontSize = 20.dp, - color = MSTheme.color.greyG5 + fontSize = 16.dp, + color = MSTheme.color.greyG5, + textAlign = TextAlign.Center + ) + } + maxLineItem { + MSDashHorizontalDivider( + thickness = 2.dp, + dashWidth = 10.dp, + gapWidth = 10.dp ) } items(data.capsules) { @@ -128,7 +150,28 @@ private fun LazyGridScope.maxLineItem( @Composable private fun ProfileScreenPreview() { ProfileScreen( - data = ProfileData(), + data = ProfileData( + user = ProfileResponse( + id = 0L, + nickname = "용감한 사자처럼", + profileImageUrl = "", + email = "", + isOnboarding = true + ), + capsules = listOf( + MyTimeCapsuleResponse( + timeCapsuleId = 0L, + title = "제목입니다. 제목입니다.", + createdAt = Clock.System + .todayIn(TimeZone.currentSystemDefault()) + .atTime(0, 0, 0, 0), + mainImageUrl = "", + role = TimeCapsuleRole.CONTRIBUTOR, + timeCapsuleStatus = TimeCapsuleStatus.BURIED + + ) + ) + ), onAction = {}, ) } \ No newline at end of file From 170eb2b12521aaf53ab639c711451770d23afcd6 Mon Sep 17 00:00:00 2001 From: hyeonseo Date: Mon, 15 Jun 2026 20:38:41 +0900 Subject: [PATCH 02/10] =?UTF-8?q?:sparkles:=20::=20=EC=B5=9C=EB=8C=80=20?= =?UTF-8?q?=EC=A4=84=20=EC=88=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/idiotfrogs/profile/component/ProfileTicketCard.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feature/profile/src/main/java/com/idiotfrogs/profile/component/ProfileTicketCard.kt b/feature/profile/src/main/java/com/idiotfrogs/profile/component/ProfileTicketCard.kt index 87054ee1..9403c4fb 100644 --- a/feature/profile/src/main/java/com/idiotfrogs/profile/component/ProfileTicketCard.kt +++ b/feature/profile/src/main/java/com/idiotfrogs/profile/component/ProfileTicketCard.kt @@ -59,7 +59,7 @@ fun ProfileTicketCard( fontWeight = FontWeight.Bold, fontSize = 16.dp, color = MSTheme.color.greyG5, - maxLines = 1, + maxLines = 2, overflow = TextOverflow.Ellipsis ) Spacer(modifier = Modifier.height(6.dp)) From 43482110e90c4c78a9f526e1a36f14ee6a1e4b3f Mon Sep 17 00:00:00 2001 From: hyeonseo Date: Mon, 15 Jun 2026 20:57:58 +0900 Subject: [PATCH 03/10] =?UTF-8?q?:sparkles:=20::=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=ED=95=84=20=EC=88=98=EC=A0=95=20=ED=99=94=EB=A9=B4=20v2=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../designsystem/component/button/MSButton.kt | 4 +- .../profile/component/EditProfileHeader.kt | 49 +++++++---- .../profile/editprofile/EditProfileScreen.kt | 87 +++++++++++++++---- 3 files changed, 106 insertions(+), 34 deletions(-) diff --git a/core/designsystem/src/main/java/com/idiotfrogs/designsystem/component/button/MSButton.kt b/core/designsystem/src/main/java/com/idiotfrogs/designsystem/component/button/MSButton.kt index 77293ba2..0d05c867 100644 --- a/core/designsystem/src/main/java/com/idiotfrogs/designsystem/component/button/MSButton.kt +++ b/core/designsystem/src/main/java/com/idiotfrogs/designsystem/component/button/MSButton.kt @@ -22,6 +22,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.idiotfrogs.designsystem.component.MSText import com.idiotfrogs.designsystem.theme.MSTheme @@ -33,6 +34,7 @@ fun MSButton( modifier: Modifier = Modifier, enabled: Boolean = true, isRounded: Boolean = true, + cornerRadius: Dp = 12.dp, colors: ButtonColors = ButtonDefaults.buttonColors( containerColor = MSTheme.color.primaryNormal, disabledContainerColor = MSTheme.color.primaryLight @@ -51,7 +53,7 @@ fun MSButton( ) { val isPressed by interactionSource.collectIsPressedAsState() val cornerRadius by animateDpAsState( - targetValue = if (isRounded) 12.dp else 0.dp, + targetValue = if (isRounded) cornerRadius else 0.dp, animationSpec = tween(durationMillis = 300), label = "cornerRadiusAnimation", ) diff --git a/feature/profile/src/main/java/com/idiotfrogs/profile/component/EditProfileHeader.kt b/feature/profile/src/main/java/com/idiotfrogs/profile/component/EditProfileHeader.kt index ced46455..05e89138 100644 --- a/feature/profile/src/main/java/com/idiotfrogs/profile/component/EditProfileHeader.kt +++ b/feature/profile/src/main/java/com/idiotfrogs/profile/component/EditProfileHeader.kt @@ -1,10 +1,12 @@ package com.idiotfrogs.profile.component import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues 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.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -12,11 +14,13 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue 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.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.idiotfrogs.designsystem.component.MSText +import com.idiotfrogs.designsystem.component.button.MSButton import com.idiotfrogs.designsystem.theme.MSTheme import com.idiotfrogs.designsystem.util.noRippleClickable import com.idiotfrogs.resource.R @@ -28,33 +32,42 @@ fun ProfileHeader( onBack: () -> Unit, onSave: () -> Unit, ) { - Row( + Box( modifier = modifier .fillMaxWidth() - .padding(vertical = 16.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically + .height(56.dp), ) { Image( - modifier = Modifier.noRippleClickable(onBack), + modifier = Modifier + .align(Alignment.CenterStart) + .size(24.dp) + .noRippleClickable(onBack), painter = painterResource(R.drawable.ic_chevron_left), contentDescription = "chevron left" ) MSText( - text = "프로필", + modifier = Modifier.align(Alignment.Center), + text = "프로필 수정", fontWeight = FontWeight.Bold, - fontSize = 14.dp, + fontSize = 20.dp, color = MSTheme.color.black ) - MSText( - modifier = Modifier.noRippleClickable { - if (isChanged) onSave() - }, - text = "저장", - fontWeight = FontWeight.Bold, - fontSize = 14.dp, - color = if (isChanged) MSTheme.color.primaryNormal else MSTheme.color.greyG2, - ) + MSButton( + modifier = Modifier + .align(Alignment.CenterEnd) + .height(32.dp), + cornerRadius = 8.dp, + contentPadding = PaddingValues(horizontal = 1.dp), + enabled = isChanged, + onClick = onSave + ) { + MSText( + text = "저장", + color = if (isChanged) MSTheme.color.primaryDark else Color(0xFF84B591), + fontSize = 14.dp, + fontWeight = FontWeight.Bold + ) + } } } diff --git a/feature/profile/src/main/java/com/idiotfrogs/profile/editprofile/EditProfileScreen.kt b/feature/profile/src/main/java/com/idiotfrogs/profile/editprofile/EditProfileScreen.kt index 42dfa519..d8ef3784 100644 --- a/feature/profile/src/main/java/com/idiotfrogs/profile/editprofile/EditProfileScreen.kt +++ b/feature/profile/src/main/java/com/idiotfrogs/profile/editprofile/EditProfileScreen.kt @@ -3,6 +3,7 @@ package com.idiotfrogs.profile.editprofile import android.util.Log import androidx.compose.foundation.Image import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -22,6 +23,7 @@ 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.ColorFilter import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.FontWeight @@ -33,6 +35,7 @@ import com.idiotfrogs.designsystem.component.MSTextField import com.idiotfrogs.designsystem.theme.MSTheme import com.idiotfrogs.designsystem.util.noRippleClickable import com.idiotfrogs.designsystem.util.rememberPickerState +import com.idiotfrogs.designsystem.util.wavyStroke import com.idiotfrogs.extension.toFile import com.idiotfrogs.navigation.LocalComposeMSNavigator import com.idiotfrogs.profile.component.EditProfileBottomSheet @@ -112,22 +115,76 @@ fun EditProfileScreen( ) Spacer(modifier = Modifier.height(16.dp)) imageUri?.let { - GlideImage( - imageModel = { imageUri }, + Box { + GlideImage( + imageModel = { imageUri }, + modifier = Modifier + .noRippleClickable { showBottomSheet = true } + .size(120.dp) + .clip(CircleShape) + ) + Box( + modifier = Modifier + .align(Alignment.BottomEnd) + .size(40.dp) + .wavyStroke( + color = MSTheme.color.black, + cornerRadius = 20.dp, + fillColor = MSTheme.color.black, + amplitude = 1.dp, + spacing = 1.dp + ), + contentAlignment = Alignment.Center + ) { + Image( + modifier = Modifier.size(24.dp), + painter = painterResource(R.drawable.ic_edit), + contentDescription = "edit", + colorFilter = ColorFilter.tint(MSTheme.color.white) + ) + } + } + + } ?: run { + Box( modifier = Modifier - .noRippleClickable { showBottomSheet = true } - .size(128.dp) - .clip(CircleShape) - .align(Alignment.CenterHorizontally), - ) - } ?: Image( - modifier = Modifier - .noRippleClickable { showBottomSheet = true } - .size(128.dp) - .align(Alignment.CenterHorizontally), - painter = painterResource(R.drawable.img_empty_profile), - contentDescription = "Profile" - ) + .size(120.dp) + .background( + color = MSTheme.color.greyG1, + shape = CircleShape + ) + .align(Alignment.CenterHorizontally) + .noRippleClickable { showBottomSheet = true }, + contentAlignment = Alignment.Center + ) { + Image( + modifier = Modifier.size(48.dp), + painter = painterResource(R.drawable.ic_photo), + contentDescription = "photo", + colorFilter = ColorFilter.tint(MSTheme.color.greyG3) + ) + Box( + modifier = Modifier + .align(Alignment.BottomEnd) + .size(40.dp) + .wavyStroke( + color = MSTheme.color.black, + cornerRadius = 20.dp, + fillColor = MSTheme.color.black, + amplitude = 1.dp, + spacing = 1.dp + ), + contentAlignment = Alignment.Center + ) { + Image( + modifier = Modifier.size(24.dp), + painter = painterResource(R.drawable.ic_edit), + contentDescription = "edit", + colorFilter = ColorFilter.tint(MSTheme.color.white) + ) + } + } + } Spacer(modifier = Modifier.height(16.dp)) MSText( text = "닉네임", From 6f854a2ca394a0a4c7bf04573aeb090f3bc74b68 Mon Sep 17 00:00:00 2001 From: hyeonseo Date: Tue, 16 Jun 2026 13:01:58 +0900 Subject: [PATCH 04/10] =?UTF-8?q?:memo:=20::=20stability=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/designsystem/stability/designsystem-debug.stability | 3 ++- core/designsystem/stability/designsystem-release.stability | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/core/designsystem/stability/designsystem-debug.stability b/core/designsystem/stability/designsystem-debug.stability index 8eccedfc..e2abcfac 100644 --- a/core/designsystem/stability/designsystem-debug.stability +++ b/core/designsystem/stability/designsystem-debug.stability @@ -298,13 +298,14 @@ public fun com.idiotfrogs.designsystem.component.TwoByTwo(images: kotlin.collect - images: RUNTIME (requires runtime check) @Composable -public fun com.idiotfrogs.designsystem.component.button.MSButton(modifier: androidx.compose.ui.Modifier, enabled: kotlin.Boolean, isRounded: kotlin.Boolean, colors: androidx.compose.material3.ButtonColors, pressColors: androidx.compose.material3.ButtonColors, elevation: androidx.compose.material3.ButtonElevation?, border: androidx.compose.foundation.BorderStroke?, wavyStrokeColor: androidx.compose.ui.graphics.Color?, contentPadding: androidx.compose.foundation.layout.PaddingValues, interactionSource: androidx.compose.foundation.interaction.MutableInteractionSource, onClick: kotlin.Function0, content: @[Composable] @[ExtensionFunctionType] androidx.compose.runtime.internal.ComposableFunction1): kotlin.Unit +public fun com.idiotfrogs.designsystem.component.button.MSButton(modifier: androidx.compose.ui.Modifier, enabled: kotlin.Boolean, isRounded: kotlin.Boolean, cornerRadius: androidx.compose.ui.unit.Dp, colors: androidx.compose.material3.ButtonColors, pressColors: androidx.compose.material3.ButtonColors, elevation: androidx.compose.material3.ButtonElevation?, border: androidx.compose.foundation.BorderStroke?, wavyStrokeColor: androidx.compose.ui.graphics.Color?, contentPadding: androidx.compose.foundation.layout.PaddingValues, interactionSource: androidx.compose.foundation.interaction.MutableInteractionSource, onClick: kotlin.Function0, content: @[Composable] @[ExtensionFunctionType] androidx.compose.runtime.internal.ComposableFunction1): kotlin.Unit skippable: true restartable: true params: - modifier: STABLE (marked @Stable or @Immutable) - enabled: STABLE (primitive type) - isRounded: STABLE (primitive type) + - cornerRadius: STABLE (marked @Stable or @Immutable) - colors: STABLE (marked @Stable or @Immutable) - pressColors: STABLE (marked @Stable or @Immutable) - elevation: STABLE (marked @Stable or @Immutable) diff --git a/core/designsystem/stability/designsystem-release.stability b/core/designsystem/stability/designsystem-release.stability index 8eccedfc..e2abcfac 100644 --- a/core/designsystem/stability/designsystem-release.stability +++ b/core/designsystem/stability/designsystem-release.stability @@ -298,13 +298,14 @@ public fun com.idiotfrogs.designsystem.component.TwoByTwo(images: kotlin.collect - images: RUNTIME (requires runtime check) @Composable -public fun com.idiotfrogs.designsystem.component.button.MSButton(modifier: androidx.compose.ui.Modifier, enabled: kotlin.Boolean, isRounded: kotlin.Boolean, colors: androidx.compose.material3.ButtonColors, pressColors: androidx.compose.material3.ButtonColors, elevation: androidx.compose.material3.ButtonElevation?, border: androidx.compose.foundation.BorderStroke?, wavyStrokeColor: androidx.compose.ui.graphics.Color?, contentPadding: androidx.compose.foundation.layout.PaddingValues, interactionSource: androidx.compose.foundation.interaction.MutableInteractionSource, onClick: kotlin.Function0, content: @[Composable] @[ExtensionFunctionType] androidx.compose.runtime.internal.ComposableFunction1): kotlin.Unit +public fun com.idiotfrogs.designsystem.component.button.MSButton(modifier: androidx.compose.ui.Modifier, enabled: kotlin.Boolean, isRounded: kotlin.Boolean, cornerRadius: androidx.compose.ui.unit.Dp, colors: androidx.compose.material3.ButtonColors, pressColors: androidx.compose.material3.ButtonColors, elevation: androidx.compose.material3.ButtonElevation?, border: androidx.compose.foundation.BorderStroke?, wavyStrokeColor: androidx.compose.ui.graphics.Color?, contentPadding: androidx.compose.foundation.layout.PaddingValues, interactionSource: androidx.compose.foundation.interaction.MutableInteractionSource, onClick: kotlin.Function0, content: @[Composable] @[ExtensionFunctionType] androidx.compose.runtime.internal.ComposableFunction1): kotlin.Unit skippable: true restartable: true params: - modifier: STABLE (marked @Stable or @Immutable) - enabled: STABLE (primitive type) - isRounded: STABLE (primitive type) + - cornerRadius: STABLE (marked @Stable or @Immutable) - colors: STABLE (marked @Stable or @Immutable) - pressColors: STABLE (marked @Stable or @Immutable) - elevation: STABLE (marked @Stable or @Immutable) From ecfad0f7135d02d8b92cca12882c40dbfa86d3d8 Mon Sep 17 00:00:00 2001 From: hyeonseo Date: Sun, 21 Jun 2026 17:35:39 +0900 Subject: [PATCH 05/10] =?UTF-8?q?:sparkles:=20::=20=ED=8B=B0=EC=BC=93=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=20=EB=84=A4=EB=B9=84=EA=B2=8C=EC=9D=B4?= =?UTF-8?q?=EC=85=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/idiotfrogs/profile/profile/ProfileScreen.kt | 8 ++------ .../com/idiotfrogs/profile/profile/ProfileViewModel.kt | 3 +++ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/feature/profile/src/main/java/com/idiotfrogs/profile/profile/ProfileScreen.kt b/feature/profile/src/main/java/com/idiotfrogs/profile/profile/ProfileScreen.kt index f0246e4d..9cbd288a 100644 --- a/feature/profile/src/main/java/com/idiotfrogs/profile/profile/ProfileScreen.kt +++ b/feature/profile/src/main/java/com/idiotfrogs/profile/profile/ProfileScreen.kt @@ -14,9 +14,7 @@ import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.items 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 import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview @@ -37,8 +35,6 @@ import com.idiotfrogs.profile.component.ProfileCard import com.idiotfrogs.profile.component.ProfileHeader import com.idiotfrogs.profile.component.ProfileTicketCard import com.idiotfrogs.util.UiState -import kotlinx.datetime.LocalDate -import kotlinx.datetime.LocalDateTime import kotlinx.datetime.TimeZone import kotlinx.datetime.atTime import kotlinx.datetime.todayIn @@ -60,6 +56,7 @@ fun ProfileRoute( ProfileSideEffect.NavigateToBack -> navigator.popBackStack() ProfileSideEffect.NavigateToEditProfile -> navigator.navigate(Routes.EditProfile) ProfileSideEffect.NavigateToSetting -> navigator.navigate(Routes.Setting) + is ProfileSideEffect.NavigateToDetail -> navigator.navigate(Routes.Detail(event.id)) } } @@ -71,7 +68,6 @@ fun ProfileRoute( onAction = viewModel::onAction ) } - is UiState.Error -> Unit } } @@ -130,7 +126,7 @@ fun ProfileScreen( imageUrl = it.mainImageUrl, title = it.title, date = it.createdAt.toYearMonthDay(), - onClick = {} + onClick = { onAction.invoke(ProfileAction.NavigateToDetail(it.timeCapsuleId))} ) } } diff --git a/feature/profile/src/main/java/com/idiotfrogs/profile/profile/ProfileViewModel.kt b/feature/profile/src/main/java/com/idiotfrogs/profile/profile/ProfileViewModel.kt index 85673ca7..246e8f7d 100644 --- a/feature/profile/src/main/java/com/idiotfrogs/profile/profile/ProfileViewModel.kt +++ b/feature/profile/src/main/java/com/idiotfrogs/profile/profile/ProfileViewModel.kt @@ -71,6 +71,7 @@ class ProfileViewModel @Inject constructor( ProfileAction.NavigateToEditProfile -> intent { postSideEffect(ProfileSideEffect.NavigateToEditProfile) } ProfileAction.NavigateToSetting -> intent { postSideEffect(ProfileSideEffect.NavigateToSetting) } ProfileAction.NavigateToBack -> intent { postSideEffect(ProfileSideEffect.NavigateToBack) } + is ProfileAction.NavigateToDetail -> intent { postSideEffect(ProfileSideEffect.NavigateToDetail(action.id))} } } } @@ -85,10 +86,12 @@ sealed interface ProfileAction { data object NavigateToSetting : ProfileAction data object NavigateToEditProfile : ProfileAction data object NavigateToBack : ProfileAction + data class NavigateToDetail(val id: Long) : ProfileAction } sealed interface ProfileSideEffect { data object NavigateToSetting : ProfileSideEffect data object NavigateToEditProfile : ProfileSideEffect data object NavigateToBack : ProfileSideEffect + data class NavigateToDetail(val id: Long) : ProfileSideEffect } \ No newline at end of file From 1536d8379e00928c35b12df93c1c3d7f140560ea Mon Sep 17 00:00:00 2001 From: hyeonseo Date: Mon, 22 Jun 2026 21:11:09 +0900 Subject: [PATCH 06/10] =?UTF-8?q?:sparkles:=20::=20=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=EC=A7=80=20=EB=B0=8F=20=ED=85=8D=EC=8A=A4=ED=8A=B8=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20=EB=A1=9C=EC=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../idiotfrogs/network/service/UserService.kt | 4 +- .../data/datasource/user/UserDataSource.kt | 2 +- .../datasource/user/UserDataSourceImpl.kt | 4 +- .../data/repository/user/UserRepository.kt | 4 +- .../repository/user/UserRepositoryImpl.kt | 18 +++-- .../usecase/user/UpdateMyProfileUseCase.kt | 7 +- .../profile/editprofile/EditProfileScreen.kt | 52 +++++++++---- .../editprofile/EditProfileViewModel.kt | 74 +++++++++++++++++-- 8 files changed, 126 insertions(+), 39 deletions(-) diff --git a/core/network/src/main/java/com/idiotfrogs/network/service/UserService.kt b/core/network/src/main/java/com/idiotfrogs/network/service/UserService.kt index b1b890d1..2681a36e 100644 --- a/core/network/src/main/java/com/idiotfrogs/network/service/UserService.kt +++ b/core/network/src/main/java/com/idiotfrogs/network/service/UserService.kt @@ -24,8 +24,8 @@ interface UserService { @Multipart suspend fun updateMyProfile( @Path("userId") userId: Long, - @Part("profileImage") profileImage: MultipartBody.Part, - @Part("userUpdateDto") userUpdateRequest: UserUpdateRequest + @Part profileImage: MultipartBody.Part, + @Query("nickname") nickname: String ): UserResponse @PATCH("users/sign-up") diff --git a/data/src/main/java/com/idiotfrogs/data/datasource/user/UserDataSource.kt b/data/src/main/java/com/idiotfrogs/data/datasource/user/UserDataSource.kt index 0756b9b6..3a842479 100644 --- a/data/src/main/java/com/idiotfrogs/data/datasource/user/UserDataSource.kt +++ b/data/src/main/java/com/idiotfrogs/data/datasource/user/UserDataSource.kt @@ -13,7 +13,7 @@ interface UserDataSource { suspend fun updateMyProfile( userId: Long, profileImage: MultipartBody.Part, - userUpdateRequest: UserUpdateRequest + nickname: String, ): UserResponse suspend fun signUp( diff --git a/data/src/main/java/com/idiotfrogs/data/datasource/user/UserDataSourceImpl.kt b/data/src/main/java/com/idiotfrogs/data/datasource/user/UserDataSourceImpl.kt index 116e9db9..9cf95bac 100644 --- a/data/src/main/java/com/idiotfrogs/data/datasource/user/UserDataSourceImpl.kt +++ b/data/src/main/java/com/idiotfrogs/data/datasource/user/UserDataSourceImpl.kt @@ -21,12 +21,12 @@ class UserDataSourceImpl @Inject constructor( override suspend fun updateMyProfile( userId: Long, profileImage: MultipartBody.Part, - userUpdateRequest: UserUpdateRequest + nickname: String, ): UserResponse { return userService.updateMyProfile( userId = userId, profileImage = profileImage, - userUpdateRequest = userUpdateRequest + nickname = nickname ) } diff --git a/data/src/main/java/com/idiotfrogs/data/repository/user/UserRepository.kt b/data/src/main/java/com/idiotfrogs/data/repository/user/UserRepository.kt index a9e18239..0cd9c322 100644 --- a/data/src/main/java/com/idiotfrogs/data/repository/user/UserRepository.kt +++ b/data/src/main/java/com/idiotfrogs/data/repository/user/UserRepository.kt @@ -12,8 +12,8 @@ interface UserRepository { suspend fun updateMyProfile( userId: Long, - profileImage: File, - userUpdateRequest: UserUpdateRequest + profileImage: File?, + nickname: String ): UserResponse suspend fun signUp( diff --git a/data/src/main/java/com/idiotfrogs/data/repository/user/UserRepositoryImpl.kt b/data/src/main/java/com/idiotfrogs/data/repository/user/UserRepositoryImpl.kt index c14025e6..c832dbc8 100644 --- a/data/src/main/java/com/idiotfrogs/data/repository/user/UserRepositoryImpl.kt +++ b/data/src/main/java/com/idiotfrogs/data/repository/user/UserRepositoryImpl.kt @@ -3,10 +3,10 @@ package com.idiotfrogs.data.repository.user import com.idiotfrogs.data.datasource.user.UserDataSource import com.idiotfrogs.model.user.ProfileResponse import com.idiotfrogs.model.user.UserResponse -import com.idiotfrogs.model.user.UserUpdateRequest import okhttp3.MediaType.Companion.toMediaType import okhttp3.MultipartBody import okhttp3.RequestBody.Companion.asRequestBody +import okhttp3.RequestBody.Companion.toRequestBody import java.io.File import javax.inject.Inject @@ -23,15 +23,21 @@ class UserRepositoryImpl @Inject constructor( override suspend fun updateMyProfile( userId: Long, - profileImage: File, - userUpdateRequest: UserUpdateRequest + profileImage: File?, + nickname: String, ): UserResponse { - val imageRequestBody = profileImage.asRequestBody("image/jpeg".toMediaType()) - val imagePart = MultipartBody.Part.createFormData("profileImage", profileImage.name, imageRequestBody) + val imageRequestBody = profileImage?.asRequestBody("image/jpeg".toMediaType()) + ?: "".toRequestBody("image/*".toMediaType()) + val imagePart = MultipartBody.Part.createFormData( + "profileImage", + profileImage?.name ?: "profileImage", // 기본 이미지 대응 + imageRequestBody + ) + return userDataSource.updateMyProfile( userId = userId, profileImage = imagePart, - userUpdateRequest = userUpdateRequest + nickname = nickname ) } diff --git a/domain/src/main/java/com/idiotfrogs/domain/usecase/user/UpdateMyProfileUseCase.kt b/domain/src/main/java/com/idiotfrogs/domain/usecase/user/UpdateMyProfileUseCase.kt index cac343d4..af721fdd 100644 --- a/domain/src/main/java/com/idiotfrogs/domain/usecase/user/UpdateMyProfileUseCase.kt +++ b/domain/src/main/java/com/idiotfrogs/domain/usecase/user/UpdateMyProfileUseCase.kt @@ -2,7 +2,6 @@ package com.idiotfrogs.domain.usecase.user import com.idiotfrogs.data.repository.user.UserRepository import com.idiotfrogs.model.user.UserResponse -import com.idiotfrogs.model.user.UserUpdateRequest import com.idiotfrogs.util.safeCatching import java.io.File import javax.inject.Inject @@ -12,13 +11,13 @@ class UpdateMyProfileUseCase @Inject constructor( ) { suspend operator fun invoke( userId: Long, - profileImage: File, - userUpdateRequest: UserUpdateRequest + profileImage: File?, + nickname: String, ): Result = safeCatching { userRepository.updateMyProfile( userId, profileImage, - userUpdateRequest + nickname ) } } \ No newline at end of file diff --git a/feature/profile/src/main/java/com/idiotfrogs/profile/editprofile/EditProfileScreen.kt b/feature/profile/src/main/java/com/idiotfrogs/profile/editprofile/EditProfileScreen.kt index d8ef3784..4a31aa78 100644 --- a/feature/profile/src/main/java/com/idiotfrogs/profile/editprofile/EditProfileScreen.kt +++ b/feature/profile/src/main/java/com/idiotfrogs/profile/editprofile/EditProfileScreen.kt @@ -45,6 +45,7 @@ import com.idiotfrogs.util.UiState import com.skydoves.landscapist.glide.GlideImage import org.orbitmvi.orbit.compose.collectAsState import org.orbitmvi.orbit.compose.collectSideEffect +import java.io.File @Composable fun EditProfileRoute( @@ -59,10 +60,13 @@ fun EditProfileRoute( } } - when (uiState) { + when (val state = uiState) { UiState.Init -> Unit is UiState.Success -> { - EditProfileScreen(onAction = viewModel::onAction) + EditProfileScreen( + data = state.data, + onAction = viewModel::onAction + ) } is UiState.Error -> Unit } @@ -70,29 +74,36 @@ fun EditProfileRoute( @Composable fun EditProfileScreen( + data: EditProfileData, onAction: (EditProfileAction) -> Unit ) { val context = LocalContext.current var isChanged by remember { mutableStateOf(false) } var showBottomSheet by remember { mutableStateOf(false) } + // 기본 이미지 사용 플래그 (이미지 없는 경우와 구분 용도) + var useDefaultImage by remember { mutableStateOf(false) } val pickerState = rememberPickerState() var imageUri by remember(pickerState.first) { mutableStateOf(pickerState.first) } val launchImagePicker = pickerState.second - val textFieldState = rememberTextFieldState() + val textFieldState = rememberTextFieldState(initialText = data.user?.nickname ?: "") - LaunchedEffect(textFieldState.text) { - // TODO: 추후 기존 프로필과 비교 로직 작성 - isChanged = textFieldState.text.isNotEmpty() + LaunchedEffect(textFieldState.text, imageUri) { + isChanged = data.user?.nickname != textFieldState.text || // 닉네임이 변경 되었거나 + imageUri != null // 이미지가 로드되어 Uri가 채워진 경우 } if (showBottomSheet) { EditProfileBottomSheet( onDismiss = { showBottomSheet = false }, onSelectImage = { launchImagePicker() }, - onDefaultImage = { imageUri = null } + onDefaultImage = { + useDefaultImage = true // 기본 이미지 + imageUri = null + isChanged = true + } ) } @@ -108,16 +119,23 @@ fun EditProfileScreen( onBack = { onAction(EditProfileAction.NavigateToBack) }, onSave = { val file = imageUri?.toFile(context, "profileImage") - Log.d("test", file?.name.toString()) - /** TODO: 저장 로직 */ - onAction(EditProfileAction.NavigateToBack) + + data.user?.let { + onAction.invoke( + EditProfileAction.UpdateMyProfile( + userId = it.id, + profileImage = file, + nickname = textFieldState.text.toString(), + ) + ) + } } ) Spacer(modifier = Modifier.height(16.dp)) - imageUri?.let { - Box { + if ((imageUri != null || data.user?.profileImageUrl != null) && !useDefaultImage) { + Box(modifier = Modifier.align(Alignment.CenterHorizontally)) { GlideImage( - imageModel = { imageUri }, + imageModel = { imageUri ?: data.user?.profileImageUrl }, // 둘 중 하나는 not-null modifier = Modifier .noRippleClickable { showBottomSheet = true } .size(120.dp) @@ -144,8 +162,7 @@ fun EditProfileScreen( ) } } - - } ?: run { + } else { Box( modifier = Modifier .size(120.dp) @@ -204,5 +221,8 @@ fun EditProfileScreen( @Preview @Composable fun EditProfileScreenPreview() { - EditProfileScreen(onAction = {}) + EditProfileScreen( + data = EditProfileData(), + onAction = {} + ) } \ No newline at end of file diff --git a/feature/profile/src/main/java/com/idiotfrogs/profile/editprofile/EditProfileViewModel.kt b/feature/profile/src/main/java/com/idiotfrogs/profile/editprofile/EditProfileViewModel.kt index e3ea2035..688a414f 100644 --- a/feature/profile/src/main/java/com/idiotfrogs/profile/editprofile/EditProfileViewModel.kt +++ b/feature/profile/src/main/java/com/idiotfrogs/profile/editprofile/EditProfileViewModel.kt @@ -1,35 +1,97 @@ package com.idiotfrogs.profile.editprofile +import android.util.Log +import androidx.compose.runtime.Immutable +import com.idiotfrogs.domain.usecase.user.GetMyProfileUseCase +import com.idiotfrogs.domain.usecase.user.UpdateMyProfileUseCase +import com.idiotfrogs.model.timecapsule.MyTimeCapsuleResponse +import com.idiotfrogs.model.timecapsule.TimeCapsuleStatus +import com.idiotfrogs.model.user.ProfileResponse +import com.idiotfrogs.model.user.UserUpdateRequest +import com.idiotfrogs.profile.profile.ProfileData import com.idiotfrogs.util.UiState import com.idiotfrogs.util.base.BaseViewModel +import com.idiotfrogs.util.sideEffect.RefreshEvent +import com.idiotfrogs.util.sideEffect.RefreshSideEffect import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.async import org.orbitmvi.orbit.Container import org.orbitmvi.orbit.viewmodel.container +import java.io.File import javax.inject.Inject @HiltViewModel class EditProfileViewModel @Inject constructor( + private val getMyProfileUseCase: GetMyProfileUseCase, + private val updateMyProfileUseCase: UpdateMyProfileUseCase, +) : BaseViewModel, EditProfileSideEffect, EditProfileAction>() { -) : BaseViewModel, EditProfileSideEffect, EditProfileAction>() { - - override val container: Container, EditProfileSideEffect> = container( + override val container: Container, EditProfileSideEffect> = container( initialState = UiState.Init, onCreate = { - // TODO 초기 데이터 로딩 or 네비게이션에서 response 받아오기 safeLaunch { - intent { reduce { UiState.Success(Unit) } } + fetchProfile() } } ) override fun onAction(action: EditProfileAction) { when (action) { - EditProfileAction.NavigateToBack -> intent { postSideEffect(EditProfileSideEffect.NavigateToBack) } + is EditProfileAction.UpdateMyProfile -> { + updateMyProfile(action.userId, action.profileImage, action.nickname) + } + EditProfileAction.NavigateToBack -> { + intent { postSideEffect(EditProfileSideEffect.NavigateToBack) } + } + } + } + + + private fun fetchProfile() { + safeLaunch { + val result = getMyProfileUseCase() + + intent { + if (result.isFailure) { + reduce { UiState.Error(result.exceptionOrNull()?.message) } + } else { + reduce { + UiState.Success( + EditProfileData(user = result.getOrNull()) + ) + } + } + } + } + } + + private fun updateMyProfile(userId: Long, profileImage: File?, nickname: String) { + safeLaunch { + updateMyProfileUseCase( + userId = userId, + profileImage = profileImage, + nickname = nickname + ) + .onSuccess { + RefreshSideEffect.tryEmit(RefreshEvent.Profile) + intent { postSideEffect(EditProfileSideEffect.NavigateToBack) } + } + .onFailure { + Log.d("TAG", "updateMyProfile: ${it.message}") + } } } } +@Immutable +data class EditProfileData( + val user: ProfileResponse? = null, +) + sealed interface EditProfileAction { + data class UpdateMyProfile( + val userId: Long, val profileImage: File?, val nickname: String + ) : EditProfileAction data object NavigateToBack : EditProfileAction } From a99d09c7d64ae11116d98c74f7eb9c1965e2f264 Mon Sep 17 00:00:00 2001 From: hyeonseo Date: Sun, 28 Jun 2026 10:57:21 +0900 Subject: [PATCH 07/10] =?UTF-8?q?:bug:=20::=20=EB=B3=91=ED=95=A9=20?= =?UTF-8?q?=ED=9B=84=20=EC=B6=A9=EB=8F=8C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../profile/editprofile/EditProfileScreen.kt | 47 +++++++------- .../editprofile/EditProfileViewModel.kt | 62 +++++++++---------- .../profile/profile/ProfileScreen.kt | 1 - .../profile/profile/ProfileViewModel.kt | 4 +- 4 files changed, 57 insertions(+), 57 deletions(-) diff --git a/feature/profile/src/main/java/com/idiotfrogs/profile/editprofile/EditProfileScreen.kt b/feature/profile/src/main/java/com/idiotfrogs/profile/editprofile/EditProfileScreen.kt index f51ac589..7656cc72 100644 --- a/feature/profile/src/main/java/com/idiotfrogs/profile/editprofile/EditProfileScreen.kt +++ b/feature/profile/src/main/java/com/idiotfrogs/profile/editprofile/EditProfileScreen.kt @@ -1,6 +1,5 @@ package com.idiotfrogs.profile.editprofile -import android.util.Log import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box @@ -38,15 +37,14 @@ import com.idiotfrogs.designsystem.util.noRippleClickable import com.idiotfrogs.designsystem.util.rememberPickerState import com.idiotfrogs.designsystem.util.wavyStroke import com.idiotfrogs.extension.toFile +import com.idiotfrogs.model.user.ProfileResponse import com.idiotfrogs.navigation.LocalComposeMSNavigator import com.idiotfrogs.profile.component.EditProfileBottomSheet import com.idiotfrogs.profile.component.ProfileHeader import com.idiotfrogs.resource.R -import com.idiotfrogs.util.UiState import com.skydoves.landscapist.glide.GlideImage import org.orbitmvi.orbit.compose.collectAsState import org.orbitmvi.orbit.compose.collectSideEffect -import java.io.File @Composable fun EditProfileRoute( @@ -62,18 +60,19 @@ fun EditProfileRoute( } Box(modifier = Modifier.fillMaxSize()) { - EditProfileScreen( - data = state.data, - onAction = viewModel::onAction - ) - + uiState.data?.let { data -> + EditProfileScreen( + data = data, + onAction = viewModel::onAction + ) + } MSLoadingOverlay(visible = uiState.isLoading) } } @Composable fun EditProfileScreen( - data: EditProfileData, + data: ProfileResponse, onAction: (EditProfileAction) -> Unit ) { val context = LocalContext.current @@ -87,10 +86,10 @@ fun EditProfileScreen( var imageUri by remember(pickerState.first) { mutableStateOf(pickerState.first) } val launchImagePicker = pickerState.second - val textFieldState = rememberTextFieldState(initialText = data.user?.nickname ?: "") + val textFieldState = rememberTextFieldState(initialText = data.nickname) LaunchedEffect(textFieldState.text, imageUri) { - isChanged = data.user?.nickname != textFieldState.text || // 닉네임이 변경 되었거나 + isChanged = data.nickname != textFieldState.text || // 닉네임이 변경 되었거나 imageUri != null // 이미지가 로드되어 Uri가 채워진 경우 } @@ -119,22 +118,20 @@ fun EditProfileScreen( onSave = { val file = imageUri?.toFile(context, "profileImage") - data.user?.let { - onAction.invoke( - EditProfileAction.UpdateProfile( - userId = it.id, - profileImage = file, - nickname = textFieldState.text.toString(), - ) + onAction.invoke( + EditProfileAction.UpdateProfile( + userId = data.id, + profileImage = file, + nickname = textFieldState.text.toString() ) - } + ) } ) Spacer(modifier = Modifier.height(16.dp)) - if ((imageUri != null || data.user?.profileImageUrl != null) && !useDefaultImage) { + if (imageUri != null && !useDefaultImage) { Box(modifier = Modifier.align(Alignment.CenterHorizontally)) { GlideImage( - imageModel = { imageUri ?: data.user?.profileImageUrl }, // 둘 중 하나는 not-null + imageModel = { imageUri ?: data.profileImageUrl }, // 둘 중 하나는 not-null modifier = Modifier .noRippleClickable { showBottomSheet = true } .size(120.dp) @@ -221,7 +218,13 @@ fun EditProfileScreen( @Composable fun EditProfileScreenPreview() { EditProfileScreen( - data = EditProfileData(), + data = ProfileResponse( + id = 0L, + nickname = "", + profileImageUrl = "", + email = "", + isOnboarding = true, + ), onAction = {} ) } \ No newline at end of file diff --git a/feature/profile/src/main/java/com/idiotfrogs/profile/editprofile/EditProfileViewModel.kt b/feature/profile/src/main/java/com/idiotfrogs/profile/editprofile/EditProfileViewModel.kt index 6f498bf7..0fdc0471 100644 --- a/feature/profile/src/main/java/com/idiotfrogs/profile/editprofile/EditProfileViewModel.kt +++ b/feature/profile/src/main/java/com/idiotfrogs/profile/editprofile/EditProfileViewModel.kt @@ -1,21 +1,14 @@ package com.idiotfrogs.profile.editprofile import com.idiotfrogs.util.base.BaseUiState -import android.util.Log -import androidx.compose.runtime.Immutable import com.idiotfrogs.domain.usecase.user.GetMyProfileUseCase import com.idiotfrogs.domain.usecase.user.UpdateMyProfileUseCase -import com.idiotfrogs.model.timecapsule.MyTimeCapsuleResponse -import com.idiotfrogs.model.timecapsule.TimeCapsuleStatus import com.idiotfrogs.model.user.ProfileResponse -import com.idiotfrogs.model.user.UserUpdateRequest -import com.idiotfrogs.profile.profile.ProfileData -import com.idiotfrogs.util.UiState import com.idiotfrogs.util.base.BaseViewModel +import com.idiotfrogs.util.base.DataUiState import com.idiotfrogs.util.sideEffect.RefreshEvent import com.idiotfrogs.util.sideEffect.RefreshSideEffect import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.async import org.orbitmvi.orbit.Container import org.orbitmvi.orbit.viewmodel.container import java.io.File @@ -27,8 +20,8 @@ class EditProfileViewModel @Inject constructor( private val updateMyProfileUseCase: UpdateMyProfileUseCase, ) : BaseViewModel() { - override val container: Container = container( - initialState = UiState.Init, + override val container: Container = container( + initialState = EditProfileUiState(), onCreate = { safeLaunch { fetchProfile() @@ -38,7 +31,7 @@ class EditProfileViewModel @Inject constructor( override fun onAction(action: EditProfileAction) { when (action) { - EditProfileAction.NavigateToBack -> intent { postSideEffect(EditProfileSideEffect.NavigateToBack) } + EditProfileAction.BackClicked -> intent { postSideEffect(EditProfileSideEffect.NavigateToBack) } is EditProfileAction.UpdateProfile -> { updateProfile(action.userId, action.profileImage, action.nickname) } @@ -47,50 +40,55 @@ class EditProfileViewModel @Inject constructor( private fun fetchProfile() { safeLaunch { - intent { reduce { UiState.} } + intent { reduce { state.copy(isLoading = true) } } val result = getMyProfileUseCase() intent { if (result.isFailure) { - reduce { UiState.Error(result.exceptionOrNull()?.message) } + reduce { + state.copy( + isLoading = false, + errorMessage = (result.exceptionOrNull()?.message) + ) + } } else { reduce { - UiState.Success( - EditProfileData(user = result.getOrNull()) + state.copy( + isLoading = false, + data = result.getOrNull() ) } } } } + } - private fun updateMyProfile(userId: Long, profileImage: File?, nickname: String) { - safeLaunch { - updateMyProfileUseCase( - userId = userId, - profileImage = profileImage, - nickname = nickname - ) - .onSuccess { - RefreshSideEffect.tryEmit(RefreshEvent.Profile) - intent { postSideEffect(com.idiotfrogs.profile.editprofile.EditProfileSideEffect.NavigateToBack) } - } - .onFailure { - Log.d("TAG", "updateMyProfile: ${it.message}") - } - } + private fun updateProfile(userId: Long, profileImage: File?, nickname: String) { + safeLaunch { + updateMyProfileUseCase( + userId = userId, + profileImage = profileImage, + nickname = nickname + ) + .onSuccess { + RefreshSideEffect.tryEmit(RefreshEvent.Profile) + intent { postSideEffect(EditProfileSideEffect.NavigateToBack) } + } } } +} data class EditProfileUiState( + override val data: ProfileResponse? = null, override val isLoading: Boolean = false, override val errorMessage: String? = null, -) : BaseUiState +) : DataUiState sealed interface EditProfileAction { data object BackClicked : EditProfileAction data class UpdateProfile( val userId: Long, val profileImage: File?, val nickname: String - ): EditProfileAction + ) : EditProfileAction } sealed interface EditProfileSideEffect { diff --git a/feature/profile/src/main/java/com/idiotfrogs/profile/profile/ProfileScreen.kt b/feature/profile/src/main/java/com/idiotfrogs/profile/profile/ProfileScreen.kt index 91405100..c6c6ac6a 100644 --- a/feature/profile/src/main/java/com/idiotfrogs/profile/profile/ProfileScreen.kt +++ b/feature/profile/src/main/java/com/idiotfrogs/profile/profile/ProfileScreen.kt @@ -35,7 +35,6 @@ import com.idiotfrogs.navigation.Routes import com.idiotfrogs.profile.component.ProfileCard import com.idiotfrogs.profile.component.ProfileHeader import com.idiotfrogs.profile.component.ProfileTicketCard -import com.idiotfrogs.util.UiState import kotlinx.datetime.TimeZone import kotlinx.datetime.atTime import kotlinx.datetime.todayIn diff --git a/feature/profile/src/main/java/com/idiotfrogs/profile/profile/ProfileViewModel.kt b/feature/profile/src/main/java/com/idiotfrogs/profile/profile/ProfileViewModel.kt index 890b17ae..1fa37d00 100644 --- a/feature/profile/src/main/java/com/idiotfrogs/profile/profile/ProfileViewModel.kt +++ b/feature/profile/src/main/java/com/idiotfrogs/profile/profile/ProfileViewModel.kt @@ -6,8 +6,8 @@ import com.idiotfrogs.domain.usecase.user.GetMyProfileUseCase import com.idiotfrogs.model.timecapsule.MyTimeCapsuleResponse import com.idiotfrogs.model.timecapsule.TimeCapsuleStatus import com.idiotfrogs.model.user.ProfileResponse -import com.idiotfrogs.util.UiState import com.idiotfrogs.util.base.BaseViewModel +import com.idiotfrogs.util.base.DataUiState import com.idiotfrogs.util.sideEffect.RefreshEvent import com.idiotfrogs.util.sideEffect.RefreshSideEffect import dagger.hilt.android.lifecycle.HiltViewModel @@ -99,7 +99,7 @@ sealed interface ProfileAction { data object SettingClicked : ProfileAction data object EditProfileClicked : ProfileAction data object BackClicked : ProfileAction - data class TicketClicked(val id: long) : ProfileAction + data class TicketClicked(val id: Long) : ProfileAction } sealed interface ProfileSideEffect { From ef4f9e0cca989355954d779547c56630fac68d16 Mon Sep 17 00:00:00 2001 From: hyeonseo Date: Sun, 28 Jun 2026 14:25:41 +0900 Subject: [PATCH 08/10] =?UTF-8?q?:bug:=20::=20stability=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EC=88=98=EC=A0=95=EC=82=AC=ED=95=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../profile/editprofile/EditProfileScreen.kt | 2 +- .../profile/editprofile/EditProfileViewModel.kt | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/feature/profile/src/main/java/com/idiotfrogs/profile/editprofile/EditProfileScreen.kt b/feature/profile/src/main/java/com/idiotfrogs/profile/editprofile/EditProfileScreen.kt index 7656cc72..14f63c5a 100644 --- a/feature/profile/src/main/java/com/idiotfrogs/profile/editprofile/EditProfileScreen.kt +++ b/feature/profile/src/main/java/com/idiotfrogs/profile/editprofile/EditProfileScreen.kt @@ -62,7 +62,7 @@ fun EditProfileRoute( Box(modifier = Modifier.fillMaxSize()) { uiState.data?.let { data -> EditProfileScreen( - data = data, + data = data.user ?: return, onAction = viewModel::onAction ) } diff --git a/feature/profile/src/main/java/com/idiotfrogs/profile/editprofile/EditProfileViewModel.kt b/feature/profile/src/main/java/com/idiotfrogs/profile/editprofile/EditProfileViewModel.kt index 0fdc0471..42bf314b 100644 --- a/feature/profile/src/main/java/com/idiotfrogs/profile/editprofile/EditProfileViewModel.kt +++ b/feature/profile/src/main/java/com/idiotfrogs/profile/editprofile/EditProfileViewModel.kt @@ -1,6 +1,6 @@ package com.idiotfrogs.profile.editprofile -import com.idiotfrogs.util.base.BaseUiState +import androidx.compose.runtime.Immutable import com.idiotfrogs.domain.usecase.user.GetMyProfileUseCase import com.idiotfrogs.domain.usecase.user.UpdateMyProfileUseCase import com.idiotfrogs.model.user.ProfileResponse @@ -55,7 +55,9 @@ class EditProfileViewModel @Inject constructor( reduce { state.copy( isLoading = false, - data = result.getOrNull() + data = EditProfileData( + result.getOrNull() + ) ) } } @@ -78,11 +80,17 @@ class EditProfileViewModel @Inject constructor( } } +@Immutable data class EditProfileUiState( - override val data: ProfileResponse? = null, + override val data: EditProfileData? = null, override val isLoading: Boolean = false, override val errorMessage: String? = null, -) : DataUiState +) : DataUiState + +@Immutable +data class EditProfileData( + val user: ProfileResponse? = null +) sealed interface EditProfileAction { data object BackClicked : EditProfileAction From 16159836beb959e9a5df250a926993c078ccb97c Mon Sep 17 00:00:00 2001 From: hyeonseo Date: Sun, 28 Jun 2026 14:29:59 +0900 Subject: [PATCH 09/10] =?UTF-8?q?:bug:=20::=20EditProfileScreen=20?= =?UTF-8?q?=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../profile/editprofile/EditProfileScreen.kt | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/feature/profile/src/main/java/com/idiotfrogs/profile/editprofile/EditProfileScreen.kt b/feature/profile/src/main/java/com/idiotfrogs/profile/editprofile/EditProfileScreen.kt index 14f63c5a..69146ade 100644 --- a/feature/profile/src/main/java/com/idiotfrogs/profile/editprofile/EditProfileScreen.kt +++ b/feature/profile/src/main/java/com/idiotfrogs/profile/editprofile/EditProfileScreen.kt @@ -62,7 +62,7 @@ fun EditProfileRoute( Box(modifier = Modifier.fillMaxSize()) { uiState.data?.let { data -> EditProfileScreen( - data = data.user ?: return, + data = data, onAction = viewModel::onAction ) } @@ -72,7 +72,7 @@ fun EditProfileRoute( @Composable fun EditProfileScreen( - data: ProfileResponse, + data: EditProfileData, onAction: (EditProfileAction) -> Unit ) { val context = LocalContext.current @@ -86,10 +86,13 @@ fun EditProfileScreen( var imageUri by remember(pickerState.first) { mutableStateOf(pickerState.first) } val launchImagePicker = pickerState.second - val textFieldState = rememberTextFieldState(initialText = data.nickname) + val user = data.user ?: return // 유저 정보 없을 시 early-return + + val textFieldState = rememberTextFieldState(initialText = user.nickname) + LaunchedEffect(textFieldState.text, imageUri) { - isChanged = data.nickname != textFieldState.text || // 닉네임이 변경 되었거나 + isChanged = user.nickname != textFieldState.text || // 닉네임이 변경 되었거나 imageUri != null // 이미지가 로드되어 Uri가 채워진 경우 } @@ -120,7 +123,7 @@ fun EditProfileScreen( onAction.invoke( EditProfileAction.UpdateProfile( - userId = data.id, + userId = user.id, profileImage = file, nickname = textFieldState.text.toString() ) @@ -131,7 +134,7 @@ fun EditProfileScreen( if (imageUri != null && !useDefaultImage) { Box(modifier = Modifier.align(Alignment.CenterHorizontally)) { GlideImage( - imageModel = { imageUri ?: data.profileImageUrl }, // 둘 중 하나는 not-null + imageModel = { imageUri ?: user.profileImageUrl }, // 둘 중 하나는 not-null modifier = Modifier .noRippleClickable { showBottomSheet = true } .size(120.dp) @@ -218,12 +221,14 @@ fun EditProfileScreen( @Composable fun EditProfileScreenPreview() { EditProfileScreen( - data = ProfileResponse( - id = 0L, - nickname = "", - profileImageUrl = "", - email = "", - isOnboarding = true, + data = EditProfileData( + user = ProfileResponse( + id = 0L, + nickname = "", + profileImageUrl = "", + email = "", + isOnboarding = true, + ) ), onAction = {} ) From 44b581297f72366fd4e031b3f3dab28c635094a1 Mon Sep 17 00:00:00 2001 From: hyeonseo Date: Sun, 28 Jun 2026 14:31:28 +0900 Subject: [PATCH 10/10] =?UTF-8?q?:memo:=20::=20stability=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- feature/profile/stability/profile-debug.stability | 5 +++-- feature/profile/stability/profile-release.stability | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/feature/profile/stability/profile-debug.stability b/feature/profile/stability/profile-debug.stability index be27ddb2..3ac14264 100644 --- a/feature/profile/stability/profile-debug.stability +++ b/feature/profile/stability/profile-debug.stability @@ -57,13 +57,14 @@ public fun com.idiotfrogs.profile.editprofile.EditProfileRoute(viewModel: com.id skippable: false restartable: true params: - - viewModel: RUNTIME (requires runtime check) + - viewModel: UNSTABLE (has mutable properties or unstable members) @Composable -public fun com.idiotfrogs.profile.editprofile.EditProfileScreen(onAction: kotlin.Function1): kotlin.Unit +public fun com.idiotfrogs.profile.editprofile.EditProfileScreen(data: com.idiotfrogs.profile.editprofile.EditProfileData, onAction: kotlin.Function1): kotlin.Unit skippable: true restartable: true params: + - data: STABLE (marked @Stable or @Immutable) - onAction: STABLE (function type) @Composable diff --git a/feature/profile/stability/profile-release.stability b/feature/profile/stability/profile-release.stability index be27ddb2..3ac14264 100644 --- a/feature/profile/stability/profile-release.stability +++ b/feature/profile/stability/profile-release.stability @@ -57,13 +57,14 @@ public fun com.idiotfrogs.profile.editprofile.EditProfileRoute(viewModel: com.id skippable: false restartable: true params: - - viewModel: RUNTIME (requires runtime check) + - viewModel: UNSTABLE (has mutable properties or unstable members) @Composable -public fun com.idiotfrogs.profile.editprofile.EditProfileScreen(onAction: kotlin.Function1): kotlin.Unit +public fun com.idiotfrogs.profile.editprofile.EditProfileScreen(data: com.idiotfrogs.profile.editprofile.EditProfileData, onAction: kotlin.Function1): kotlin.Unit skippable: true restartable: true params: + - data: STABLE (marked @Stable or @Immutable) - onAction: STABLE (function type) @Composable