diff --git a/app/src/main/java/com/easyhz/noffice/NofficeApp.kt b/app/src/main/java/com/easyhz/noffice/NofficeApp.kt index 4313addd..a0ff9ba6 100644 --- a/app/src/main/java/com/easyhz/noffice/NofficeApp.kt +++ b/app/src/main/java/com/easyhz/noffice/NofficeApp.kt @@ -1,5 +1,9 @@ package com.easyhz.noffice +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.tween +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.material3.FabPosition @@ -7,14 +11,19 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.navigation.compose.NavHost +import androidx.navigation.navOptions import com.easyhz.noffice.core.design_system.component.bottomBar.HomeBottomBar import com.easyhz.noffice.core.design_system.component.button.HomeAddButton import com.easyhz.noffice.core.design_system.component.scaffold.NofficeScaffold import com.easyhz.noffice.navigation.home.homeScreen +import com.easyhz.noffice.navigation.home.navigateToHome +import com.easyhz.noffice.navigation.home.screen.Home +import com.easyhz.noffice.navigation.organization.navigateToOrganizationCreation +import com.easyhz.noffice.navigation.organization.navigateToOrganizationInvitation import com.easyhz.noffice.navigation.organization.organizationScreen import com.easyhz.noffice.navigation.rememberNofficeNavController -import com.easyhz.noffice.navigation.sign.screen.SignUp import com.easyhz.noffice.navigation.sign.signScreen +import com.easyhz.noffice.navigation.util.BOTTOM_BAR_DURATION import com.easyhz.noffice.navigation.util.BottomMenuTabs @Composable @@ -27,7 +36,17 @@ fun NofficeApp() { NofficeScaffold( floatingActionButton = { - if(isVisibleBottomBar) { + AnimatedVisibility( + visible = isVisibleBottomBar, + enter = slideInVertically( + initialOffsetY = { it }, + animationSpec = tween(durationMillis = BOTTOM_BAR_DURATION) + ), + exit = slideOutVertically( + targetOffsetY = { it + 52 }, + animationSpec = tween(durationMillis = BOTTOM_BAR_DURATION) + ) + ) { HomeAddButton( modifier = Modifier.offset(y = 52.dp) ) { } @@ -35,7 +54,17 @@ fun NofficeApp() { }, floatingActionButtonPosition = FabPosition.Center, bottomBar = { - if(isVisibleBottomBar) { + AnimatedVisibility( + visible = isVisibleBottomBar, + enter = slideInVertically( + initialOffsetY = { it }, + animationSpec = tween(durationMillis = BOTTOM_BAR_DURATION) + ), + exit = slideOutVertically( + targetOffsetY = { it }, + animationSpec = tween(durationMillis = BOTTOM_BAR_DURATION) + ) + ) { HomeBottomBar( tabs = enumValues(), current = currentTab, @@ -45,10 +74,22 @@ fun NofficeApp() { } ) { NavHost( - navController = navController, startDestination = SignUp + navController = navController, startDestination = Home ) { homeScreen(modifier = Modifier.padding(it)) - organizationScreen(modifier = Modifier.padding(it)) + organizationScreen( + modifier = Modifier.padding(it), + navigateToCreation = navController::navigateToOrganizationCreation, + navigateToInvitation = navController::navigateToOrganizationInvitation, + navigateToHome = { + val navOptions = navOptions { + popUpTo(navController.graph.id) { + inclusive = true + } + } + navController.navigateToHome(navOptions) + } + ) signScreen() } } diff --git a/app/src/main/java/com/easyhz/noffice/navigation/NavController.kt b/app/src/main/java/com/easyhz/noffice/navigation/NavController.kt index 59770e45..39a52470 100644 --- a/app/src/main/java/com/easyhz/noffice/navigation/NavController.kt +++ b/app/src/main/java/com/easyhz/noffice/navigation/NavController.kt @@ -33,12 +33,12 @@ internal class NofficeNavController( fun isInBottomTabs(): Boolean = currentRoute in routes @Composable - fun mapRouteToTab(): BottomMenuTabs { + fun mapRouteToTab(): BottomMenuTabs? { return when (currentRoute) { Home::class.java.name -> BottomMenuTabs.HOME // Add::class.java.name -> BottomMenuTabs.ADD // FIXME Organization::class.java.name -> BottomMenuTabs.ORGANIZATION - else -> BottomMenuTabs.HOME + else -> null } } diff --git a/app/src/main/java/com/easyhz/noffice/navigation/organization/OrganizationNavigation.kt b/app/src/main/java/com/easyhz/noffice/navigation/organization/OrganizationNavigation.kt index 96d2911e..0a949556 100644 --- a/app/src/main/java/com/easyhz/noffice/navigation/organization/OrganizationNavigation.kt +++ b/app/src/main/java/com/easyhz/noffice/navigation/organization/OrganizationNavigation.kt @@ -1,17 +1,59 @@ package com.easyhz.noffice.navigation.organization +import androidx.compose.animation.AnimatedContentTransitionScope +import androidx.compose.animation.core.tween import androidx.compose.ui.Modifier import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptions import androidx.navigation.compose.composable +import androidx.navigation.navOptions +import androidx.navigation.toRoute +import com.easyhz.noffice.feature.organization.screen.creation.OrganizationCreationScreen +import com.easyhz.noffice.feature.organization.screen.invitation.OrganizationInvitationScreen import com.easyhz.noffice.feature.organization.screen.organization.OrganizationScreen import com.easyhz.noffice.navigation.organization.screen.Organization +import com.easyhz.noffice.navigation.organization.screen.OrganizationCreation +import com.easyhz.noffice.navigation.organization.screen.OrganizationInvitation +import com.easyhz.noffice.navigation.util.DURATION -internal fun NavGraphBuilder.organizationScreen(modifier: Modifier) { +internal fun NavGraphBuilder.organizationScreen( + modifier: Modifier, + navigateToCreation: () -> Unit, + navigateToInvitation: (String, String)-> Unit, + navigateToHome: () -> Unit +) { composable { - OrganizationScreen(modifier = modifier) + OrganizationScreen( + modifier = modifier, + navigateToCreation = navigateToCreation + ) } + composable( + enterTransition = { slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.Start, tween(DURATION)) }, + exitTransition = { slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.Start, tween(DURATION)) }, + popEnterTransition = { slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.End, tween(DURATION)) }, + popExitTransition = { slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.End, tween(DURATION)) } + ) { + OrganizationCreationScreen( + navigateToInvitation = navigateToInvitation, + navigateToHome = navigateToHome + ) + } + composable( + enterTransition = { slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.Start, tween(DURATION)) }, + exitTransition = { slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.Start, tween(DURATION)) }, + popEnterTransition = { slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.End, tween(DURATION)) }, + popExitTransition = { slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.End, tween(DURATION)) } + ) { + val args = it.toRoute() + OrganizationInvitationScreen( + invitationUrl = args.invitationUrl, + imageUrl = args.imageUrl, + navigateToHome = navigateToHome + ) + } + } internal fun NavController.navigateToOrganization(navOptions: NavOptions) { @@ -19,4 +61,15 @@ internal fun NavController.navigateToOrganization(navOptions: NavOptions) { route = Organization, navOptions = navOptions ) +} + +internal fun NavController.navigateToOrganizationCreation() { + navigate(OrganizationCreation) +} + +internal fun NavController.navigateToOrganizationInvitation(invitationUrl: String, imageUrl: String) { + val navOptions = navOptions { + popUpTo(OrganizationCreation) { inclusive = true } + } + navigate(OrganizationInvitation(invitationUrl = invitationUrl, imageUrl = imageUrl), navOptions) } \ No newline at end of file diff --git a/app/src/main/java/com/easyhz/noffice/navigation/organization/screen/OrganizationCreation.kt b/app/src/main/java/com/easyhz/noffice/navigation/organization/screen/OrganizationCreation.kt new file mode 100644 index 00000000..91ec7969 --- /dev/null +++ b/app/src/main/java/com/easyhz/noffice/navigation/organization/screen/OrganizationCreation.kt @@ -0,0 +1,6 @@ +package com.easyhz.noffice.navigation.organization.screen + +import kotlinx.serialization.Serializable + +@Serializable +object OrganizationCreation \ No newline at end of file diff --git a/app/src/main/java/com/easyhz/noffice/navigation/organization/screen/OrganizationInvitation.kt b/app/src/main/java/com/easyhz/noffice/navigation/organization/screen/OrganizationInvitation.kt new file mode 100644 index 00000000..2d3f204c --- /dev/null +++ b/app/src/main/java/com/easyhz/noffice/navigation/organization/screen/OrganizationInvitation.kt @@ -0,0 +1,9 @@ +package com.easyhz.noffice.navigation.organization.screen + +import kotlinx.serialization.Serializable + +@Serializable +data class OrganizationInvitation( + val invitationUrl: String, + val imageUrl: String = "", +) diff --git a/app/src/main/java/com/easyhz/noffice/navigation/util/Constant.kt b/app/src/main/java/com/easyhz/noffice/navigation/util/Constant.kt new file mode 100644 index 00000000..4e443e80 --- /dev/null +++ b/app/src/main/java/com/easyhz/noffice/navigation/util/Constant.kt @@ -0,0 +1,4 @@ +package com.easyhz.noffice.navigation.util + +const val DURATION = 500 +const val BOTTOM_BAR_DURATION = 300 \ No newline at end of file diff --git a/core/common/src/main/java/com/easyhz/noffice/core/common/util/DateFormat.kt b/core/common/src/main/java/com/easyhz/noffice/core/common/util/DateFormat.kt new file mode 100644 index 00000000..b38b7ede --- /dev/null +++ b/core/common/src/main/java/com/easyhz/noffice/core/common/util/DateFormat.kt @@ -0,0 +1,9 @@ + package com.easyhz.noffice.core.common.util + +import java.time.LocalDate +import java.time.format.DateTimeFormatter + +object DateFormat { + fun fullText(date: LocalDate): String = + DateTimeFormatter.ofPattern("yyyy년 MM월 d일").format(date) +} \ No newline at end of file diff --git a/core/common/src/main/java/com/easyhz/noffice/core/common/util/Step.kt b/core/common/src/main/java/com/easyhz/noffice/core/common/util/Step.kt new file mode 100644 index 00000000..bd83cd12 --- /dev/null +++ b/core/common/src/main/java/com/easyhz/noffice/core/common/util/Step.kt @@ -0,0 +1,23 @@ +package com.easyhz.noffice.core.common.util + +import java.util.EnumMap + +data class Step( + val currentStep: T, + val previousStep: T? +) +interface StepRequired { + val isRequired: Boolean +} +inline fun List.toEnabledStepButton(): EnumMap where T: Enum, T: StepRequired = + EnumMap(T::class.java).apply { + this@toEnabledStepButton.forEach { step -> + this[step] = !step.isRequired + } + } + +fun EnumMap.updateStepButton(key: T, isEnabled: Boolean): EnumMap +where T: Enum { + this[key] = isEnabled + return this +} \ No newline at end of file diff --git a/core/design-system/build.gradle.kts b/core/design-system/build.gradle.kts index f4118276..39664e44 100644 --- a/core/design-system/build.gradle.kts +++ b/core/design-system/build.gradle.kts @@ -13,4 +13,7 @@ dependencies { // Glide implementation(libs.glide) + + // Calendar + implementation(libs.calendar.compose) } \ No newline at end of file diff --git a/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/component/bottomBar/HomeBottomBar.kt b/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/component/bottomBar/HomeBottomBar.kt index a208d661..5b9f2f6e 100644 --- a/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/component/bottomBar/HomeBottomBar.kt +++ b/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/component/bottomBar/HomeBottomBar.kt @@ -28,7 +28,7 @@ import com.easyhz.noffice.core.design_system.util.bottomBar.BottomMenu @Composable fun HomeBottomBar( modifier: Modifier = Modifier, - current: T, + current: T?, tabs: Array, onClick: (T) -> Unit, ) where T: Enum, T: BottomMenu { diff --git a/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/component/button/CheckButton.kt b/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/component/button/CheckButton.kt index 0d176e31..6914b54b 100644 --- a/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/component/button/CheckButton.kt +++ b/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/component/button/CheckButton.kt @@ -17,6 +17,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.easyhz.noffice.core.design_system.R @@ -38,6 +39,7 @@ import com.easyhz.noffice.core.design_system.theme.Grey600 @Composable fun CheckButton( modifier: Modifier = Modifier, + textAlign: TextAlign = TextAlign.Start, text: String, isComplete: Boolean, color: CheckButtonDefaults = CheckButtonDefaults.default(), @@ -52,7 +54,7 @@ fun CheckButton( .heightIn(min = 42.dp) .clip(RoundedCornerShape(8.dp)) .background(if (isComplete) color.completeContainerColor else color.incompleteContainerColor) - .padding(vertical = 10.dp) + .padding(vertical = 14.dp) .screenHorizonPadding(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(10.dp) @@ -61,13 +63,14 @@ fun CheckButton( modifier = Modifier.weight(1f), text = text, color = if(isComplete) color.completeContentColor else color.incompleteContentColor, - style = Body14 + style = Body14, + textAlign = textAlign ) - if (isComplete || color.incompleteIconColor != null) { + if ((isComplete && color.completeIconColor != null) || color.incompleteIconColor != null) { Icon( painter = painterResource(id = R.drawable.ic_check), contentDescription = "check", - tint = if (isComplete) color.completeIconColor else color.incompleteIconColor ?: color.completeIconColor + tint = (if (isComplete) color.completeIconColor else color.incompleteIconColor) ?: color.completeContentColor ) } } @@ -79,7 +82,7 @@ fun CheckButton( data class CheckButtonDefaults( val completeContainerColor: Color, val completeContentColor: Color, - val completeIconColor: Color, + val completeIconColor: Color?, val incompleteContainerColor: Color, val incompleteContentColor: Color, val incompleteIconColor: Color?, @@ -187,3 +190,24 @@ private fun CheckButtonOrganizationCompletePrev() { } +@Preview(group = "checkButton", name = "organization - complete") +@Composable +private fun CheckButtonOrganizationCompleteNullPrev() { + CheckButton( + modifier = Modifier.width(300.dp), + text = "CMC 15th", + isComplete = true, + color = CheckButtonDefaults( + completeContainerColor = Green100, + completeContentColor = Green700, + completeIconColor = null, + incompleteContainerColor = Grey50, + incompleteContentColor = Grey600, + incompleteIconColor = null + ) + ) { + + } +} + + diff --git a/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/component/calendar/MonthCalendarView.kt b/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/component/calendar/MonthCalendarView.kt new file mode 100644 index 00000000..4a834bac --- /dev/null +++ b/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/component/calendar/MonthCalendarView.kt @@ -0,0 +1,132 @@ +package com.easyhz.noffice.core.design_system.component.calendar + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.Stable +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import com.easyhz.noffice.core.design_system.theme.Grey700 +import com.easyhz.noffice.core.design_system.theme.SubBody14 +import com.easyhz.noffice.core.design_system.util.calendar.RANGE_MONTH +import com.easyhz.noffice.core.design_system.util.calendar.displayText +import com.easyhz.noffice.core.design_system.util.calendar.rememberFirstMostVisibleMonth +import com.kizitonwose.calendar.compose.CalendarState +import com.kizitonwose.calendar.compose.HorizontalCalendar +import com.kizitonwose.calendar.compose.rememberCalendarState +import com.kizitonwose.calendar.core.OutDateStyle +import com.kizitonwose.calendar.core.daysOfWeek +import com.kizitonwose.calendar.core.firstDayOfWeekFromLocale +import com.kizitonwose.calendar.core.yearMonth +import kotlinx.coroutines.launch +import java.time.LocalDate + +@Composable +fun MonthCalendarView( + modifier: Modifier = Modifier, + selection: LocalDate, + calendarPadding: Dp, + onChangeDate: (LocalDate) -> Unit +) { + val scope = rememberCoroutineScope() + val today = remember { LocalDate.now() } + val startMonth = remember { today.yearMonth } + val endMonth = remember { today.yearMonth.plusMonths(RANGE_MONTH) } + val monthState = rememberCalendarState( + startMonth = startMonth, + endMonth = endMonth, + firstDayOfWeek = firstDayOfWeekFromLocale(), + outDateStyle = OutDateStyle.EndOfRow, + ) + val title = rememberFirstMostVisibleMonth(state = monthState) + + LaunchedEffect(selection) { + if(title.yearMonth == selection.yearMonth) return@LaunchedEffect + monthState.animateScrollToMonth(selection.yearMonth) + } + Column( + modifier = modifier, + verticalArrangement = Arrangement.spacedBy(24.dp) + ) { + MonthHeader( + modifier = Modifier + .fillMaxWidth() + .align(Alignment.CenterHorizontally), + title = title.yearMonth.displayText(), + onClickBefore = { + scope.launch { + monthState.animateScrollToMonth(title.yearMonth.minusMonths(1)) + } + }, + onClickNext = { + scope.launch { + monthState.animateScrollToMonth(title.yearMonth.plusMonths(1)) + } + } + ) + MonthCalendarContent( + modifier = Modifier + .align(Alignment.CenterHorizontally) + .padding(horizontal = calendarPadding), + monthState = monthState, + selection = selection, + today = today, + onChangeDate = onChangeDate + ) + } +} + +@Composable +fun MonthCalendarContent( + modifier: Modifier = Modifier, + monthState: CalendarState, + selection: LocalDate, + today: LocalDate, + onChangeDate: (LocalDate) -> Unit +) { + HorizontalCalendar( + modifier = modifier, + state = monthState, + monthHeader = { CalendarHeader() }, + dayContent = {day -> + Day( + day = day, + isSelected = selection == day.date, + today = today, + ) { clickedDay -> + onChangeDate(clickedDay) + } + }, + ) +} + +@Stable +@Composable +internal fun CalendarHeader() { + val daysOfWeek = remember { daysOfWeek() } + Row( + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 8.dp), + ) { + for (dayOfWeek in daysOfWeek) { + Text( + modifier = Modifier.weight(1f), + text = dayOfWeek.displayText(), + style = SubBody14, + color = Grey700, + textAlign = TextAlign.Center + ) + } + } +} \ No newline at end of file diff --git a/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/component/calendar/MonthDay.kt b/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/component/calendar/MonthDay.kt new file mode 100644 index 00000000..1baace03 --- /dev/null +++ b/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/component/calendar/MonthDay.kt @@ -0,0 +1,55 @@ +package com.easyhz.noffice.core.design_system.component.calendar + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import com.easyhz.noffice.core.design_system.extension.noRippleClickable +import com.easyhz.noffice.core.design_system.theme.SemiBold14 +import com.easyhz.noffice.core.design_system.util.calendar.dateFormatter +import com.easyhz.noffice.core.design_system.util.calendar.getSelectionBoxColor +import com.easyhz.noffice.core.design_system.util.calendar.getTextColor +import com.kizitonwose.calendar.core.CalendarDay +import java.time.LocalDate + +@Composable +internal fun Day( + modifier: Modifier = Modifier, + day: CalendarDay, + today: LocalDate, + isSelected: Boolean, + onClick: (LocalDate) -> Unit +) { + val enabled = day.date >= today + val selectionBoxColor = getSelectionBoxColor(isSelected) + val textColor = getTextColor(day, isSelected, enabled) + Box( + modifier = modifier + .fillMaxWidth() + .noRippleClickable(enabled = enabled) { onClick(day.date) }, + contentAlignment = Alignment.TopCenter, + ) { + Box(modifier = Modifier.padding(bottom = 6.dp) + .size(40.dp) + .clip(RoundedCornerShape(8.dp)) + .background(selectionBoxColor), + ) { + Text( + modifier = Modifier.align(Alignment.TopCenter).padding(top = 4.dp), + text = dateFormatter.format(day.date), + style = SemiBold14, + textAlign = TextAlign.Center, + color = textColor + ) + } + } +} \ No newline at end of file diff --git a/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/component/calendar/MonthHeader.kt b/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/component/calendar/MonthHeader.kt new file mode 100644 index 00000000..a011c4bc --- /dev/null +++ b/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/component/calendar/MonthHeader.kt @@ -0,0 +1,70 @@ +package com.easyhz.noffice.core.design_system.component.calendar + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.scale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import com.easyhz.noffice.core.design_system.R +import com.easyhz.noffice.core.design_system.extension.noRippleClickable +import com.easyhz.noffice.core.design_system.theme.Grey500 +import com.easyhz.noffice.core.design_system.theme.InputDialogTitle +import com.easyhz.noffice.core.design_system.util.interaction.useInteraction + +@Composable +internal fun MonthHeader( + modifier: Modifier = Modifier, + title: String, + onClickBefore: () -> Unit, + onClickNext: () -> Unit +) { + val (interactionSource, scale) = useInteraction() + Row( + modifier = modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ) { + Box(modifier = Modifier + .size(32.dp) + .scale(scale) + .noRippleClickable(interactionSource) { onClickBefore() }) { + Icon( + modifier = Modifier + .align(Alignment.CenterStart) + .size(24.dp), + painter = painterResource(id = R.drawable.ic_chevron_left), + contentDescription = "before", + tint = Grey500 + ) + } + Text( + modifier = Modifier.width(76.dp), + text = title, + style = InputDialogTitle, + textAlign = TextAlign.Center + ) + Box(modifier = Modifier + .size(32.dp) + .scale(scale) + .noRippleClickable(interactionSource) { onClickNext() }) { + Icon( + modifier = Modifier + .align(Alignment.CenterEnd) + .size(24.dp), + painter = painterResource(id = R.drawable.ic_chevron_right), + contentDescription = "next", + tint = Grey500 + ) + } + } +} \ No newline at end of file diff --git a/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/component/exception/ExceptionView.kt b/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/component/exception/ExceptionView.kt index 2fd47be0..00131425 100644 --- a/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/component/exception/ExceptionView.kt +++ b/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/component/exception/ExceptionView.kt @@ -32,7 +32,7 @@ fun ExceptionView( ) { Image( painter = painterResource(id = type.resId), - contentDescription = "no_group" + contentDescription = "no_organization" ) Column(modifier = Modifier .padding(top = 10.dp) @@ -59,10 +59,10 @@ fun ExceptionView( @Preview @Composable -private fun NoGroupPrev() { +private fun NoOrganizationPrev() { ExceptionView( modifier = Modifier.fillMaxSize(), - type = ExceptionType.NO_GROUP + type = ExceptionType.NO_ORGANIZATION ) } diff --git a/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/component/image/OrganizationCreationImageView.kt b/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/component/image/OrganizationCreationImageView.kt new file mode 100644 index 00000000..9c56669a --- /dev/null +++ b/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/component/image/OrganizationCreationImageView.kt @@ -0,0 +1,28 @@ +package com.easyhz.noffice.core.design_system.component.image + +import android.net.Uri +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import com.bumptech.glide.integration.compose.CrossFade +import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi +import com.bumptech.glide.integration.compose.GlideImage +import com.bumptech.glide.integration.compose.placeholder +import com.easyhz.noffice.core.design_system.R + +@OptIn(ExperimentalGlideComposeApi::class) +@Composable +fun OrganizationCreationImageView( + modifier: Modifier = Modifier, + image: Uri +) { + GlideImage( + modifier = modifier, + model = image, + contentDescription = image.toString(), + loading = placeholder(R.drawable.ic_profile_group), + failure = placeholder(R.drawable.ic_profile_group), + contentScale = ContentScale.Crop, + transition = CrossFade, + ) +} \ No newline at end of file diff --git a/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/component/image/OrganizationImage.kt b/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/component/image/OrganizationImage.kt index 15af88e7..758029f2 100644 --- a/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/component/image/OrganizationImage.kt +++ b/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/component/image/OrganizationImage.kt @@ -26,7 +26,7 @@ fun OrganizationImage( Image( modifier = modifier.clip(CircleShape), painter = painterResource(id = R.drawable.ic_profile_group), - contentDescription = "group_profile", + contentDescription = "organization_profile", contentScale = ContentScale.Crop ) } else { diff --git a/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/component/scaffold/NofficeScaffold.kt b/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/component/scaffold/NofficeScaffold.kt index 9d35955a..58ed2493 100644 --- a/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/component/scaffold/NofficeScaffold.kt +++ b/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/component/scaffold/NofficeScaffold.kt @@ -13,6 +13,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalView +import com.easyhz.noffice.core.design_system.theme.Grey50 import com.easyhz.noffice.core.design_system.theme.White @Composable @@ -28,6 +29,16 @@ fun NofficeScaffold( contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets, content: @Composable (PaddingValues) -> Unit ) { + val view = LocalView.current + if (!view.isInEditMode) { + SideEffect { + updateWindowColors( + view = view, + statusBarColor = null, + navigationBarColor = Grey50 + ) + } + } Scaffold( modifier = modifier, topBar = topBar, diff --git a/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/component/textField/MainTextField.kt b/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/component/textField/MainTextField.kt index 2d8042d3..f8b327d3 100644 --- a/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/component/textField/MainTextField.kt +++ b/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/component/textField/MainTextField.kt @@ -18,12 +18,13 @@ import com.easyhz.noffice.core.design_system.util.textField.getTextFieldState @Composable fun MainTextField( modifier: Modifier = Modifier, - value: TextFieldValue, - onValueChange: (TextFieldValue) -> Unit, + value: String, + onValueChange: (String) -> Unit, title: String?, placeholder: String, isFilled: Boolean, maxCount: Int? = null, + readOnly: Boolean = false, singleLine: Boolean, minLines: Int = 1, icon: TextFieldIcon? = TextFieldIcon.CLEAR, @@ -32,7 +33,7 @@ fun MainTextField( keyboardOptions: KeyboardOptions = KeyboardOptions.Default, keyboardActions: KeyboardActions = KeyboardActions.Default, ) { - val state = getTextFieldState(text = value.text, isFilled = isFilled) + val state = getTextFieldState(text = value, isFilled = isFilled) BasicTextField( value = value, @@ -41,6 +42,7 @@ fun MainTextField( textStyle = SubBody16.copy( color = Grey800 ), + readOnly = readOnly, keyboardOptions = keyboardOptions, keyboardActions = keyboardActions, singleLine = singleLine, @@ -57,7 +59,7 @@ fun MainTextField( onClickIcon = { onClickIcon() }, - textCount = value.text.length, + textCount = value.length, maxCount = maxCount, innerTextField = innerTextField ) @@ -69,7 +71,7 @@ fun MainTextField( @Composable private fun MainTextFieldPrev() { MainTextField( - value = TextFieldValue("내용이 잇음"), + value = "내용이 잇음", onValueChange = { }, title = null, placeholder = "내용으 입력", @@ -83,7 +85,7 @@ private fun MainTextFieldPrev() { @Composable private fun MainTextFieldPlaceholderPrev() { MainTextField( - value = TextFieldValue(""), + value = "", onValueChange = { }, title = null, placeholder = "내용dmf dlqfur 입력", @@ -97,7 +99,7 @@ private fun MainTextFieldPlaceholderPrev() { @Composable private fun MainTextFieldTitlePrev() { MainTextField( - value = TextFieldValue(""), + value = "", onValueChange = { }, title = "내용", placeholder = "내용dmf dlqfur 입력", @@ -111,7 +113,7 @@ private fun MainTextFieldTitlePrev() { @Composable private fun MainTextFieldMaxCountPrev() { MainTextField( - value = TextFieldValue(""), + value = "", onValueChange = { }, title = null, placeholder = "내용dmf dlqfur 입력", diff --git a/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/extension/Modifier.kt b/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/extension/Modifier.kt index ce2a0ae7..49b8af55 100644 --- a/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/extension/Modifier.kt +++ b/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/extension/Modifier.kt @@ -15,9 +15,12 @@ fun Modifier.screenHorizonPadding(): Modifier = padding(horizontal = 16.dp) inline fun Modifier.noRippleClickable( interactionSource: MutableInteractionSource? = null, + enabled: Boolean = true, crossinline onClick: () -> Unit, ): Modifier = composed { - clickable(indication = null, + clickable( + indication = null, + enabled = enabled, interactionSource = interactionSource ?: remember { MutableInteractionSource() }) { onClick() } diff --git a/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/theme/Color.kt b/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/theme/Color.kt index 159b190b..6856a841 100644 --- a/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/theme/Color.kt +++ b/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/theme/Color.kt @@ -84,4 +84,7 @@ val Blue600 = Color(0xFF00A0EB) val Blue700 = Color(0xFF007DB8) @Stable -val DimColor = Color(0xFF121212).copy(alpha = 0.5f) \ No newline at end of file +val DimColor = Color(0xFF121212).copy(alpha = 0.5f) + +@Stable +val Red = Color(0xFFFF5757) \ No newline at end of file diff --git a/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/theme/Type.kt b/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/theme/Type.kt index 9483bf81..28ff0b96 100644 --- a/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/theme/Type.kt +++ b/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/theme/Type.kt @@ -45,6 +45,31 @@ val Title2 = TextStyle( includeFontPadding = false, ) ) + +val Title3 = TextStyle( + fontFamily = Pretendard, + fontWeight = FontWeight.SemiBold, + color = Grey800, + fontSize = 22.sp, + letterSpacing = LetterSpacing, + textAlign = TextAlign.Justify, + platformStyle = PlatformTextStyle( + includeFontPadding = false, + ) +) + +val Title4 = TextStyle( + fontFamily = Pretendard, + fontWeight = FontWeight.Medium, + color = Grey800, + fontSize = 20.sp, + letterSpacing = LetterSpacing, + textAlign = TextAlign.Justify, + platformStyle = PlatformTextStyle( + includeFontPadding = false, + ) +) + val SubTitle1 = TextStyle( fontFamily = Pretendard, fontWeight = FontWeight.Medium, @@ -219,4 +244,28 @@ val CardExceptionSubTitle = TextStyle( platformStyle = PlatformTextStyle( includeFontPadding = false, ) -) \ No newline at end of file +) + +val CalendarCaption1 = TextStyle( + fontFamily = Pretendard, + fontWeight = FontWeight.SemiBold, + color = Green800, + fontSize = 16.sp, + letterSpacing = LetterSpacing, + textAlign = TextAlign.Justify, + platformStyle = PlatformTextStyle( + includeFontPadding = false, + ) +) + +val CalendarCaption2 = TextStyle( + fontFamily = Pretendard, + fontWeight = FontWeight.SemiBold, + color = Grey400, + fontSize = 16.sp, + letterSpacing = LetterSpacing, + textAlign = TextAlign.Justify, + platformStyle = PlatformTextStyle( + includeFontPadding = false, + ) +) diff --git a/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/util/calendar/Constant.kt b/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/util/calendar/Constant.kt new file mode 100644 index 00000000..be9ea889 --- /dev/null +++ b/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/util/calendar/Constant.kt @@ -0,0 +1,8 @@ +package com.easyhz.noffice.core.design_system.util.calendar + +internal const val CONTENT_SIZE = 40 +internal const val NUM_OF_DAYS_IN_WEEK = 7 +internal const val PADDING_NUM_OF_DAYS_IN_WEEK = NUM_OF_DAYS_IN_WEEK * 2 +internal const val WITHOUT_PADDING_NUM = PADDING_NUM_OF_DAYS_IN_WEEK - 2 +fun getCalendarPadding(target: Int, screenWidth: Int) = + (((PADDING_NUM_OF_DAYS_IN_WEEK * target) + (NUM_OF_DAYS_IN_WEEK * CONTENT_SIZE) - screenWidth) / WITHOUT_PADDING_NUM).coerceAtLeast(0) \ No newline at end of file diff --git a/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/util/calendar/DisplayText.kt b/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/util/calendar/DisplayText.kt new file mode 100644 index 00000000..1f83d1ca --- /dev/null +++ b/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/util/calendar/DisplayText.kt @@ -0,0 +1,16 @@ +package com.easyhz.noffice.core.design_system.util.calendar + +import java.time.DayOfWeek +import java.time.YearMonth +import java.time.format.DateTimeFormatter +import java.time.format.TextStyle +import java.util.Locale + +internal val dateFormatter: DateTimeFormatter = DateTimeFormatter.ofPattern("d") + +internal fun YearMonth.displayText(): String = + "${year}.${month.value}" +internal fun DayOfWeek.displayText(uppercase: Boolean = false): String = + getDisplayName(TextStyle.SHORT, Locale.KOREA).let { value -> + if (uppercase) value.uppercase(Locale.KOREA) else value + } \ No newline at end of file diff --git a/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/util/calendar/MonthCalendar.kt b/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/util/calendar/MonthCalendar.kt new file mode 100644 index 00000000..134cc9b1 --- /dev/null +++ b/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/util/calendar/MonthCalendar.kt @@ -0,0 +1,77 @@ +package com.easyhz.noffice.core.design_system.util.calendar + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.snapshotFlow +import androidx.compose.ui.graphics.Color +import com.easyhz.noffice.core.design_system.theme.Green500 +import com.easyhz.noffice.core.design_system.theme.Grey300 +import com.easyhz.noffice.core.design_system.theme.Grey700 +import com.easyhz.noffice.core.design_system.theme.Red +import com.easyhz.noffice.core.design_system.theme.White +import com.kizitonwose.calendar.compose.CalendarLayoutInfo +import com.kizitonwose.calendar.compose.CalendarState +import com.kizitonwose.calendar.core.CalendarDay +import com.kizitonwose.calendar.core.CalendarMonth +import com.kizitonwose.calendar.core.DayPosition +import com.kizitonwose.calendar.core.Week +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filterNotNull +import java.time.DayOfWeek + +const val RANGE_MONTH = 500L + + +/** + * 스크롤 감지하고 해당 월 반환 + * + * @param state HorizontalCalendar 의 상태인 [CalendarState] + * @param viewportPercent 넘어오는 정도 default : 50f + * + * @return 해당 주 [Week] + */ +@Composable +internal fun rememberFirstMostVisibleMonth( + state: CalendarState, + viewportPercent: Float = 50f, +): CalendarMonth { + val visibleMonth = remember(state) { mutableStateOf(state.firstVisibleMonth) } + LaunchedEffect(state) { + snapshotFlow { state.layoutInfo.firstMostVisibleMonth(viewportPercent) } + .distinctUntilChanged() + .filterNotNull() + .collect { month -> visibleMonth.value = month } + } + return visibleMonth.value +} + + +private fun CalendarLayoutInfo.firstMostVisibleMonth(viewportPercent: Float = 50f): CalendarMonth? { + return if (visibleMonthsInfo.isEmpty()) { + null + } else { + val viewportSize = (viewportEndOffset + viewportStartOffset) * viewportPercent / 100f + visibleMonthsInfo.firstOrNull { itemInfo -> + if (itemInfo.offset < 0) { + itemInfo.offset + itemInfo.size >= viewportSize + } else { + itemInfo.size - itemInfo.offset >= viewportSize + } + }?.month + } +} + +internal fun getSelectionBoxColor(isSelected: Boolean): Color { + return if (isSelected) Green500 else White +} + +internal fun getTextColor(day: CalendarDay, isSelected: Boolean, enabled: Boolean): Color { + return when { + isSelected -> White + !enabled || day.position != DayPosition.MonthDate -> Grey300 + day.date.dayOfWeek == DayOfWeek.SUNDAY -> Red + else -> Grey700 + } +} \ No newline at end of file diff --git a/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/util/exception/ExceptionType.kt b/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/util/exception/ExceptionType.kt index 9bf942ff..d78ba4bc 100644 --- a/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/util/exception/ExceptionType.kt +++ b/core/design-system/src/main/java/com/easyhz/noffice/core/design_system/util/exception/ExceptionType.kt @@ -9,10 +9,10 @@ enum class ExceptionType( @StringRes val titleStringId: Int, @StringRes val subTitleStringId: Int ) { - NO_GROUP( + NO_ORGANIZATION( resId = R.drawable.ic_empty_no_group, - titleStringId = R.string.exception_no_group_title, - subTitleStringId = R.string.exception_no_group_sub_title, + titleStringId = R.string.exception_no_organization_title, + subTitleStringId = R.string.exception_no_organization_sub_title, ), NO_TASK( resId = R.drawable.ic_empty_no_task, titleStringId = R.string.exception_no_task_title, diff --git a/core/design-system/src/main/res/drawable/ic_upload.xml b/core/design-system/src/main/res/drawable/ic_upload.xml new file mode 100644 index 00000000..c9f7e82e --- /dev/null +++ b/core/design-system/src/main/res/drawable/ic_upload.xml @@ -0,0 +1,27 @@ + + + + + diff --git a/core/design-system/src/main/res/values/strings.xml b/core/design-system/src/main/res/values/strings.xml index 1fd7d6be..83002739 100644 --- a/core/design-system/src/main/res/values/strings.xml +++ b/core/design-system/src/main/res/values/strings.xml @@ -39,10 +39,10 @@ 앞으로 어떤 즐거운 이벤트가 있을까요? - + 참여한 그룹이 없어요 - + 그룹에 참여해서 공지를 확인해 보세요! @@ -60,4 +60,18 @@ 새로운 그룹 + 그룹 만들기 + 그룹 이름이 무엇인가요? + 이름을 입력해 주세요. + 그룹의 카테고리를 모두 선택해 주세요 + 대표 이미지를 설정해 주세요 + 활동 종료 날짜가 언제인가요? + 에 활동을 종료할 예정이에요! + 프로모션 코드를 입력해 주세요 + 코드를 입력해 주세요. + + 그룹을 만들었어요! + 링크를 통해 멤버들을 초대해 보세요. + 그룹 메인으로 + 초대 링크 복사 \ No newline at end of file diff --git a/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/component/creation/CategoryView.kt b/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/component/creation/CategoryView.kt new file mode 100644 index 00000000..3454705a --- /dev/null +++ b/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/component/creation/CategoryView.kt @@ -0,0 +1,80 @@ +package com.easyhz.noffice.feature.organization.component.creation + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.itemsIndexed +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.easyhz.noffice.core.design_system.R +import com.easyhz.noffice.core.design_system.component.button.CheckButton +import com.easyhz.noffice.core.design_system.component.button.CheckButtonDefaults +import com.easyhz.noffice.core.design_system.component.button.MediumButton +import com.easyhz.noffice.core.design_system.theme.Green100 +import com.easyhz.noffice.core.design_system.theme.Green700 +import com.easyhz.noffice.core.design_system.theme.Grey50 +import com.easyhz.noffice.core.design_system.theme.Grey600 +import com.easyhz.noffice.feature.organization.contract.creation.CreationIntent +import com.easyhz.noffice.feature.organization.screen.creation.OrganizationCreationViewModel + +@Composable +internal fun CategoryView( + modifier: Modifier = Modifier, + viewModel: OrganizationCreationViewModel = hiltViewModel() +) { + val uiState by viewModel.uiState.collectAsStateWithLifecycle() + + Column( + modifier = modifier + ) { + CommonHeader( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 16.dp, horizontal = 8.dp), + title = stringResource(id = R.string.organization_creation_category_title) + ) + LazyVerticalGrid( + modifier = Modifier.weight(1f).padding(top = 8.dp), + columns = GridCells.Fixed(2), + verticalArrangement = Arrangement.spacedBy(10.dp), + horizontalArrangement = Arrangement.spacedBy(7.dp) + ) { + itemsIndexed(uiState.category) { index, item -> + CheckButton( + modifier = Modifier.weight(1f), + textAlign = TextAlign.Center, + text = item.title, + isComplete = item.isSelected, + color = CheckButtonDefaults( + completeContainerColor = Green100, + completeContentColor = Green700, + completeIconColor = null, + incompleteContainerColor = Grey50, + incompleteContentColor = Grey600, + incompleteIconColor = null + ) + ) { + viewModel.postIntent(CreationIntent.ClickCategoryItem(index)) + } + } + } + MediumButton( + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 16.dp), + text = stringResource(id = R.string.sign_up_button), + enabled = uiState.enabledStepButton[uiState.step.currentStep] ?: false + ) { + viewModel.postIntent(CreationIntent.ClickNextButton) + } + } +} \ No newline at end of file diff --git a/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/component/creation/CommonHeader.kt b/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/component/creation/CommonHeader.kt new file mode 100644 index 00000000..f38cf407 --- /dev/null +++ b/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/component/creation/CommonHeader.kt @@ -0,0 +1,59 @@ +package com.easyhz.noffice.feature.organization.component.creation + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Icon +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 +import com.easyhz.noffice.core.design_system.R +import com.easyhz.noffice.core.design_system.theme.Green500 +import com.easyhz.noffice.core.design_system.theme.SemiBold16 +import com.easyhz.noffice.core.design_system.theme.Title3 + +@Composable +internal fun CommonHeader( + modifier: Modifier = Modifier, + title: String +) { + Column( + modifier = modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(4.dp) + ) { + Icon( + modifier = Modifier.size(18.dp), + painter = painterResource(id = R.drawable.ic_grid), + contentDescription = "grid", + tint = Green500 + ) + Text( + text = stringResource(id = R.string.organization_creation_title), + style = SemiBold16, + color = Green500 + ) + } + Text(text = title, style = Title3) + } +} + +@Preview(showBackground = true) +@Composable +private fun CommonHeaderPrev() { + CommonHeader( + modifier = Modifier.padding(vertical = 16.dp), + title = "그룹 이름 머임?" + ) +} \ No newline at end of file diff --git a/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/component/creation/EndDateView.kt b/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/component/creation/EndDateView.kt new file mode 100644 index 00000000..56d0ca39 --- /dev/null +++ b/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/component/creation/EndDateView.kt @@ -0,0 +1,83 @@ +package com.easyhz.noffice.feature.organization.component.creation + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.withStyle +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.easyhz.noffice.core.common.util.DateFormat +import com.easyhz.noffice.core.design_system.R +import com.easyhz.noffice.core.design_system.component.button.MediumButton +import com.easyhz.noffice.core.design_system.component.calendar.MonthCalendarView +import com.easyhz.noffice.core.design_system.extension.screenHorizonPadding +import com.easyhz.noffice.core.design_system.theme.CalendarCaption1 +import com.easyhz.noffice.core.design_system.theme.CalendarCaption2 +import com.easyhz.noffice.core.design_system.util.calendar.getCalendarPadding +import com.easyhz.noffice.feature.organization.contract.creation.CreationIntent +import com.easyhz.noffice.feature.organization.screen.creation.OrganizationCreationViewModel + +@Composable +internal fun EndDateView( + modifier: Modifier = Modifier, + viewModel: OrganizationCreationViewModel = hiltViewModel() +) { + val uiState by viewModel.uiState.collectAsStateWithLifecycle() + val screenWidth = LocalConfiguration.current.screenWidthDp + val calendarPadding = getCalendarPadding(16, screenWidth).dp + val caption = stringResource(id = R.string.organization_creation_end_date_caption) + val annotatedString = buildAnnotatedString { + withStyle(style = CalendarCaption1.toSpanStyle()) { + append(DateFormat.fullText(uiState.endDate)) + } + withStyle(style = CalendarCaption2.toSpanStyle()) { + append(caption) + } + } + + Column( + modifier = modifier + ) { + CommonHeader( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 16.dp, horizontal = 24.dp), + title = stringResource(id = R.string.organization_creation_end_date_title) + ) + MonthCalendarView( + modifier = Modifier.weight(1f), + selection = uiState.endDate, + calendarPadding = calendarPadding + ) { + viewModel.postIntent(CreationIntent.ChangeEndDate(it)) + } + Text( + modifier = Modifier + .screenHorizonPadding() + .fillMaxWidth() + .align(Alignment.CenterHorizontally), + text = annotatedString, + textAlign = TextAlign.Center + ) + MediumButton( + modifier = Modifier + .screenHorizonPadding() + .fillMaxWidth() + .padding(vertical = 16.dp), + text = stringResource(id = R.string.sign_up_button), + enabled = uiState.enabledStepButton[uiState.step.currentStep] ?: false + ) { + viewModel.postIntent(CreationIntent.ClickNextButton) + } + } +} \ No newline at end of file diff --git a/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/component/creation/ImageView.kt b/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/component/creation/ImageView.kt new file mode 100644 index 00000000..28f26308 --- /dev/null +++ b/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/component/creation/ImageView.kt @@ -0,0 +1,105 @@ +package com.easyhz.noffice.feature.organization.component.creation + +import android.net.Uri +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.easyhz.noffice.core.design_system.R +import com.easyhz.noffice.core.design_system.component.button.MediumButton +import com.easyhz.noffice.core.design_system.component.image.OrganizationCreationImageView +import com.easyhz.noffice.core.design_system.extension.noRippleClickable +import com.easyhz.noffice.core.design_system.theme.Grey300 +import com.easyhz.noffice.core.design_system.theme.Grey50 +import com.easyhz.noffice.feature.organization.contract.creation.CreationIntent +import com.easyhz.noffice.feature.organization.screen.creation.OrganizationCreationViewModel + +@Composable +internal fun ImageView( + modifier: Modifier = Modifier, + viewModel: OrganizationCreationViewModel = hiltViewModel() +) { + val uiState by viewModel.uiState.collectAsStateWithLifecycle() + + Column( + modifier = modifier + ) { + CommonHeader( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 16.dp, horizontal = 8.dp), + title = stringResource(id = R.string.organization_creation_image_title) + ) + Box( + modifier = Modifier.padding(vertical = 28.dp) + .size(280.dp) + .align(Alignment.CenterHorizontally) + .clip(RoundedCornerShape(24.dp)) + .background(Grey50) + .noRippleClickable { + viewModel.postIntent(CreationIntent.ClickImageView) + } + ) { + when (uiState.organizationImage) { + Uri.EMPTY -> { + EmptyImageView( + modifier = Modifier.align(Alignment.Center) + ) + } + else -> { + OrganizationCreationImageView( + modifier = Modifier.size(280.dp), + image = uiState.organizationImage + ) + } + } + } + Spacer( + modifier = Modifier + .fillMaxWidth() + .weight(1f) + ) + MediumButton( + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 16.dp), + text = stringResource(id = R.string.sign_up_button), + enabled = uiState.enabledStepButton[uiState.step.currentStep] ?: false + ) { + viewModel.postIntent(CreationIntent.ClickNextButton) + } + } +} + +@Composable +private fun EmptyImageView( + modifier: Modifier = Modifier, +) { + Box( + modifier = modifier + ) { + Icon( + modifier = Modifier + .align(Alignment.Center) + .size(36.dp), + painter = painterResource(id = R.drawable.ic_upload), + contentDescription = "upload", + tint = Grey300 + ) + } +} \ No newline at end of file diff --git a/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/component/creation/OrganizationNameView.kt b/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/component/creation/OrganizationNameView.kt new file mode 100644 index 00000000..d5542d24 --- /dev/null +++ b/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/component/creation/OrganizationNameView.kt @@ -0,0 +1,64 @@ +package com.easyhz.noffice.feature.organization.component.creation + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.easyhz.noffice.core.design_system.R +import com.easyhz.noffice.core.design_system.component.button.MediumButton +import com.easyhz.noffice.core.design_system.component.textField.MainTextField +import com.easyhz.noffice.core.design_system.extension.noRippleClickable +import com.easyhz.noffice.feature.organization.contract.creation.CreationIntent +import com.easyhz.noffice.feature.organization.contract.creation.CreationState.Companion.ORGANIZATION_NAME_MAX +import com.easyhz.noffice.feature.organization.screen.creation.OrganizationCreationViewModel + +@Composable +internal fun OrganizationNameView( + modifier: Modifier = Modifier, + viewModel: OrganizationCreationViewModel = hiltViewModel() +) { + val uiState by viewModel.uiState.collectAsStateWithLifecycle() + + Column( + modifier = modifier + ) { + CommonHeader( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 16.dp, horizontal = 8.dp), + title = stringResource(id = R.string.organization_creation_name_title) + ) + MainTextField( + modifier = Modifier.weight(0.3f), + value = uiState.organizationName, + onValueChange = { viewModel.postIntent(CreationIntent.ChangeOrganizationNameTextValue(it)) }, + title = null, + placeholder = stringResource(id = R.string.organization_creation_name_placeholder), + isFilled = false, + singleLine = true, + maxCount = ORGANIZATION_NAME_MAX, + onClickIcon = { viewModel.postIntent(CreationIntent.ClearOrganizationName) } + ) + Spacer(modifier = Modifier + .fillMaxWidth() + .weight(1f) + .noRippleClickable { viewModel.postIntent(CreationIntent.ClearFocus) } + ) + MediumButton( + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 16.dp), + text = stringResource(id = R.string.sign_up_button), + enabled = uiState.enabledStepButton[uiState.step.currentStep] ?: false + ) { + viewModel.postIntent(CreationIntent.ClickNextButton) + } + } +} \ No newline at end of file diff --git a/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/component/creation/PromotionView.kt b/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/component/creation/PromotionView.kt new file mode 100644 index 00000000..14cf4dc8 --- /dev/null +++ b/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/component/creation/PromotionView.kt @@ -0,0 +1,62 @@ +package com.easyhz.noffice.feature.organization.component.creation + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.easyhz.noffice.core.design_system.R +import com.easyhz.noffice.core.design_system.component.button.MediumButton +import com.easyhz.noffice.core.design_system.component.textField.MainTextField +import com.easyhz.noffice.core.design_system.extension.noRippleClickable +import com.easyhz.noffice.feature.organization.contract.creation.CreationIntent +import com.easyhz.noffice.feature.organization.screen.creation.OrganizationCreationViewModel + +@Composable +internal fun PromotionView( + modifier: Modifier = Modifier, + viewModel: OrganizationCreationViewModel = hiltViewModel() +) { + val uiState by viewModel.uiState.collectAsStateWithLifecycle() + + Column( + modifier = modifier + ) { + CommonHeader( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 16.dp, horizontal = 8.dp), + title = stringResource(id = R.string.organization_creation_promotion_title) + ) + MainTextField( + modifier = Modifier.weight(0.3f), + value = uiState.promotionCode, + onValueChange = { viewModel.postIntent(CreationIntent.ChangePromotionTextValue(it)) }, + title = null, + placeholder = stringResource(id = R.string.organization_creation_promotion_placeholder), + isFilled = false, + singleLine = true, + onClickIcon = { viewModel.postIntent(CreationIntent.ClearPromotionCode) } + ) + Spacer(modifier = Modifier + .fillMaxWidth() + .weight(1f) + .noRippleClickable { viewModel.postIntent(CreationIntent.ClearFocus) } + ) + MediumButton( + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 16.dp), + text = stringResource(id = R.string.sign_up_button), + enabled = uiState.enabledStepButton[uiState.step.currentStep] ?: false + ) { + viewModel.postIntent(CreationIntent.ClickNextButton) + } + } +} \ No newline at end of file diff --git a/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/component/invitation/UrlView.kt b/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/component/invitation/UrlView.kt new file mode 100644 index 00000000..a4cddfeb --- /dev/null +++ b/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/component/invitation/UrlView.kt @@ -0,0 +1,41 @@ +package com.easyhz.noffice.feature.organization.component.invitation + +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import com.easyhz.noffice.core.design_system.extension.noRippleClickable +import com.easyhz.noffice.core.design_system.theme.Grey100 +import com.easyhz.noffice.core.design_system.theme.Grey600 +import com.easyhz.noffice.core.design_system.theme.SubBody14 + +@Composable +internal fun UrlView( + modifier: Modifier = Modifier, + text: String, + onClick: () -> Unit +) { + Box( + modifier = modifier + .height(48.dp) + .border(width = 1.dp, color = Grey100, shape = RoundedCornerShape(8.dp)) + .padding(horizontal = 12.dp) + .noRippleClickable { onClick() } + ) { + Text( + text = text, + modifier = Modifier.align(Alignment.CenterStart), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = SubBody14, + color = Grey600 + ) + } +} \ No newline at end of file diff --git a/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/contract/creation/CreationIntent.kt b/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/contract/creation/CreationIntent.kt new file mode 100644 index 00000000..cf5dee80 --- /dev/null +++ b/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/contract/creation/CreationIntent.kt @@ -0,0 +1,19 @@ +package com.easyhz.noffice.feature.organization.contract.creation + +import android.net.Uri +import com.easyhz.noffice.core.common.base.UiIntent +import java.time.LocalDate + +sealed class CreationIntent: UiIntent() { + data object ClickBackButton: CreationIntent() + data object ClickNextButton: CreationIntent() + data class ChangeOrganizationNameTextValue(val text: String): CreationIntent() + data object ClearOrganizationName: CreationIntent() + data object ClearFocus: CreationIntent() + data class ClickCategoryItem(val selectedIndex: Int): CreationIntent() + data object ClickImageView : CreationIntent() + data class PickImage(val uri: Uri?) : CreationIntent() + data class ChangeEndDate(val date: LocalDate): CreationIntent() + data class ChangePromotionTextValue(val text: String): CreationIntent() + data object ClearPromotionCode: CreationIntent() +} \ No newline at end of file diff --git a/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/contract/creation/CreationSideEffect.kt b/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/contract/creation/CreationSideEffect.kt new file mode 100644 index 00000000..ba8ae291 --- /dev/null +++ b/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/contract/creation/CreationSideEffect.kt @@ -0,0 +1,10 @@ +package com.easyhz.noffice.feature.organization.contract.creation + +import com.easyhz.noffice.core.common.base.UiSideEffect + +sealed class CreationSideEffect: UiSideEffect() { + data object ClearFocus: CreationSideEffect() + data object NavigateToGallery: CreationSideEffect() + data object NavigateToHome: CreationSideEffect() + data class NavigateToInvitation(val invitationUrl: String, val imageUrl: String): CreationSideEffect() +} \ No newline at end of file diff --git a/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/contract/creation/CreationState.kt b/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/contract/creation/CreationState.kt new file mode 100644 index 00000000..a61f5984 --- /dev/null +++ b/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/contract/creation/CreationState.kt @@ -0,0 +1,61 @@ +package com.easyhz.noffice.feature.organization.contract.creation + +import android.net.Uri +import com.easyhz.noffice.core.common.base.UiState +import com.easyhz.noffice.core.common.extension.toEnumMap +import com.easyhz.noffice.core.common.util.Step +import com.easyhz.noffice.core.common.util.toEnabledStepButton +import com.easyhz.noffice.feature.organization.util.creation.Category +import com.easyhz.noffice.feature.organization.util.creation.CreationStep +import com.easyhz.noffice.feature.organization.util.creation.toState +import java.time.LocalDate +import java.util.EnumMap + +data class CreationState( + val step: Step, + val enabledStepButton: EnumMap, + val organizationName: String, + val category: List, + val organizationImage: Uri, + val isEnabledGallery: Boolean, + val endDate: LocalDate, + val promotionCode: String, +): UiState() { + companion object { + const val ORGANIZATION_NAME_MAX = 10 + fun init() = CreationState( + step = Step(currentStep = CreationStep.ORGANIZATION_NAME, previousStep = null), + enabledStepButton = CreationStep.entries.toEnabledStepButton(), + organizationName = "", + category = Category.toState(), + organizationImage = Uri.EMPTY, + isEnabledGallery = true, + endDate = LocalDate.now(), + promotionCode = "" + ) + } + + fun CreationState.updateStep(currentStep: CreationStep): CreationState = this.copy( + step = step.copy( + previousStep = step.currentStep, + currentStep = currentStep, + ), + ) + + fun CreationState.updateCategoryItem(selectedIndex: Int): CreationState { + val updatedCategory = category.mapIndexed { index, categoryState -> + categoryState.copy(isSelected = categoryState.isSelected.xor(index == selectedIndex)) + } + return copy( + category = updatedCategory, + enabledStepButton = enabledStepButton.toMutableMap().apply { + this[step.currentStep] = updatedCategory.any { it.isSelected } + }.toEnumMap() + ) + } +} + +data class CategoryState( + val title: String, + val isSelected: Boolean +) \ No newline at end of file diff --git a/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/contract/invitation/InvitationIntent.kt b/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/contract/invitation/InvitationIntent.kt new file mode 100644 index 00000000..8e0b6365 --- /dev/null +++ b/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/contract/invitation/InvitationIntent.kt @@ -0,0 +1,9 @@ +package com.easyhz.noffice.feature.organization.contract.invitation + +import com.easyhz.noffice.core.common.base.UiIntent + +sealed class InvitationIntent: UiIntent() { + data class InitScreen(val invitationUrl: String, val imageUrl: String): InvitationIntent() + data object ClickHomeButton: InvitationIntent() + data object ClickCopyUrl: InvitationIntent() +} \ No newline at end of file diff --git a/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/contract/invitation/InvitationSideEffect.kt b/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/contract/invitation/InvitationSideEffect.kt new file mode 100644 index 00000000..0bbfd2c1 --- /dev/null +++ b/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/contract/invitation/InvitationSideEffect.kt @@ -0,0 +1,8 @@ +package com.easyhz.noffice.feature.organization.contract.invitation + +import com.easyhz.noffice.core.common.base.UiSideEffect + +sealed class InvitationSideEffect: UiSideEffect() { + data object NavigateToHome: InvitationSideEffect() + data class CopyUrl(val url: String): InvitationSideEffect() +} \ No newline at end of file diff --git a/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/contract/invitation/InvitationState.kt b/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/contract/invitation/InvitationState.kt new file mode 100644 index 00000000..aced2148 --- /dev/null +++ b/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/contract/invitation/InvitationState.kt @@ -0,0 +1,15 @@ +package com.easyhz.noffice.feature.organization.contract.invitation + +import com.easyhz.noffice.core.common.base.UiState + +data class InvitationState( + val invitationUrl: String, + val imageUrl: String, +): UiState() { + companion object { + fun init() = InvitationState( + invitationUrl = "", + imageUrl = "" + ) + } +} diff --git a/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/contract/organization/OrganizationIntent.kt b/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/contract/organization/OrganizationIntent.kt index 33d5b3cf..0d7e934d 100644 --- a/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/contract/organization/OrganizationIntent.kt +++ b/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/contract/organization/OrganizationIntent.kt @@ -3,4 +3,5 @@ package com.easyhz.noffice.feature.organization.contract.organization import com.easyhz.noffice.core.common.base.UiIntent sealed class OrganizationIntent: UiIntent() { + data object ClickOrganizationCreation: OrganizationIntent() } \ No newline at end of file diff --git a/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/contract/organization/OrganizationSideEffect.kt b/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/contract/organization/OrganizationSideEffect.kt index 21c281b2..5aef5e21 100644 --- a/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/contract/organization/OrganizationSideEffect.kt +++ b/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/contract/organization/OrganizationSideEffect.kt @@ -3,4 +3,6 @@ package com.easyhz.noffice.feature.organization.contract.organization import com.easyhz.noffice.core.common.base.UiSideEffect sealed class OrganizationSideEffect: UiSideEffect() { + + data object NavigateToCreation: OrganizationSideEffect() } \ No newline at end of file diff --git a/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/screen/creation/OrganizationCreationScreen.kt b/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/screen/creation/OrganizationCreationScreen.kt new file mode 100644 index 00000000..6f56de1b --- /dev/null +++ b/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/screen/creation/OrganizationCreationScreen.kt @@ -0,0 +1,118 @@ +package com.easyhz.noffice.feature.organization.screen.creation + +import androidx.activity.compose.BackHandler +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.PickVisualMediaRequest +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.SizeTransform +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.slideInHorizontally +import androidx.compose.animation.slideOutHorizontally +import androidx.compose.animation.togetherWith +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Icon +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.easyhz.noffice.core.common.util.collectInSideEffectWithLifecycle +import com.easyhz.noffice.core.design_system.R +import com.easyhz.noffice.core.design_system.component.scaffold.NofficeBasicScaffold +import com.easyhz.noffice.core.design_system.component.topBar.DetailTopBar +import com.easyhz.noffice.core.design_system.extension.screenHorizonPadding +import com.easyhz.noffice.core.design_system.theme.Grey400 +import com.easyhz.noffice.core.design_system.theme.White +import com.easyhz.noffice.core.design_system.util.topBar.DetailTopBarMenu +import com.easyhz.noffice.feature.organization.component.creation.CategoryView +import com.easyhz.noffice.feature.organization.component.creation.EndDateView +import com.easyhz.noffice.feature.organization.component.creation.OrganizationNameView +import com.easyhz.noffice.feature.organization.component.creation.ImageView +import com.easyhz.noffice.feature.organization.component.creation.PromotionView +import com.easyhz.noffice.feature.organization.contract.creation.CreationIntent +import com.easyhz.noffice.feature.organization.contract.creation.CreationSideEffect +import com.easyhz.noffice.feature.organization.util.creation.CreationStep + +@Composable +fun OrganizationCreationScreen( + modifier: Modifier = Modifier, + viewModel: OrganizationCreationViewModel = hiltViewModel(), + navigateToInvitation: (String, String) -> Unit, + navigateToHome: () -> Unit +) { + val uiState by viewModel.uiState.collectAsStateWithLifecycle() + val focusManager = LocalFocusManager.current + val galleryLauncher = + rememberLauncherForActivityResult( + contract = ActivityResultContracts.PickVisualMedia(), + onResult = { viewModel.postIntent(CreationIntent.PickImage(it)) } + ) + + BackHandler(onBack = { + viewModel.postIntent(CreationIntent.ClickBackButton) + }) + + NofficeBasicScaffold( + modifier = modifier, + containerColor = White, + topBar = { + DetailTopBar( + leadingItem = DetailTopBarMenu( + content = { + Icon( + modifier = Modifier.size(24.dp), + painter = painterResource(id = R.drawable.ic_chevron_left), + contentDescription = "left", + tint = Grey400 + ) + }, + onClick = { viewModel.postIntent(CreationIntent.ClickBackButton) } + ) + ) + } + ) { + AnimatedContent( + modifier = Modifier + .fillMaxSize() + .padding(it), + targetState = uiState.step.currentStep, + transitionSpec = { + if (uiState.step.currentStep.ordinal >= (uiState.step.previousStep?.ordinal ?: 0)) { + (slideInHorizontally { width -> width } + fadeIn()).togetherWith( + slideOutHorizontally { width -> -width } + fadeOut()) + } else { + (slideInHorizontally { width -> -width } + fadeIn()).togetherWith( + slideOutHorizontally { width -> width } + fadeOut()) + }.using(SizeTransform(clip = false)) + }, label = "organizationCreationFlow" + ) { targetScreen -> + when(targetScreen) { + CreationStep.ORGANIZATION_NAME -> { OrganizationNameView(modifier = Modifier.screenHorizonPadding()) } + CreationStep.CATEGORY -> { CategoryView(modifier = Modifier.screenHorizonPadding()) } + CreationStep.IMAGE -> { ImageView(modifier = Modifier.screenHorizonPadding()) } + CreationStep.END_DATE -> { EndDateView() } + CreationStep.PROMOTION -> { PromotionView(modifier = Modifier.screenHorizonPadding()) } + } + } + } + + viewModel.sideEffect.collectInSideEffectWithLifecycle {sideEffect -> + when(sideEffect) { + is CreationSideEffect.ClearFocus -> { focusManager.clearFocus() } + is CreationSideEffect.NavigateToGallery -> { + galleryLauncher.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)) + } + is CreationSideEffect.NavigateToHome -> { navigateToHome() } + is CreationSideEffect.NavigateToInvitation -> { + navigateToInvitation(sideEffect.invitationUrl, sideEffect.imageUrl) + } + } + } +} \ No newline at end of file diff --git a/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/screen/creation/OrganizationCreationViewModel.kt b/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/screen/creation/OrganizationCreationViewModel.kt new file mode 100644 index 00000000..1beddefd --- /dev/null +++ b/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/screen/creation/OrganizationCreationViewModel.kt @@ -0,0 +1,110 @@ +package com.easyhz.noffice.feature.organization.screen.creation + +import android.net.Uri +import com.easyhz.noffice.core.common.base.BaseViewModel +import com.easyhz.noffice.core.common.util.updateStepButton +import com.easyhz.noffice.feature.organization.contract.creation.CreationIntent +import com.easyhz.noffice.feature.organization.contract.creation.CreationSideEffect +import com.easyhz.noffice.feature.organization.contract.creation.CreationState +import com.easyhz.noffice.feature.organization.contract.creation.CreationState.Companion.ORGANIZATION_NAME_MAX +import dagger.hilt.android.lifecycle.HiltViewModel +import java.time.LocalDate +import javax.inject.Inject + +@HiltViewModel +class OrganizationCreationViewModel @Inject constructor( + +) : BaseViewModel( + initialState = CreationState.init() +) { + override fun handleIntent(intent: CreationIntent) { + when (intent) { + is CreationIntent.ClickBackButton -> { onClickBackButton() } + is CreationIntent.ClickNextButton -> { onClickNextButton() } + is CreationIntent.ChangeOrganizationNameTextValue -> { onChangeOrganizationNameTextValue(intent.text) } + is CreationIntent.ClearOrganizationName -> { onClearOrganizationName() } + is CreationIntent.ClearFocus -> { onClearFocus() } + is CreationIntent.ClickCategoryItem -> { onClickCategoryItem(intent.selectedIndex) } + is CreationIntent.ClickImageView -> { onClickImageView() } + is CreationIntent.PickImage -> { onPickImage(intent.uri) } + is CreationIntent.ChangeEndDate -> { onChangeEndDate(intent.date) } + is CreationIntent.ChangePromotionTextValue -> { onChangePromotionTextValue(intent.text) } + is CreationIntent.ClearPromotionCode -> { onClearPromotionCode() } + } + } + + private fun onClickBackButton() { + currentState.step.currentStep.beforeStep()?.let { beforeStep -> + reduce { updateStep(currentStep = beforeStep) } + } ?: onNavigateToHome() + } + + private fun onClickNextButton() { + currentState.step.currentStep.nextStep()?.let { nextStep -> + reduce { updateStep(currentStep = nextStep) } + postSideEffect { CreationSideEffect.ClearFocus } + } ?: onNavigateToInvitation() + } + + private fun onChangeOrganizationNameTextValue(newText: String) { + if (newText.length > ORGANIZATION_NAME_MAX) return + val isEnabledButton = newText.isNotBlank() + reduce { + copy( + organizationName = newText, + enabledStepButton = enabledStepButton.updateStepButton( + step.currentStep, + isEnabledButton + ) + ) + } + } + + private fun onClearOrganizationName() { + reduce { copy(organizationName = "") } + } + + private fun onClearFocus() { + postSideEffect { CreationSideEffect.ClearFocus } + } + + private fun onClickCategoryItem(selectedIndex: Int) { + reduce { updateCategoryItem(selectedIndex) } + } + + private fun onClickImageView() { + if (!currentState.isEnabledGallery) return + postSideEffect { CreationSideEffect.NavigateToGallery } + reduce { copy(isEnabledGallery = false) } + } + + private fun onPickImage(uri: Uri?) { + reduce { copy(organizationImage = uri ?: Uri.EMPTY, isEnabledGallery = true) } + } + + private fun onChangeEndDate(date: LocalDate) { + reduce { copy(endDate = date) } + } + + private fun onChangePromotionTextValue(newText: String) { + reduce { copy(promotionCode = newText) } + } + + private fun onClearPromotionCode() { + reduce { copy(promotionCode = "") } + } + + private fun onNavigateToHome() { + postSideEffect { CreationSideEffect.NavigateToHome } + } + + // TODO: 서버 통신 로직 추가 + private fun onNavigateToInvitation() { + postSideEffect { + CreationSideEffect.NavigateToInvitation( + "www.noffice/dhedgyeqi3e83", + currentState.organizationImage.toString() + ) + } + } +} \ No newline at end of file diff --git a/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/screen/invitation/InvitationScreen.kt b/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/screen/invitation/InvitationScreen.kt new file mode 100644 index 00000000..9d9a4771 --- /dev/null +++ b/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/screen/invitation/InvitationScreen.kt @@ -0,0 +1,118 @@ +package com.easyhz.noffice.feature.organization.screen.invitation + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalClipboardManager +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.easyhz.noffice.core.common.util.collectInSideEffectWithLifecycle +import com.easyhz.noffice.core.design_system.R +import com.easyhz.noffice.core.design_system.component.button.MediumButton +import com.easyhz.noffice.core.design_system.component.image.OrganizationImage +import com.easyhz.noffice.core.design_system.component.scaffold.NofficeBasicScaffold +import com.easyhz.noffice.core.design_system.extension.screenHorizonPadding +import com.easyhz.noffice.core.design_system.theme.Grey100 +import com.easyhz.noffice.core.design_system.theme.Grey600 +import com.easyhz.noffice.core.design_system.theme.SubTitle1 +import com.easyhz.noffice.core.design_system.theme.Title4 +import com.easyhz.noffice.feature.organization.component.invitation.UrlView +import com.easyhz.noffice.feature.organization.contract.invitation.InvitationIntent +import com.easyhz.noffice.feature.organization.contract.invitation.InvitationSideEffect + +@Composable +fun OrganizationInvitationScreen( + modifier: Modifier = Modifier, + viewModel: InvitationViewModel = hiltViewModel(), + invitationUrl: String, + imageUrl: String, + navigateToHome: () -> Unit, +) { + val uiState by viewModel.uiState.collectAsStateWithLifecycle() + val clipboardManager = LocalClipboardManager.current + + LaunchedEffect(Unit) { + viewModel.postIntent(InvitationIntent.InitScreen(invitationUrl, imageUrl)) + } + + NofficeBasicScaffold { + Column( + modifier = modifier + .screenHorizonPadding() + .fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Column( + modifier = Modifier.weight(1f), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Spacer(modifier = Modifier.weight(0.8f)) + OrganizationImage( + modifier = Modifier.size(120.dp), + imageUrl = uiState.imageUrl + ) + Spacer(modifier = Modifier.height(18.dp)) + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + Text(text = stringResource(id = R.string.organization_creation_success_title), style = Title4) + Text(text = stringResource(id = R.string.organization_creation_success_sub_title), style = SubTitle1) + } + Spacer(modifier = Modifier.height(18.dp)) + UrlView( + modifier = Modifier + .padding(horizontal = 32.dp) + .fillMaxWidth(), + text = uiState.invitationUrl + ) { + viewModel.postIntent(InvitationIntent.ClickCopyUrl) + } + Spacer(modifier = Modifier.weight(1f)) + } + Row( + modifier = Modifier + .padding(bottom = 16.dp), + horizontalArrangement = Arrangement.spacedBy(10.dp) + ) { + MediumButton( + modifier = Modifier.weight(1f), + text = stringResource(id = R.string.organization_creation_success_home_button), + contentColor = Grey600, + containerColor = Grey100 + ) { + viewModel.postIntent(InvitationIntent.ClickHomeButton) + } + MediumButton( + modifier = Modifier.weight(1f), + text = stringResource(id = R.string.organization_creation_success_copy_button) + ) { + viewModel.postIntent(InvitationIntent.ClickCopyUrl) + } + } + } + } + + viewModel.sideEffect.collectInSideEffectWithLifecycle {sideEffect -> + when(sideEffect) { + is InvitationSideEffect.NavigateToHome -> { navigateToHome() } + is InvitationSideEffect.CopyUrl -> { clipboardManager.setText(AnnotatedString(sideEffect.url)) } + } + } +} \ No newline at end of file diff --git a/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/screen/invitation/InvitationViewModel.kt b/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/screen/invitation/InvitationViewModel.kt new file mode 100644 index 00000000..c2466023 --- /dev/null +++ b/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/screen/invitation/InvitationViewModel.kt @@ -0,0 +1,35 @@ +package com.easyhz.noffice.feature.organization.screen.invitation + +import com.easyhz.noffice.core.common.base.BaseViewModel +import com.easyhz.noffice.feature.organization.contract.invitation.InvitationIntent +import com.easyhz.noffice.feature.organization.contract.invitation.InvitationSideEffect +import com.easyhz.noffice.feature.organization.contract.invitation.InvitationState +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject + +@HiltViewModel +class InvitationViewModel @Inject constructor( + +): BaseViewModel( + initialState = InvitationState.init() +) { + override fun handleIntent(intent: InvitationIntent) { + when(intent) { + is InvitationIntent.InitScreen -> { initScreen(intent.invitationUrl, intent.imageUrl) } + is InvitationIntent.ClickHomeButton -> { onClickHomeButton() } + is InvitationIntent.ClickCopyUrl -> { onClickCopyUrl() } + } + } + + private fun initScreen(invitationUrl: String, imageUrl: String) { + reduce { copy(invitationUrl = invitationUrl, imageUrl = imageUrl) } + } + + private fun onClickHomeButton() { + postSideEffect { InvitationSideEffect.NavigateToHome } + } + + private fun onClickCopyUrl() { + postSideEffect { InvitationSideEffect.CopyUrl(currentState.invitationUrl) } + } +} \ No newline at end of file diff --git a/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/screen/organization/OrganizationScreen.kt b/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/screen/organization/OrganizationScreen.kt index e69b9e08..aea1ce31 100644 --- a/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/screen/organization/OrganizationScreen.kt +++ b/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/screen/organization/OrganizationScreen.kt @@ -13,6 +13,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.easyhz.noffice.core.common.util.collectInSideEffectWithLifecycle import com.easyhz.noffice.core.design_system.R import com.easyhz.noffice.core.design_system.component.button.IconMediumButton import com.easyhz.noffice.core.design_system.component.exception.ExceptionView @@ -21,17 +22,19 @@ import com.easyhz.noffice.core.design_system.component.topBar.HomeTopBar import com.easyhz.noffice.core.design_system.extension.screenHorizonPadding import com.easyhz.noffice.core.design_system.util.exception.ExceptionType import com.easyhz.noffice.feature.organization.component.organization.OrganizationItem +import com.easyhz.noffice.feature.organization.contract.organization.OrganizationIntent +import com.easyhz.noffice.feature.organization.contract.organization.OrganizationSideEffect import com.easyhz.noffice.feature.organization.util.OrganizationTopBarMenu @Composable fun OrganizationScreen( modifier: Modifier = Modifier, - viewModel: OrganizationViewModel = hiltViewModel() + viewModel: OrganizationViewModel = hiltViewModel(), + navigateToCreation: () -> Unit ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() NofficeScaffold( - modifier = modifier, topBar = { HomeTopBar( tabs = enumValues(), @@ -44,11 +47,11 @@ fun OrganizationScreen( if(uiState.organizationList.isEmpty()) { ExceptionView( modifier = Modifier.fillMaxSize(), - type = ExceptionType.NO_GROUP + type = ExceptionType.NO_ORGANIZATION ) } Column( - modifier = Modifier + modifier = modifier .padding(top = it.calculateTopPadding()) .screenHorizonPadding() ) { @@ -57,7 +60,7 @@ fun OrganizationScreen( text = stringResource(id = R.string.organization_new), iconId = R.drawable.ic_plus ) { - + viewModel.postIntent(OrganizationIntent.ClickOrganizationCreation) } LazyColumn( modifier = Modifier.fillMaxSize() @@ -75,4 +78,10 @@ fun OrganizationScreen( } } + + viewModel.sideEffect.collectInSideEffectWithLifecycle {sideEffect -> + when(sideEffect) { + is OrganizationSideEffect.NavigateToCreation -> { navigateToCreation() } + } + } } \ No newline at end of file diff --git a/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/screen/organization/OrganizationViewModel.kt b/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/screen/organization/OrganizationViewModel.kt index 3737379e..e5cdea6d 100644 --- a/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/screen/organization/OrganizationViewModel.kt +++ b/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/screen/organization/OrganizationViewModel.kt @@ -14,6 +14,12 @@ class OrganizationViewModel @Inject constructor( initialState = OrganizationState.init() ) { override fun handleIntent(intent: OrganizationIntent) { - TODO("Not yet implemented") + when(intent) { + is OrganizationIntent.ClickOrganizationCreation -> { onClickOrganizationCreation() } + } + } + + private fun onClickOrganizationCreation() { + postSideEffect { OrganizationSideEffect.NavigateToCreation } } } \ No newline at end of file diff --git a/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/util/creation/Category.kt b/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/util/creation/Category.kt new file mode 100644 index 00000000..c55a5ad9 --- /dev/null +++ b/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/util/creation/Category.kt @@ -0,0 +1,14 @@ +package com.easyhz.noffice.feature.organization.util.creation + +import com.easyhz.noffice.feature.organization.contract.creation.CategoryState + +//enum class Category { +//} + +val Category = listOf("IT 계열", "문화 생활", "어학", "예술", "음악 · 공연", "스터디 · 연구", "스포츠", "창업", "종교", "마케팅 · 홍보", "자연과학", "기타") + +fun List.toState(): List = map { + CategoryState( + it, false + ) +} \ No newline at end of file diff --git a/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/util/creation/CreationStep.kt b/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/util/creation/CreationStep.kt new file mode 100644 index 00000000..cb0c87f7 --- /dev/null +++ b/feature/organization/src/main/java/com/easyhz/noffice/feature/organization/util/creation/CreationStep.kt @@ -0,0 +1,28 @@ +package com.easyhz.noffice.feature.organization.util.creation + +import com.easyhz.noffice.core.common.util.StepRequired + +enum class CreationStep: StepRequired { + ORGANIZATION_NAME { + override val isRequired: Boolean + get() = true + }, CATEGORY { + override val isRequired: Boolean + get() = true + }, IMAGE { + override val isRequired: Boolean + get() = false + }, END_DATE { + override val isRequired: Boolean + get() = false + }, PROMOTION { + override val isRequired: Boolean + get() = false + }; + + fun nextStep(): CreationStep? = + entries.getOrNull(this.ordinal + 1) + + fun beforeStep(): CreationStep? = + entries.getOrNull(this.ordinal - 1) +} \ No newline at end of file diff --git a/feature/sign/src/main/java/com/easyhz/noffice/feature/sign/component/signUp/NameView.kt b/feature/sign/src/main/java/com/easyhz/noffice/feature/sign/component/signUp/NameView.kt index be320a0b..1823c894 100644 --- a/feature/sign/src/main/java/com/easyhz/noffice/feature/sign/component/signUp/NameView.kt +++ b/feature/sign/src/main/java/com/easyhz/noffice/feature/sign/component/signUp/NameView.kt @@ -47,7 +47,7 @@ internal fun NameView( placeholder = stringResource(id = R.string.sign_up_name_placeholder), isFilled = false, singleLine = true, - icon = null + icon = null // FIXME ) Spacer(modifier = Modifier .fillMaxWidth() diff --git a/feature/sign/src/main/java/com/easyhz/noffice/feature/sign/component/signUp/TermsView.kt b/feature/sign/src/main/java/com/easyhz/noffice/feature/sign/component/signUp/TermsView.kt index bad2726d..600de8fb 100644 --- a/feature/sign/src/main/java/com/easyhz/noffice/feature/sign/component/signUp/TermsView.kt +++ b/feature/sign/src/main/java/com/easyhz/noffice/feature/sign/component/signUp/TermsView.kt @@ -1,5 +1,6 @@ package com.easyhz.noffice.feature.sign.component.signUp +import androidx.compose.animation.Crossfade import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -114,14 +115,19 @@ private fun TermsCheck( modifier = Modifier.padding(vertical = 16.dp) ) { Box(modifier = Modifier.size(32.dp).noRippleClickable { onClickAllCheck() }) { - Image( - modifier = Modifier - .align(Alignment.TopStart) - .padding(top = 3.dp) - .size(18.dp), - painter = painterResource(id = allCheckIconId), - contentDescription = "check" - ) + Crossfade( + modifier = Modifier.align(Alignment.TopStart), + targetState = allCheckIconId, + label = "Check" + ) { iconId -> + Image( + modifier = Modifier + .padding(top = 3.dp) + .size(18.dp), + painter = painterResource(id = iconId), + contentDescription = "check" + ) + } } Column( verticalArrangement = Arrangement.spacedBy(8.dp) @@ -173,13 +179,18 @@ private fun TermsItem( verticalAlignment = Alignment.CenterVertically ) { Box(modifier = Modifier.size(32.dp).noRippleClickable { onClickCheck() }) { - Image( - modifier = Modifier - .align(Alignment.CenterStart) - .size(18.dp), - painter = painterResource(id = checkedIconId), - contentDescription = "check" - ) + Crossfade( + modifier = Modifier.align(Alignment.CenterStart), + targetState = checkedIconId, + label = "Check" + ) { iconId -> + Image( + modifier = Modifier + .size(18.dp), + painter = painterResource(id = iconId), + contentDescription = "check" + ) + } } Row( modifier = Modifier.noRippleClickable { onClickDetail() }, diff --git a/feature/sign/src/main/java/com/easyhz/noffice/feature/sign/contract/signUp/SignUpIntent.kt b/feature/sign/src/main/java/com/easyhz/noffice/feature/sign/contract/signUp/SignUpIntent.kt index 9f86d5d7..8cf8f990 100644 --- a/feature/sign/src/main/java/com/easyhz/noffice/feature/sign/contract/signUp/SignUpIntent.kt +++ b/feature/sign/src/main/java/com/easyhz/noffice/feature/sign/contract/signUp/SignUpIntent.kt @@ -11,6 +11,6 @@ sealed class SignUpIntent: UiIntent() { data object ClickTermsAllCheck: SignUpIntent() data class ClickTermsCheck(val terms: Terms): SignUpIntent() data class ClickTermsDetail(val terms: Terms): SignUpIntent() - data class ChangeNameTextValue(val text: TextFieldValue): SignUpIntent() + data class ChangeNameTextValue(val text: String): SignUpIntent() data object ClearFocus: SignUpIntent() } \ No newline at end of file diff --git a/feature/sign/src/main/java/com/easyhz/noffice/feature/sign/contract/signUp/SignUpState.kt b/feature/sign/src/main/java/com/easyhz/noffice/feature/sign/contract/signUp/SignUpState.kt index 8f6857e8..631ed6ad 100644 --- a/feature/sign/src/main/java/com/easyhz/noffice/feature/sign/contract/signUp/SignUpState.kt +++ b/feature/sign/src/main/java/com/easyhz/noffice/feature/sign/contract/signUp/SignUpState.kt @@ -3,18 +3,20 @@ package com.easyhz.noffice.feature.sign.contract.signUp import androidx.compose.ui.text.input.TextFieldValue import com.easyhz.noffice.core.common.base.UiState import com.easyhz.noffice.core.common.extension.toEnumMap +import com.easyhz.noffice.core.common.util.Step +import com.easyhz.noffice.core.common.util.toEnabledStepButton +import com.easyhz.noffice.core.common.util.updateStepButton import com.easyhz.noffice.feature.sign.util.signUp.SignUpStep import com.easyhz.noffice.feature.sign.util.signUp.Terms -import com.easyhz.noffice.feature.sign.util.signUp.toEnabledStepButton import com.easyhz.noffice.feature.sign.util.signUp.toTermsMap import java.util.EnumMap data class SignUpState( - val step: Step, + val step: Step, val enabledStepButton: EnumMap, val isCheckedAllTerms: Boolean, val termsStatusMap: EnumMap, - val name: TextFieldValue, + val name: String, ) : UiState() { companion object { fun init() = SignUpState( @@ -22,7 +24,7 @@ data class SignUpState( enabledStepButton = SignUpStep.entries.toEnabledStepButton(), isCheckedAllTerms = false, termsStatusMap = Terms.entries.toTermsMap(), - name = TextFieldValue("") + name = "" ) } @@ -43,7 +45,7 @@ data class SignUpState( return copy( termsStatusMap = newMap, isCheckedAllTerms = isCheckedAll, - enabledStepButton = enabledStepButton.updateStatus(step.currentStep, isEnabledButton) + enabledStepButton = enabledStepButton.updateStepButton(step.currentStep, isEnabledButton) ) } @@ -53,17 +55,7 @@ data class SignUpState( return copy( termsStatusMap = newMap, isCheckedAllTerms = !isCheckedAllTerms, - enabledStepButton = enabledStepButton.updateStatus(step.currentStep, isEnabledButton) + enabledStepButton = enabledStepButton.updateStepButton(step.currentStep, isEnabledButton) ) } -} - -data class Step( - val currentStep: SignUpStep, - val previousStep: SignUpStep? -) - -internal fun EnumMap.updateStatus(key: SignUpStep, isEnabled: Boolean): EnumMap { - this[key] = isEnabled - return this } \ No newline at end of file diff --git a/feature/sign/src/main/java/com/easyhz/noffice/feature/sign/screen/signUp/SignUpViewModel.kt b/feature/sign/src/main/java/com/easyhz/noffice/feature/sign/screen/signUp/SignUpViewModel.kt index 64a641a4..1b11c6f1 100644 --- a/feature/sign/src/main/java/com/easyhz/noffice/feature/sign/screen/signUp/SignUpViewModel.kt +++ b/feature/sign/src/main/java/com/easyhz/noffice/feature/sign/screen/signUp/SignUpViewModel.kt @@ -2,10 +2,10 @@ package com.easyhz.noffice.feature.sign.screen.signUp import androidx.compose.ui.text.input.TextFieldValue import com.easyhz.noffice.core.common.base.BaseViewModel +import com.easyhz.noffice.core.common.util.updateStepButton import com.easyhz.noffice.feature.sign.contract.signUp.SignUpIntent import com.easyhz.noffice.feature.sign.contract.signUp.SignUpSideEffect import com.easyhz.noffice.feature.sign.contract.signUp.SignUpState -import com.easyhz.noffice.feature.sign.contract.signUp.updateStatus import com.easyhz.noffice.feature.sign.util.signUp.Terms import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject @@ -51,9 +51,9 @@ class SignUpViewModel @Inject constructor( reduce { updateTermsCheck(terms) } } - private fun onChangeNameTextValue(newText: TextFieldValue) { - val isEnabledButton = newText.text.isNotBlank() - reduce { copy(name = newText, enabledStepButton = enabledStepButton.updateStatus(step.currentStep, isEnabledButton)) } + private fun onChangeNameTextValue(newText: String) { + val isEnabledButton = newText.isNotBlank() + reduce { copy(name = newText, enabledStepButton = enabledStepButton.updateStepButton(step.currentStep, isEnabledButton)) } } private fun onClearFocus() { diff --git a/feature/sign/src/main/java/com/easyhz/noffice/feature/sign/util/signUp/SignUpStep.kt b/feature/sign/src/main/java/com/easyhz/noffice/feature/sign/util/signUp/SignUpStep.kt index 5e97d736..9de29a39 100644 --- a/feature/sign/src/main/java/com/easyhz/noffice/feature/sign/util/signUp/SignUpStep.kt +++ b/feature/sign/src/main/java/com/easyhz/noffice/feature/sign/util/signUp/SignUpStep.kt @@ -1,20 +1,19 @@ package com.easyhz.noffice.feature.sign.util.signUp -import java.util.EnumMap +import com.easyhz.noffice.core.common.util.StepRequired -enum class SignUpStep { - TERMS, NAME; +enum class SignUpStep: StepRequired { + TERMS { + override val isRequired: Boolean + get() = true + }, NAME { + override val isRequired: Boolean + get() = true + }; fun nextStep(): SignUpStep? = entries.getOrNull(this.ordinal + 1) fun beforeStep(): SignUpStep? = entries.getOrNull(this.ordinal - 1) -} - -fun List.toEnabledStepButton(): EnumMap = - EnumMap(SignUpStep::class.java).apply { - this@toEnabledStepButton.forEach { step -> - this[step] = false - } - } \ No newline at end of file +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d57b5cf4..e2857a08 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -26,6 +26,7 @@ retrofit = "2.9.0" okhttp = "4.12.0" serialization = "1.6.3" glide = "1.0.0-beta01" +calendar = "2.6.0-beta01" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -65,6 +66,7 @@ retrofit-core = { group = "com.squareup.retrofit2", name = "retrofit", version.r retrofit-gson-converter = { group = "com.squareup.retrofit2", name = "converter-gson", version.ref = "retrofit" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization"} glide = { group = "com.github.bumptech.glide", name = "compose", version.ref = "glide" } +calendar-compose = { group = "com.kizitonwose.calendar", name = "compose", version.ref = "calendar" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" }