diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png index 73688362..9d7b269d 100644 Binary files a/app/src/main/ic_launcher-playstore.png and b/app/src/main/ic_launcher-playstore.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp index 886c2d43..dd2f183d 100644 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.webp and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp index d0cb1031..3f60bf43 100644 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp and b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp index bdc0577a..969a6694 100644 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp index 34cfdd1f..b3b640a5 100644 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.webp and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp index 63b4cf71..b98c7de9 100644 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp and b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp index 21c3fc6b..9e0257c8 100644 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp index 202a4e35..4c10f834 100644 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp index 8300957c..ae18f6d6 100644 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp and b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp index d3cdbe82..90347123 100644 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp index 390317b9..5826d74a 100644 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp index 7758c027..79db8199 100644 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp index f72a2000..4480f255 100644 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp index 3eb90f67..257ad0d6 100644 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp index fc398678..c7e4f3b7 100644 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp index ef3fee5c..6be407b7 100644 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/values/ic_launcher_background.xml b/app/src/main/res/values/ic_launcher_background.xml index 60bbf7bf..173e0cac 100644 --- a/app/src/main/res/values/ic_launcher_background.xml +++ b/app/src/main/res/values/ic_launcher_background.xml @@ -1,4 +1,4 @@ - #FF2B0F + #FF472F \ No newline at end of file diff --git a/core/designsystem/src/main/java/co/kr/tnt/designsystem/component/card/Card.kt b/core/designsystem/src/main/java/co/kr/tnt/designsystem/component/card/Card.kt index 4fdac04c..12b87f37 100644 --- a/core/designsystem/src/main/java/co/kr/tnt/designsystem/component/card/Card.kt +++ b/core/designsystem/src/main/java/co/kr/tnt/designsystem/component/card/Card.kt @@ -283,6 +283,7 @@ fun TnTSessionRecordCard( } @Composable +@Suppress("UnusedParameter") fun TnTMemberProfileCard( name: String, profileImage: Painter, @@ -293,12 +294,12 @@ fun TnTMemberProfileCard( modifier: Modifier = Modifier, onClick: () -> Unit, ) { + // TODO: clickable 추가 Column( modifier = modifier .fillMaxWidth() .clip(RoundedCornerShape(12.dp)) .background(TnTTheme.colors.commonColors.Common0) - .clickable(onClick = onClick) .padding(12.dp), ) { Row( diff --git a/core/navigation/src/main/java/co/kr/tnt/navigation/RouteModel.kt b/core/navigation/src/main/java/co/kr/tnt/navigation/RouteModel.kt index 88453a1d..68ce7151 100644 --- a/core/navigation/src/main/java/co/kr/tnt/navigation/RouteModel.kt +++ b/core/navigation/src/main/java/co/kr/tnt/navigation/RouteModel.kt @@ -1,5 +1,6 @@ package co.kr.tnt.navigation +import co.kr.tnt.navigation.model.ScreenMode import kotlinx.serialization.Serializable // TODO Route 정리 @@ -35,7 +36,7 @@ sealed interface Route { ) : Route @Serializable - data class TrainerInvite(val isSkippable: Boolean) : Route + data class TrainerInvite(val screenMode: ScreenMode) : Route @Serializable data class TrainerConnect( @@ -44,7 +45,7 @@ sealed interface Route { ) : Route @Serializable - data class TraineeConnect(val isSkippable: Boolean) : Route + data class TraineeConnect(val screenMode: ScreenMode) : Route @Serializable data object TrainerMain : Route diff --git a/core/navigation/src/main/java/co/kr/tnt/navigation/model/ScreenMode.kt b/core/navigation/src/main/java/co/kr/tnt/navigation/model/ScreenMode.kt new file mode 100644 index 00000000..84319672 --- /dev/null +++ b/core/navigation/src/main/java/co/kr/tnt/navigation/model/ScreenMode.kt @@ -0,0 +1,7 @@ +package co.kr.tnt.navigation.model + +enum class ScreenMode { + BACK, + SKIP, + CLOSE, +} diff --git a/data/repository/src/main/java/co/kr/data/repository/TraineeRepositoryImpl.kt b/data/repository/src/main/java/co/kr/data/repository/TraineeRepositoryImpl.kt index 4b293194..87a39421 100644 --- a/data/repository/src/main/java/co/kr/data/repository/TraineeRepositoryImpl.kt +++ b/data/repository/src/main/java/co/kr/data/repository/TraineeRepositoryImpl.kt @@ -20,7 +20,6 @@ import okhttp3.RequestBody.Companion.asRequestBody import okhttp3.RequestBody.Companion.toRequestBody import java.io.File import java.time.LocalDate -import java.time.LocalDateTime import javax.inject.Inject import javax.inject.Singleton @@ -54,7 +53,7 @@ internal class TraineeRepositoryImpl @Inject constructor( override suspend fun postMealRecord( mealImage: File?, - date: LocalDateTime, + date: String, mealType: String, memo: String, ) { @@ -64,7 +63,7 @@ internal class TraineeRepositoryImpl @Inject constructor( } val mealRecordRequest = MealRecordRequest( - date = date.toString(), + date = date, dietType = mealType, memo = memo, ) diff --git a/domain/src/main/java/co/kr/tnt/domain/model/User.kt b/domain/src/main/java/co/kr/tnt/domain/model/User.kt index a2183f93..125fc00f 100644 --- a/domain/src/main/java/co/kr/tnt/domain/model/User.kt +++ b/domain/src/main/java/co/kr/tnt/domain/model/User.kt @@ -32,7 +32,7 @@ sealed class User { val birthday: LocalDate?, val weight: Double?, val height: Int?, - val ptPurpose: List, + val ptPurpose: List?, val caution: String?, val isConnected: Boolean, ) : User() { diff --git a/domain/src/main/java/co/kr/tnt/domain/repository/TraineeRepository.kt b/domain/src/main/java/co/kr/tnt/domain/repository/TraineeRepository.kt index a0413d23..44693fea 100644 --- a/domain/src/main/java/co/kr/tnt/domain/repository/TraineeRepository.kt +++ b/domain/src/main/java/co/kr/tnt/domain/repository/TraineeRepository.kt @@ -6,13 +6,12 @@ import co.kr.tnt.domain.model.trainee.TraineeDailyRecordStatus import co.kr.tnt.domain.model.trainee.TraineeMealRecordDetail import java.io.File import java.time.LocalDate -import java.time.LocalDateTime interface TraineeRepository { suspend fun getMyInfo(): User.Trainee suspend fun postMealRecord( mealImage: File?, - date: LocalDateTime, + date: String, mealType: String, memo: String, ) diff --git a/feature/main/src/main/java/co/kr/tnt/main/ui/TnTNavHost.kt b/feature/main/src/main/java/co/kr/tnt/main/ui/TnTNavHost.kt index 16548c8f..015f6e29 100644 --- a/feature/main/src/main/java/co/kr/tnt/main/ui/TnTNavHost.kt +++ b/feature/main/src/main/java/co/kr/tnt/main/ui/TnTNavHost.kt @@ -8,6 +8,7 @@ import androidx.navigation.compose.NavHost import co.kr.tnt.domain.model.UserType import co.kr.tnt.login.navigation.loginScreen import co.kr.tnt.login.navigation.navigateToLogin +import co.kr.tnt.navigation.model.ScreenMode import co.kr.tnt.roleselect.navigateToRoleSelection import co.kr.tnt.roleselect.roleSelectionScreen import co.kr.tnt.trainee.connect.navigation.navigateToTraineeConnect @@ -72,7 +73,7 @@ fun TnTNavHost( traineeSignUpScreen( navigateToPrevious = navController::safePopBackStack, navigateToConnect = { - navController.navigateToTraineeConnect(isSkippable = true) + navController.navigateToTraineeConnect(screenMode = ScreenMode.SKIP) }, ) trainerInviteScreen( diff --git a/feature/trainee/connect/src/main/java/co/kr/tnt/trainee/connect/CodeEntryPage.kt b/feature/trainee/connect/src/main/java/co/kr/tnt/trainee/connect/CodeEntryPage.kt index 55d6507f..554ae053 100644 --- a/feature/trainee/connect/src/main/java/co/kr/tnt/trainee/connect/CodeEntryPage.kt +++ b/feature/trainee/connect/src/main/java/co/kr/tnt/trainee/connect/CodeEntryPage.kt @@ -7,11 +7,14 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.Scaffold 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.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -23,16 +26,18 @@ import co.kr.tnt.designsystem.component.button.TnTTextButton import co.kr.tnt.designsystem.component.button.model.ButtonSize import co.kr.tnt.designsystem.theme.TnTTheme import co.kr.tnt.feature.trainee.connect.R +import co.kr.tnt.navigation.model.ScreenMode import co.kr.tnt.trainee.connect.component.CodeTextField import co.kr.tnt.trainee.connect.model.InputState -import co.kr.tnt.core.ui.R as uiResource +import co.kr.tnt.core.designsystem.R as uiResource +import co.kr.tnt.core.ui.R as coreR @Composable internal fun CodeEntryPage( showDialog: Boolean, inviteCode: String, inputState: InputState, - isSkippable: Boolean, + screenMode: ScreenMode, onSkipClick: () -> Unit, onBackClick: () -> Unit, onNextClick: () -> Unit, @@ -42,34 +47,52 @@ internal fun CodeEntryPage( onDismissPopup: () -> Unit, ) { BackHandler { - if (isSkippable) { - onSkipClick() - } else { - onBackClick() + when (screenMode) { + ScreenMode.BACK -> onBackClick() + ScreenMode.SKIP -> onSkipClick() + ScreenMode.CLOSE -> onBackClick() } } Scaffold( topBar = { - if (isSkippable) { - TnTTopBar( - title = stringResource(uiResource.string.connect), - trailingComponent = { - Text( - text = stringResource(uiResource.string.skip), - color = TnTTheme.colors.neutralColors.Neutral400, - style = TnTTheme.typography.body2Medium, - modifier = Modifier.clickable { - onSkipClick() - }, - ) - }, - ) - } else { - TnTTopBarWithBackButton( - title = stringResource(uiResource.string.connect), - onBackClick = onBackClick, - ) + when (screenMode) { + ScreenMode.BACK -> { + TnTTopBarWithBackButton( + title = stringResource(coreR.string.connect), + onBackClick = onBackClick, + ) + } + ScreenMode.SKIP -> { + TnTTopBar( + title = stringResource(coreR.string.connect), + trailingComponent = { + Text( + text = stringResource(coreR.string.skip), + color = TnTTheme.colors.neutralColors.Neutral400, + style = TnTTheme.typography.body2Medium, + modifier = Modifier.clickable { + onSkipClick() + }, + ) + }, + ) + } + ScreenMode.CLOSE -> { + TnTTopBar( + title = stringResource(coreR.string.connect), + trailingComponent = { + IconButton( + onClick = onBackClick, + ) { + Icon( + painter = painterResource(uiResource.drawable.ic_delete), + contentDescription = null, + ) + } + }, + ) + } } }, containerColor = TnTTheme.colors.commonColors.Common0, @@ -100,7 +123,7 @@ internal fun CodeEntryPage( ) } TnTBottomButton( - text = stringResource(uiResource.string.next), + text = stringResource(coreR.string.next), enabled = inputState.isValid, onClick = onNextClick, modifier = Modifier.align(Alignment.BottomCenter), @@ -126,10 +149,10 @@ internal fun CodeEntryPage( private fun CodeEntryPagePreview() { TnTTheme { CodeEntryPage( - showDialog = true, + showDialog = false, inputState = InputState.FOCUS, inviteCode = "23A4SDA31", - isSkippable = false, + screenMode = ScreenMode.CLOSE, onSkipClick = {}, onNextClick = {}, onValidateClick = {}, diff --git a/feature/trainee/connect/src/main/java/co/kr/tnt/trainee/connect/TraineeConnectScreen.kt b/feature/trainee/connect/src/main/java/co/kr/tnt/trainee/connect/TraineeConnectScreen.kt index 7a384b25..020f0bcb 100644 --- a/feature/trainee/connect/src/main/java/co/kr/tnt/trainee/connect/TraineeConnectScreen.kt +++ b/feature/trainee/connect/src/main/java/co/kr/tnt/trainee/connect/TraineeConnectScreen.kt @@ -6,6 +6,7 @@ import androidx.compose.runtime.getValue import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import co.kr.tnt.designsystem.snackbar.LocalSnackbar +import co.kr.tnt.navigation.model.ScreenMode import co.kr.tnt.trainee.connect.TraineeConnectContract.TraineeConnectPage import co.kr.tnt.trainee.connect.TraineeConnectContract.TraineeConnectSideEffect import co.kr.tnt.trainee.connect.TraineeConnectContract.TraineeConnectUiEvent @@ -14,7 +15,7 @@ import java.time.LocalDate @Composable internal fun TraineeConnectRoute( - isSkippable: Boolean, + screenMode: ScreenMode, navigateToPrevious: () -> Unit, navigateToHome: (Boolean) -> Unit, viewModel: TraineeConnectViewModel = hiltViewModel(), @@ -24,7 +25,7 @@ internal fun TraineeConnectRoute( TraineeConnectScreen( state = state, - isSkippable = isSkippable, + screenMode = screenMode, onBackClick = { viewModel.setEvent(TraineeConnectUiEvent.OnChangeDialogState) }, onNextClick = { viewModel.setEvent(TraineeConnectUiEvent.OnNextClick) }, onSkipClick = { viewModel.setEvent(TraineeConnectUiEvent.OnSkipClick) }, @@ -61,7 +62,7 @@ internal fun TraineeConnectRoute( @Composable private fun TraineeConnectScreen( state: TraineeConnectUiState, - isSkippable: Boolean, + screenMode: ScreenMode, onChangeInviteCode: (code: String) -> Unit, onCodeValidationClick: (code: String) -> Unit, onCancelConnectClick: () -> Unit, @@ -78,7 +79,7 @@ private fun TraineeConnectScreen( showDialog = state.showDialog, inputState = state.inviteCodeInputState, inviteCode = state.inviteCode, - isSkippable = isSkippable, + screenMode = screenMode, onNextClick = onNextClick, onBackClick = onBackClick, onSkipClick = onSkipClick, diff --git a/feature/trainee/connect/src/main/java/co/kr/tnt/trainee/connect/navigation/TraineeConnectNavigation.kt b/feature/trainee/connect/src/main/java/co/kr/tnt/trainee/connect/navigation/TraineeConnectNavigation.kt index 7f6d007b..be47b143 100644 --- a/feature/trainee/connect/src/main/java/co/kr/tnt/trainee/connect/navigation/TraineeConnectNavigation.kt +++ b/feature/trainee/connect/src/main/java/co/kr/tnt/trainee/connect/navigation/TraineeConnectNavigation.kt @@ -6,13 +6,14 @@ import androidx.navigation.NavOptionsBuilder import androidx.navigation.compose.composable import androidx.navigation.toRoute import co.kr.tnt.navigation.Route +import co.kr.tnt.navigation.model.ScreenMode import co.kr.tnt.trainee.connect.TraineeConnectRoute fun NavController.navigateToTraineeConnect( - isSkippable: Boolean, + screenMode: ScreenMode, navOptions: NavOptionsBuilder.() -> Unit = {}, ) = navigate( - route = Route.TraineeConnect(isSkippable), + route = Route.TraineeConnect(screenMode), builder = navOptions, ) @@ -23,7 +24,7 @@ fun NavGraphBuilder.traineeConnectScreen( composable { backstackEntry -> backstackEntry.toRoute().apply { TraineeConnectRoute( - isSkippable = isSkippable, + screenMode = screenMode, navigateToPrevious = navigateToPrevious, navigateToHome = { navigateToHome(false) }, ) diff --git a/feature/trainee/home/src/main/java/co/kr/tnt/trainee/home/TraineeHomeScreen.kt b/feature/trainee/home/src/main/java/co/kr/tnt/trainee/home/TraineeHomeScreen.kt index fe82c3ba..73d38481 100644 --- a/feature/trainee/home/src/main/java/co/kr/tnt/trainee/home/TraineeHomeScreen.kt +++ b/feature/trainee/home/src/main/java/co/kr/tnt/trainee/home/TraineeHomeScreen.kt @@ -121,7 +121,6 @@ internal fun TraineeHomeRoute( }, content = { RecordBottomSheetContent( - onClickExercise = { viewModel.setEvent(TraineeHomeUiEvent.OnClickExerciseRecord) }, onClickDiet = { viewModel.setEvent(TraineeHomeUiEvent.OnClickMealRecord) }, ) }, @@ -331,6 +330,7 @@ private fun Calendar( } @Composable +@Suppress("UnusedParameter") private fun DailyPtSession( session: TraineePtSession, context: Context, @@ -357,7 +357,7 @@ private fun DailyPtSession( profileImage = session.trainerImage?.let { painter }, showSessionRecordCreation = false, showSessionRecordDetails = session.hasRecord, - onClick = { onClickPtSessionCard(session.ptSessionId) }, + onClick = { }, modifier = Modifier.padding( start = 20.dp, end = 20.dp, @@ -442,7 +442,6 @@ private fun EmptyDailyRecords() { @Composable private fun RecordBottomSheetContent( - onClickExercise: () -> Unit, onClickDiet: () -> Unit, ) { Column( @@ -454,12 +453,6 @@ private fun RecordBottomSheetContent( style = TnTTheme.typography.h3, modifier = Modifier.padding(vertical = 20.dp), ) - RecordItem( - icon = "\uD83C\uDFCB\uD83C\uDFFB\u200D♀\uFE0F", - text = "개인 운동", - modifier = Modifier.clickable(onClick = onClickExercise), - ) - Spacer(Modifier.height(12.dp)) RecordItem( icon = "\uD83E\uDD57", text = "식단", @@ -550,7 +543,6 @@ private fun TraineeHomeScreenPreview() { private fun RecordBottomSheetContentPreview() { TnTTheme { RecordBottomSheetContent( - onClickExercise = { }, onClickDiet = { }, ) } diff --git a/feature/trainee/main/src/main/java/co/kr/tnt/trainee/main/TraineeMainScreen.kt b/feature/trainee/main/src/main/java/co/kr/tnt/trainee/main/TraineeMainScreen.kt index 80982e6f..ab656feb 100644 --- a/feature/trainee/main/src/main/java/co/kr/tnt/trainee/main/TraineeMainScreen.kt +++ b/feature/trainee/main/src/main/java/co/kr/tnt/trainee/main/TraineeMainScreen.kt @@ -3,13 +3,13 @@ package co.kr.tnt.trainee.main import android.annotation.SuppressLint import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.navigationBarsPadding -import androidx.compose.foundation.layout.padding import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.navigation.compose.NavHost import co.kr.tnt.designsystem.component.bottombar.TnTBottomBar import co.kr.tnt.designsystem.theme.TnTTheme +import co.kr.tnt.navigation.model.ScreenMode import co.kr.tnt.trainee.home.navigation.traineeHomeNavGraph import co.kr.tnt.trainee.mypage.navigation.traineeMyPageNavGraph import co.kr.tnt.trainee.notification.navigation.navigateToTraineeNotification @@ -18,7 +18,7 @@ import co.kr.tnt.ui.extensions.safePopBackStack @Composable internal fun TraineeMainRoute( - navigateToConnect: (Boolean) -> Unit, + navigateToConnect: (ScreenMode) -> Unit, navigateToLogin: () -> Unit, navigateToWebView: (url: String) -> Unit, navigateToMealRecord: () -> Unit, @@ -42,7 +42,7 @@ internal fun TraineeMainRoute( @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") private fun TraineeMainScreen( state: TraineeMainState, - navigateToConnect: (Boolean) -> Unit, + navigateToConnect: (ScreenMode) -> Unit, navigateToLogin: () -> Unit, navigateToWebView: (url: String) -> Unit, navigateToMealRecord: () -> Unit, @@ -72,7 +72,7 @@ private fun TraineeMainScreen( navigateToNotification = navController::navigateToTraineeNotification, navigateToMealRecord = navigateToMealRecord, navigateToMealDetail = navigateToMealDetail, - navigateToConnect = { navigateToConnect(false) }, + navigateToConnect = { navigateToConnect(ScreenMode.CLOSE) }, ) { traineeNotification( navigateToPrevious = navController::safePopBackStack, diff --git a/feature/trainee/main/src/main/java/co/kr/tnt/trainee/main/navigation/TraineeMainNavigation.kt b/feature/trainee/main/src/main/java/co/kr/tnt/trainee/main/navigation/TraineeMainNavigation.kt index 6afa763c..1f2d4ec6 100644 --- a/feature/trainee/main/src/main/java/co/kr/tnt/trainee/main/navigation/TraineeMainNavigation.kt +++ b/feature/trainee/main/src/main/java/co/kr/tnt/trainee/main/navigation/TraineeMainNavigation.kt @@ -6,6 +6,7 @@ import androidx.navigation.NavOptionsBuilder import androidx.navigation.compose.composable import androidx.navigation.navOptions import co.kr.tnt.navigation.Route +import co.kr.tnt.navigation.model.ScreenMode import co.kr.tnt.trainee.main.TraineeMainRoute fun NavController.navigateToTraineeMain( @@ -22,7 +23,7 @@ fun NavController.navigateToTraineeMain( ) fun NavGraphBuilder.traineeMainScreen( - navigateToConnect: (Boolean) -> Unit, + navigateToConnect: (ScreenMode) -> Unit, navigateToLogin: () -> Unit, navigateToWebView: (url: String) -> Unit, navigateToMealRecord: () -> Unit, diff --git a/feature/trainee/mealrecord/src/main/java/co/kr/tnt/trainee/mealrecord/detail/TraineeMealRecordDetailScreen.kt b/feature/trainee/mealrecord/src/main/java/co/kr/tnt/trainee/mealrecord/detail/TraineeMealRecordDetailScreen.kt index e68a0cae..c1ac6b69 100644 --- a/feature/trainee/mealrecord/src/main/java/co/kr/tnt/trainee/mealrecord/detail/TraineeMealRecordDetailScreen.kt +++ b/feature/trainee/mealrecord/src/main/java/co/kr/tnt/trainee/mealrecord/detail/TraineeMealRecordDetailScreen.kt @@ -3,7 +3,6 @@ package co.kr.tnt.trainee.mealrecord.detail import android.content.Context import androidx.compose.foundation.Image import androidx.compose.foundation.background -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -83,6 +82,7 @@ internal fun TraineeMealRecordDetailRoute( } @Composable +@Suppress("UnusedParameter") private fun TraineeMealRecordDetailScreen( state: TraineeMealRecordDetailUiState, context: Context, @@ -109,7 +109,6 @@ private fun TraineeMealRecordDetailScreen( Icon( painter = painterResource(R.drawable.ic_more), contentDescription = null, - modifier = Modifier.clickable(onClick = onClickMore), ) }, ) diff --git a/feature/trainee/mealrecord/src/main/java/co/kr/tnt/trainee/mealrecord/record/TraineeMealRecordContract.kt b/feature/trainee/mealrecord/src/main/java/co/kr/tnt/trainee/mealrecord/record/TraineeMealRecordContract.kt index 1a696cf4..c77cafe2 100644 --- a/feature/trainee/mealrecord/src/main/java/co/kr/tnt/trainee/mealrecord/record/TraineeMealRecordContract.kt +++ b/feature/trainee/mealrecord/src/main/java/co/kr/tnt/trainee/mealrecord/record/TraineeMealRecordContract.kt @@ -29,13 +29,12 @@ internal class TraineeMealRecordContract { EXIT, } - val mealDateTime: LocalDateTime? - get() = time?.let { LocalDateTime.of(date, it) } - fun validateMealRecord(): TraineeMealRecordUiState { + val now = LocalDateTime.now() + val selectedDateTime = time?.let { LocalDateTime.of(date, it) } + return copy( - isMealRecordValid = date <= LocalDate.now() && - time != null && + isMealRecordValid = selectedDateTime != null && selectedDateTime <= now && mealType.isNotBlank() && memo.isNotBlank(), ) diff --git a/feature/trainee/mealrecord/src/main/java/co/kr/tnt/trainee/mealrecord/record/TraineeMealRecordViewModel.kt b/feature/trainee/mealrecord/src/main/java/co/kr/tnt/trainee/mealrecord/record/TraineeMealRecordViewModel.kt index 7434d235..9581e126 100644 --- a/feature/trainee/mealrecord/src/main/java/co/kr/tnt/trainee/mealrecord/record/TraineeMealRecordViewModel.kt +++ b/feature/trainee/mealrecord/src/main/java/co/kr/tnt/trainee/mealrecord/record/TraineeMealRecordViewModel.kt @@ -4,6 +4,7 @@ import android.content.Context import androidx.core.net.toUri import androidx.lifecycle.viewModelScope import co.kr.tnt.domain.repository.TraineeRepository +import co.kr.tnt.domain.utils.DateFormatter import co.kr.tnt.trainee.mealrecord.record.TraineeMealRecordContract.TraineeMealRecordSideEffect import co.kr.tnt.trainee.mealrecord.record.TraineeMealRecordContract.TraineeMealRecordUiEvent import co.kr.tnt.trainee.mealrecord.record.TraineeMealRecordContract.TraineeMealRecordUiState @@ -21,6 +22,7 @@ import javax.inject.Inject @HiltViewModel internal class TraineeMealRecordViewModel @Inject constructor( private val traineeRepository: TraineeRepository, + private val dateFormatter: DateFormatter, ) : BaseViewModel( TraineeMealRecordUiState(), @@ -94,6 +96,11 @@ internal class TraineeMealRecordViewModel @Inject constructor( private fun postMealRecord(context: Context) { updateState { copy(isLoading = true) } + val mealDateTime = dateFormatter.format( + LocalDateTime.of(currentState.date, currentState.time), + "yyyy-MM-dd'T'HH:mm:ss", + ) + viewModelScope.launch { val state = currentState val imageFile: File? = state.image?.toFile(context)?.let { file -> @@ -106,7 +113,7 @@ internal class TraineeMealRecordViewModel @Inject constructor( runCatching { traineeRepository.postMealRecord( mealImage = imageFile, - date = state.mealDateTime ?: LocalDateTime.now(), + date = mealDateTime, mealType = state.mealType, memo = state.memo, ) diff --git a/feature/trainee/mypage/src/main/java/co/kr/tnt/trainee/mypage/TraineeMyPageScreen.kt b/feature/trainee/mypage/src/main/java/co/kr/tnt/trainee/mypage/TraineeMyPageScreen.kt index 610c58c6..8948c4ff 100644 --- a/feature/trainee/mypage/src/main/java/co/kr/tnt/trainee/mypage/TraineeMyPageScreen.kt +++ b/feature/trainee/mypage/src/main/java/co/kr/tnt/trainee/mypage/TraineeMyPageScreen.kt @@ -35,6 +35,7 @@ import co.kr.tnt.designsystem.snackbar.LocalSnackbar import co.kr.tnt.designsystem.theme.TnTTheme import co.kr.tnt.domain.model.User import co.kr.tnt.feature.trainee.mypage.R +import co.kr.tnt.navigation.model.ScreenMode import co.kr.tnt.trainee.mypage.TraineeMyPageContract.TraineeMyPageEffect import co.kr.tnt.trainee.mypage.TraineeMyPageContract.TraineeMyPageUiEvent import co.kr.tnt.trainee.mypage.TraineeMyPageContract.TraineeMyPageUiState @@ -58,7 +59,7 @@ import co.kr.tnt.core.ui.R as coreR @Composable internal fun TraineeMyPageRoute( padding: PaddingValues, - navigateToConnect: (Boolean) -> Unit, + navigateToConnect: (ScreenMode) -> Unit, navigateToLogin: () -> Unit, navigateToWebView: (url: String) -> Unit, viewModel: TraineeMyPageViewModel = hiltViewModel(), @@ -98,7 +99,7 @@ internal fun TraineeMyPageRoute( LaunchedEffect(viewModel.effect) { viewModel.effect.collect { effect -> when (effect) { - TraineeMyPageEffect.NavigateToConnect -> navigateToConnect(false) + TraineeMyPageEffect.NavigateToConnect -> navigateToConnect(ScreenMode.BACK) TraineeMyPageEffect.NavigateToLogin -> navigateToLogin() is TraineeMyPageEffect.ShowToast -> snackbar.show(effect.message) is TraineeMyPageEffect.NavigateToWebView -> navigateToWebView(effect.url) diff --git a/feature/trainee/mypage/src/main/java/co/kr/tnt/trainee/mypage/navigation/TraineeMyPageNavigation.kt b/feature/trainee/mypage/src/main/java/co/kr/tnt/trainee/mypage/navigation/TraineeMyPageNavigation.kt index 4f277450..db318bab 100644 --- a/feature/trainee/mypage/src/main/java/co/kr/tnt/trainee/mypage/navigation/TraineeMyPageNavigation.kt +++ b/feature/trainee/mypage/src/main/java/co/kr/tnt/trainee/mypage/navigation/TraineeMyPageNavigation.kt @@ -7,6 +7,7 @@ import androidx.navigation.NavOptionsBuilder import androidx.navigation.compose.composable import androidx.navigation.navigation import co.kr.tnt.navigation.Route +import co.kr.tnt.navigation.model.ScreenMode import co.kr.tnt.trainee.mypage.TraineeMyPageRoute fun NavController.navigateToTraineeMyPage( @@ -18,7 +19,7 @@ fun NavController.navigateToTraineeMyPage( fun NavGraphBuilder.traineeMyPageNavGraph( padding: PaddingValues, - navigateToTraineeConnect: (Boolean) -> Unit, + navigateToTraineeConnect: (ScreenMode) -> Unit, navigateToLogin: () -> Unit, navigateToWebView: (url: String) -> Unit, ) { diff --git a/feature/trainee/notification/src/main/java/co/kr/tnt/trainee/notification/TraineeNotificationViewModel.kt b/feature/trainee/notification/src/main/java/co/kr/tnt/trainee/notification/TraineeNotificationViewModel.kt index b83cea53..ba0d5de3 100644 --- a/feature/trainee/notification/src/main/java/co/kr/tnt/trainee/notification/TraineeNotificationViewModel.kt +++ b/feature/trainee/notification/src/main/java/co/kr/tnt/trainee/notification/TraineeNotificationViewModel.kt @@ -1,15 +1,12 @@ package co.kr.tnt.trainee.notification import co.kr.tnt.domain.model.NotificationInfo -import co.kr.tnt.domain.model.NotificationType import co.kr.tnt.trainee.notification.TraineeNotificationContract.TraineeNotificationEffect import co.kr.tnt.trainee.notification.TraineeNotificationContract.TraineeNotificationUiEvent import co.kr.tnt.trainee.notification.TraineeNotificationContract.TraineeNotificationUiState import co.kr.tnt.ui.base.BaseViewModel import co.kr.tnt.ui.model.NotificationState import dagger.hilt.android.lifecycle.HiltViewModel -import java.time.LocalDateTime -import java.time.format.DateTimeFormatter import javax.inject.Inject @HiltViewModel @@ -29,31 +26,7 @@ internal class TraineeNotificationViewModel @Inject constructor() : private fun getNotification() { // TODO 알림 불러오기 - val formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME - - val sampleNotifications = listOf( - NotificationInfo( - type = NotificationType.DISCONNECT, - title = "트레이너 연결 해제", - contents = "박헬린 트레이너가 연결을 끊었어요", - time = LocalDateTime.parse("2025-02-03T23:12:00", formatter), - isChecked = false, - ), - NotificationInfo( - type = NotificationType.DISCONNECT, - title = "트레이너 연결 해제", - contents = "김헬스 트레이너가 연결을 끊었어요", - time = LocalDateTime.parse("2025-02-03T23:03:00", formatter), - isChecked = true, - ), - NotificationInfo( - type = NotificationType.DISCONNECT, - title = "트레이너 연결 해제", - contents = "김피티 트레이너가 연결을 끊었어요", - time = LocalDateTime.parse("2025-02-02T22:29:00", formatter), - isChecked = true, - ), - ) + val sampleNotifications = emptyList() updateState { copy(notifications = sampleNotifications.map(NotificationState::fromDomain)) } } diff --git a/feature/trainee/signup/src/main/java/co/kr/tnt/trainee/signup/TraineePTPurposePage.kt b/feature/trainee/signup/src/main/java/co/kr/tnt/trainee/signup/TraineePTPurposePage.kt index 7b4fe4cf..f4f1a889 100644 --- a/feature/trainee/signup/src/main/java/co/kr/tnt/trainee/signup/TraineePTPurposePage.kt +++ b/feature/trainee/signup/src/main/java/co/kr/tnt/trainee/signup/TraineePTPurposePage.kt @@ -67,7 +67,7 @@ internal fun TraineePTPurposePage( val purposeText = stringResource(purpose.textResId) PurposeButton( text = purposeText, - isSelected = purposeText in state.ptPurpose, + isSelected = state.ptPurpose?.contains(purposeText) == true, onClick = { onPurposeSelected(purposeText) }, modifier = Modifier.weight(1f), ) @@ -78,7 +78,6 @@ internal fun TraineePTPurposePage( TnTBottomButton( text = stringResource(uiResource.string.next), onClick = onNextClick, - enabled = state.ptPurpose.isNotEmpty(), modifier = Modifier.align(Alignment.BottomCenter), ) } diff --git a/feature/trainee/signup/src/main/java/co/kr/tnt/trainee/signup/TraineeProfileSetupPage.kt b/feature/trainee/signup/src/main/java/co/kr/tnt/trainee/signup/TraineeProfileSetupPage.kt index 98f89573..fa907d9b 100644 --- a/feature/trainee/signup/src/main/java/co/kr/tnt/trainee/signup/TraineeProfileSetupPage.kt +++ b/feature/trainee/signup/src/main/java/co/kr/tnt/trainee/signup/TraineeProfileSetupPage.kt @@ -99,8 +99,7 @@ internal fun TraineeProfileSetupPage( title = stringResource(coreR.string.name), value = state.name, onValueChange = { newValue -> - val filteredText = validateInput(newValue) - onNameChange(filteredText) + onNameChange(newValue) }, modifier = Modifier.padding(horizontal = 20.dp), placeholder = stringResource(R.string.enter_your_name), @@ -121,13 +120,6 @@ internal fun TraineeProfileSetupPage( } } -/** - * 입력 값을 검사해 한글/영어/공백만 허용하고 특수문자는 제거 - */ -private fun validateInput(input: String): String { - return input.filter { it.isLetter() || it.isWhitespace() } -} - @Preview(showBackground = true) @Composable private fun TraineeProfileSetupPagePreview() { diff --git a/feature/trainee/signup/src/main/java/co/kr/tnt/trainee/signup/TraineeSignUpContract.kt b/feature/trainee/signup/src/main/java/co/kr/tnt/trainee/signup/TraineeSignUpContract.kt index da2c66f6..0a978836 100644 --- a/feature/trainee/signup/src/main/java/co/kr/tnt/trainee/signup/TraineeSignUpContract.kt +++ b/feature/trainee/signup/src/main/java/co/kr/tnt/trainee/signup/TraineeSignUpContract.kt @@ -19,11 +19,15 @@ internal class TraineeSignUpContract { val birthday: LocalDate? = null, val height: String? = null, val weight: String? = null, - val ptPurpose: List = emptyList(), + val ptPurpose: List? = emptyList(), val caution: String? = "", val isLoading: Boolean = false, ) : UiState { - val isNameValid get() = name.length <= MAX_NAME_LENGTH + /** + * 입력 값을 검사해 한글/영어/공백만 허용하고 특수문자는 제거 + */ + private val nameRegex = Regex("^[a-zA-Zㄱ-ㅎㅏ-ㅣ가-힣 ]+\$") + val isNameValid get() = name.isBlank() || name.matches(nameRegex) && name.length <= MAX_NAME_LENGTH /** * 키가 유효한 입력값인지 검사 diff --git a/feature/trainee/signup/src/main/java/co/kr/tnt/trainee/signup/TraineeSignUpViewModel.kt b/feature/trainee/signup/src/main/java/co/kr/tnt/trainee/signup/TraineeSignUpViewModel.kt index 0ef6aa85..2e42986e 100644 --- a/feature/trainee/signup/src/main/java/co/kr/tnt/trainee/signup/TraineeSignUpViewModel.kt +++ b/feature/trainee/signup/src/main/java/co/kr/tnt/trainee/signup/TraineeSignUpViewModel.kt @@ -115,7 +115,7 @@ internal class TraineeSignUpViewModel @Inject constructor( } private fun updateSelectedPurposes(purpose: String) { - val updatedPurposes = currentState.ptPurpose.toMutableList().apply { + val updatedPurposes = currentState.ptPurpose.orEmpty().toMutableList().apply { if (contains(purpose)) { remove(purpose) } else { diff --git a/feature/trainer/connect/src/main/java/co/kr/tnt/trainer/connect/TraineeProfilePage.kt b/feature/trainer/connect/src/main/java/co/kr/tnt/trainer/connect/TraineeProfilePage.kt index 7785722d..ed2154c2 100644 --- a/feature/trainer/connect/src/main/java/co/kr/tnt/trainer/connect/TraineeProfilePage.kt +++ b/feature/trainer/connect/src/main/java/co/kr/tnt/trainer/connect/TraineeProfilePage.kt @@ -145,7 +145,7 @@ internal fun TraineeProfilePage( } TextWithBackground( label = stringResource(R.string.purpose_of_pt), - text = trainee.ptPurpose.joinToString(", "), + text = trainee.ptPurpose?.joinToString(", ") ?: "", ) Spacer(Modifier.height(32.dp)) if (!trainee.caution.isNullOrEmpty()) { diff --git a/feature/trainer/home/src/main/java/co/kr/tnt/trainer/home/TrainerHomeScreen.kt b/feature/trainer/home/src/main/java/co/kr/tnt/trainer/home/TrainerHomeScreen.kt index 2bb50cda..983a68b5 100644 --- a/feature/trainer/home/src/main/java/co/kr/tnt/trainer/home/TrainerHomeScreen.kt +++ b/feature/trainer/home/src/main/java/co/kr/tnt/trainer/home/TrainerHomeScreen.kt @@ -50,6 +50,7 @@ import co.kr.tnt.designsystem.snackbar.LocalSnackbar import co.kr.tnt.designsystem.theme.TnTTheme import co.kr.tnt.domain.model.PtSession import co.kr.tnt.domain.utils.DateFormatter +import co.kr.tnt.navigation.model.ScreenMode import co.kr.tnt.trainer.home.TrainerHomeContract.TrainerHomeSideEffect import co.kr.tnt.trainer.home.TrainerHomeContract.TrainerHomeUiEvent import co.kr.tnt.trainer.home.TrainerHomeContract.TrainerHomeUiState @@ -71,7 +72,7 @@ internal fun TrainerHomeRoute( padding: PaddingValues, navigateToNotification: () -> Unit, navigateToAddPtSession: () -> Unit, - navigateToInvite: (Boolean) -> Unit, + navigateToInvite: (ScreenMode) -> Unit, ) { val toast = LocalSnackbar.current val state by viewModel.uiState.collectAsStateWithLifecycle() @@ -91,7 +92,7 @@ internal fun TrainerHomeRoute( when (effect) { TrainerHomeSideEffect.NavigateToNotification -> navigateToNotification() TrainerHomeSideEffect.NavigateToAddPtSession -> navigateToAddPtSession() - TrainerHomeSideEffect.NavigateToInvite -> navigateToInvite(false) + TrainerHomeSideEffect.NavigateToInvite -> navigateToInvite(ScreenMode.CLOSE) is TrainerHomeSideEffect.ShowToast -> toast.show(effect.message) } } diff --git a/feature/trainer/home/src/main/java/co/kr/tnt/trainer/home/navigation/TrainerHomeNavigation.kt b/feature/trainer/home/src/main/java/co/kr/tnt/trainer/home/navigation/TrainerHomeNavigation.kt index 0dc71dd2..9c693fc1 100644 --- a/feature/trainer/home/src/main/java/co/kr/tnt/trainer/home/navigation/TrainerHomeNavigation.kt +++ b/feature/trainer/home/src/main/java/co/kr/tnt/trainer/home/navigation/TrainerHomeNavigation.kt @@ -8,6 +8,7 @@ import androidx.navigation.compose.composable import androidx.navigation.compose.navigation import androidx.navigation.navOptions import co.kr.tnt.navigation.Route +import co.kr.tnt.navigation.model.ScreenMode import co.kr.tnt.trainer.home.TrainerHomeRoute fun NavController.navigateToTrainerHome( @@ -27,7 +28,7 @@ fun NavGraphBuilder.trainerHomeNavGraph( padding: PaddingValues, navigateToNotification: () -> Unit, navigateToAddPtSession: () -> Unit, - navigateToInvite: (Boolean) -> Unit, + navigateToInvite: (ScreenMode) -> Unit, homeDestination: NavGraphBuilder.() -> Unit = { }, ) { navigation(startDestination = Route.TrainerHome) { diff --git a/feature/trainer/invite/src/main/java/co/kr/tnt/trainer/invite/TrainerInviteScreen.kt b/feature/trainer/invite/src/main/java/co/kr/tnt/trainer/invite/TrainerInviteScreen.kt index 9ca9583f..eb1963f2 100644 --- a/feature/trainer/invite/src/main/java/co/kr/tnt/trainer/invite/TrainerInviteScreen.kt +++ b/feature/trainer/invite/src/main/java/co/kr/tnt/trainer/invite/TrainerInviteScreen.kt @@ -16,6 +16,8 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -24,6 +26,7 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -37,14 +40,15 @@ import co.kr.tnt.designsystem.component.button.model.ButtonType import co.kr.tnt.designsystem.snackbar.LocalSnackbar import co.kr.tnt.designsystem.theme.TnTTheme import co.kr.tnt.feature.trainer.invite.R +import co.kr.tnt.navigation.model.ScreenMode import co.kr.tnt.trainer.invite.TrainerInviteContract.TrainerInviteSideEffect import co.kr.tnt.trainer.invite.TrainerInviteContract.TrainerInviteUiEvent import co.kr.tnt.trainer.invite.TrainerInviteContract.TrainerInviteUiState -import co.kr.tnt.core.ui.R as uiResource +import co.kr.tnt.core.ui.R as coreR @Composable internal fun TrainerInviteRoute( - isSkippable: Boolean, + screenMode: ScreenMode, navigateToPrevious: () -> Unit, navigateToHome: (Boolean) -> Unit, viewModel: TrainerInviteViewModel = hiltViewModel(), @@ -56,7 +60,7 @@ internal fun TrainerInviteRoute( TrainerInviteScreen( state = state, - isSkippable = isSkippable, + screenMode = screenMode, onRegenerateClick = { viewModel.setEvent(TrainerInviteUiEvent.OnRegenerateClick) }, onCodeClick = { code -> viewModel.setEvent(TrainerInviteUiEvent.OnCodeClick(code)) }, onBackClick = { viewModel.setEvent(TrainerInviteUiEvent.OnBackClick) }, @@ -78,41 +82,61 @@ internal fun TrainerInviteRoute( @Composable internal fun TrainerInviteScreen( state: TrainerInviteUiState, - isSkippable: Boolean, + screenMode: ScreenMode, onCodeClick: (code: String) -> Unit, onRegenerateClick: () -> Unit, onBackClick: () -> Unit, onSkipClick: () -> Unit, ) { BackHandler { - if (isSkippable) { - onSkipClick() - } else { - onBackClick() + when (screenMode) { + ScreenMode.BACK -> onBackClick() + ScreenMode.SKIP -> onSkipClick() + ScreenMode.CLOSE -> onBackClick() } } Scaffold( topBar = { - if (isSkippable) { - TnTTopBar( - title = stringResource(uiResource.string.connect), - trailingComponent = { - Text( - text = stringResource(uiResource.string.skip), - color = TnTTheme.colors.neutralColors.Neutral400, - style = TnTTheme.typography.body2Medium, - modifier = Modifier.clickable { - onSkipClick() - }, - ) - }, - ) - } else { - TnTTopBarWithBackButton( - title = stringResource(R.string.add_member), - onBackClick = onBackClick, - ) + when (screenMode) { + ScreenMode.BACK -> { + TnTTopBarWithBackButton( + title = stringResource(R.string.add_member), + onBackClick = onBackClick, + ) + } + + ScreenMode.SKIP -> { + TnTTopBar( + title = stringResource(coreR.string.connect), + trailingComponent = { + Text( + text = stringResource(coreR.string.skip), + color = TnTTheme.colors.neutralColors.Neutral400, + style = TnTTheme.typography.body2Medium, + modifier = Modifier.clickable { + onSkipClick() + }, + ) + }, + ) + } + + ScreenMode.CLOSE -> { + TnTTopBar( + title = stringResource(R.string.add_member), + trailingComponent = { + IconButton( + onClick = onBackClick, + ) { + Icon( + painter = painterResource(co.kr.tnt.core.designsystem.R.drawable.ic_delete), + contentDescription = null, + ) + } + }, + ) + } } }, containerColor = TnTTheme.colors.commonColors.Common0, @@ -198,7 +222,7 @@ private fun CodeGenerationPagePreview() { onBackClick = {}, onSkipClick = {}, onRegenerateClick = {}, - isSkippable = false, + screenMode = ScreenMode.SKIP, ) } } diff --git a/feature/trainer/invite/src/main/java/co/kr/tnt/trainer/invite/navigation/TrainerInviteNavigation.kt b/feature/trainer/invite/src/main/java/co/kr/tnt/trainer/invite/navigation/TrainerInviteNavigation.kt index cebaf1f2..b10019c1 100644 --- a/feature/trainer/invite/src/main/java/co/kr/tnt/trainer/invite/navigation/TrainerInviteNavigation.kt +++ b/feature/trainer/invite/src/main/java/co/kr/tnt/trainer/invite/navigation/TrainerInviteNavigation.kt @@ -6,13 +6,14 @@ import androidx.navigation.NavOptionsBuilder import androidx.navigation.compose.composable import androidx.navigation.toRoute import co.kr.tnt.navigation.Route +import co.kr.tnt.navigation.model.ScreenMode import co.kr.tnt.trainer.invite.TrainerInviteRoute fun NavController.navigateToTrainerInvite( - isSkippable: Boolean, + screenMode: ScreenMode, navOptions: NavOptionsBuilder.() -> Unit = {}, ) = navigate( - route = Route.TrainerInvite(isSkippable), + route = Route.TrainerInvite(screenMode), builder = navOptions, ) @@ -23,7 +24,7 @@ fun NavGraphBuilder.trainerInviteScreen( composable { backstackEntry -> backstackEntry.toRoute().apply { TrainerInviteRoute( - isSkippable = isSkippable, + screenMode = screenMode, navigateToPrevious = navigateToPrevious, navigateToHome = { navigateToHome(true) }, ) diff --git a/feature/trainer/main/src/main/java/co/kr/tnt/trainer/main/TrainerMainScreen.kt b/feature/trainer/main/src/main/java/co/kr/tnt/trainer/main/TrainerMainScreen.kt index e9ce587f..259a26a8 100644 --- a/feature/trainer/main/src/main/java/co/kr/tnt/trainer/main/TrainerMainScreen.kt +++ b/feature/trainer/main/src/main/java/co/kr/tnt/trainer/main/TrainerMainScreen.kt @@ -2,13 +2,13 @@ package co.kr.tnt.trainer.main import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.navigationBarsPadding -import androidx.compose.foundation.layout.padding import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.navigation.compose.NavHost import co.kr.tnt.designsystem.component.bottombar.TnTBottomBar import co.kr.tnt.designsystem.theme.TnTTheme +import co.kr.tnt.navigation.model.ScreenMode import co.kr.tnt.trainer.addptsession.navigation.addPtSession import co.kr.tnt.trainer.addptsession.navigation.navigateToAddPtSession import co.kr.tnt.trainer.feedback.navigation.trainerFeedbackNavGraph @@ -22,7 +22,7 @@ import co.kr.tnt.ui.extensions.safePopBackStack @Composable internal fun TrainerMainRoute( navigateToConnect: (trainerId: String, traineeId: String) -> Unit, - navigateToInvite: (Boolean) -> Unit, + navigateToInvite: (ScreenMode) -> Unit, navigateToLogin: () -> Unit, navigateToWebView: (url: String) -> Unit, ) { @@ -43,7 +43,7 @@ internal fun TrainerMainRoute( private fun TrainerMainScreen( state: TrainerMainState, navigateToConnect: (trainerId: String, traineeId: String) -> Unit, - navigateToInvite: (Boolean) -> Unit, + navigateToInvite: (ScreenMode) -> Unit, navigateToLogin: () -> Unit, navigateToWebView: (url: String) -> Unit, ) { diff --git a/feature/trainer/main/src/main/java/co/kr/tnt/trainer/main/navigation/TrainerMainNavigation.kt b/feature/trainer/main/src/main/java/co/kr/tnt/trainer/main/navigation/TrainerMainNavigation.kt index 9717b57d..e3c598cc 100644 --- a/feature/trainer/main/src/main/java/co/kr/tnt/trainer/main/navigation/TrainerMainNavigation.kt +++ b/feature/trainer/main/src/main/java/co/kr/tnt/trainer/main/navigation/TrainerMainNavigation.kt @@ -6,6 +6,7 @@ import androidx.navigation.NavOptionsBuilder import androidx.navigation.compose.composable import androidx.navigation.navOptions import co.kr.tnt.navigation.Route +import co.kr.tnt.navigation.model.ScreenMode import co.kr.tnt.trainer.main.TrainerMainRoute fun NavController.navigateToTrainerMain( @@ -23,7 +24,7 @@ fun NavController.navigateToTrainerMain( fun NavGraphBuilder.trainerMainScreen( navigateToConnect: (trainerId: String, traineeId: String) -> Unit, - navigateToInvite: (Boolean) -> Unit, + navigateToInvite: (ScreenMode) -> Unit, navigateToLogin: () -> Unit, navigateToWebView: (url: String) -> Unit, ) { diff --git a/feature/trainer/members/src/main/java/co/kr/tnt/trainer/members/TrainerMembersScreen.kt b/feature/trainer/members/src/main/java/co/kr/tnt/trainer/members/TrainerMembersScreen.kt index ad5ef625..9298a7a3 100644 --- a/feature/trainer/members/src/main/java/co/kr/tnt/trainer/members/TrainerMembersScreen.kt +++ b/feature/trainer/members/src/main/java/co/kr/tnt/trainer/members/TrainerMembersScreen.kt @@ -31,6 +31,7 @@ import co.kr.tnt.core.designsystem.R import co.kr.tnt.designsystem.component.card.TnTMemberProfileCard import co.kr.tnt.designsystem.theme.TnTTheme import co.kr.tnt.domain.model.MemberInfo +import co.kr.tnt.navigation.model.ScreenMode import co.kr.tnt.trainer.members.TrainerMemberContract.TrainerMemberUiState import co.kr.tnt.ui.component.TnTCountTopBar import coil.compose.rememberAsyncImagePainter @@ -39,7 +40,7 @@ import coil.request.ImageRequest @Composable internal fun TrainerMembersRoute( padding: PaddingValues, - navigateToInvite: (Boolean) -> Unit, + navigateToInvite: (ScreenMode) -> Unit, viewModel: TrainerMembersViewModel = hiltViewModel(), ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() @@ -47,7 +48,7 @@ internal fun TrainerMembersRoute( TrainerMembersScreen( state = uiState, padding = padding, - onClickInviteButton = { navigateToInvite(false) }, + onClickInviteButton = { navigateToInvite(ScreenMode.BACK) }, ) } @@ -158,7 +159,7 @@ private fun MemberList(member: MemberInfo) { Spacer(modifier = Modifier.height(16.dp)) } -@Preview(widthDp = 300) +@Preview(showBackground = true, widthDp = 300) @Composable private fun TrainerMembersScreenPreview() { TnTTheme { diff --git a/feature/trainer/members/src/main/java/co/kr/tnt/trainer/members/navigation/TrainerMembersNavigation.kt b/feature/trainer/members/src/main/java/co/kr/tnt/trainer/members/navigation/TrainerMembersNavigation.kt index 03063e85..5960fd80 100644 --- a/feature/trainer/members/src/main/java/co/kr/tnt/trainer/members/navigation/TrainerMembersNavigation.kt +++ b/feature/trainer/members/src/main/java/co/kr/tnt/trainer/members/navigation/TrainerMembersNavigation.kt @@ -7,6 +7,7 @@ import androidx.navigation.NavOptionsBuilder import androidx.navigation.compose.composable import androidx.navigation.compose.navigation import co.kr.tnt.navigation.Route +import co.kr.tnt.navigation.model.ScreenMode import co.kr.tnt.trainer.members.TrainerMembersRoute fun NavController.navigateToTrainerMembers( @@ -18,7 +19,7 @@ fun NavController.navigateToTrainerMembers( fun NavGraphBuilder.trainerMembersNavGraph( padding: PaddingValues, - navigateToInvite: (Boolean) -> Unit, + navigateToInvite: (ScreenMode) -> Unit, membersDestination: NavGraphBuilder.() -> Unit = { }, ) { navigation(startDestination = Route.TrainerMembers) { diff --git a/feature/trainer/notification/src/main/java/co/kr/tnt/trainer/notification/TrainerNotificationViewModel.kt b/feature/trainer/notification/src/main/java/co/kr/tnt/trainer/notification/TrainerNotificationViewModel.kt index 5ec719ca..199965a3 100644 --- a/feature/trainer/notification/src/main/java/co/kr/tnt/trainer/notification/TrainerNotificationViewModel.kt +++ b/feature/trainer/notification/src/main/java/co/kr/tnt/trainer/notification/TrainerNotificationViewModel.kt @@ -1,15 +1,12 @@ package co.kr.tnt.trainer.notification import co.kr.tnt.domain.model.NotificationInfo -import co.kr.tnt.domain.model.NotificationType import co.kr.tnt.trainer.notification.TrainerNotificationContract.TrainerNotificationEffect import co.kr.tnt.trainer.notification.TrainerNotificationContract.TrainerNotificationUiEvent import co.kr.tnt.trainer.notification.TrainerNotificationContract.TrainerNotificationUiState import co.kr.tnt.ui.base.BaseViewModel import co.kr.tnt.ui.model.NotificationState import dagger.hilt.android.lifecycle.HiltViewModel -import java.time.LocalDateTime -import java.time.format.DateTimeFormatter import javax.inject.Inject @HiltViewModel @@ -30,31 +27,7 @@ internal class TrainerNotificationViewModel @Inject constructor() : private fun getNotification() { // TODO 알림 불러오기 - val formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME - - val sampleNotifications = listOf( - NotificationInfo( - type = NotificationType.CONNECT_COMPLETE, - title = "트레이니 연결 완료", - contents = "김회원 회원과 연결되었어요", - time = LocalDateTime.parse("2025-02-03T23:59:00", formatter), - isChecked = false, - ), - NotificationInfo( - type = NotificationType.CONNECT_COMPLETE, - title = "트레이니 연결 완료", - contents = "김돌돌 회원과 연결되었어요", - time = LocalDateTime.parse("2025-02-03T11:12:00", formatter), - isChecked = false, - ), - NotificationInfo( - type = NotificationType.DISCONNECT, - title = "트레이니 연결 해제", - contents = "박헬린 회원이 연결을 끊었어요", - time = LocalDateTime.parse("2025-02-03T23:12:00", formatter), - isChecked = true, - ), - ) + val sampleNotifications = emptyList() updateState { copy(notifications = sampleNotifications.map(NotificationState::fromDomain)) } } diff --git a/feature/trainer/signup/src/main/java/co/kr/tnt/trainer/signup/TrainerProfileSetupPage.kt b/feature/trainer/signup/src/main/java/co/kr/tnt/trainer/signup/TrainerProfileSetupPage.kt index a3d42c2c..65d3b764 100644 --- a/feature/trainer/signup/src/main/java/co/kr/tnt/trainer/signup/TrainerProfileSetupPage.kt +++ b/feature/trainer/signup/src/main/java/co/kr/tnt/trainer/signup/TrainerProfileSetupPage.kt @@ -17,9 +17,7 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext @@ -55,10 +53,6 @@ internal fun TrainerProfileSetupPage( val context = LocalContext.current - val isWarning by remember(state.name) { - derivedStateOf { state.name.length > MAX_LENGTH } - } - val pickMediaLauncher = rememberLauncherForActivityResult(PickVisualMedia()) { uri -> if (uri != null) { onProfileImageSelect(uri) @@ -108,14 +102,13 @@ internal fun TrainerProfileSetupPage( title = stringResource(coreR.string.name), value = state.name, onValueChange = { newValue -> - val filteredText = validateInput(newValue) - onNameChange(filteredText) + onNameChange(newValue) }, modifier = Modifier.padding(horizontal = 20.dp), placeholder = stringResource(R.string.name_placeholder), maxLength = MAX_LENGTH, isSingleLine = true, - showWarning = isWarning, + showWarning = state.isNameValid.not(), isRequired = true, warningMessage = stringResource(coreR.string.text_length_and_format_warning, MAX_LENGTH), ) @@ -123,20 +116,13 @@ internal fun TrainerProfileSetupPage( TnTBottomButton( text = stringResource(coreR.string.next), modifier = Modifier.align(Alignment.BottomCenter), - enabled = state.name.isNotBlank() && !isWarning, + enabled = state.name.isNotBlank() && state.isNameValid, onClick = onNextClick, ) } } } -/** - * 입력 값을 검사해 한글/영어/공백만 허용하고 특수문자는 제거 - */ -private fun validateInput(input: String): String { - return input.filter { it.isLetter() || it.isWhitespace() } -} - @Preview(showBackground = true) @Composable private fun TrainerProfileSetupPagePreview() { diff --git a/feature/trainer/signup/src/main/java/co/kr/tnt/trainer/signup/TrainerSignUpContract.kt b/feature/trainer/signup/src/main/java/co/kr/tnt/trainer/signup/TrainerSignUpContract.kt index cde2c903..4a854edb 100644 --- a/feature/trainer/signup/src/main/java/co/kr/tnt/trainer/signup/TrainerSignUpContract.kt +++ b/feature/trainer/signup/src/main/java/co/kr/tnt/trainer/signup/TrainerSignUpContract.kt @@ -6,13 +6,21 @@ import co.kr.tnt.ui.base.UiEvent import co.kr.tnt.ui.base.UiSideEffect import co.kr.tnt.ui.base.UiState +private const val MAX_LENGTH = 15 + internal class TrainerSignUpContract { data class TrainerSignUpUiState( val page: TrainerSignUpPage = TrainerSignUpPage.ProfileSetUp, val name: String = "", val image: Uri? = null, val isLoading: Boolean = false, - ) : UiState + ) : UiState { + /** + * 입력 값을 검사해 한글/영어/공백만 허용하고 특수문자는 제거 + */ + private val nameRegex = Regex("^[a-zA-Zㄱ-ㅎㅏ-ㅣ가-힣 ]+\$") + val isNameValid get() = name.isBlank() || name.matches(nameRegex) && name.length <= MAX_LENGTH + } sealed interface TrainerSignUpUiEvent : UiEvent { data class OnImageChange(val imageUri: Uri) : TrainerSignUpUiEvent diff --git a/feature/trainer/signup/src/main/java/co/kr/tnt/trainer/signup/TrainerSignUpScreen.kt b/feature/trainer/signup/src/main/java/co/kr/tnt/trainer/signup/TrainerSignUpScreen.kt index 73f1be7a..99159da3 100644 --- a/feature/trainer/signup/src/main/java/co/kr/tnt/trainer/signup/TrainerSignUpScreen.kt +++ b/feature/trainer/signup/src/main/java/co/kr/tnt/trainer/signup/TrainerSignUpScreen.kt @@ -8,6 +8,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import co.kr.tnt.designsystem.snackbar.LocalSnackbar +import co.kr.tnt.navigation.model.ScreenMode import co.kr.tnt.trainer.signup.TrainerSignUpContract.TrainerSignUpUiEvent import co.kr.tnt.trainer.signup.TrainerSignUpContract.TrainerSignUpUiState @@ -17,7 +18,7 @@ internal fun TrainerSignUpRoute( authType: String, email: String, navigateToPrevious: () -> Unit, - navigateToInvite: (Boolean) -> Unit, + navigateToInvite: (ScreenMode) -> Unit, viewModel: TrainerSignUpViewModel = hiltViewModel(), ) { val context = LocalContext.current @@ -47,7 +48,7 @@ internal fun TrainerSignUpRoute( viewModel.effect.collect { effect -> when (effect) { TrainerSignUpContract.TrainerSignUpEffect.NavigateToBack -> navigateToPrevious() - TrainerSignUpContract.TrainerSignUpEffect.NavigateToConnect -> navigateToInvite(true) + TrainerSignUpContract.TrainerSignUpEffect.NavigateToConnect -> navigateToInvite(ScreenMode.SKIP) is TrainerSignUpContract.TrainerSignUpEffect.ShowToast -> snackbar.show(effect.message) } } diff --git a/feature/trainer/signup/src/main/java/co/kr/tnt/trainer/signup/navigation/TrainerSignUpNavigation.kt b/feature/trainer/signup/src/main/java/co/kr/tnt/trainer/signup/navigation/TrainerSignUpNavigation.kt index d4919034..813985d6 100644 --- a/feature/trainer/signup/src/main/java/co/kr/tnt/trainer/signup/navigation/TrainerSignUpNavigation.kt +++ b/feature/trainer/signup/src/main/java/co/kr/tnt/trainer/signup/navigation/TrainerSignUpNavigation.kt @@ -6,6 +6,7 @@ import androidx.navigation.NavOptionsBuilder import androidx.navigation.compose.composable import androidx.navigation.toRoute import co.kr.tnt.navigation.Route +import co.kr.tnt.navigation.model.ScreenMode import co.kr.tnt.trainer.signup.TrainerSignUpRoute fun NavController.navigateToTrainerSignUp( @@ -24,7 +25,7 @@ fun NavController.navigateToTrainerSignUp( fun NavGraphBuilder.trainerSignUpScreen( navigateToPrevious: () -> Unit, - navigateToInvite: (Boolean) -> Unit, + navigateToInvite: (ScreenMode) -> Unit, ) { composable { backstackEntry -> backstackEntry.toRoute().apply {