diff --git a/Near/app/src/main/java/com/alarmy/near/model/.gitkeep b/Near/app/src/main/java/com/alarmy/near/model/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/Near/app/src/main/java/com/alarmy/near/model/Relation.kt b/Near/app/src/main/java/com/alarmy/near/model/Relation.kt new file mode 100644 index 00000000..e01a7a52 --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/model/Relation.kt @@ -0,0 +1,7 @@ +package com.alarmy.near.model + +enum class Relation { + FRIEND, + FAMILY, + ACQUAINTANCE, +} diff --git a/Near/app/src/main/java/com/alarmy/near/model/ReminderInterval.kt b/Near/app/src/main/java/com/alarmy/near/model/ReminderInterval.kt new file mode 100644 index 00000000..0d39369d --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/model/ReminderInterval.kt @@ -0,0 +1,14 @@ +package com.alarmy.near.model + +import androidx.annotation.StringRes +import com.alarmy.near.R + +enum class ReminderInterval( + @param:StringRes val labelRes: Int, +) { + DAILY(R.string.reminder_interval_daily), // 매일 + WEEKLY(R.string.reminder_interval_weekly), // 매주 + BIWEEKLY(R.string.reminder_interval_biweekly), // 2주 + MONTHLY(R.string.reminder_interval_monthly), // 매달 + SEMIANNUAL(R.string.reminder_interval_semiannual), // 6개월 +} diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/friendprofile/FriendProfileScreen.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/friendprofile/FriendProfileScreen.kt new file mode 100644 index 00000000..95e1a2d5 --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/friendprofile/FriendProfileScreen.kt @@ -0,0 +1,464 @@ +package com.alarmy.near.presentation.feature.friendprofile + +import androidx.compose.animation.core.FastOutSlowInEasing +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.animation.core.tween +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +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.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Surface +import androidx.compose.material3.Tab +import androidx.compose.material3.TabPosition +import androidx.compose.material3.TabRow +import androidx.compose.material3.TabRowDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.composed +import androidx.compose.ui.platform.debugInspectorInfo +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import com.alarmy.near.R +import com.alarmy.near.presentation.feature.friendprofile.component.CallButton +import com.alarmy.near.presentation.feature.friendprofile.component.MessageButton +import com.alarmy.near.presentation.ui.component.appbar.NearTopAppbar +import com.alarmy.near.presentation.ui.component.button.NearSolidTypeButton +import com.alarmy.near.presentation.ui.extension.onNoRippleClick +import com.alarmy.near.presentation.ui.theme.NearTheme + +@Composable +fun FriendProfileRoute( + onShowErrorSnackBar: (throwable: Throwable?) -> Unit, + onClickBackButton: () -> Unit = {}, +) { + FriendProfileScreen( + onClickBackButton = onClickBackButton, + ) +} + +@Composable +fun FriendProfileScreen( + modifier: Modifier = Modifier, + onClickBackButton: () -> Unit = {}, +) { + // TODO Home 머지시 상단 패딩 + status 색상 변경 + val currentTabPosition = remember { mutableIntStateOf(0) } + Box(modifier = modifier.padding(bottom = 24.dp)) { + Column( + modifier = + Modifier + .align(Alignment.TopStart) + .fillMaxSize() + .background(NearTheme.colors.WHITE_FFFFFF), + ) { + NearTopAppbar( + title = "프로필 상세", + onClickBackButton = onClickBackButton, + menuButton = { + Image( + modifier = Modifier.onNoRippleClick(onClick = {}).padding(end = 20.dp), + painter = painterResource(R.drawable.ic_32_menu), + contentDescription = stringResource(R.string.common_menu_button_description), + ) + }, + ) + Spacer(modifier = Modifier.height(18.dp)) + Row( + modifier = + Modifier + .fillMaxWidth() + .padding(horizontal = 32.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Box( + modifier = + Modifier, + ) { + Image( + modifier = Modifier.align(Alignment.Center), + painter = painterResource(R.drawable.img_80_user1), + contentDescription = null, + ) + Image( + modifier = + Modifier + .align(Alignment.TopEnd) + .offset(x = 2.dp, y = (-2).dp), + painter = painterResource(R.drawable.ic_visual_24_emoji_100), + contentDescription = null, + ) + } + Spacer(modifier = Modifier.width(24.dp)) + Column { + Text( + modifier = Modifier.widthIn(max = 145.dp), + text = "일이삼사오육칠", + style = NearTheme.typography.B1_16_BOLD, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = "3월 22일 더 가까워졌어요", + style = NearTheme.typography.B2_14_MEDIUM, + color = NearTheme.colors.BLUE01_5AA2E9, + ) + } + } + Spacer(modifier = Modifier.height(24.dp)) + Row( + modifier = + Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp), + ) { + CallButton(modifier = Modifier.weight(1f), onClick = {}) + Spacer(modifier = Modifier.width(7.dp)) + MessageButton(Modifier.weight(1f), onClick = {}) + } + Spacer(modifier = Modifier.height(24.dp)) + TabRow( + modifier = + Modifier + .padding(horizontal = 25.dp) + .width(170.dp), + containerColor = NearTheme.colors.WHITE_FFFFFF, + selectedTabIndex = 0, + divider = {}, + indicator = { + TabRowDefaults.SecondaryIndicator( + modifier = + Modifier + .customTabIndicatorOffset( + it[currentTabPosition.intValue], + 80.dp, + ), // 넓이, 애니메이션 지정 + // 모양 지정 + height = 3.dp, + color = NearTheme.colors.BLUE01_5AA2E9, + ) + }, + ) { + Tab( + modifier = + Modifier + .width(85.dp) + .height(50.dp), + selected = true, + onClick = { + currentTabPosition.intValue = 0 + }, + ) { + if (currentTabPosition.intValue == 0) { + Text( + text = stringResource(R.string.friend_profile_tab_text_profile), + style = NearTheme.typography.B2_14_BOLD, + color = NearTheme.colors.BLACK_1A1A1A, + ) + } else { + Text( + text = stringResource(R.string.friend_profile_tab_text_profile), + style = NearTheme.typography.B2_14_MEDIUM, + color = NearTheme.colors.GRAY02_B7B7B7, + ) + } + } + Tab( + modifier = + Modifier + .width(85.dp) + .height(50.dp), + selected = true, + onClick = { + currentTabPosition.intValue = 1 + }, + ) { + if (currentTabPosition.intValue == 1) { + Text( + text = stringResource(R.string.friend_profile_tab_text_record), + style = NearTheme.typography.B2_14_BOLD, + color = NearTheme.colors.BLACK_1A1A1A, + ) + } else { + Text( + text = stringResource(R.string.friend_profile_tab_text_record), + style = NearTheme.typography.B2_14_MEDIUM, + color = NearTheme.colors.GRAY02_B7B7B7, + ) + } + } + } + HorizontalDivider(thickness = 1.dp, color = NearTheme.colors.GRAY03_EBEBEB) + if (currentTabPosition.intValue == 0) { + ProfileTab() + } else { + RecordTab() + } + } + NearSolidTypeButton( + modifier = + Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp) + .align(Alignment.BottomCenter), + contentPadding = PaddingValues(vertical = 17.dp), + enabled = true, + onClick = {}, + text = stringResource(R.string.friend_profile_record_button_text), + ) + } +} + +@Composable +private fun ProfileTab(modifier: Modifier = Modifier) { + Column(modifier = modifier) { + Spacer(modifier = Modifier.height(32.dp)) + ProfileDetailInfo( + category = stringResource(R.string.friend_profile_info_category_relation), + content = "친구", + ) + Spacer(modifier = Modifier.height(16.dp)) + ProfileDetailInfo( + category = stringResource(R.string.friend_profile_info_category_term_of_contact), + content = "2주", + ) + Spacer(modifier = Modifier.height(16.dp)) + ProfileDetailInfo( + category = stringResource(R.string.friend_profile_info_category_birthday), + content = "1996.03.21", + ) + Spacer(modifier = Modifier.height(16.dp)) + ProfileDetailInfo( + category = stringResource(R.string.friend_profile_info_category_anniversary), + content = "결혼기념일 (2020.06.24)", + ) + Spacer(modifier = Modifier.height(16.dp)) + ProfileMemoInfo( + content = null, + ) + } +} + +@Composable +private fun RecordTab(modifier: Modifier = Modifier) { + Column(modifier = modifier.padding(horizontal = 24.dp)) { + Spacer(modifier = Modifier.height(24.dp)) + Text( + "챙김 기록", + style = NearTheme.typography.B2_14_BOLD, + color = NearTheme.colors.BLACK_1A1A1A, + ) + Spacer(modifier = Modifier.height(13.dp)) + + LazyVerticalGrid( + GridCells.Fixed(3), + verticalArrangement = Arrangement.spacedBy(24.dp), + contentPadding = PaddingValues(bottom = 60.dp), + ) { + items(15) { + RecordItem() + } + } + } +} + +@Composable +private fun RecordItem(modifier: Modifier = Modifier) { + Column( + modifier = modifier, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Surface( + shape = RoundedCornerShape(44.dp), + border = + BorderStroke( + color = NearTheme.colors.GRAY03_EBEBEB, + width = 1.dp, + ), + color = NearTheme.colors.WHITE_FFFFFF, + ) { + Column( + modifier = + Modifier.padding( + top = 16.dp, + bottom = 20.dp, + start = 17.dp, + end = 17.dp, + ), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + ) { + Image( + painter = painterResource(R.drawable.img_40_character), + contentDescription = null, + ) + Text( + "11번째 챙김", + style = NearTheme.typography.B2_14_MEDIUM, + color = NearTheme.colors.BLUE01_5AA2E9, + ) + } + } + Spacer(modifier = Modifier.height(8.dp)) + Text( + "25.03.20", + style = NearTheme.typography.B2_14_MEDIUM, + color = NearTheme.colors.GRAY01_888888, + ) + } +} + +@Composable +private fun ProfileDetailInfo( + modifier: Modifier = Modifier, + category: String, + content: String, +) { + Surface( + modifier = + modifier + .fillMaxWidth() + .padding(horizontal = 20.dp), + shape = RoundedCornerShape(12.dp), + border = + BorderStroke( + width = 1.dp, + color = NearTheme.colors.GRAY03_EBEBEB, + ), + color = NearTheme.colors.WHITE_FFFFFF, + ) { + Row( + modifier = + Modifier + .padding(horizontal = 16.dp, vertical = 16.dp) + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Text( + category, + style = NearTheme.typography.B2_14_MEDIUM, + color = NearTheme.colors.GRAY01_888888, + ) + Text( + content, + style = NearTheme.typography.B2_14_MEDIUM, + color = NearTheme.colors.BLACK_1A1A1A, + ) + } + } +} + +@Composable +private fun ProfileMemoInfo( + modifier: Modifier = Modifier, + content: String?, +) { + Surface( + modifier = + modifier + .fillMaxWidth() + .padding(horizontal = 20.dp), + shape = RoundedCornerShape(12.dp), + border = + BorderStroke( + width = 1.dp, + color = NearTheme.colors.GRAY03_EBEBEB, + ), + color = NearTheme.colors.WHITE_FFFFFF, + ) { + Row( + modifier = + Modifier + .padding(horizontal = 16.dp, vertical = 16.dp) + .fillMaxWidth(), + verticalAlignment = Alignment.Top, + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Text( + text = stringResource(R.string.friend_profile_info_category_memo), + style = NearTheme.typography.B2_14_MEDIUM, + color = NearTheme.colors.GRAY01_888888, + ) + if (content.isNullOrBlank()) { + Text( + modifier = Modifier.padding(start = 54.dp), + text = + stringResource(R.string.friend_profile_info_memo_default_text), + style = NearTheme.typography.B2_14_MEDIUM, + color = NearTheme.colors.GRAY02_B7B7B7, + textAlign = TextAlign.End, + ) + } else { + Text( + modifier = Modifier.padding(start = 54.dp), + text = content, + style = NearTheme.typography.B2_14_MEDIUM, + color = NearTheme.colors.BLACK_1A1A1A, + ) + } + } + } +} + +fun Modifier.customTabIndicatorOffset( + currentTabPosition: TabPosition, + tabWidth: Dp, +): Modifier = + composed( + inspectorInfo = + debugInspectorInfo { + name = "customTabIndicatorOffset" + value = currentTabPosition + }, + ) { + val currentTabWidth by animateDpAsState( + targetValue = tabWidth, + animationSpec = tween(durationMillis = 250, easing = FastOutSlowInEasing), + label = "", + ) + val indicatorOffset by animateDpAsState( + targetValue = ((currentTabPosition.left + currentTabPosition.right - tabWidth) / 2), + animationSpec = tween(durationMillis = 250, easing = FastOutSlowInEasing), + label = "", + ) + fillMaxWidth() + .wrapContentSize(Alignment.BottomStart) // indicator 표시 위치 + .offset(x = indicatorOffset) + .width(currentTabWidth) + } + +@Preview(showBackground = true) +@Composable +fun FriendProfileScreenPreview() { + NearTheme { + FriendProfileScreen() + } +} diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/friendprofile/component/CallButton.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/friendprofile/component/CallButton.kt new file mode 100644 index 00000000..e18333db --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/friendprofile/component/CallButton.kt @@ -0,0 +1,67 @@ +package com.alarmy.near.presentation.feature.friendprofile.component + +import androidx.compose.foundation.Image +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.height +import androidx.compose.foundation.layout.width +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Text +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.alarmy.near.R +import com.alarmy.near.presentation.ui.component.button.NearBasicButton +import com.alarmy.near.presentation.ui.theme.NearTheme + +@Composable +fun CallButton( + modifier: Modifier = Modifier, + onClick: () -> Unit, + enabled: Boolean = false, +) { + NearBasicButton( + modifier = modifier, + onClick = onClick, + colors = + ButtonDefaults.buttonColors( + containerColor = NearTheme.colors.BG02_F4F9FD, + contentColor = NearTheme.colors.BLACK_1A1A1A, + disabledContainerColor = Color(0xfff7f7f7), + disabledContentColor = NearTheme.colors.GRAY02_B7B7B7, + ), + contentPadding = PaddingValues(start = 42.dp, end = 45.dp, top = 12.dp, bottom = 12.dp), + enabled = enabled, + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + Image( + painter = painterResource(if (enabled) R.drawable.ic_visual_24_call else R.drawable.ic_visual_24_call_gray), + contentDescription = stringResource(R.string.friend_profile_call), + ) + Spacer(modifier = Modifier.width(4.dp)) + Text( + text = stringResource(R.string.friend_profile_call), + style = NearTheme.typography.B2_14_MEDIUM, + ) + } + } +} + +@Preview(widthDp = 360, heightDp = 300, showBackground = true) +@Composable +fun CallButtonPreview() { + NearTheme { + Column { + CallButton(onClick = {}, enabled = false) + Spacer(modifier = Modifier.height(10.dp)) + CallButton(onClick = {}, enabled = true) + } + } +} diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/friendprofile/component/MessageButton.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/friendprofile/component/MessageButton.kt new file mode 100644 index 00000000..971016d1 --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/friendprofile/component/MessageButton.kt @@ -0,0 +1,67 @@ +package com.alarmy.near.presentation.feature.friendprofile.component + +import androidx.compose.foundation.Image +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.height +import androidx.compose.foundation.layout.width +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Text +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.alarmy.near.R +import com.alarmy.near.presentation.ui.component.button.NearBasicButton +import com.alarmy.near.presentation.ui.theme.NearTheme + +@Composable +fun MessageButton( + modifier: Modifier = Modifier, + onClick: () -> Unit, + enabled: Boolean = false, +) { + NearBasicButton( + modifier = modifier, + onClick = onClick, + colors = + ButtonDefaults.buttonColors( + containerColor = NearTheme.colors.BG02_F4F9FD, + contentColor = NearTheme.colors.BLACK_1A1A1A, + disabledContainerColor = Color(0xfff7f7f7), + disabledContentColor = NearTheme.colors.GRAY02_B7B7B7, + ), + contentPadding = PaddingValues(start = 42.dp, end = 45.dp, top = 12.dp, bottom = 12.dp), + enabled = enabled, + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + Image( + painter = painterResource(if (enabled) R.drawable.ic_visual_24_sms else R.drawable.ic_visual_24_sms_gray), + contentDescription = stringResource(R.string.friend_profile_send_message), + ) + Spacer(modifier = Modifier.width(4.dp)) + Text( + text = stringResource(R.string.friend_profile_send_message), + style = NearTheme.typography.B2_14_MEDIUM, + ) + } + } +} + +@Preview(widthDp = 360, heightDp = 300, showBackground = true) +@Composable +fun MessageButtonPreview() { + NearTheme { + Column { + MessageButton(onClick = {}, enabled = false) + Spacer(modifier = Modifier.height(10.dp)) + MessageButton(onClick = {}, enabled = true) + } + } +} diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/friendprofile/navigation/FriendProfileNavigation.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/friendprofile/navigation/FriendProfileNavigation.kt new file mode 100644 index 00000000..04100225 --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/friendprofile/navigation/FriendProfileNavigation.kt @@ -0,0 +1,27 @@ +package com.alarmy.near.presentation.feature.friendprofile.navigation + +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavOptions +import androidx.navigation.compose.composable +import com.alarmy.near.presentation.feature.friendprofile.FriendProfileRoute +import kotlinx.serialization.Serializable + +@Serializable +object RouteFriendProfile + +fun NavController.navigateToFriendProfile(navOptions: NavOptions) { + navigate(RouteFriendProfile, navOptions) +} + +fun NavGraphBuilder.friendProfileNavGraph( + onShowErrorSnackBar: (throwable: Throwable?) -> Unit, + onClickBackButton: () -> Unit, +) { + composable { backStackEntry -> + FriendProfileRoute( + onShowErrorSnackBar = onShowErrorSnackBar, + onClickBackButton = onClickBackButton, + ) + } +} diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/friendprofileedittor/FriendProfileEditorScreen.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/friendprofileedittor/FriendProfileEditorScreen.kt new file mode 100644 index 00000000..fab2cd0f --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/friendprofileedittor/FriendProfileEditorScreen.kt @@ -0,0 +1,474 @@ +package com.alarmy.near.presentation.feature.friendprofileedittor + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +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.WindowInsets +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.statusBars +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.rememberDatePickerState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.text.withStyle +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.alarmy.near.R +import com.alarmy.near.presentation.feature.friendprofileedittor.component.NearDatePicker +import com.alarmy.near.presentation.feature.friendprofileedittor.component.ReminderIntervalBottomSheet +import com.alarmy.near.presentation.ui.component.appbar.NearTopAppbar +import com.alarmy.near.presentation.ui.component.radiobutton.NearSmallRadioButton +import com.alarmy.near.presentation.ui.component.textfield.NearLimitedTextField +import com.alarmy.near.presentation.ui.component.textfield.NearTextField +import com.alarmy.near.presentation.ui.extension.onNoRippleClick +import com.alarmy.near.presentation.ui.theme.NearTheme + +@Composable +fun FriendProfileEditorRoute( + onShowErrorSnackBar: (throwable: Throwable?) -> Unit, + onClickBackButton: () -> Unit = {}, +) { + FriendProfileEditorScreen( + onClickBackButton = onClickBackButton, + ) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun FriendProfileEditorScreen( + modifier: Modifier = Modifier, + onClickBackButton: () -> Unit = {}, +) { + val isErrorMessageVisible = remember { mutableStateOf(false) } + val density = LocalDensity.current + val statusBarHeightDp = with(density) { WindowInsets.statusBars.getTop(density).toDp() } + val showBottomSheet = remember { mutableStateOf(false) } + if (showBottomSheet.value) { + ReminderIntervalBottomSheet(onDismissRequest = { + showBottomSheet.value = false + }) + } + Column( + modifier = + modifier + .fillMaxSize() + .background(NearTheme.colors.WHITE_FFFFFF), + ) { + Spacer(modifier = Modifier.padding(top = statusBarHeightDp)) + NearTopAppbar( + modifier = Modifier.padding(end = 24.dp), + title = "", + onClickBackButton = onClickBackButton, + menuButton = { + Text( + text = "완료", + style = NearTheme.typography.B1_16_BOLD, + color = NearTheme.colors.BLACK_1A1A1A, + ) + }, + ) + Spacer(modifier = Modifier.height(16.dp)) + Column( + modifier = + Modifier + .fillMaxWidth() + .padding(start = 24.dp, end = 20.dp), + ) { + Row( + modifier = + Modifier + .fillMaxWidth(), + ) { + Text( + modifier = Modifier.padding(top = 16.dp), + text = + buildAnnotatedString { + append("이름") + withStyle( + style = + SpanStyle( + color = NearTheme.colors.BLUE01_5AA2E9, + ), + ) { + append("*") + } + }, + textAlign = TextAlign.Center, + style = NearTheme.typography.B2_14_MEDIUM, + color = NearTheme.colors.GRAY01_888888, + ) + Spacer(modifier = Modifier.width(55.dp)) + NearTextField( + modifier = Modifier.weight(1f), + value = "가나다", + onValueChange = { + }, + ) + } + if (isErrorMessageVisible.value) { + Spacer(modifier = Modifier.height(8.dp)) + Text( + "이름을 입력해주세요.", + style = NearTheme.typography.FC_12_MEDIUM, + color = NearTheme.colors.NEGATIVE_F04E4E, + ) + } + Spacer(modifier = Modifier.height(32.dp)) + Row( + modifier = + Modifier + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + "관계", + style = NearTheme.typography.B2_14_MEDIUM, + color = NearTheme.colors.GRAY01_888888, + ) + Spacer(modifier = Modifier.width(72.dp)) + Row( + modifier = + Modifier + .weight(1f) + .padding(end = 35.dp), + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + NearSmallRadioButton( + selected = false, + onClick = {}, + ) + Spacer(modifier = Modifier.width(8.dp)) + Text( + text = stringResource(R.string.friend_profile_editor_relation_freind), + style = NearTheme.typography.B2_14_MEDIUM, + color = NearTheme.colors.BLACK_1A1A1A, + ) + } + Row(verticalAlignment = Alignment.CenterVertically) { + NearSmallRadioButton( + selected = false, + onClick = {}, + ) + Spacer(modifier = Modifier.width(8.dp)) + Text( + text = stringResource(R.string.friend_profile_editor_relation_family), + style = NearTheme.typography.B2_14_MEDIUM, + color = NearTheme.colors.BLACK_1A1A1A, + ) + } + Row(verticalAlignment = Alignment.CenterVertically) { + NearSmallRadioButton( + selected = false, + onClick = {}, + ) + Spacer(modifier = Modifier.width(8.dp)) + Text( + text = stringResource(R.string.friend_profile_editor_relation_acquaintance), + style = NearTheme.typography.B2_14_MEDIUM, + color = NearTheme.colors.BLACK_1A1A1A, + ) + } + } + } + Spacer(modifier = Modifier.height(33.dp)) + Row( + modifier = + Modifier + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + "연락 주기", + style = NearTheme.typography.B2_14_MEDIUM, + color = NearTheme.colors.GRAY01_888888, + ) + Spacer(modifier = Modifier.width(35.dp)) + Surface( + modifier = + modifier + .weight(1f) + .onNoRippleClick({ + showBottomSheet.value = true + }), + shape = RoundedCornerShape(12.dp), + border = + BorderStroke( + width = 1.dp, + color = NearTheme.colors.GRAY03_EBEBEB, + ), + color = NearTheme.colors.WHITE_FFFFFF, + ) { + Row( + modifier = + Modifier.padding( + start = 16.dp, + end = 12.dp, + top = 14.dp, + bottom = 14.dp, + ), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Text( + "2주 (수요일 마다)", + style = NearTheme.typography.B2_14_MEDIUM, + color = NearTheme.colors.BLACK_1A1A1A, + ) + Image( + painter = painterResource(id = R.drawable.ic_24_down), + contentDescription = null, + ) + } + } + } + Spacer(modifier = Modifier.height(16.dp)) + Row( + modifier = + Modifier + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + ) { + val birthdayDatePickerState = remember { mutableStateOf(false) } + val datePickerState = + rememberDatePickerState() + if (birthdayDatePickerState.value) { + NearDatePicker( + datePickerState = datePickerState, + onDismiss = { birthdayDatePickerState.value = false }, + onDateSelected = {}, + ) + } + Text( + "생일", + style = NearTheme.typography.B2_14_MEDIUM, + color = NearTheme.colors.GRAY01_888888, + ) + Spacer(modifier = Modifier.width(62.dp)) + Surface( + modifier = + modifier + .weight(1f), + shape = RoundedCornerShape(12.dp), + border = + BorderStroke( + width = 1.dp, + color = NearTheme.colors.GRAY03_EBEBEB, + ), + color = NearTheme.colors.WHITE_FFFFFF, + ) { + Row( + modifier = + Modifier + .padding( + start = 16.dp, + end = 12.dp, + top = 14.dp, + bottom = 14.dp, + ).onNoRippleClick({ + birthdayDatePickerState.value = true + }), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Text( + "2020.06.24", + style = NearTheme.typography.B2_14_MEDIUM, + color = NearTheme.colors.BLACK_1A1A1A, + ) + Image( + painter = painterResource(id = R.drawable.ic_24_down), + contentDescription = null, + ) + } + } + } + Spacer(modifier = Modifier.height(32.dp)) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Text( + text = "기념일", + style = NearTheme.typography.B2_14_MEDIUM, + color = NearTheme.colors.GRAY01_888888, + ) + Text( + modifier = Modifier.onNoRippleClick(onClick = {}), + text = "추가하기", + style = NearTheme.typography.B2_14_MEDIUM, + color = NearTheme.colors.BLUE01_5AA2E9, + ) + } + Spacer(modifier = Modifier.height(16.dp)) + } + LazyColumn( + modifier = Modifier.background(color = NearTheme.colors.BG02_F4F9FD), + contentPadding = + PaddingValues( + top = 20.dp, + bottom = 32.dp, + start = 24.dp, + end = 20.dp, + ), + verticalArrangement = Arrangement.spacedBy(32.dp), + ) { + item { + Column { + val anniversaryDatePickerState = remember { mutableStateOf(false) } + val datePickerState = + rememberDatePickerState() + if (anniversaryDatePickerState.value) { + NearDatePicker( + datePickerState = datePickerState, + onDismiss = { anniversaryDatePickerState.value = false }, + onDateSelected = {}, + ) + } + Row(verticalAlignment = Alignment.CenterVertically) { + Text( + text = "기념일 이름", + style = NearTheme.typography.B2_14_MEDIUM, + color = NearTheme.colors.GRAY01_888888, + ) + Spacer(modifier = Modifier.width(23.dp)) + NearTextField( + modifier = Modifier.weight(1f), + value = "가나다", + onValueChange = { + }, + ) + } + if (isErrorMessageVisible.value) { + Spacer(modifier = Modifier.height(8.dp)) + Text( + "이름을 입력해주세요.", + style = NearTheme.typography.FC_12_MEDIUM, + color = NearTheme.colors.NEGATIVE_F04E4E, + ) + } + Spacer(modifier = Modifier.height(16.dp)) + Row( + modifier = + Modifier + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + "날짜", + style = NearTheme.typography.B2_14_MEDIUM, + color = NearTheme.colors.GRAY01_888888, + ) + Spacer(modifier = Modifier.width(62.dp)) + Surface( + modifier = + modifier + .weight(1f) + .onNoRippleClick(onClick = { + anniversaryDatePickerState.value = true + }), + shape = RoundedCornerShape(12.dp), + border = + BorderStroke( + width = 1.dp, + color = NearTheme.colors.GRAY03_EBEBEB, + ), + color = NearTheme.colors.WHITE_FFFFFF, + ) { + Row( + modifier = + Modifier.padding( + start = 16.dp, + end = 12.dp, + top = 14.dp, + bottom = 14.dp, + ), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Text( + "2020.06.24", + style = NearTheme.typography.B2_14_MEDIUM, + color = NearTheme.colors.BLACK_1A1A1A, + ) + Image( + painter = painterResource(id = R.drawable.ic_24_down), + contentDescription = null, + ) + } + } + } + Spacer(modifier = Modifier.height(32.dp)) + Text( + modifier = Modifier.fillMaxWidth(), + textAlign = TextAlign.End, + text = "삭제하기", + textDecoration = TextDecoration.Underline, + color = NearTheme.colors.GRAY01_888888, + ) + } + } + } + Spacer(modifier = Modifier.height(24.dp)) + Row( + modifier = + Modifier + .fillMaxWidth() + .padding(horizontal = 24.dp), + ) { + Text( + modifier = Modifier.padding(top = 16.dp), + text = "메모", + style = NearTheme.typography.B2_14_MEDIUM, + color = NearTheme.colors.GRAY01_888888, + ) + Spacer(modifier = Modifier.width(23.dp)) + NearLimitedTextField( + modifier = + Modifier + .weight(1f) + .height(180.dp), + value = "", + onValueChange = { + }, + placeHolderText = + "꼭 기억해야 할 내용을 기록해보세요.\n" + + "예) 날생선 X, 작년 생일에\n" + + "키링 선물함 등", + maxTextCount = 100, + ) + } + Spacer(modifier = Modifier.height(80.dp)) + } +} + +@Preview(showBackground = true) +@Composable +fun FriendProfileEditorScreenPreview() { + NearTheme { + FriendProfileEditorScreen() + } +} diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/friendprofileedittor/FriendProfileEditorViewModel.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/friendprofileedittor/FriendProfileEditorViewModel.kt new file mode 100644 index 00000000..d3cbb6ce --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/friendprofileedittor/FriendProfileEditorViewModel.kt @@ -0,0 +1,21 @@ +package com.alarmy.near.presentation.feature.friendprofileedittor + +import androidx.lifecycle.ViewModel +import com.alarmy.near.model.Relation +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 FriendProfileEditorViewModel + @Inject + constructor() : ViewModel() { + private val _relation: MutableStateFlow = MutableStateFlow(null) + val relation = _relation.asStateFlow() + + fun setRelation(relation: Relation) { + _relation.update { relation } + } + } diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/friendprofileedittor/component/NearDatePicker.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/friendprofileedittor/component/NearDatePicker.kt new file mode 100644 index 00000000..a3bfca5b --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/friendprofileedittor/component/NearDatePicker.kt @@ -0,0 +1,73 @@ +package com.alarmy.near.presentation.feature.friendprofileedittor.component + +import androidx.compose.material3.DatePicker +import androidx.compose.material3.DatePickerColors +import androidx.compose.material3.DatePickerDefaults +import androidx.compose.material3.DatePickerDialog +import androidx.compose.material3.DatePickerState +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.material3.rememberDatePickerState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.tooling.preview.Preview +import com.alarmy.near.presentation.ui.theme.NearColor +import com.alarmy.near.presentation.ui.theme.NearTheme +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun NearDatePicker( + datePickerState: DatePickerState = + rememberDatePickerState(), + onDateSelected: (Long?) -> Unit, + onDismiss: () -> Unit, +) { + DatePickerDialog( + colors = DatePickerDefaults.colors().copy( + containerColor = NearTheme.colors.WHITE_FFFFFF, + ), + onDismissRequest = onDismiss, + confirmButton = { + TextButton(onClick = { + onDateSelected(datePickerState.selectedDateMillis) + onDismiss() + }) { + Text("확인") + } + }, + dismissButton = { + TextButton(onClick = onDismiss) { + Text("닫기") + } + }, + ) { + DatePicker(state = datePickerState, title = null, headline = null, showModeToggle = false, + dateFormatter = + remember + { DatePickerDefaults.dateFormatter() }, + colors = DatePickerDefaults.colors().copy( + containerColor = NearTheme.colors.WHITE_FFFFFF, + )) + } +} + +private fun convertMillisToDate(millis: Long): String { + val formatter = SimpleDateFormat("MM/dd/yyyy", Locale.getDefault()) + return formatter.format(Date(millis)) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Preview(showBackground = true) +@Composable +fun DatePickerDockedPreview() { + NearTheme { + NearDatePicker( + onDateSelected = {}, + onDismiss = {}, + ) + } +} diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/friendprofileedittor/component/ReminderIntervalBottomSheet.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/friendprofileedittor/component/ReminderIntervalBottomSheet.kt new file mode 100644 index 00000000..b4f43b77 --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/friendprofileedittor/component/ReminderIntervalBottomSheet.kt @@ -0,0 +1,205 @@ +package com.alarmy.near.presentation.feature.friendprofileedittor.component + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +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.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.SheetState +import androidx.compose.material3.SheetValue +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.VerticalDivider +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.material3.rememberStandardBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +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.stringResource +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.withStyle +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.alarmy.near.model.ReminderInterval +import com.alarmy.near.presentation.ui.component.button.NearLineTypeButton +import com.alarmy.near.presentation.ui.component.button.NearSolidTypeButton +import com.alarmy.near.presentation.ui.component.checkbox.NearCheckbox +import com.alarmy.near.presentation.ui.extension.onNoRippleClick +import com.alarmy.near.presentation.ui.theme.NearTheme + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ReminderIntervalBottomSheet( + modifier: Modifier = Modifier, + selectedReminderInterval: ReminderInterval = ReminderInterval.WEEKLY, + onSelectReminderInterval: (ReminderInterval) -> Unit = {}, + sheetState: SheetState = + rememberModalBottomSheetState( + skipPartiallyExpanded = true, + ), + onDismissRequest: () -> Unit = {}, +) { + val initialReminderInterval = remember { selectedReminderInterval } + + val tempSelected = remember { mutableStateOf(initialReminderInterval) } + ModalBottomSheet( + modifier = modifier, + containerColor = NearTheme.colors.WHITE_FFFFFF, + sheetState = sheetState, + onDismissRequest = { + tempSelected.value = initialReminderInterval + onDismissRequest() + }, + dragHandle = { + Surface( + modifier = Modifier.padding(top = 12.dp, bottom = 24.dp), + color = Color.Black.copy(alpha = 0.1f), + ) { + Box( + modifier = + Modifier + .width(36.dp) + .height(5.dp), + ) + } + }, + ) { + Text( + modifier = Modifier.padding(start = 24.dp), + text = "주기 설정", + style = NearTheme.typography.B1_16_BOLD, + color = Color.Black, + ) + Spacer(modifier = Modifier.height(24.dp)) + Surface( + modifier = + Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp), + color = NearTheme.colors.BG02_F4F9FD, + shape = RoundedCornerShape(12.dp), + ) { + Row( + modifier = Modifier.padding(horizontal = 20.dp, vertical = 14.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Text( + buildAnnotatedString { + append("매주") + withStyle( + style = + SpanStyle( + color = NearTheme.colors.BLUE01_5AA2E9, + fontWeight = FontWeight.Bold, + ), + ) { + append(" 화요일") + } + }, + color = NearTheme.colors.BLACK_1A1A1A, + style = NearTheme.typography.B2_14_MEDIUM, + ) + Row(verticalAlignment = Alignment.CenterVertically) { + VerticalDivider( + thickness = 1.dp, + modifier = Modifier.height(28.dp), + color = NearTheme.colors.BLACK_1A1A1A.copy(0.1f), + ) + Spacer(modifier = Modifier.width(20.dp)) + Text( + text = "다음 주기 : 4/8 화", + style = NearTheme.typography.B2_14_MEDIUM, + color = NearTheme.colors.GRAY01_888888, + ) + } + } + } + Spacer(modifier = Modifier.height(8.dp)) + LazyColumn( + verticalArrangement = Arrangement.spacedBy(28.dp), + contentPadding = PaddingValues(vertical = 14.dp), + ) { + items( + count = ReminderInterval.entries.size, + key = { index -> ReminderInterval.entries[index] }, + ) { item -> + val reminderInterval = ReminderInterval.entries[item] + Row( + modifier = + Modifier + .fillMaxWidth() + .padding(start = 24.dp, end = 14.dp) + .onNoRippleClick(onClick = { + tempSelected.value = reminderInterval + }), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + stringResource(reminderInterval.labelRes), + style = + if (reminderInterval == tempSelected.value) { + NearTheme.typography.B1_16_BOLD + } else { + NearTheme.typography.B1_16_MEDIUM + }, + color = NearTheme.colors.BLACK_1A1A1A, + ) + NearCheckbox( + checked = tempSelected.value == reminderInterval, + onCheckedChange = {}, + ) + } + } + } + Spacer(modifier = Modifier.height(24.dp)) + Row(modifier = Modifier.padding(horizontal = 20.dp)) { + NearLineTypeButton( + modifier = Modifier.weight(1f), + text = "취소", + onClick = { + tempSelected.value = initialReminderInterval + onDismissRequest() + }, + contentPadding = PaddingValues(vertical = 17.dp), + enabled = true, + ) + Spacer(modifier = Modifier.width(7.dp)) + NearSolidTypeButton( + modifier = Modifier.weight(1f), + text = "확인", + onClick = { + onSelectReminderInterval(tempSelected.value) + }, + enabled = true, + contentPadding = PaddingValues(vertical = 17.dp), + ) + } + Spacer(modifier = Modifier.height(24.dp)) + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Preview(showBackground = true) +@Composable +fun ReminderIntervalBottomSheetPreview() { + NearTheme { + ReminderIntervalBottomSheet( + sheetState = rememberStandardBottomSheetState(initialValue = SheetValue.Expanded), + ) + } +} diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/friendprofileedittor/dialog/EditorExitDialog.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/friendprofileedittor/dialog/EditorExitDialog.kt new file mode 100644 index 00000000..5aa9c093 --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/friendprofileedittor/dialog/EditorExitDialog.kt @@ -0,0 +1,59 @@ +package com.alarmy.near.presentation.feature.friendprofileedittor.dialog + +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.alarmy.near.presentation.ui.theme.NearTheme + +@Composable +internal fun EditorExitDialog( + modifier: Modifier = Modifier, + onDismissRequest: () -> Unit, + onConfirm: () -> Unit, +) { + AlertDialog( + modifier = modifier, + onDismissRequest = onDismissRequest, + title = { + Text(text = "수정을 그만두시나요?") + }, + text = { + Text( + text = + "화면을 나가면 \n" + + "수정 내용은 저장되지 않아요.", + ) + }, + confirmButton = { + TextButton( + onClick = onConfirm, + ) { + Text("확인") + } + }, + dismissButton = { + TextButton( + onClick = onDismissRequest, + ) { + Text("취소") + } + }, + shape = RoundedCornerShape(24.dp), + ) +} + +@Preview(showBackground = true) +@Composable +fun EditorExitDialogPreview() { + NearTheme { + EditorExitDialog( + onDismissRequest = { }, + onConfirm = {}, + ) + } +} diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/friendprofileedittor/navigation/FriendProfileNavigation.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/friendprofileedittor/navigation/FriendProfileNavigation.kt new file mode 100644 index 00000000..d7521c67 --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/friendprofileedittor/navigation/FriendProfileNavigation.kt @@ -0,0 +1,27 @@ +package com.alarmy.near.presentation.feature.friendprofileedittor.navigation + +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavOptions +import androidx.navigation.compose.composable +import com.alarmy.near.presentation.feature.friendprofileedittor.FriendProfileEditorRoute +import kotlinx.serialization.Serializable + +@Serializable +object RouteFriendProfileEditor + +fun NavController.navigateToFriendProfileEditor(navOptions: NavOptions) { + navigate(RouteFriendProfileEditor, navOptions) +} + +fun NavGraphBuilder.friendProfileEditorNavGraph( + onShowErrorSnackBar: (throwable: Throwable?) -> Unit, + onClickBackButton: () -> Unit = {}, +) { + composable { backStackEntry -> + FriendProfileEditorRoute( + onShowErrorSnackBar = onShowErrorSnackBar, + onClickBackButton = onClickBackButton, + ) + } +} diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/main/NearApp.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/main/NearApp.kt index 03abcdf1..106f00b5 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/main/NearApp.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/main/NearApp.kt @@ -32,7 +32,7 @@ internal fun NearApp( Scaffold( modifier = - Modifier.fillMaxSize(), + modifier.fillMaxSize(), snackbarHost = { SnackbarHost( hostState = snackBarState, diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/main/NearNavHost.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/main/NearNavHost.kt index cb862357..239fa4e1 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/main/NearNavHost.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/main/NearNavHost.kt @@ -4,7 +4,9 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost -import com.alarmy.near.presentation.feature.home.navigation.RouteHome +import com.alarmy.near.presentation.feature.friendprofile.navigation.friendProfileNavGraph +import com.alarmy.near.presentation.feature.friendprofileedittor.navigation.RouteFriendProfileEditor +import com.alarmy.near.presentation.feature.friendprofileedittor.navigation.friendProfileEditorNavGraph import com.alarmy.near.presentation.feature.home.navigation.homeNavGraph @Composable @@ -19,8 +21,14 @@ internal fun NearNavHost( NavHost( modifier = modifier, navController = navController, - startDestination = RouteHome, + startDestination = RouteFriendProfileEditor, ) { + friendProfileNavGraph(onShowErrorSnackBar = onShowSnackbar, onClickBackButton = { + navController.popBackStack() + }) + friendProfileEditorNavGraph(onShowErrorSnackBar = onShowSnackbar, onClickBackButton = { + navController.popBackStack() + }) homeNavGraph( onShowErrorSnackBar = onShowSnackbar, onContactClick = { contactId -> diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/appbar/NearTopAppbar.kt b/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/appbar/NearTopAppbar.kt new file mode 100644 index 00000000..61e7db88 --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/appbar/NearTopAppbar.kt @@ -0,0 +1,57 @@ +package com.alarmy.near.presentation.ui.component.appbar + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.alarmy.near.R +import com.alarmy.near.presentation.ui.extension.onNoRippleClick +import com.alarmy.near.presentation.ui.theme.NearTheme + +@Composable +fun NearTopAppbar( + modifier: Modifier = Modifier, + title: String, + onClickBackButton: () -> Unit = {}, + menuButton: (@Composable () -> Unit)? = null, +) { + Row( + modifier = + modifier + .fillMaxWidth() + .padding(vertical = 8.dp) + .padding(start = 20.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + Image( + modifier = Modifier.onNoRippleClick(onClick = onClickBackButton), + painter = painterResource(R.drawable.ic_back_32_black), + contentDescription = + stringResource( + R.string.common_back_button_description, + ), + ) + Spacer(modifier = Modifier.width(4.dp)) + Text( + title, + style = NearTheme.typography.B1_16_BOLD, + color = NearTheme.colors.BLACK_1A1A1A, + ) + } + if (menuButton != null) { + menuButton() + } + } +} diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/checkbox/NearBackgroundCheckbox.kt b/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/checkbox/NearBackgroundCheckbox.kt index 3e71272a..0bb96626 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/checkbox/NearBackgroundCheckbox.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/checkbox/NearBackgroundCheckbox.kt @@ -13,6 +13,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.alarmy.near.R +import com.alarmy.near.presentation.ui.extension.onNoRippleClick @Composable fun NearBackgroundCheckbox( @@ -22,7 +23,7 @@ fun NearBackgroundCheckbox( ) { Image( modifier = - modifier.clickable( + modifier.onNoRippleClick( onClick = { onCheckedChange(!checked) }, ), painter = diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/radiobutton/NearSmallRadioButton.kt b/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/radiobutton/NearSmallRadioButton.kt index a8e10f30..43e825b8 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/radiobutton/NearSmallRadioButton.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/radiobutton/NearSmallRadioButton.kt @@ -12,6 +12,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.alarmy.near.R +import com.alarmy.near.presentation.ui.extension.onNoRippleClick @Composable fun NearSmallRadioButton( @@ -21,7 +22,7 @@ fun NearSmallRadioButton( ) { Image( modifier = - modifier.clickable( + modifier.onNoRippleClick( onClick = { onClick(!selected) }, ), painter = diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/textfield/NearLimitedTextField.kt b/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/textfield/NearLimitedTextField.kt index c5fb4381..908f22dd 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/textfield/NearLimitedTextField.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/textfield/NearLimitedTextField.kt @@ -29,7 +29,7 @@ fun NearLimitedTextField( value: String, maxTextCount: Int = MAX_TEXT_COUNT, onValueChange: (String) -> Unit, - enabled: Boolean, + enabled: Boolean = true, placeHolderText: String, singleLine: Boolean = false, interactionSource: InteractionSource = remember { MutableInteractionSource() }, @@ -75,12 +75,12 @@ fun NearLimitedTextField( interactionSource = interactionSource, colors = colors, ) - if (value.count() > 0 && enabled) { + if (!singleLine || value.count() > 0 && enabled) { Row( modifier = Modifier, horizontalArrangement = Arrangement.End, verticalAlignment = - if (lineCount.intValue == 1) { + if (singleLine && lineCount.intValue == 1) { Alignment.CenterVertically } else { Alignment.Bottom @@ -91,7 +91,7 @@ fun NearLimitedTextField( modifier = Modifier.padding( end = 16.dp, - bottom = if (lineCount.intValue == 1) 0.dp else 16.dp, + bottom = if (singleLine && lineCount.intValue == 1) 0.dp else 16.dp, ), style = NearTheme.typography.B2_14_MEDIUM, color = NearTheme.colors.GRAY02_B7B7B7, diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/textfield/NearTextField.kt b/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/textfield/NearTextField.kt index 77c47934..2aabb7e6 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/textfield/NearTextField.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/textfield/NearTextField.kt @@ -2,6 +2,7 @@ package com.alarmy.near.presentation.ui.component.textfield import androidx.compose.foundation.interaction.InteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.text.BasicTextField @@ -35,7 +36,7 @@ fun NearTextField( BasicTextField( value = value, - modifier = modifier, + modifier = modifier.heightIn(min = 0.dp, max = 52.dp), enabled = enabled, textStyle = NearTheme.typography.B2_14_MEDIUM.copy( diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/textfield/internal/NearOutlinedTextFieldDecorationBox.kt b/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/textfield/internal/NearOutlinedTextFieldDecorationBox.kt index 9e48fad5..3447be12 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/textfield/internal/NearOutlinedTextFieldDecorationBox.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/textfield/internal/NearOutlinedTextFieldDecorationBox.kt @@ -27,9 +27,9 @@ internal fun NearOutlinedTextFieldDecorationBox( NearTextFieldDecorationContainer( enabled = enabled, interactionSource = interactionSource, - colors = colors + colors = colors, ) - } + }, ) { OutlinedTextFieldDefaults.DecorationBox( contentPadding = contentPadding, @@ -63,7 +63,7 @@ internal fun NearTextFieldDecorationContainer( interactionSource = interactionSource, colors = colors, shape = RoundedCornerShape(12.dp), - focusedBorderThickness = (1.5).dp, - unfocusedBorderThickness = (1.5).dp, + focusedBorderThickness = (1).dp, + unfocusedBorderThickness = (1).dp, ) } diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/ui/theme/Type.kt b/Near/app/src/main/java/com/alarmy/near/presentation/ui/theme/Type.kt index 99b830e4..ced751f5 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/ui/theme/Type.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/ui/theme/Type.kt @@ -6,10 +6,13 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.ui.Modifier +import androidx.compose.ui.text.PlatformParagraphStyle +import androidx.compose.ui.text.PlatformTextStyle import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.LineHeightStyle import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.dp @@ -80,6 +83,11 @@ internal val Typography = fontSize = 24.sp, letterSpacingPercent = -0.25f, ), + lineHeightStyle = + LineHeightStyle( + alignment = LineHeightStyle.Alignment.Center, + trim = LineHeightStyle.Trim.None, + ), ), H1_24_MEDIUM = TextStyle( @@ -92,6 +100,11 @@ internal val Typography = fontSize = 24.sp, letterSpacingPercent = -0.25f, ), + lineHeightStyle = + LineHeightStyle( + alignment = LineHeightStyle.Alignment.Center, + trim = LineHeightStyle.Trim.None, + ), ), H1_24_REGULAR = TextStyle( @@ -104,6 +117,11 @@ internal val Typography = fontSize = 24.sp, letterSpacingPercent = -0.25f, ), + lineHeightStyle = + LineHeightStyle( + alignment = LineHeightStyle.Alignment.Center, + trim = LineHeightStyle.Trim.None, + ), ), H2_18_BOLD = TextStyle( @@ -116,6 +134,11 @@ internal val Typography = fontSize = 18.sp, letterSpacingPercent = -0.25f, ), + lineHeightStyle = + LineHeightStyle( + alignment = LineHeightStyle.Alignment.Center, + trim = LineHeightStyle.Trim.None, + ), ), B1_16_BOLD = TextStyle( @@ -128,6 +151,11 @@ internal val Typography = fontSize = 16.sp, letterSpacingPercent = -0.25f, ), + lineHeightStyle = + LineHeightStyle( + alignment = LineHeightStyle.Alignment.Center, + trim = LineHeightStyle.Trim.None, + ), ), B1_16_MEDIUM = TextStyle( @@ -140,6 +168,11 @@ internal val Typography = fontSize = 16.sp, letterSpacingPercent = -0.25f, ), + lineHeightStyle = + LineHeightStyle( + alignment = LineHeightStyle.Alignment.Center, + trim = LineHeightStyle.Trim.None, + ), ), B2_14_BOLD = TextStyle( @@ -152,6 +185,11 @@ internal val Typography = fontSize = 14.sp, letterSpacingPercent = -0.25f, ), + lineHeightStyle = + LineHeightStyle( + alignment = LineHeightStyle.Alignment.Center, + trim = LineHeightStyle.Trim.None, + ), ), B2_14_MEDIUM = TextStyle( @@ -164,6 +202,11 @@ internal val Typography = fontSize = 14.sp, letterSpacingPercent = -0.25f, ), + lineHeightStyle = + LineHeightStyle( + alignment = LineHeightStyle.Alignment.Center, + trim = LineHeightStyle.Trim.None, + ), ), FC_12_BOLD = TextStyle( @@ -176,6 +219,11 @@ internal val Typography = fontSize = 12.sp, letterSpacingPercent = -0.25f, ), + lineHeightStyle = + LineHeightStyle( + alignment = LineHeightStyle.Alignment.Center, + trim = LineHeightStyle.Trim.None, + ), ), FC_12_MEDIUM = TextStyle( @@ -188,6 +236,11 @@ internal val Typography = fontSize = 12.sp, letterSpacingPercent = -0.25f, ), + lineHeightStyle = + LineHeightStyle( + alignment = LineHeightStyle.Alignment.Center, + trim = LineHeightStyle.Trim.None, + ), ), ) diff --git a/Near/app/src/main/res/drawable/ic_24_down.xml b/Near/app/src/main/res/drawable/ic_24_down.xml new file mode 100644 index 00000000..9811b5f3 --- /dev/null +++ b/Near/app/src/main/res/drawable/ic_24_down.xml @@ -0,0 +1,13 @@ + + + diff --git a/Near/app/src/main/res/drawable/ic_back_32_black.xml b/Near/app/src/main/res/drawable/ic_back_32_black.xml new file mode 100644 index 00000000..72fb7a39 --- /dev/null +++ b/Near/app/src/main/res/drawable/ic_back_32_black.xml @@ -0,0 +1,13 @@ + + + diff --git a/Near/app/src/main/res/drawable/ic_visual_24_call.xml b/Near/app/src/main/res/drawable/ic_visual_24_call.xml new file mode 100644 index 00000000..98f799bd --- /dev/null +++ b/Near/app/src/main/res/drawable/ic_visual_24_call.xml @@ -0,0 +1,9 @@ + + + diff --git a/Near/app/src/main/res/drawable/ic_visual_24_call_gray.xml b/Near/app/src/main/res/drawable/ic_visual_24_call_gray.xml new file mode 100644 index 00000000..e4be642e --- /dev/null +++ b/Near/app/src/main/res/drawable/ic_visual_24_call_gray.xml @@ -0,0 +1,9 @@ + + + diff --git a/Near/app/src/main/res/drawable/ic_visual_24_sms.xml b/Near/app/src/main/res/drawable/ic_visual_24_sms.xml new file mode 100644 index 00000000..1a98cea3 --- /dev/null +++ b/Near/app/src/main/res/drawable/ic_visual_24_sms.xml @@ -0,0 +1,10 @@ + + + diff --git a/Near/app/src/main/res/drawable/ic_visual_24_sms_gray.xml b/Near/app/src/main/res/drawable/ic_visual_24_sms_gray.xml new file mode 100644 index 00000000..5315601a --- /dev/null +++ b/Near/app/src/main/res/drawable/ic_visual_24_sms_gray.xml @@ -0,0 +1,10 @@ + + + diff --git a/Near/app/src/main/res/drawable/img_100_character_empty.xml b/Near/app/src/main/res/drawable/img_100_character_empty.xml new file mode 100644 index 00000000..f790f7b8 --- /dev/null +++ b/Near/app/src/main/res/drawable/img_100_character_empty.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + diff --git a/Near/app/src/main/res/drawable/img_40_character.xml b/Near/app/src/main/res/drawable/img_40_character.xml new file mode 100644 index 00000000..eb55a9ba --- /dev/null +++ b/Near/app/src/main/res/drawable/img_40_character.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + diff --git a/Near/app/src/main/res/drawable/img_80_user1.xml b/Near/app/src/main/res/drawable/img_80_user1.xml new file mode 100644 index 00000000..24b21bef --- /dev/null +++ b/Near/app/src/main/res/drawable/img_80_user1.xml @@ -0,0 +1,24 @@ + + + + + + + + + diff --git a/Near/app/src/main/res/values/strings.xml b/Near/app/src/main/res/values/strings.xml index 833a5f50..3d71e0ec 100644 --- a/Near/app/src/main/res/values/strings.xml +++ b/Near/app/src/main/res/values/strings.xml @@ -3,6 +3,10 @@ 체크 체크 해제 + + 뒤로 가기 + 메뉴 + MY 이번달 챙길 사람 @@ -12,4 +16,25 @@ 연락처 추가 가까워 지고 싶은 사람을\n추가해보세요. 사람 추가 + + + 전화걸기 + 문자하기 + 챙김 기록하기 + 프로필 + 기록 + 관계 + 연락 주기 + 생일 + 기념일 + 메모 + 꼭 기억해야 할 내용을 기록해보세요.\n예) 날생선 X, 작년 생일에 키링 선물함 등 + 친구 + 가족 + 지인 + 매일 + 매주 + 2주 + 매달 + 6개월