From 6420e0eef7d2f5ede466896c5be7312b4ae02962 Mon Sep 17 00:00:00 2001 From: YeongSang Jeon Date: Sun, 28 May 2023 21:57:21 +0900 Subject: [PATCH 01/18] =?UTF-8?q?fix:=20deploy=20preview=EC=8B=9C=20Crasht?= =?UTF-8?q?ics=20Id=20=EC=97=90=EB=9F=AC=EA=B0=80=20=EB=B0=9C=EC=83=9D?= =?UTF-8?q?=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /** The Crashlytics build ID is missing. This occurs when Crashlytics tooling is absent from your app's build configuration. Please review Crashlytics onboarding instructions and ensure you have a valid Crashlytics account. */ 해당 이슈를 수정합니다. --- buildSrc/src/main/java/com/yapp/buildsrc/Dependencies.kt | 2 +- presentation/src/main/res/values/strings.xml | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/com/yapp/buildsrc/Dependencies.kt b/buildSrc/src/main/java/com/yapp/buildsrc/Dependencies.kt index 30898b72..8db2c679 100644 --- a/buildSrc/src/main/java/com/yapp/buildsrc/Dependencies.kt +++ b/buildSrc/src/main/java/com/yapp/buildsrc/Dependencies.kt @@ -81,7 +81,7 @@ object Dependencies { } object Firebase { - const val CRASHLYTICS_GRADLE = "com.google.firebase:firebase-crashlytics-gradle:2.8.1" + const val CRASHLYTICS_GRADLE = "com.google.firebase:firebase-crashlytics-gradle:2.9.5" const val COMMON = "com.google.firebase:firebase-common-ktx:20.0.0" const val BOM = "com.google.firebase:firebase-bom:29.1.0" const val CONFIG_KTX = "com.google.firebase:firebase-config-ktx" diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index 8931ff25..e3211146 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -1,6 +1,8 @@ + none + 3초만에 끝나는\n간편한 출석체크 카카오 로그인 취소 From b2f1b6b1e6ea280b6296f8d476e756e2e9f9cf7c Mon Sep 17 00:00:00 2001 From: YeongSang Jeon Date: Mon, 29 May 2023 00:26:36 +0900 Subject: [PATCH 02/18] =?UTF-8?q?feat:=20Management=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?=EC=8B=A0=EA=B7=9C=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EB=B0=8F=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 신규 컴포넌트들을 생성합니다. - 기존 컴포넌트에서 디자인이 변경된 부분들을 수정합니다. - Management.kt파일에서 컴포넌트들을 모두 파일로 분리하고, 각자의 State를 가지도록 수정합니다. - UiState에 큰 변화가 있음에 따라 ViewModel의 로직을 모두 삭제합니다. --- .../ui/admin/management/Management.kt | 276 ++---------------- .../ui/admin/management/ManagementContract.kt | 40 +-- .../admin/management/ManagementViewModel.kt | 106 +------ .../components/AnimatedCounterText.kt | 65 +++++ .../AttendanceBottomSheetItemLayout.kt | 101 +++++++ .../AttendanceBottomSheetItemLayoutState.kt | 23 ++ .../AttendanceTypeButton.kt | 100 +++++++ .../AttendanceTypeButtonState.kt | 11 + .../FoldableItemContentLayout.kt | 89 ++++++ .../FoldableItemContentLayoutState.kt | 11 + .../FoldableItemHeaderLayout.kt | 121 ++++++++ .../FoldableItemHeaderLayoutState.kt | 8 + .../foldableItem/FoldableItemLayout.kt | 146 +++++++++ .../foldableItem/FoldableItemLayoutState.kt | 10 + .../StatisticalTableLayout.kt | 188 ++++++++++++ .../StatisticalTableLayoutState.kt | 14 + 16 files changed, 938 insertions(+), 371 deletions(-) create mode 100644 presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/AnimatedCounterText.kt create mode 100644 presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceBottomSheet/AttendanceBottomSheetItemLayout.kt create mode 100644 presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceBottomSheet/AttendanceBottomSheetItemLayoutState.kt create mode 100644 presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceTypeButton/AttendanceTypeButton.kt create mode 100644 presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceTypeButton/AttendanceTypeButtonState.kt create mode 100644 presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableContentItem/FoldableItemContentLayout.kt create mode 100644 presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableContentItem/FoldableItemContentLayoutState.kt create mode 100644 presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableHeaderItem/FoldableItemHeaderLayout.kt create mode 100644 presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableHeaderItem/FoldableItemHeaderLayoutState.kt create mode 100644 presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/FoldableItemLayout.kt create mode 100644 presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/FoldableItemLayoutState.kt create mode 100644 presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/statisticalTable/StatisticalTableLayout.kt create mode 100644 presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/statisticalTable/StatisticalTableLayoutState.kt diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/Management.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/Management.kt index f987ca43..1516894b 100644 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/Management.kt +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/Management.kt @@ -1,28 +1,20 @@ package com.yapp.presentation.ui.admin.management import androidx.compose.animation.* -import androidx.compose.animation.core.Spring -import androidx.compose.animation.core.spring -import androidx.compose.animation.core.tween import androidx.compose.foundation.background -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* import androidx.compose.runtime.* -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalLifecycleOwner -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 androidx.constraintlayout.compose.ConstraintLayout import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle @@ -31,15 +23,15 @@ import com.google.accompanist.insets.systemBarsPadding import com.yapp.common.theme.AttendanceTheme import com.yapp.common.theme.AttendanceTypography import com.yapp.common.yds.YDSAppBar -import com.yapp.common.yds.YDSDropDownButton import com.yapp.common.yds.YDSEmptyScreen import com.yapp.common.yds.YDSProgressBar import com.yapp.domain.model.Attendance -import com.yapp.presentation.R import com.yapp.presentation.ui.admin.management.ManagementContract.ManagementEvent import com.yapp.presentation.ui.admin.management.ManagementContract.ManagementState.LoadState.* -import com.yapp.presentation.ui.admin.management.ManagementContract.ManagementState.MemberState -import com.yapp.presentation.ui.admin.management.ManagementContract.ManagementState.TeamState +import com.yapp.presentation.ui.admin.management.components.attendanceBottomSheet.AttendanceBottomSheetItemLayout +import com.yapp.presentation.ui.admin.management.components.attendanceBottomSheet.AttendanceBottomSheetItemLayoutState +import com.yapp.presentation.ui.admin.management.components.statisticalTable.StatisticalTableLayout +import com.yapp.presentation.ui.admin.management.components.foldableItem.FoldableItemLayout import kotlinx.coroutines.launch @@ -48,7 +40,7 @@ import kotlinx.coroutines.launch @Composable fun AttendanceManagement( viewModel: ManagementViewModel = hiltViewModel(), - onBackButtonClicked: (() -> Unit)? = null + onBackButtonClicked: (() -> Unit)? = null, ) { val uiState by viewModel.uiState.collectAsState() @@ -91,26 +83,17 @@ fun AttendanceManagement( @ExperimentalMaterialApi @Composable -fun ManagementScreen( +internal fun ManagementScreen( uiState: ManagementContract.ManagementState, sheetState: ModalBottomSheetState, onBackButtonClicked: (() -> Unit), onBottomSheetDialogItemClicked: (Attendance.Status) -> Unit, - onDropDownClicked: ((MemberState) -> Unit) + onDropDownClicked: ((memberId: Long) -> Unit), ) { - val attendanceTypes = remember { - listOf( - Attendance.Status.ABSENT, - Attendance.Status.NORMAL, - Attendance.Status.LATE, - Attendance.Status.ADMIT - ) - } - ModalBottomSheetLayout( sheetContent = { BottomSheetDialog( - attendanceTypes = attendanceTypes, + itemStates = uiState.bottomSheetDialogState, onClickItem = { attendanceType -> onBottomSheetDialogItemClicked.invoke( attendanceType @@ -130,7 +113,7 @@ fun ManagementScreen( modifier = Modifier .background(AttendanceTheme.colors.backgroundColors.background) .padding(horizontal = 4.dp), - title = uiState.sessionTitle, + title = uiState.topBarState.sessionTitle, onClickBackButton = { onBackButtonClicked.invoke() } ) }, @@ -143,22 +126,19 @@ fun ManagementScreen( .background(AttendanceTheme.colors.backgroundColors.background) .padding(innerPadding) ) { - item { Column( - modifier = Modifier.padding(start = 24.dp, end = 24.dp) + modifier = Modifier.padding(24.dp) ) { - Spacer(modifier = Modifier.height(28.dp)) - AttendCountText(memberCount = uiState.memberCount) - Spacer(modifier = Modifier.height(28.dp)) + StatisticalTableLayout(state = uiState.attendanceStatisticalTableState) } } itemsIndexed( - items = uiState.teams, - key = { _, team -> team.teamName } + items = uiState.foldableItemStates, + key = { _, team -> team.headerState.label } ) { _, team -> - ExpandableTeam( + FoldableItemLayout( state = team, onDropDownClicked = { changedMember -> onDropDownClicked.invoke( @@ -176,196 +156,11 @@ fun ManagementScreen( } } -@Preview @Composable -fun AttendCountText( +private fun BottomSheetDialog( modifier: Modifier = Modifier, - memberCount: Int = 0 -) { - Box( - modifier = modifier - .fillMaxWidth() - .height(48.dp) - .clip(RoundedCornerShape(10.dp)) - .background(color = AttendanceTheme.colors.mainColors.YappOrangeAlpha) - ) { - Crossfade(targetState = memberCount) { count -> - Text( - modifier = Modifier - .fillMaxSize() - .padding(horizontal = 24.dp, vertical = 12.dp), - text = "${count}명이 출석했어요", - textAlign = TextAlign.Center, - style = AttendanceTypography.body1, - color = AttendanceTheme.colors.mainColors.YappOrange - ) - } - } -} - -@Preview -@Composable -fun ExpandableTeam( - modifier: Modifier = Modifier, - state: TeamState = TeamState(), - onDropDownClicked: ((MemberState) -> Unit)? = null -) { - var expanded by rememberSaveable { mutableStateOf(false) } - - Column( - modifier = modifier - .fillMaxWidth() - .wrapContentHeight() - .padding(start = 24.dp, end = 24.dp) - .background(AttendanceTheme.colors.backgroundColors.background) - .animateContentSize( - animationSpec = spring( - dampingRatio = Spring.DampingRatioLowBouncy, - stiffness = Spring.StiffnessLow - ) - ), - horizontalAlignment = Alignment.CenterHorizontally - ) { - - TeamHeader( - state = state, - expanded = expanded, - onExpandClicked = { changedState -> expanded = changedState } - ) - - AnimatedVisibility( - visible = expanded, - enter = fadeIn(animationSpec = tween(50)) + - expandVertically( - animationSpec = spring( - dampingRatio = Spring.DampingRatioLowBouncy, - stiffness = Spring.StiffnessLow - ) - ), - exit = fadeOut(animationSpec = tween(50)) + - shrinkVertically( - animationSpec = spring( - dampingRatio = Spring.DampingRatioLowBouncy, - stiffness = Spring.StiffnessLow - ) - ) - ) { - Column { - Divider( - modifier = Modifier.height(1.dp), - color = AttendanceTheme.colors.grayScale.Gray300 - ) - for (member in state.members) { - MemberContent( - state = member, - onDropDownClicked = { clickedMember -> - onDropDownClicked?.invoke(clickedMember) - } - ) - } - Divider( - modifier = Modifier.height(1.dp), - color = AttendanceTheme.colors.grayScale.Gray300 - ) - } - } - } -} - -@Preview -@Composable -fun TeamHeader( - modifier: Modifier = Modifier, - state: TeamState = TeamState(), - expanded: Boolean = false, - onExpandClicked: (Boolean) -> Unit = {} -) { - Row( - modifier = modifier - .height(62.dp) - .fillMaxWidth() - .background(AttendanceTheme.colors.backgroundColors.background) - .clickable { onExpandClicked(!expanded) } - ) { - Text( - modifier = Modifier - .width(0.dp) - .weight(0.9F) - .fillMaxHeight() - .padding(vertical = 18.dp), - text = state.teamName, - textAlign = TextAlign.Start, - style = AttendanceTypography.h3, - color = AttendanceTheme.colors.grayScale.Gray1200 - ) - - IconButton( - modifier = Modifier - .weight(0.1F) - .fillMaxHeight() - .width(0.dp), - onClick = { onExpandClicked(!expanded) } - ) { - Icon( - painter = if (expanded) painterResource(id = R.drawable.icon_chevron_up) else painterResource( - id = R.drawable.icon_chevron_down - ), - tint = AttendanceTheme.colors.grayScale.Gray600, - contentDescription = null - ) - } - } -} - -@Preview -@Composable -fun MemberContent( - modifier: Modifier = Modifier, - state: MemberState = MemberState(), - onDropDownClicked: (MemberState) -> Unit = {} -) { - ConstraintLayout( - modifier = modifier - .fillMaxWidth() - .height(59.dp) - ) { - val (ydsDropdownButton, nameText) = createRefs() - - Text( - modifier = Modifier - .wrapContentWidth() - .constrainAs(nameText) { - start.linkTo(parent.start, margin = 8.dp) - top.linkTo(parent.top, margin = 17.5.dp) - bottom.linkTo(parent.bottom, margin = 17.5.dp) - }, - text = state.name, - textAlign = TextAlign.Start, - style = AttendanceTypography.body1, - color = AttendanceTheme.colors.grayScale.Gray800 - ) - - YDSDropDownButton( - modifier = Modifier - .wrapContentHeight() - .animateContentSize() - .constrainAs(ydsDropdownButton) { - end.linkTo(parent.end, margin = 8.dp) - top.linkTo(parent.top, margin = 13.dp) - bottom.linkTo(parent.bottom, margin = 13.dp) - }, - text = state.attendance.status.text, - onClick = { onDropDownClicked.invoke(state) } - ) - } - -} - -@Composable -fun BottomSheetDialog( - modifier: Modifier = Modifier, - attendanceTypes: List, - onClickItem: (Attendance.Status) -> Unit = {} + itemStates: List, + onClickItem: (Attendance.Status) -> Unit = {}, ) { Column( modifier = modifier @@ -381,10 +176,10 @@ fun BottomSheetDialog( .background(color = AttendanceTheme.colors.backgroundColors.backgroundElevated) ) - for (type in attendanceTypes) { - BottomSheetDialogItem( - attendanceType = type, - onClickItem = { onClickItem.invoke(type) } + for (itemState in itemStates) { + AttendanceBottomSheetItemLayout( + state = itemState, + onClickItem = { onClickItem.invoke(itemState.onClickIcon) } ) } @@ -397,31 +192,4 @@ fun BottomSheetDialog( } } -@Preview -@Composable -fun BottomSheetDialogItem( - modifier: Modifier = Modifier, - attendanceType: Attendance.Status = Attendance.Status.NORMAL, - onClickItem: (Attendance.Status) -> Unit = {} -) { - Column( - modifier = modifier - .fillMaxWidth() - .height(52.dp) - .background(color = AttendanceTheme.colors.backgroundColors.backgroundElevated) - .clickable { onClickItem.invoke(attendanceType) }, - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center - ) { - Text( - modifier = modifier - .fillMaxWidth() - .fillMaxHeight() - .padding(vertical = 14.dp), - text = attendanceType.text, - style = AttendanceTypography.subtitle1, - color = AttendanceTheme.colors.grayScale.Gray1200, - textAlign = TextAlign.Center, - ) - } -} + diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementContract.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementContract.kt index 3e2109cc..f9df3654 100644 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementContract.kt +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementContract.kt @@ -4,36 +4,42 @@ import com.yapp.common.base.UiEvent import com.yapp.common.base.UiSideEffect import com.yapp.common.base.UiState import com.yapp.domain.model.Attendance +import com.yapp.presentation.ui.admin.management.components.attendanceBottomSheet.AttendanceBottomSheetItemLayoutState +import com.yapp.presentation.ui.admin.management.components.statisticalTable.StatisticalTableLayoutState +import com.yapp.presentation.ui.admin.management.components.foldableItem.FoldableItemLayoutState +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf class ManagementContract { + + private companion object { + const val NOT_SELECTED = -1L + } + data class ManagementState( val loadState: LoadState = LoadState.Idle, - val sessionId: Int = 0, - val sessionTitle: String = "", - val memberCount: Int = 0, - val selectedMember: MemberState? = null, - val teams: List = emptyList(), + val shared: Shared = Shared(), + val topBarState: TopBarLayoutState = TopBarLayoutState(), + val attendanceStatisticalTableState: StatisticalTableLayoutState = StatisticalTableLayoutState(), + val foldableItemStates: ImmutableList = persistentListOf(), + val bottomSheetDialogState: ImmutableList = persistentListOf() ) : UiState { + data class Shared( + val sessionId: Int = 0, + val selectedMemberId: Long = NOT_SELECTED + ) + + data class TopBarLayoutState(val sessionTitle: String = "") + enum class LoadState { Loading, Idle, Error } - - data class TeamState( - val teamName: String = "", - val members: List = emptyList(), - ) - - data class MemberState( - val id: Long = 0L, - val name: String = "", - val attendance: Attendance = Attendance(sessionId = 0, status = Attendance.Status.NORMAL), - ) } sealed class ManagementEvent : UiEvent { - data class OnDropDownButtonClicked(val member: ManagementState.MemberState) : ManagementEvent() + data class OnDropDownButtonClicked(val memberId: Long) : ManagementEvent() data class OnAttendanceTypeChanged(val attendanceType: Attendance.Status) : ManagementEvent() } diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementViewModel.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementViewModel.kt index c2326e51..39187f3d 100644 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementViewModel.kt +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementViewModel.kt @@ -1,19 +1,15 @@ package com.yapp.presentation.ui.admin.management import androidx.lifecycle.SavedStateHandle -import androidx.lifecycle.viewModelScope import com.yapp.common.base.BaseViewModel -import com.yapp.domain.model.Attendance -import com.yapp.domain.model.Member -import com.yapp.domain.model.Team import com.yapp.domain.usecases.GetAllMemberUseCase import com.yapp.domain.usecases.SetMemberAttendanceUseCase import com.yapp.presentation.ui.admin.AdminConstants.KEY_SESSION_ID import com.yapp.presentation.ui.admin.AdminConstants.KEY_SESSION_TITLE -import com.yapp.presentation.ui.admin.management.ManagementContract.* -import com.yapp.presentation.ui.admin.management.ManagementContract.ManagementState.* +import com.yapp.presentation.ui.admin.management.ManagementContract.ManagementEvent +import com.yapp.presentation.ui.admin.management.ManagementContract.ManagementSideEffect +import com.yapp.presentation.ui.admin.management.ManagementContract.ManagementState import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel @@ -26,106 +22,16 @@ class ManagementViewModel @Inject constructor( companion object { const val DEFAULT_SESSION_ID = 1 - const val DEFAULT_SESSION_TITLE = "YAPP" + const val TEXT_LOAD_SESSION_TITLE_FAILED = "LOAD FAILED" } init { - val sessionId = savedStateHandle.get(KEY_SESSION_ID) ?: DEFAULT_SESSION_ID - val sessionTitle = savedStateHandle.get(KEY_SESSION_TITLE) ?: DEFAULT_SESSION_TITLE - setState { this.copy(sessionId = sessionId, sessionTitle = sessionTitle) } - - viewModelScope.launch { - setState { this.copy(loadState = LoadState.Loading) } - getAllMemberState(sessionId = uiState.value.sessionId) - } + val sessionId = savedStateHandle.get(KEY_SESSION_ID) ?: error("세션 아이디를 불러올 수 없습니다.") + val sessionTitle = savedStateHandle.get(KEY_SESSION_TITLE) ?: TEXT_LOAD_SESSION_TITLE_FAILED } override suspend fun handleEvent(event: ManagementEvent) { - when (event) { - is ManagementEvent.OnDropDownButtonClicked -> { - setState { this.copy(selectedMember = event.member) } - setEffect(ManagementSideEffect.OpenBottomSheetDialog) - } - - is ManagementEvent.OnAttendanceTypeChanged -> { - uiState.value.selectedMember?.let { selectedMember -> - changeMemberAttendance( - selectedMember = selectedMember, - changedAttendanceStatus = event.attendanceType, - sessionId = uiState.value.sessionId - ) - } - } - } - } - - - - private suspend fun getAllMemberState(sessionId: Int) { - //getAllMemberUseCase().collect { } - - getAllMemberUseCase().collect { result -> - result.onSuccess { members -> - val membersByTeam = members.groupBy { member -> member.team } - .map { (team, members) -> mapToTeamState(team, members, sessionId) } - .sortedBy { teamState -> teamState.teamName } - - val attendCount = membersByTeam.flatMap { it.members } - .count { it.attendance.status == Attendance.Status.NORMAL } - - setState { - this.copy( - loadState = LoadState.Idle, - memberCount = attendCount, - teams = membersByTeam - ) - } - }.onFailure { - setState { this.copy(loadState = LoadState.Error) } - } - } - - } - - private fun mapToTeamState(team: Team, members: List, sessionId: Int): TeamState { - return TeamState( - teamName = String.format("${team.type.value} ${team.number}팀"), - members = members.sortedWith( - compareBy { it.attendances.getAttendanceBySessionId(sessionId).status.ordinal } - .thenBy { it.name } - ).map { member -> - MemberState( - id = member.id, - name = member.name, - attendance = member.attendances.getAttendanceBySessionId(sessionId = sessionId) - ) - } - ) - } - private suspend fun changeMemberAttendance( - selectedMember: MemberState, - changedAttendanceStatus: Attendance.Status, - sessionId: Int, - ) { - if (selectedMember.attendance.status == changedAttendanceStatus) { - return - } - setMemberAttendanceUseCase( - params = SetMemberAttendanceUseCase.Params( - memberId = selectedMember.id, - sessionId = sessionId, - changedAttendance = Attendance( - sessionId = sessionId, - status = changedAttendanceStatus - ) - ) - ).onSuccess { - setState { this.copy(selectedMember = null) } - getAllMemberState(sessionId = sessionId) - }.onFailure { - setState { this.copy(loadState = LoadState.Error) } - } } } diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/AnimatedCounterText.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/AnimatedCounterText.kt new file mode 100644 index 00000000..558a4b9b --- /dev/null +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/AnimatedCounterText.kt @@ -0,0 +1,65 @@ +package com.yapp.presentation.ui.admin.management.components + +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically +import androidx.compose.animation.with +import androidx.compose.foundation.layout.Row +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.TextStyle + + +@OptIn(ExperimentalAnimationApi::class) +@Composable +internal fun AnimatedCounterText( + count: Int, + modifier: Modifier = Modifier, + style: TextStyle, + prefix: @Composable () -> Unit = {}, + suffix: @Composable () -> Unit = {}, +) { + var oldCount by remember { + mutableStateOf(count) + } + SideEffect { + oldCount = count + } + Row(modifier = modifier) { + val countString = count.toString() + val oldCountString = oldCount.toString() + + prefix() + for (i in countString.indices) { + val oldChar = oldCountString.getOrNull(i) + val newChar = countString[i] + val char = if (oldChar == newChar) { + oldCountString[i] + } else { + countString[i] + } + AnimatedContent( + targetState = char, + transitionSpec = { + slideInVertically { it } with slideOutVertically { -it } + } + ) { char -> + + Text( + text = char.toString(), + style = style, + softWrap = false + ) + + } + } + suffix() + } +} \ No newline at end of file diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceBottomSheet/AttendanceBottomSheetItemLayout.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceBottomSheet/AttendanceBottomSheetItemLayout.kt new file mode 100644 index 00000000..bfd11d58 --- /dev/null +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceBottomSheet/AttendanceBottomSheetItemLayout.kt @@ -0,0 +1,101 @@ +package com.yapp.presentation.ui.admin.management.components.attendanceBottomSheet + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.material.Icon +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +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.yapp.common.theme.AttendanceTheme +import com.yapp.common.theme.AttendanceTypography +import com.yapp.presentation.R + + +@Preview(backgroundColor = 0xFFFFFFFF, showBackground = true) +@Composable +private fun AttendanceBottomSheetItemLayoutPreview() { + var states by remember { + mutableStateOf( + listOf( + AttendanceBottomSheetItemLayoutState(label = "출석", iconType = AttendanceBottomSheetItemLayoutState.IconType.ATTEND), + AttendanceBottomSheetItemLayoutState(label = "지각", iconType = AttendanceBottomSheetItemLayoutState.IconType.TARDY), + AttendanceBottomSheetItemLayoutState(label = "결석", iconType = AttendanceBottomSheetItemLayoutState.IconType.ABSENT), + AttendanceBottomSheetItemLayoutState(label = "출석 인정", iconType = AttendanceBottomSheetItemLayoutState.IconType.ADMIT) + ) + ) + } + + AttendanceTheme { + Column(modifier = Modifier) { + for (state in states) { + AttendanceBottomSheetItemLayout( + state = state, + onClickItem = { + + } + ) + } + } + } +} + +@Composable +fun AttendanceBottomSheetItemLayout( + modifier: Modifier = Modifier, + state: AttendanceBottomSheetItemLayoutState, + onClickItem: (AttendanceBottomSheetItemLayoutState.IconType) -> Unit, +) { + Row( + modifier = modifier + .fillMaxWidth() + .height(52.dp) + .background(color = AttendanceTheme.colors.backgroundColors.backgroundElevated) + .clickable { + onClickItem.invoke(state.iconType) + }, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ) { + Icon( + modifier = Modifier.size(20.dp), + painter = when (state.iconType) { + AttendanceBottomSheetItemLayoutState.IconType.ATTEND -> painterResource(id = R.drawable.icon_attend) + AttendanceBottomSheetItemLayoutState.IconType.ADMIT -> painterResource(id = R.drawable.icon_attend) + AttendanceBottomSheetItemLayoutState.IconType.TARDY -> painterResource(id = R.drawable.icon_tardy) + AttendanceBottomSheetItemLayoutState.IconType.ABSENT -> painterResource(id = R.drawable.icon_absent) + }, + contentDescription = "attendance_type_icon" + ) + + Spacer(modifier = Modifier.width(6.dp)) + + Text( + modifier = modifier + .fillMaxHeight() + .padding(vertical = 14.dp), + text = state.label, + style = AttendanceTypography.subtitle1, + color = AttendanceTheme.colors.grayScale.Gray1200, + textAlign = TextAlign.Center, + ) + } +} \ No newline at end of file diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceBottomSheet/AttendanceBottomSheetItemLayoutState.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceBottomSheet/AttendanceBottomSheetItemLayoutState.kt new file mode 100644 index 00000000..6f1089f8 --- /dev/null +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceBottomSheet/AttendanceBottomSheetItemLayoutState.kt @@ -0,0 +1,23 @@ +package com.yapp.presentation.ui.admin.management.components.attendanceBottomSheet + +import com.yapp.domain.model.Attendance + + +data class AttendanceBottomSheetItemLayoutState( + val label: String, + val iconType: IconType, +) { + + val onClickIcon: Attendance.Status + get() = when (iconType) { + IconType.ATTEND -> Attendance.Status.NORMAL + IconType.TARDY -> Attendance.Status.LATE + IconType.ADMIT -> Attendance.Status.ADMIT + IconType.ABSENT -> Attendance.Status.ABSENT + } + + enum class IconType { + ATTEND, TARDY, ADMIT, ABSENT + } + +} \ No newline at end of file diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceTypeButton/AttendanceTypeButton.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceTypeButton/AttendanceTypeButton.kt new file mode 100644 index 00000000..51e0de18 --- /dev/null +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceTypeButton/AttendanceTypeButton.kt @@ -0,0 +1,100 @@ +package com.yapp.presentation.ui.admin.management.components.attendanceTypeButton + +import androidx.compose.animation.Crossfade +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Button +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.Icon +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.yapp.common.R +import com.yapp.common.theme.AttendanceTheme +import com.yapp.common.theme.AttendanceTypography + + +@Preview(backgroundColor = 0xFFFFFFFF, showBackground = true) +@Composable +private fun AttendanceTypeButtonPreview() { + var state by remember { + mutableStateOf( + AttendanceTypeButtonState( + label = "출석", + iconType = AttendanceTypeButtonState.IconType.ATTEND + ) + ) + } + + AttendanceTheme { + Box(modifier = Modifier) { + AttendanceTypeButton( + state = state, + onClick = {} + ) + } + } +} + +@Composable +internal fun AttendanceTypeButton( + modifier: Modifier = Modifier, + state: AttendanceTypeButtonState, + onClick: () -> Unit, +) { + Button( + onClick = onClick, + modifier = modifier + .wrapContentWidth(), + shape = RoundedCornerShape(8.dp), + colors = ButtonDefaults.buttonColors(backgroundColor = AttendanceTheme.colors.grayScale.Gray200), + elevation = null + ) { + Row( + modifier = Modifier + .align(Alignment.CenterVertically) + .wrapContentSize(), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + modifier = Modifier.size(20.dp), + painter = when (state.iconType) { + AttendanceTypeButtonState.IconType.ATTEND -> painterResource(id = R.drawable.icon_attend) + AttendanceTypeButtonState.IconType.ADMIT -> painterResource(id = R.drawable.icon_attend) + AttendanceTypeButtonState.IconType.TARDY -> painterResource(id = R.drawable.icon_tardy) + AttendanceTypeButtonState.IconType.ABSENT -> painterResource(id = R.drawable.icon_absent) + }, + contentDescription = "drop down", + tint = AttendanceTheme.colors.grayScale.Gray800 + ) + + Spacer(modifier = Modifier.width(4.dp)) + + Crossfade(targetState = state.label) { + Text( + text = it, + modifier = Modifier + .height(20.dp) + .align(alignment = Alignment.CenterVertically), + style = AttendanceTypography.subtitle2, + color = AttendanceTheme.colors.grayScale.Gray800 + ) + } + } + } +} \ No newline at end of file diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceTypeButton/AttendanceTypeButtonState.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceTypeButton/AttendanceTypeButtonState.kt new file mode 100644 index 00000000..adee47d1 --- /dev/null +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceTypeButton/AttendanceTypeButtonState.kt @@ -0,0 +1,11 @@ +package com.yapp.presentation.ui.admin.management.components.attendanceTypeButton + + +data class AttendanceTypeButtonState( + val label: String = "", + val iconType: IconType = IconType.ATTEND, +) { + enum class IconType { + ATTEND, TARDY, ADMIT, ABSENT + } +} \ No newline at end of file diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableContentItem/FoldableItemContentLayout.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableContentItem/FoldableItemContentLayout.kt new file mode 100644 index 00000000..3af04e12 --- /dev/null +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableContentItem/FoldableItemContentLayout.kt @@ -0,0 +1,89 @@ +package com.yapp.presentation.ui.admin.management.components.foldableContentItem + +import androidx.compose.animation.animateContentSize +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.constraintlayout.compose.ConstraintLayout +import com.yapp.common.theme.AttendanceTheme +import com.yapp.common.theme.AttendanceTypography +import com.yapp.presentation.ui.admin.management.components.attendanceTypeButton.AttendanceTypeButton +import com.yapp.presentation.ui.admin.management.components.attendanceTypeButton.AttendanceTypeButtonState + + +@Preview(backgroundColor = 0xFFFFFFFF, showBackground = true) +@Composable +private fun FoldableItemContentLayoutPreview() { + var state by remember { + mutableStateOf( + FoldableItemContentLayoutState( + id = 0L, + label = "", + subLabel = "", + attendanceTypeButtonState = AttendanceTypeButtonState() + ) + ) + } + + AttendanceTheme { + Box(modifier = Modifier) { + FoldableItemContentLayout(state = state) + } + } +} + +@Composable +internal fun FoldableItemContentLayout( + modifier: Modifier = Modifier, + state: FoldableItemContentLayoutState, + onDropDownClicked: (memberId: Long) -> Unit = {}, +) { + ConstraintLayout( + modifier = modifier + .fillMaxWidth() + .height(59.dp) + ) { + val (ydsDropdownButton, nameText) = createRefs() + + Text( + modifier = Modifier + .wrapContentWidth() + .constrainAs(nameText) { + start.linkTo(parent.start, margin = 8.dp) + top.linkTo(parent.top, margin = 17.5.dp) + bottom.linkTo(parent.bottom, margin = 17.5.dp) + }, + text = state.label, + textAlign = TextAlign.Start, + style = AttendanceTypography.body1, + color = AttendanceTheme.colors.grayScale.Gray800 + ) + + AttendanceTypeButton( + modifier = Modifier + .wrapContentHeight() + .animateContentSize() + .constrainAs(ydsDropdownButton) { + end.linkTo(parent.end, margin = 8.dp) + top.linkTo(parent.top, margin = 13.dp) + bottom.linkTo(parent.bottom, margin = 13.dp) + }, + state = state.attendanceTypeButtonState, + onClick = { onDropDownClicked.invoke(state.id) } + ) + } + +} + diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableContentItem/FoldableItemContentLayoutState.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableContentItem/FoldableItemContentLayoutState.kt new file mode 100644 index 00000000..4a1da89b --- /dev/null +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableContentItem/FoldableItemContentLayoutState.kt @@ -0,0 +1,11 @@ +package com.yapp.presentation.ui.admin.management.components.foldableContentItem + +import com.yapp.presentation.ui.admin.management.components.attendanceTypeButton.AttendanceTypeButtonState + + +data class FoldableItemContentLayoutState( + val id: Long, + val label: String, + val subLabel: String, + val attendanceTypeButtonState: AttendanceTypeButtonState, +) \ No newline at end of file diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableHeaderItem/FoldableItemHeaderLayout.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableHeaderItem/FoldableItemHeaderLayout.kt new file mode 100644 index 00000000..660f9ecf --- /dev/null +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableHeaderItem/FoldableItemHeaderLayout.kt @@ -0,0 +1,121 @@ +package com.yapp.presentation.ui.admin.management.components.foldableHeaderItem + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.IntrinsicSize +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +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.yapp.common.theme.AttendanceTheme +import com.yapp.common.theme.AttendanceTypography +import com.yapp.presentation.R +import com.yapp.presentation.ui.admin.management.components.AnimatedCounterText + + +@Preview(backgroundColor = 0xFFFFFFFF, showBackground = true) +@Composable +private fun FoldableItemHeaderLayoutPreview() { + var state by remember { + mutableStateOf( + FoldableItemHeaderLayoutState( + label = "Android 1", + attendMemberCount = 4, + allTeamMemberCount = 6 + ) + ) + } + + AttendanceTheme { + Box(modifier = Modifier) { + FoldableItemHeaderLayout(state = state) + } + } +} + +@Composable +internal fun FoldableItemHeaderLayout( + modifier: Modifier = Modifier, + state: FoldableItemHeaderLayoutState, + expanded: Boolean = false, + onExpandClicked: (Boolean) -> Unit = {}, +) { + Row( + modifier = modifier + .fillMaxWidth() + .background(AttendanceTheme.colors.backgroundColors.background) + .clickable { onExpandClicked(!expanded) }, + verticalAlignment = Alignment.CenterVertically + ) { + Row( + modifier = Modifier + .width(0.dp) + .weight(0.9F) + .padding(vertical = 18.dp), + horizontalArrangement = Arrangement.Start, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + modifier = Modifier, + text = state.label, + textAlign = TextAlign.Start, + style = AttendanceTypography.h3, + color = AttendanceTheme.colors.grayScale.Gray1200 + ) + + Spacer(modifier = Modifier.width(6.dp)) + + AnimatedCounterText( + count = state.attendMemberCount, + style = AttendanceTypography.subtitle2.copy(color = AttendanceTheme.colors.mainColors.YappOrange) + ) + + Text( + text = "/", + textAlign = TextAlign.Start, + style = AttendanceTypography.subtitle2, + color = AttendanceTheme.colors.mainColors.YappOrange + ) + + AnimatedCounterText( + count = state.allTeamMemberCount, + style = AttendanceTypography.subtitle2.copy(color = AttendanceTheme.colors.mainColors.YappOrange) + ) + } + + + IconButton( + modifier = Modifier + .width(0.dp) + .height(IntrinsicSize.Min) + .weight(0.1F), + onClick = { onExpandClicked(!expanded) } + ) { + Icon( + painter = if (expanded) painterResource(id = R.drawable.icon_chevron_up) else painterResource( + id = R.drawable.icon_chevron_down + ), + tint = AttendanceTheme.colors.grayScale.Gray600, + contentDescription = null + ) + } + } +} \ No newline at end of file diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableHeaderItem/FoldableItemHeaderLayoutState.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableHeaderItem/FoldableItemHeaderLayoutState.kt new file mode 100644 index 00000000..898e4ea0 --- /dev/null +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableHeaderItem/FoldableItemHeaderLayoutState.kt @@ -0,0 +1,8 @@ +package com.yapp.presentation.ui.admin.management.components.foldableHeaderItem + + +data class FoldableItemHeaderLayoutState( + val label: String = "", + val attendMemberCount: Int = 0, + val allTeamMemberCount: Int = 0, +) \ No newline at end of file diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/FoldableItemLayout.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/FoldableItemLayout.kt new file mode 100644 index 00000000..5ac5c660 --- /dev/null +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/FoldableItemLayout.kt @@ -0,0 +1,146 @@ +package com.yapp.presentation.ui.admin.management.components.foldableItem + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.animateContentSize +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.spring +import androidx.compose.animation.core.tween +import androidx.compose.animation.expandVertically +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.shrinkVertically +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.material.Divider +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.yapp.common.theme.AttendanceTheme +import com.yapp.domain.model.Attendance +import com.yapp.presentation.ui.admin.management.components.attendanceTypeButton.AttendanceTypeButtonState +import com.yapp.presentation.ui.admin.management.components.foldableContentItem.FoldableItemContentLayout +import com.yapp.presentation.ui.admin.management.components.foldableContentItem.FoldableItemContentLayoutState +import com.yapp.presentation.ui.admin.management.components.foldableHeaderItem.FoldableItemHeaderLayout +import com.yapp.presentation.ui.admin.management.components.foldableHeaderItem.FoldableItemHeaderLayoutState + + +@Preview +@Composable +private fun FoldableItemLayoutPreview() { + var state by remember { + mutableStateOf( + FoldableItemLayoutState( + headerState = FoldableItemHeaderLayoutState( + label = "Android 1", + attendMemberCount = 4, + allTeamMemberCount = 6 + ), + contentStates = listOf( + FoldableItemContentLayoutState( + id = 0L, + label = "김철수", + subLabel = "ProducDesign", + attendanceTypeButtonState = AttendanceTypeButtonState() + ), + FoldableItemContentLayoutState( + id = 0L, + label = "", + subLabel = "", + attendanceTypeButtonState = AttendanceTypeButtonState() + ) + ) + ) + ) + } + + AttendanceTheme { + Box(modifier = Modifier) { + FoldableItemLayout( + state = state, + onDropDownClicked = { + + } + ) + } + } +} + +@Composable +internal fun FoldableItemLayout( + modifier: Modifier = Modifier, + state: FoldableItemLayoutState, + onDropDownClicked: ((memberId: Long) -> Unit), +) { + var expanded by rememberSaveable { mutableStateOf(false) } + + Column( + modifier = modifier + .fillMaxWidth() + .wrapContentHeight() + .padding(start = 24.dp, end = 24.dp) + .background(AttendanceTheme.colors.backgroundColors.background) + .animateContentSize( + animationSpec = spring( + dampingRatio = Spring.DampingRatioLowBouncy, + stiffness = Spring.StiffnessLow + ) + ), + horizontalAlignment = Alignment.CenterHorizontally + ) { + + FoldableItemHeaderLayout( + state = state.headerState, + expanded = expanded, + onExpandClicked = { changedState -> expanded = changedState } + ) + + AnimatedVisibility( + visible = expanded, + enter = fadeIn(animationSpec = tween(50)) + + expandVertically( + animationSpec = spring( + dampingRatio = Spring.DampingRatioLowBouncy, + stiffness = Spring.StiffnessLow + ) + ), + exit = fadeOut(animationSpec = tween(50)) + + shrinkVertically( + animationSpec = spring( + dampingRatio = Spring.DampingRatioLowBouncy, + stiffness = Spring.StiffnessLow + ) + ) + ) { + Column { + Divider( + modifier = Modifier.height(1.dp), + color = AttendanceTheme.colors.grayScale.Gray300 + ) + for (contentItem in state.contentStates) { + FoldableItemContentLayout( + state = contentItem, + onDropDownClicked = { clickedMemberId -> + onDropDownClicked.invoke(clickedMemberId) + } + ) + } + Divider( + modifier = Modifier.height(1.dp), + color = AttendanceTheme.colors.grayScale.Gray300 + ) + } + } + } +} \ No newline at end of file diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/FoldableItemLayoutState.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/FoldableItemLayoutState.kt new file mode 100644 index 00000000..fd3700cb --- /dev/null +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/FoldableItemLayoutState.kt @@ -0,0 +1,10 @@ +package com.yapp.presentation.ui.admin.management.components.foldableItem + +import com.yapp.presentation.ui.admin.management.components.foldableContentItem.FoldableItemContentLayoutState +import com.yapp.presentation.ui.admin.management.components.foldableHeaderItem.FoldableItemHeaderLayoutState + + +data class FoldableItemLayoutState( + val headerState: FoldableItemHeaderLayoutState, + val contentStates: List, +) \ No newline at end of file diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/statisticalTable/StatisticalTableLayout.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/statisticalTable/StatisticalTableLayout.kt new file mode 100644 index 00000000..7c942ed6 --- /dev/null +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/statisticalTable/StatisticalTableLayout.kt @@ -0,0 +1,188 @@ +package com.yapp.presentation.ui.admin.management.components.statisticalTable + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.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.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Button +import androidx.compose.material.Divider +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.clipToBounds +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.yapp.common.theme.AttendanceTheme +import com.yapp.common.theme.AttendanceTypography +import com.yapp.presentation.ui.admin.management.components.AnimatedCounterText + + +@Preview(backgroundColor = 0xFFFFFFFF, showBackground = true) +@Composable +private fun StatisticalTableLayoutPreview() { + var state by remember { + mutableStateOf( + StatisticalTableLayoutState( + totalCount = 60, + tardyCount = 2, + absentCount = 3, + admitCount = 0 + ) + ) + } + + AttendanceTheme { + Box( + modifier = Modifier + .fillMaxSize() + .background(color = Color.White) + .padding(horizontal = 24.dp) + ) { + Button( + modifier = Modifier.align(Alignment.Center), + onClick = { state = state.copy(absentCount = state.absentCount + 1) } + ) { + Text(text = "dd") + } + + StatisticalTableLayout(state = state) + } + } + +} + +@Composable +internal fun StatisticalTableLayout( + state: StatisticalTableLayoutState, +) { + Column( + modifier = Modifier + .fillMaxWidth() + .clip(shape = RoundedCornerShape(10.dp)) + .background(color = AttendanceTheme.colors.grayScale.Gray200) + .padding((1.5).dp) + .clipToBounds() + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 12.dp), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + AnimatedCounterText( + count = state.attendanceCount, + style = AttendanceTypography.subtitle2.copy(color = AttendanceTheme.colors.etcColors.EtcGreen), + prefix = { + Text( + style = AttendanceTypography.body2, + text = "${state.totalCount}명 중 ", + color = AttendanceTheme.colors.grayScale.Gray1200 + ) + }, + suffix = { + Text( + style = AttendanceTypography.subtitle2, + text = "명", + color = AttendanceTheme.colors.etcColors.EtcGreen + ) + + Text( + style = AttendanceTypography.body2, + text = "이 출석했어요", + color = AttendanceTheme.colors.grayScale.Gray1200 + ) + } + ) + } + + Row( + modifier = Modifier + .fillMaxWidth() + .clip(shape = RoundedCornerShape(bottomStart = 10.dp, bottomEnd = 10.dp)) + .background(color = AttendanceTheme.colors.backgroundColors.background) + .padding(vertical = 12.dp, horizontal = 16.dp) + .clipToBounds(), + horizontalArrangement = Arrangement.spacedBy(space = 10.dp, alignment = Alignment.CenterHorizontally), + verticalAlignment = Alignment.CenterVertically + ) { + TableItemText( + label = "지각", + count = state.tardyCount + ) + + Divider( + modifier = Modifier + .width((1.5).dp) + .height(16.dp), + color = AttendanceTheme.colors.grayScale.Gray200, + thickness = (1.5).dp + ) + + TableItemText( + label = "결석", + count = state.absentCount + ) + + Divider( + modifier = Modifier + .width((1.5).dp) + .height(16.dp), + color = AttendanceTheme.colors.grayScale.Gray200, + thickness = (1.5).dp + ) + + TableItemText( + label = "출석인정", + count = state.admitCount + ) + } + } +} + +@Composable +private fun TableItemText( + label: String, + count: Int, +) { + Row( + modifier = Modifier.width(80.dp), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = label, + style = AttendanceTypography.body2, + color = AttendanceTheme.colors.grayScale.Gray600 + ) + + Spacer(modifier = Modifier.width(4.dp)) + + AnimatedCounterText( + count = count, + style = AttendanceTypography.subtitle2.copy(color = AttendanceTheme.colors.grayScale.Gray1200), + suffix = { + Text( + text = "명", + style = AttendanceTypography.subtitle2, + color = AttendanceTheme.colors.grayScale.Gray1200 + ) + } + ) + } +} \ No newline at end of file diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/statisticalTable/StatisticalTableLayoutState.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/statisticalTable/StatisticalTableLayoutState.kt new file mode 100644 index 00000000..ce035d30 --- /dev/null +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/statisticalTable/StatisticalTableLayoutState.kt @@ -0,0 +1,14 @@ +package com.yapp.presentation.ui.admin.management.components.statisticalTable + + +data class StatisticalTableLayoutState( + val totalCount: Int = 0, + val tardyCount: Int = 0, + val absentCount: Int = 0, + val admitCount: Int = 0, +) { + + val attendanceCount: Int + get() = totalCount - absentCount + +} \ No newline at end of file From b86a1065effeda94aae7bb33932dfb9a0869b0ad Mon Sep 17 00:00:00 2001 From: YeongSang Jeon Date: Mon, 29 May 2023 00:26:36 +0900 Subject: [PATCH 03/18] =?UTF-8?q?feat:=20Management=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?=EC=8B=A0=EA=B7=9C=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EB=B0=8F=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 신규 컴포넌트들을 생성합니다. - 기존 컴포넌트에서 디자인이 변경된 부분들을 수정합니다. - Management.kt파일에서 컴포넌트들을 모두 파일로 분리하고, 각자의 State를 가지도록 수정합니다. - UiState에 큰 변화가 있음에 따라 ViewModel의 로직을 모두 삭제합니다. --- .../ui/admin/management/Management.kt | 276 ++---------------- .../ui/admin/management/ManagementContract.kt | 40 +-- .../admin/management/ManagementViewModel.kt | 106 +------ .../components/AnimatedCounterText.kt | 65 +++++ .../AttendanceBottomSheetItemLayout.kt | 101 +++++++ .../AttendanceBottomSheetItemLayoutState.kt | 23 ++ .../AttendanceTypeButton.kt | 100 +++++++ .../AttendanceTypeButtonState.kt | 11 + .../FoldableItemContentLayout.kt | 89 ++++++ .../FoldableItemContentLayoutState.kt | 11 + .../FoldableItemHeaderLayout.kt | 121 ++++++++ .../FoldableItemHeaderLayoutState.kt | 8 + .../foldableItem/FoldableItemLayout.kt | 146 +++++++++ .../foldableItem/FoldableItemLayoutState.kt | 10 + .../StatisticalTableLayout.kt | 188 ++++++++++++ .../StatisticalTableLayoutState.kt | 14 + 16 files changed, 938 insertions(+), 371 deletions(-) create mode 100644 presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/AnimatedCounterText.kt create mode 100644 presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceBottomSheet/AttendanceBottomSheetItemLayout.kt create mode 100644 presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceBottomSheet/AttendanceBottomSheetItemLayoutState.kt create mode 100644 presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceTypeButton/AttendanceTypeButton.kt create mode 100644 presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceTypeButton/AttendanceTypeButtonState.kt create mode 100644 presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableContentItem/FoldableItemContentLayout.kt create mode 100644 presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableContentItem/FoldableItemContentLayoutState.kt create mode 100644 presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableHeaderItem/FoldableItemHeaderLayout.kt create mode 100644 presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableHeaderItem/FoldableItemHeaderLayoutState.kt create mode 100644 presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/FoldableItemLayout.kt create mode 100644 presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/FoldableItemLayoutState.kt create mode 100644 presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/statisticalTable/StatisticalTableLayout.kt create mode 100644 presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/statisticalTable/StatisticalTableLayoutState.kt diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/Management.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/Management.kt index f987ca43..1516894b 100644 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/Management.kt +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/Management.kt @@ -1,28 +1,20 @@ package com.yapp.presentation.ui.admin.management import androidx.compose.animation.* -import androidx.compose.animation.core.Spring -import androidx.compose.animation.core.spring -import androidx.compose.animation.core.tween import androidx.compose.foundation.background -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* import androidx.compose.runtime.* -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalLifecycleOwner -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 androidx.constraintlayout.compose.ConstraintLayout import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle @@ -31,15 +23,15 @@ import com.google.accompanist.insets.systemBarsPadding import com.yapp.common.theme.AttendanceTheme import com.yapp.common.theme.AttendanceTypography import com.yapp.common.yds.YDSAppBar -import com.yapp.common.yds.YDSDropDownButton import com.yapp.common.yds.YDSEmptyScreen import com.yapp.common.yds.YDSProgressBar import com.yapp.domain.model.Attendance -import com.yapp.presentation.R import com.yapp.presentation.ui.admin.management.ManagementContract.ManagementEvent import com.yapp.presentation.ui.admin.management.ManagementContract.ManagementState.LoadState.* -import com.yapp.presentation.ui.admin.management.ManagementContract.ManagementState.MemberState -import com.yapp.presentation.ui.admin.management.ManagementContract.ManagementState.TeamState +import com.yapp.presentation.ui.admin.management.components.attendanceBottomSheet.AttendanceBottomSheetItemLayout +import com.yapp.presentation.ui.admin.management.components.attendanceBottomSheet.AttendanceBottomSheetItemLayoutState +import com.yapp.presentation.ui.admin.management.components.statisticalTable.StatisticalTableLayout +import com.yapp.presentation.ui.admin.management.components.foldableItem.FoldableItemLayout import kotlinx.coroutines.launch @@ -48,7 +40,7 @@ import kotlinx.coroutines.launch @Composable fun AttendanceManagement( viewModel: ManagementViewModel = hiltViewModel(), - onBackButtonClicked: (() -> Unit)? = null + onBackButtonClicked: (() -> Unit)? = null, ) { val uiState by viewModel.uiState.collectAsState() @@ -91,26 +83,17 @@ fun AttendanceManagement( @ExperimentalMaterialApi @Composable -fun ManagementScreen( +internal fun ManagementScreen( uiState: ManagementContract.ManagementState, sheetState: ModalBottomSheetState, onBackButtonClicked: (() -> Unit), onBottomSheetDialogItemClicked: (Attendance.Status) -> Unit, - onDropDownClicked: ((MemberState) -> Unit) + onDropDownClicked: ((memberId: Long) -> Unit), ) { - val attendanceTypes = remember { - listOf( - Attendance.Status.ABSENT, - Attendance.Status.NORMAL, - Attendance.Status.LATE, - Attendance.Status.ADMIT - ) - } - ModalBottomSheetLayout( sheetContent = { BottomSheetDialog( - attendanceTypes = attendanceTypes, + itemStates = uiState.bottomSheetDialogState, onClickItem = { attendanceType -> onBottomSheetDialogItemClicked.invoke( attendanceType @@ -130,7 +113,7 @@ fun ManagementScreen( modifier = Modifier .background(AttendanceTheme.colors.backgroundColors.background) .padding(horizontal = 4.dp), - title = uiState.sessionTitle, + title = uiState.topBarState.sessionTitle, onClickBackButton = { onBackButtonClicked.invoke() } ) }, @@ -143,22 +126,19 @@ fun ManagementScreen( .background(AttendanceTheme.colors.backgroundColors.background) .padding(innerPadding) ) { - item { Column( - modifier = Modifier.padding(start = 24.dp, end = 24.dp) + modifier = Modifier.padding(24.dp) ) { - Spacer(modifier = Modifier.height(28.dp)) - AttendCountText(memberCount = uiState.memberCount) - Spacer(modifier = Modifier.height(28.dp)) + StatisticalTableLayout(state = uiState.attendanceStatisticalTableState) } } itemsIndexed( - items = uiState.teams, - key = { _, team -> team.teamName } + items = uiState.foldableItemStates, + key = { _, team -> team.headerState.label } ) { _, team -> - ExpandableTeam( + FoldableItemLayout( state = team, onDropDownClicked = { changedMember -> onDropDownClicked.invoke( @@ -176,196 +156,11 @@ fun ManagementScreen( } } -@Preview @Composable -fun AttendCountText( +private fun BottomSheetDialog( modifier: Modifier = Modifier, - memberCount: Int = 0 -) { - Box( - modifier = modifier - .fillMaxWidth() - .height(48.dp) - .clip(RoundedCornerShape(10.dp)) - .background(color = AttendanceTheme.colors.mainColors.YappOrangeAlpha) - ) { - Crossfade(targetState = memberCount) { count -> - Text( - modifier = Modifier - .fillMaxSize() - .padding(horizontal = 24.dp, vertical = 12.dp), - text = "${count}명이 출석했어요", - textAlign = TextAlign.Center, - style = AttendanceTypography.body1, - color = AttendanceTheme.colors.mainColors.YappOrange - ) - } - } -} - -@Preview -@Composable -fun ExpandableTeam( - modifier: Modifier = Modifier, - state: TeamState = TeamState(), - onDropDownClicked: ((MemberState) -> Unit)? = null -) { - var expanded by rememberSaveable { mutableStateOf(false) } - - Column( - modifier = modifier - .fillMaxWidth() - .wrapContentHeight() - .padding(start = 24.dp, end = 24.dp) - .background(AttendanceTheme.colors.backgroundColors.background) - .animateContentSize( - animationSpec = spring( - dampingRatio = Spring.DampingRatioLowBouncy, - stiffness = Spring.StiffnessLow - ) - ), - horizontalAlignment = Alignment.CenterHorizontally - ) { - - TeamHeader( - state = state, - expanded = expanded, - onExpandClicked = { changedState -> expanded = changedState } - ) - - AnimatedVisibility( - visible = expanded, - enter = fadeIn(animationSpec = tween(50)) + - expandVertically( - animationSpec = spring( - dampingRatio = Spring.DampingRatioLowBouncy, - stiffness = Spring.StiffnessLow - ) - ), - exit = fadeOut(animationSpec = tween(50)) + - shrinkVertically( - animationSpec = spring( - dampingRatio = Spring.DampingRatioLowBouncy, - stiffness = Spring.StiffnessLow - ) - ) - ) { - Column { - Divider( - modifier = Modifier.height(1.dp), - color = AttendanceTheme.colors.grayScale.Gray300 - ) - for (member in state.members) { - MemberContent( - state = member, - onDropDownClicked = { clickedMember -> - onDropDownClicked?.invoke(clickedMember) - } - ) - } - Divider( - modifier = Modifier.height(1.dp), - color = AttendanceTheme.colors.grayScale.Gray300 - ) - } - } - } -} - -@Preview -@Composable -fun TeamHeader( - modifier: Modifier = Modifier, - state: TeamState = TeamState(), - expanded: Boolean = false, - onExpandClicked: (Boolean) -> Unit = {} -) { - Row( - modifier = modifier - .height(62.dp) - .fillMaxWidth() - .background(AttendanceTheme.colors.backgroundColors.background) - .clickable { onExpandClicked(!expanded) } - ) { - Text( - modifier = Modifier - .width(0.dp) - .weight(0.9F) - .fillMaxHeight() - .padding(vertical = 18.dp), - text = state.teamName, - textAlign = TextAlign.Start, - style = AttendanceTypography.h3, - color = AttendanceTheme.colors.grayScale.Gray1200 - ) - - IconButton( - modifier = Modifier - .weight(0.1F) - .fillMaxHeight() - .width(0.dp), - onClick = { onExpandClicked(!expanded) } - ) { - Icon( - painter = if (expanded) painterResource(id = R.drawable.icon_chevron_up) else painterResource( - id = R.drawable.icon_chevron_down - ), - tint = AttendanceTheme.colors.grayScale.Gray600, - contentDescription = null - ) - } - } -} - -@Preview -@Composable -fun MemberContent( - modifier: Modifier = Modifier, - state: MemberState = MemberState(), - onDropDownClicked: (MemberState) -> Unit = {} -) { - ConstraintLayout( - modifier = modifier - .fillMaxWidth() - .height(59.dp) - ) { - val (ydsDropdownButton, nameText) = createRefs() - - Text( - modifier = Modifier - .wrapContentWidth() - .constrainAs(nameText) { - start.linkTo(parent.start, margin = 8.dp) - top.linkTo(parent.top, margin = 17.5.dp) - bottom.linkTo(parent.bottom, margin = 17.5.dp) - }, - text = state.name, - textAlign = TextAlign.Start, - style = AttendanceTypography.body1, - color = AttendanceTheme.colors.grayScale.Gray800 - ) - - YDSDropDownButton( - modifier = Modifier - .wrapContentHeight() - .animateContentSize() - .constrainAs(ydsDropdownButton) { - end.linkTo(parent.end, margin = 8.dp) - top.linkTo(parent.top, margin = 13.dp) - bottom.linkTo(parent.bottom, margin = 13.dp) - }, - text = state.attendance.status.text, - onClick = { onDropDownClicked.invoke(state) } - ) - } - -} - -@Composable -fun BottomSheetDialog( - modifier: Modifier = Modifier, - attendanceTypes: List, - onClickItem: (Attendance.Status) -> Unit = {} + itemStates: List, + onClickItem: (Attendance.Status) -> Unit = {}, ) { Column( modifier = modifier @@ -381,10 +176,10 @@ fun BottomSheetDialog( .background(color = AttendanceTheme.colors.backgroundColors.backgroundElevated) ) - for (type in attendanceTypes) { - BottomSheetDialogItem( - attendanceType = type, - onClickItem = { onClickItem.invoke(type) } + for (itemState in itemStates) { + AttendanceBottomSheetItemLayout( + state = itemState, + onClickItem = { onClickItem.invoke(itemState.onClickIcon) } ) } @@ -397,31 +192,4 @@ fun BottomSheetDialog( } } -@Preview -@Composable -fun BottomSheetDialogItem( - modifier: Modifier = Modifier, - attendanceType: Attendance.Status = Attendance.Status.NORMAL, - onClickItem: (Attendance.Status) -> Unit = {} -) { - Column( - modifier = modifier - .fillMaxWidth() - .height(52.dp) - .background(color = AttendanceTheme.colors.backgroundColors.backgroundElevated) - .clickable { onClickItem.invoke(attendanceType) }, - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center - ) { - Text( - modifier = modifier - .fillMaxWidth() - .fillMaxHeight() - .padding(vertical = 14.dp), - text = attendanceType.text, - style = AttendanceTypography.subtitle1, - color = AttendanceTheme.colors.grayScale.Gray1200, - textAlign = TextAlign.Center, - ) - } -} + diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementContract.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementContract.kt index 3e2109cc..f9df3654 100644 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementContract.kt +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementContract.kt @@ -4,36 +4,42 @@ import com.yapp.common.base.UiEvent import com.yapp.common.base.UiSideEffect import com.yapp.common.base.UiState import com.yapp.domain.model.Attendance +import com.yapp.presentation.ui.admin.management.components.attendanceBottomSheet.AttendanceBottomSheetItemLayoutState +import com.yapp.presentation.ui.admin.management.components.statisticalTable.StatisticalTableLayoutState +import com.yapp.presentation.ui.admin.management.components.foldableItem.FoldableItemLayoutState +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf class ManagementContract { + + private companion object { + const val NOT_SELECTED = -1L + } + data class ManagementState( val loadState: LoadState = LoadState.Idle, - val sessionId: Int = 0, - val sessionTitle: String = "", - val memberCount: Int = 0, - val selectedMember: MemberState? = null, - val teams: List = emptyList(), + val shared: Shared = Shared(), + val topBarState: TopBarLayoutState = TopBarLayoutState(), + val attendanceStatisticalTableState: StatisticalTableLayoutState = StatisticalTableLayoutState(), + val foldableItemStates: ImmutableList = persistentListOf(), + val bottomSheetDialogState: ImmutableList = persistentListOf() ) : UiState { + data class Shared( + val sessionId: Int = 0, + val selectedMemberId: Long = NOT_SELECTED + ) + + data class TopBarLayoutState(val sessionTitle: String = "") + enum class LoadState { Loading, Idle, Error } - - data class TeamState( - val teamName: String = "", - val members: List = emptyList(), - ) - - data class MemberState( - val id: Long = 0L, - val name: String = "", - val attendance: Attendance = Attendance(sessionId = 0, status = Attendance.Status.NORMAL), - ) } sealed class ManagementEvent : UiEvent { - data class OnDropDownButtonClicked(val member: ManagementState.MemberState) : ManagementEvent() + data class OnDropDownButtonClicked(val memberId: Long) : ManagementEvent() data class OnAttendanceTypeChanged(val attendanceType: Attendance.Status) : ManagementEvent() } diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementViewModel.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementViewModel.kt index c2326e51..39187f3d 100644 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementViewModel.kt +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementViewModel.kt @@ -1,19 +1,15 @@ package com.yapp.presentation.ui.admin.management import androidx.lifecycle.SavedStateHandle -import androidx.lifecycle.viewModelScope import com.yapp.common.base.BaseViewModel -import com.yapp.domain.model.Attendance -import com.yapp.domain.model.Member -import com.yapp.domain.model.Team import com.yapp.domain.usecases.GetAllMemberUseCase import com.yapp.domain.usecases.SetMemberAttendanceUseCase import com.yapp.presentation.ui.admin.AdminConstants.KEY_SESSION_ID import com.yapp.presentation.ui.admin.AdminConstants.KEY_SESSION_TITLE -import com.yapp.presentation.ui.admin.management.ManagementContract.* -import com.yapp.presentation.ui.admin.management.ManagementContract.ManagementState.* +import com.yapp.presentation.ui.admin.management.ManagementContract.ManagementEvent +import com.yapp.presentation.ui.admin.management.ManagementContract.ManagementSideEffect +import com.yapp.presentation.ui.admin.management.ManagementContract.ManagementState import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel @@ -26,106 +22,16 @@ class ManagementViewModel @Inject constructor( companion object { const val DEFAULT_SESSION_ID = 1 - const val DEFAULT_SESSION_TITLE = "YAPP" + const val TEXT_LOAD_SESSION_TITLE_FAILED = "LOAD FAILED" } init { - val sessionId = savedStateHandle.get(KEY_SESSION_ID) ?: DEFAULT_SESSION_ID - val sessionTitle = savedStateHandle.get(KEY_SESSION_TITLE) ?: DEFAULT_SESSION_TITLE - setState { this.copy(sessionId = sessionId, sessionTitle = sessionTitle) } - - viewModelScope.launch { - setState { this.copy(loadState = LoadState.Loading) } - getAllMemberState(sessionId = uiState.value.sessionId) - } + val sessionId = savedStateHandle.get(KEY_SESSION_ID) ?: error("세션 아이디를 불러올 수 없습니다.") + val sessionTitle = savedStateHandle.get(KEY_SESSION_TITLE) ?: TEXT_LOAD_SESSION_TITLE_FAILED } override suspend fun handleEvent(event: ManagementEvent) { - when (event) { - is ManagementEvent.OnDropDownButtonClicked -> { - setState { this.copy(selectedMember = event.member) } - setEffect(ManagementSideEffect.OpenBottomSheetDialog) - } - - is ManagementEvent.OnAttendanceTypeChanged -> { - uiState.value.selectedMember?.let { selectedMember -> - changeMemberAttendance( - selectedMember = selectedMember, - changedAttendanceStatus = event.attendanceType, - sessionId = uiState.value.sessionId - ) - } - } - } - } - - - - private suspend fun getAllMemberState(sessionId: Int) { - //getAllMemberUseCase().collect { } - - getAllMemberUseCase().collect { result -> - result.onSuccess { members -> - val membersByTeam = members.groupBy { member -> member.team } - .map { (team, members) -> mapToTeamState(team, members, sessionId) } - .sortedBy { teamState -> teamState.teamName } - - val attendCount = membersByTeam.flatMap { it.members } - .count { it.attendance.status == Attendance.Status.NORMAL } - - setState { - this.copy( - loadState = LoadState.Idle, - memberCount = attendCount, - teams = membersByTeam - ) - } - }.onFailure { - setState { this.copy(loadState = LoadState.Error) } - } - } - - } - - private fun mapToTeamState(team: Team, members: List, sessionId: Int): TeamState { - return TeamState( - teamName = String.format("${team.type.value} ${team.number}팀"), - members = members.sortedWith( - compareBy { it.attendances.getAttendanceBySessionId(sessionId).status.ordinal } - .thenBy { it.name } - ).map { member -> - MemberState( - id = member.id, - name = member.name, - attendance = member.attendances.getAttendanceBySessionId(sessionId = sessionId) - ) - } - ) - } - private suspend fun changeMemberAttendance( - selectedMember: MemberState, - changedAttendanceStatus: Attendance.Status, - sessionId: Int, - ) { - if (selectedMember.attendance.status == changedAttendanceStatus) { - return - } - setMemberAttendanceUseCase( - params = SetMemberAttendanceUseCase.Params( - memberId = selectedMember.id, - sessionId = sessionId, - changedAttendance = Attendance( - sessionId = sessionId, - status = changedAttendanceStatus - ) - ) - ).onSuccess { - setState { this.copy(selectedMember = null) } - getAllMemberState(sessionId = sessionId) - }.onFailure { - setState { this.copy(loadState = LoadState.Error) } - } } } diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/AnimatedCounterText.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/AnimatedCounterText.kt new file mode 100644 index 00000000..558a4b9b --- /dev/null +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/AnimatedCounterText.kt @@ -0,0 +1,65 @@ +package com.yapp.presentation.ui.admin.management.components + +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically +import androidx.compose.animation.with +import androidx.compose.foundation.layout.Row +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.TextStyle + + +@OptIn(ExperimentalAnimationApi::class) +@Composable +internal fun AnimatedCounterText( + count: Int, + modifier: Modifier = Modifier, + style: TextStyle, + prefix: @Composable () -> Unit = {}, + suffix: @Composable () -> Unit = {}, +) { + var oldCount by remember { + mutableStateOf(count) + } + SideEffect { + oldCount = count + } + Row(modifier = modifier) { + val countString = count.toString() + val oldCountString = oldCount.toString() + + prefix() + for (i in countString.indices) { + val oldChar = oldCountString.getOrNull(i) + val newChar = countString[i] + val char = if (oldChar == newChar) { + oldCountString[i] + } else { + countString[i] + } + AnimatedContent( + targetState = char, + transitionSpec = { + slideInVertically { it } with slideOutVertically { -it } + } + ) { char -> + + Text( + text = char.toString(), + style = style, + softWrap = false + ) + + } + } + suffix() + } +} \ No newline at end of file diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceBottomSheet/AttendanceBottomSheetItemLayout.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceBottomSheet/AttendanceBottomSheetItemLayout.kt new file mode 100644 index 00000000..bfd11d58 --- /dev/null +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceBottomSheet/AttendanceBottomSheetItemLayout.kt @@ -0,0 +1,101 @@ +package com.yapp.presentation.ui.admin.management.components.attendanceBottomSheet + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.material.Icon +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +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.yapp.common.theme.AttendanceTheme +import com.yapp.common.theme.AttendanceTypography +import com.yapp.presentation.R + + +@Preview(backgroundColor = 0xFFFFFFFF, showBackground = true) +@Composable +private fun AttendanceBottomSheetItemLayoutPreview() { + var states by remember { + mutableStateOf( + listOf( + AttendanceBottomSheetItemLayoutState(label = "출석", iconType = AttendanceBottomSheetItemLayoutState.IconType.ATTEND), + AttendanceBottomSheetItemLayoutState(label = "지각", iconType = AttendanceBottomSheetItemLayoutState.IconType.TARDY), + AttendanceBottomSheetItemLayoutState(label = "결석", iconType = AttendanceBottomSheetItemLayoutState.IconType.ABSENT), + AttendanceBottomSheetItemLayoutState(label = "출석 인정", iconType = AttendanceBottomSheetItemLayoutState.IconType.ADMIT) + ) + ) + } + + AttendanceTheme { + Column(modifier = Modifier) { + for (state in states) { + AttendanceBottomSheetItemLayout( + state = state, + onClickItem = { + + } + ) + } + } + } +} + +@Composable +fun AttendanceBottomSheetItemLayout( + modifier: Modifier = Modifier, + state: AttendanceBottomSheetItemLayoutState, + onClickItem: (AttendanceBottomSheetItemLayoutState.IconType) -> Unit, +) { + Row( + modifier = modifier + .fillMaxWidth() + .height(52.dp) + .background(color = AttendanceTheme.colors.backgroundColors.backgroundElevated) + .clickable { + onClickItem.invoke(state.iconType) + }, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ) { + Icon( + modifier = Modifier.size(20.dp), + painter = when (state.iconType) { + AttendanceBottomSheetItemLayoutState.IconType.ATTEND -> painterResource(id = R.drawable.icon_attend) + AttendanceBottomSheetItemLayoutState.IconType.ADMIT -> painterResource(id = R.drawable.icon_attend) + AttendanceBottomSheetItemLayoutState.IconType.TARDY -> painterResource(id = R.drawable.icon_tardy) + AttendanceBottomSheetItemLayoutState.IconType.ABSENT -> painterResource(id = R.drawable.icon_absent) + }, + contentDescription = "attendance_type_icon" + ) + + Spacer(modifier = Modifier.width(6.dp)) + + Text( + modifier = modifier + .fillMaxHeight() + .padding(vertical = 14.dp), + text = state.label, + style = AttendanceTypography.subtitle1, + color = AttendanceTheme.colors.grayScale.Gray1200, + textAlign = TextAlign.Center, + ) + } +} \ No newline at end of file diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceBottomSheet/AttendanceBottomSheetItemLayoutState.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceBottomSheet/AttendanceBottomSheetItemLayoutState.kt new file mode 100644 index 00000000..6f1089f8 --- /dev/null +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceBottomSheet/AttendanceBottomSheetItemLayoutState.kt @@ -0,0 +1,23 @@ +package com.yapp.presentation.ui.admin.management.components.attendanceBottomSheet + +import com.yapp.domain.model.Attendance + + +data class AttendanceBottomSheetItemLayoutState( + val label: String, + val iconType: IconType, +) { + + val onClickIcon: Attendance.Status + get() = when (iconType) { + IconType.ATTEND -> Attendance.Status.NORMAL + IconType.TARDY -> Attendance.Status.LATE + IconType.ADMIT -> Attendance.Status.ADMIT + IconType.ABSENT -> Attendance.Status.ABSENT + } + + enum class IconType { + ATTEND, TARDY, ADMIT, ABSENT + } + +} \ No newline at end of file diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceTypeButton/AttendanceTypeButton.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceTypeButton/AttendanceTypeButton.kt new file mode 100644 index 00000000..51e0de18 --- /dev/null +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceTypeButton/AttendanceTypeButton.kt @@ -0,0 +1,100 @@ +package com.yapp.presentation.ui.admin.management.components.attendanceTypeButton + +import androidx.compose.animation.Crossfade +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Button +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.Icon +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.yapp.common.R +import com.yapp.common.theme.AttendanceTheme +import com.yapp.common.theme.AttendanceTypography + + +@Preview(backgroundColor = 0xFFFFFFFF, showBackground = true) +@Composable +private fun AttendanceTypeButtonPreview() { + var state by remember { + mutableStateOf( + AttendanceTypeButtonState( + label = "출석", + iconType = AttendanceTypeButtonState.IconType.ATTEND + ) + ) + } + + AttendanceTheme { + Box(modifier = Modifier) { + AttendanceTypeButton( + state = state, + onClick = {} + ) + } + } +} + +@Composable +internal fun AttendanceTypeButton( + modifier: Modifier = Modifier, + state: AttendanceTypeButtonState, + onClick: () -> Unit, +) { + Button( + onClick = onClick, + modifier = modifier + .wrapContentWidth(), + shape = RoundedCornerShape(8.dp), + colors = ButtonDefaults.buttonColors(backgroundColor = AttendanceTheme.colors.grayScale.Gray200), + elevation = null + ) { + Row( + modifier = Modifier + .align(Alignment.CenterVertically) + .wrapContentSize(), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + modifier = Modifier.size(20.dp), + painter = when (state.iconType) { + AttendanceTypeButtonState.IconType.ATTEND -> painterResource(id = R.drawable.icon_attend) + AttendanceTypeButtonState.IconType.ADMIT -> painterResource(id = R.drawable.icon_attend) + AttendanceTypeButtonState.IconType.TARDY -> painterResource(id = R.drawable.icon_tardy) + AttendanceTypeButtonState.IconType.ABSENT -> painterResource(id = R.drawable.icon_absent) + }, + contentDescription = "drop down", + tint = AttendanceTheme.colors.grayScale.Gray800 + ) + + Spacer(modifier = Modifier.width(4.dp)) + + Crossfade(targetState = state.label) { + Text( + text = it, + modifier = Modifier + .height(20.dp) + .align(alignment = Alignment.CenterVertically), + style = AttendanceTypography.subtitle2, + color = AttendanceTheme.colors.grayScale.Gray800 + ) + } + } + } +} \ No newline at end of file diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceTypeButton/AttendanceTypeButtonState.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceTypeButton/AttendanceTypeButtonState.kt new file mode 100644 index 00000000..adee47d1 --- /dev/null +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceTypeButton/AttendanceTypeButtonState.kt @@ -0,0 +1,11 @@ +package com.yapp.presentation.ui.admin.management.components.attendanceTypeButton + + +data class AttendanceTypeButtonState( + val label: String = "", + val iconType: IconType = IconType.ATTEND, +) { + enum class IconType { + ATTEND, TARDY, ADMIT, ABSENT + } +} \ No newline at end of file diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableContentItem/FoldableItemContentLayout.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableContentItem/FoldableItemContentLayout.kt new file mode 100644 index 00000000..3af04e12 --- /dev/null +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableContentItem/FoldableItemContentLayout.kt @@ -0,0 +1,89 @@ +package com.yapp.presentation.ui.admin.management.components.foldableContentItem + +import androidx.compose.animation.animateContentSize +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.constraintlayout.compose.ConstraintLayout +import com.yapp.common.theme.AttendanceTheme +import com.yapp.common.theme.AttendanceTypography +import com.yapp.presentation.ui.admin.management.components.attendanceTypeButton.AttendanceTypeButton +import com.yapp.presentation.ui.admin.management.components.attendanceTypeButton.AttendanceTypeButtonState + + +@Preview(backgroundColor = 0xFFFFFFFF, showBackground = true) +@Composable +private fun FoldableItemContentLayoutPreview() { + var state by remember { + mutableStateOf( + FoldableItemContentLayoutState( + id = 0L, + label = "", + subLabel = "", + attendanceTypeButtonState = AttendanceTypeButtonState() + ) + ) + } + + AttendanceTheme { + Box(modifier = Modifier) { + FoldableItemContentLayout(state = state) + } + } +} + +@Composable +internal fun FoldableItemContentLayout( + modifier: Modifier = Modifier, + state: FoldableItemContentLayoutState, + onDropDownClicked: (memberId: Long) -> Unit = {}, +) { + ConstraintLayout( + modifier = modifier + .fillMaxWidth() + .height(59.dp) + ) { + val (ydsDropdownButton, nameText) = createRefs() + + Text( + modifier = Modifier + .wrapContentWidth() + .constrainAs(nameText) { + start.linkTo(parent.start, margin = 8.dp) + top.linkTo(parent.top, margin = 17.5.dp) + bottom.linkTo(parent.bottom, margin = 17.5.dp) + }, + text = state.label, + textAlign = TextAlign.Start, + style = AttendanceTypography.body1, + color = AttendanceTheme.colors.grayScale.Gray800 + ) + + AttendanceTypeButton( + modifier = Modifier + .wrapContentHeight() + .animateContentSize() + .constrainAs(ydsDropdownButton) { + end.linkTo(parent.end, margin = 8.dp) + top.linkTo(parent.top, margin = 13.dp) + bottom.linkTo(parent.bottom, margin = 13.dp) + }, + state = state.attendanceTypeButtonState, + onClick = { onDropDownClicked.invoke(state.id) } + ) + } + +} + diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableContentItem/FoldableItemContentLayoutState.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableContentItem/FoldableItemContentLayoutState.kt new file mode 100644 index 00000000..4a1da89b --- /dev/null +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableContentItem/FoldableItemContentLayoutState.kt @@ -0,0 +1,11 @@ +package com.yapp.presentation.ui.admin.management.components.foldableContentItem + +import com.yapp.presentation.ui.admin.management.components.attendanceTypeButton.AttendanceTypeButtonState + + +data class FoldableItemContentLayoutState( + val id: Long, + val label: String, + val subLabel: String, + val attendanceTypeButtonState: AttendanceTypeButtonState, +) \ No newline at end of file diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableHeaderItem/FoldableItemHeaderLayout.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableHeaderItem/FoldableItemHeaderLayout.kt new file mode 100644 index 00000000..660f9ecf --- /dev/null +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableHeaderItem/FoldableItemHeaderLayout.kt @@ -0,0 +1,121 @@ +package com.yapp.presentation.ui.admin.management.components.foldableHeaderItem + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.IntrinsicSize +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +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.yapp.common.theme.AttendanceTheme +import com.yapp.common.theme.AttendanceTypography +import com.yapp.presentation.R +import com.yapp.presentation.ui.admin.management.components.AnimatedCounterText + + +@Preview(backgroundColor = 0xFFFFFFFF, showBackground = true) +@Composable +private fun FoldableItemHeaderLayoutPreview() { + var state by remember { + mutableStateOf( + FoldableItemHeaderLayoutState( + label = "Android 1", + attendMemberCount = 4, + allTeamMemberCount = 6 + ) + ) + } + + AttendanceTheme { + Box(modifier = Modifier) { + FoldableItemHeaderLayout(state = state) + } + } +} + +@Composable +internal fun FoldableItemHeaderLayout( + modifier: Modifier = Modifier, + state: FoldableItemHeaderLayoutState, + expanded: Boolean = false, + onExpandClicked: (Boolean) -> Unit = {}, +) { + Row( + modifier = modifier + .fillMaxWidth() + .background(AttendanceTheme.colors.backgroundColors.background) + .clickable { onExpandClicked(!expanded) }, + verticalAlignment = Alignment.CenterVertically + ) { + Row( + modifier = Modifier + .width(0.dp) + .weight(0.9F) + .padding(vertical = 18.dp), + horizontalArrangement = Arrangement.Start, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + modifier = Modifier, + text = state.label, + textAlign = TextAlign.Start, + style = AttendanceTypography.h3, + color = AttendanceTheme.colors.grayScale.Gray1200 + ) + + Spacer(modifier = Modifier.width(6.dp)) + + AnimatedCounterText( + count = state.attendMemberCount, + style = AttendanceTypography.subtitle2.copy(color = AttendanceTheme.colors.mainColors.YappOrange) + ) + + Text( + text = "/", + textAlign = TextAlign.Start, + style = AttendanceTypography.subtitle2, + color = AttendanceTheme.colors.mainColors.YappOrange + ) + + AnimatedCounterText( + count = state.allTeamMemberCount, + style = AttendanceTypography.subtitle2.copy(color = AttendanceTheme.colors.mainColors.YappOrange) + ) + } + + + IconButton( + modifier = Modifier + .width(0.dp) + .height(IntrinsicSize.Min) + .weight(0.1F), + onClick = { onExpandClicked(!expanded) } + ) { + Icon( + painter = if (expanded) painterResource(id = R.drawable.icon_chevron_up) else painterResource( + id = R.drawable.icon_chevron_down + ), + tint = AttendanceTheme.colors.grayScale.Gray600, + contentDescription = null + ) + } + } +} \ No newline at end of file diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableHeaderItem/FoldableItemHeaderLayoutState.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableHeaderItem/FoldableItemHeaderLayoutState.kt new file mode 100644 index 00000000..898e4ea0 --- /dev/null +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableHeaderItem/FoldableItemHeaderLayoutState.kt @@ -0,0 +1,8 @@ +package com.yapp.presentation.ui.admin.management.components.foldableHeaderItem + + +data class FoldableItemHeaderLayoutState( + val label: String = "", + val attendMemberCount: Int = 0, + val allTeamMemberCount: Int = 0, +) \ No newline at end of file diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/FoldableItemLayout.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/FoldableItemLayout.kt new file mode 100644 index 00000000..5ac5c660 --- /dev/null +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/FoldableItemLayout.kt @@ -0,0 +1,146 @@ +package com.yapp.presentation.ui.admin.management.components.foldableItem + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.animateContentSize +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.spring +import androidx.compose.animation.core.tween +import androidx.compose.animation.expandVertically +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.shrinkVertically +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.material.Divider +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.yapp.common.theme.AttendanceTheme +import com.yapp.domain.model.Attendance +import com.yapp.presentation.ui.admin.management.components.attendanceTypeButton.AttendanceTypeButtonState +import com.yapp.presentation.ui.admin.management.components.foldableContentItem.FoldableItemContentLayout +import com.yapp.presentation.ui.admin.management.components.foldableContentItem.FoldableItemContentLayoutState +import com.yapp.presentation.ui.admin.management.components.foldableHeaderItem.FoldableItemHeaderLayout +import com.yapp.presentation.ui.admin.management.components.foldableHeaderItem.FoldableItemHeaderLayoutState + + +@Preview +@Composable +private fun FoldableItemLayoutPreview() { + var state by remember { + mutableStateOf( + FoldableItemLayoutState( + headerState = FoldableItemHeaderLayoutState( + label = "Android 1", + attendMemberCount = 4, + allTeamMemberCount = 6 + ), + contentStates = listOf( + FoldableItemContentLayoutState( + id = 0L, + label = "김철수", + subLabel = "ProducDesign", + attendanceTypeButtonState = AttendanceTypeButtonState() + ), + FoldableItemContentLayoutState( + id = 0L, + label = "", + subLabel = "", + attendanceTypeButtonState = AttendanceTypeButtonState() + ) + ) + ) + ) + } + + AttendanceTheme { + Box(modifier = Modifier) { + FoldableItemLayout( + state = state, + onDropDownClicked = { + + } + ) + } + } +} + +@Composable +internal fun FoldableItemLayout( + modifier: Modifier = Modifier, + state: FoldableItemLayoutState, + onDropDownClicked: ((memberId: Long) -> Unit), +) { + var expanded by rememberSaveable { mutableStateOf(false) } + + Column( + modifier = modifier + .fillMaxWidth() + .wrapContentHeight() + .padding(start = 24.dp, end = 24.dp) + .background(AttendanceTheme.colors.backgroundColors.background) + .animateContentSize( + animationSpec = spring( + dampingRatio = Spring.DampingRatioLowBouncy, + stiffness = Spring.StiffnessLow + ) + ), + horizontalAlignment = Alignment.CenterHorizontally + ) { + + FoldableItemHeaderLayout( + state = state.headerState, + expanded = expanded, + onExpandClicked = { changedState -> expanded = changedState } + ) + + AnimatedVisibility( + visible = expanded, + enter = fadeIn(animationSpec = tween(50)) + + expandVertically( + animationSpec = spring( + dampingRatio = Spring.DampingRatioLowBouncy, + stiffness = Spring.StiffnessLow + ) + ), + exit = fadeOut(animationSpec = tween(50)) + + shrinkVertically( + animationSpec = spring( + dampingRatio = Spring.DampingRatioLowBouncy, + stiffness = Spring.StiffnessLow + ) + ) + ) { + Column { + Divider( + modifier = Modifier.height(1.dp), + color = AttendanceTheme.colors.grayScale.Gray300 + ) + for (contentItem in state.contentStates) { + FoldableItemContentLayout( + state = contentItem, + onDropDownClicked = { clickedMemberId -> + onDropDownClicked.invoke(clickedMemberId) + } + ) + } + Divider( + modifier = Modifier.height(1.dp), + color = AttendanceTheme.colors.grayScale.Gray300 + ) + } + } + } +} \ No newline at end of file diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/FoldableItemLayoutState.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/FoldableItemLayoutState.kt new file mode 100644 index 00000000..fd3700cb --- /dev/null +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/FoldableItemLayoutState.kt @@ -0,0 +1,10 @@ +package com.yapp.presentation.ui.admin.management.components.foldableItem + +import com.yapp.presentation.ui.admin.management.components.foldableContentItem.FoldableItemContentLayoutState +import com.yapp.presentation.ui.admin.management.components.foldableHeaderItem.FoldableItemHeaderLayoutState + + +data class FoldableItemLayoutState( + val headerState: FoldableItemHeaderLayoutState, + val contentStates: List, +) \ No newline at end of file diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/statisticalTable/StatisticalTableLayout.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/statisticalTable/StatisticalTableLayout.kt new file mode 100644 index 00000000..7c942ed6 --- /dev/null +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/statisticalTable/StatisticalTableLayout.kt @@ -0,0 +1,188 @@ +package com.yapp.presentation.ui.admin.management.components.statisticalTable + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.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.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Button +import androidx.compose.material.Divider +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.clipToBounds +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.yapp.common.theme.AttendanceTheme +import com.yapp.common.theme.AttendanceTypography +import com.yapp.presentation.ui.admin.management.components.AnimatedCounterText + + +@Preview(backgroundColor = 0xFFFFFFFF, showBackground = true) +@Composable +private fun StatisticalTableLayoutPreview() { + var state by remember { + mutableStateOf( + StatisticalTableLayoutState( + totalCount = 60, + tardyCount = 2, + absentCount = 3, + admitCount = 0 + ) + ) + } + + AttendanceTheme { + Box( + modifier = Modifier + .fillMaxSize() + .background(color = Color.White) + .padding(horizontal = 24.dp) + ) { + Button( + modifier = Modifier.align(Alignment.Center), + onClick = { state = state.copy(absentCount = state.absentCount + 1) } + ) { + Text(text = "dd") + } + + StatisticalTableLayout(state = state) + } + } + +} + +@Composable +internal fun StatisticalTableLayout( + state: StatisticalTableLayoutState, +) { + Column( + modifier = Modifier + .fillMaxWidth() + .clip(shape = RoundedCornerShape(10.dp)) + .background(color = AttendanceTheme.colors.grayScale.Gray200) + .padding((1.5).dp) + .clipToBounds() + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 12.dp), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + AnimatedCounterText( + count = state.attendanceCount, + style = AttendanceTypography.subtitle2.copy(color = AttendanceTheme.colors.etcColors.EtcGreen), + prefix = { + Text( + style = AttendanceTypography.body2, + text = "${state.totalCount}명 중 ", + color = AttendanceTheme.colors.grayScale.Gray1200 + ) + }, + suffix = { + Text( + style = AttendanceTypography.subtitle2, + text = "명", + color = AttendanceTheme.colors.etcColors.EtcGreen + ) + + Text( + style = AttendanceTypography.body2, + text = "이 출석했어요", + color = AttendanceTheme.colors.grayScale.Gray1200 + ) + } + ) + } + + Row( + modifier = Modifier + .fillMaxWidth() + .clip(shape = RoundedCornerShape(bottomStart = 10.dp, bottomEnd = 10.dp)) + .background(color = AttendanceTheme.colors.backgroundColors.background) + .padding(vertical = 12.dp, horizontal = 16.dp) + .clipToBounds(), + horizontalArrangement = Arrangement.spacedBy(space = 10.dp, alignment = Alignment.CenterHorizontally), + verticalAlignment = Alignment.CenterVertically + ) { + TableItemText( + label = "지각", + count = state.tardyCount + ) + + Divider( + modifier = Modifier + .width((1.5).dp) + .height(16.dp), + color = AttendanceTheme.colors.grayScale.Gray200, + thickness = (1.5).dp + ) + + TableItemText( + label = "결석", + count = state.absentCount + ) + + Divider( + modifier = Modifier + .width((1.5).dp) + .height(16.dp), + color = AttendanceTheme.colors.grayScale.Gray200, + thickness = (1.5).dp + ) + + TableItemText( + label = "출석인정", + count = state.admitCount + ) + } + } +} + +@Composable +private fun TableItemText( + label: String, + count: Int, +) { + Row( + modifier = Modifier.width(80.dp), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = label, + style = AttendanceTypography.body2, + color = AttendanceTheme.colors.grayScale.Gray600 + ) + + Spacer(modifier = Modifier.width(4.dp)) + + AnimatedCounterText( + count = count, + style = AttendanceTypography.subtitle2.copy(color = AttendanceTheme.colors.grayScale.Gray1200), + suffix = { + Text( + text = "명", + style = AttendanceTypography.subtitle2, + color = AttendanceTheme.colors.grayScale.Gray1200 + ) + } + ) + } +} \ No newline at end of file diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/statisticalTable/StatisticalTableLayoutState.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/statisticalTable/StatisticalTableLayoutState.kt new file mode 100644 index 00000000..ce035d30 --- /dev/null +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/statisticalTable/StatisticalTableLayoutState.kt @@ -0,0 +1,14 @@ +package com.yapp.presentation.ui.admin.management.components.statisticalTable + + +data class StatisticalTableLayoutState( + val totalCount: Int = 0, + val tardyCount: Int = 0, + val absentCount: Int = 0, + val admitCount: Int = 0, +) { + + val attendanceCount: Int + get() = totalCount - absentCount + +} \ No newline at end of file From d2509dcae01d51ceb74ca5c77de91f24c4b25ef5 Mon Sep 17 00:00:00 2001 From: YeongSang Jeon Date: Mon, 29 May 2023 00:30:06 +0900 Subject: [PATCH 04/18] =?UTF-8?q?chore:=20=EC=BD=94=EB=93=9C=20=EC=A0=95?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/admin/management/Management.kt | 30 ++++++++++++++----- .../ui/admin/management/ManagementContract.kt | 6 ++-- .../AttendanceTypeButton.kt | 2 +- .../foldableItem/FoldableItemLayout.kt | 1 - 4 files changed, 26 insertions(+), 13 deletions(-) diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/Management.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/Management.kt index 1516894b..679ad3a0 100644 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/Management.kt +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/Management.kt @@ -1,18 +1,31 @@ package com.yapp.presentation.ui.admin.management -import androidx.compose.animation.* import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Column +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.wrapContentHeight import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.* -import androidx.compose.runtime.* +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.ModalBottomSheetLayout +import androidx.compose.material.ModalBottomSheetState +import androidx.compose.material.ModalBottomSheetValue +import androidx.compose.material.Scaffold +import androidx.compose.material.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalLifecycleOwner -import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel @@ -21,17 +34,18 @@ import androidx.lifecycle.repeatOnLifecycle import com.google.accompanist.insets.navigationBarsWithImePadding import com.google.accompanist.insets.systemBarsPadding import com.yapp.common.theme.AttendanceTheme -import com.yapp.common.theme.AttendanceTypography import com.yapp.common.yds.YDSAppBar import com.yapp.common.yds.YDSEmptyScreen import com.yapp.common.yds.YDSProgressBar import com.yapp.domain.model.Attendance import com.yapp.presentation.ui.admin.management.ManagementContract.ManagementEvent -import com.yapp.presentation.ui.admin.management.ManagementContract.ManagementState.LoadState.* +import com.yapp.presentation.ui.admin.management.ManagementContract.ManagementState.LoadState.Error +import com.yapp.presentation.ui.admin.management.ManagementContract.ManagementState.LoadState.Idle +import com.yapp.presentation.ui.admin.management.ManagementContract.ManagementState.LoadState.Loading import com.yapp.presentation.ui.admin.management.components.attendanceBottomSheet.AttendanceBottomSheetItemLayout import com.yapp.presentation.ui.admin.management.components.attendanceBottomSheet.AttendanceBottomSheetItemLayoutState -import com.yapp.presentation.ui.admin.management.components.statisticalTable.StatisticalTableLayout import com.yapp.presentation.ui.admin.management.components.foldableItem.FoldableItemLayout +import com.yapp.presentation.ui.admin.management.components.statisticalTable.StatisticalTableLayout import kotlinx.coroutines.launch diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementContract.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementContract.kt index f9df3654..c50c847a 100644 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementContract.kt +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementContract.kt @@ -5,8 +5,8 @@ import com.yapp.common.base.UiSideEffect import com.yapp.common.base.UiState import com.yapp.domain.model.Attendance import com.yapp.presentation.ui.admin.management.components.attendanceBottomSheet.AttendanceBottomSheetItemLayoutState -import com.yapp.presentation.ui.admin.management.components.statisticalTable.StatisticalTableLayoutState import com.yapp.presentation.ui.admin.management.components.foldableItem.FoldableItemLayoutState +import com.yapp.presentation.ui.admin.management.components.statisticalTable.StatisticalTableLayoutState import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -23,12 +23,12 @@ class ManagementContract { val topBarState: TopBarLayoutState = TopBarLayoutState(), val attendanceStatisticalTableState: StatisticalTableLayoutState = StatisticalTableLayoutState(), val foldableItemStates: ImmutableList = persistentListOf(), - val bottomSheetDialogState: ImmutableList = persistentListOf() + val bottomSheetDialogState: ImmutableList = persistentListOf(), ) : UiState { data class Shared( val sessionId: Int = 0, - val selectedMemberId: Long = NOT_SELECTED + val selectedMemberId: Long = NOT_SELECTED, ) data class TopBarLayoutState(val sessionTitle: String = "") diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceTypeButton/AttendanceTypeButton.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceTypeButton/AttendanceTypeButton.kt index 51e0de18..d5b0975f 100644 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceTypeButton/AttendanceTypeButton.kt +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceTypeButton/AttendanceTypeButton.kt @@ -29,7 +29,7 @@ import com.yapp.common.theme.AttendanceTheme import com.yapp.common.theme.AttendanceTypography -@Preview(backgroundColor = 0xFFFFFFFF, showBackground = true) +@Preview(backgroundColor = 0x00000000, showBackground = true) @Composable private fun AttendanceTypeButtonPreview() { var state by remember { diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/FoldableItemLayout.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/FoldableItemLayout.kt index 5ac5c660..3d09346a 100644 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/FoldableItemLayout.kt +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/FoldableItemLayout.kt @@ -28,7 +28,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.yapp.common.theme.AttendanceTheme -import com.yapp.domain.model.Attendance import com.yapp.presentation.ui.admin.management.components.attendanceTypeButton.AttendanceTypeButtonState import com.yapp.presentation.ui.admin.management.components.foldableContentItem.FoldableItemContentLayout import com.yapp.presentation.ui.admin.management.components.foldableContentItem.FoldableItemContentLayoutState From 51dab1a3f6dfc49ce59b67e68a806c8cc47b2285 Mon Sep 17 00:00:00 2001 From: YeongSang Jeon Date: Mon, 29 May 2023 22:35:03 +0900 Subject: [PATCH 05/18] =?UTF-8?q?fix:=20=EC=9E=98=EB=AA=BB=20=EC=A0=95?= =?UTF-8?q?=EC=9D=98=EB=90=9C=20=ED=83=80=EC=9D=B4=ED=8F=AC=EA=B7=B8?= =?UTF-8?q?=EB=9D=BC=ED=94=BC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/src/main/java/com/yapp/common/theme/Type.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/java/com/yapp/common/theme/Type.kt b/common/src/main/java/com/yapp/common/theme/Type.kt index 3badc092..2cfad7a3 100644 --- a/common/src/main/java/com/yapp/common/theme/Type.kt +++ b/common/src/main/java/com/yapp/common/theme/Type.kt @@ -43,7 +43,7 @@ val AttendanceTypography = Typography( ), body1 = TextStyle( fontFamily = Pretendard, - fontWeight = FontWeight.Normal, + fontWeight = FontWeight.Medium, fontSize = 16.sp, lineHeight = 24.sp ), From a6b8bd1ca1f46632659b73d8e6db1c987cea9f17 Mon Sep 17 00:00:00 2001 From: YeongSang Jeon Date: Mon, 29 May 2023 22:35:42 +0900 Subject: [PATCH 06/18] =?UTF-8?q?feat:=20=ED=9A=8C=EC=9B=90=20=EC=95=84?= =?UTF-8?q?=EC=9D=B4=ED=85=9C=EC=9D=84=20=EC=83=88=EB=A1=AD=EA=B2=8C=20?= =?UTF-8?q?=EA=B0=9C=ED=8E=B8=EB=90=9C=20=EB=94=94=EC=9E=90=EC=9D=B8?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FoldableItemContentLayout.kt | 44 ++++++++++++++----- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableContentItem/FoldableItemContentLayout.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableContentItem/FoldableItemContentLayout.kt index 3af04e12..326cb046 100644 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableContentItem/FoldableItemContentLayout.kt +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableContentItem/FoldableItemContentLayout.kt @@ -2,8 +2,11 @@ package com.yapp.presentation.ui.admin.management.components.foldableContentItem import androidx.compose.animation.animateContentSize import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.material.Text @@ -12,6 +15,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview @@ -30,9 +34,12 @@ private fun FoldableItemContentLayoutPreview() { mutableStateOf( FoldableItemContentLayoutState( id = 0L, - label = "", - subLabel = "", - attendanceTypeButtonState = AttendanceTypeButtonState() + label = "장덕철", + subLabel = "Android", + attendanceTypeButtonState = AttendanceTypeButtonState( + label = "출석", + iconType = AttendanceTypeButtonState.IconType.ATTEND + ) ) ) } @@ -53,23 +60,36 @@ internal fun FoldableItemContentLayout( ConstraintLayout( modifier = modifier .fillMaxWidth() - .height(59.dp) + .height(62.dp) ) { - val (ydsDropdownButton, nameText) = createRefs() + val (ydsDropdownButton, textContents) = createRefs() - Text( + Row( modifier = Modifier .wrapContentWidth() - .constrainAs(nameText) { + .constrainAs(textContents) { start.linkTo(parent.start, margin = 8.dp) top.linkTo(parent.top, margin = 17.5.dp) bottom.linkTo(parent.bottom, margin = 17.5.dp) }, - text = state.label, - textAlign = TextAlign.Start, - style = AttendanceTypography.body1, - color = AttendanceTheme.colors.grayScale.Gray800 - ) + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = state.label, + textAlign = TextAlign.Start, + style = AttendanceTypography.body1, + color = AttendanceTheme.colors.grayScale.Gray1000 + ) + + Spacer(modifier = Modifier.width(6.dp)) + + Text( + text = state.subLabel, + textAlign = TextAlign.Start, + style = AttendanceTypography.caption, + color = AttendanceTheme.colors.grayScale.Gray600 + ) + } AttendanceTypeButton( modifier = Modifier From de6df30361c225eb74a5d570eddb67db94f6505c Mon Sep 17 00:00:00 2001 From: YeongSang Jeon Date: Mon, 29 May 2023 22:36:12 +0900 Subject: [PATCH 07/18] =?UTF-8?q?feat:=20YDS=20TabLayout=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 후.. --- .../components/tablayout/YDSTabItemLayout.kt | 5 + .../components/tablayout/YDSTabLayout.kt | 246 ++++++++++++++++++ .../tablayout/YDSTabLayoutItemState.kt | 33 +++ .../components/tablayout/YDSTabLayoutState.kt | 33 +++ 4 files changed, 317 insertions(+) create mode 100644 presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/tablayout/YDSTabItemLayout.kt create mode 100644 presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/tablayout/YDSTabLayout.kt create mode 100644 presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/tablayout/YDSTabLayoutItemState.kt create mode 100644 presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/tablayout/YDSTabLayoutState.kt diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/tablayout/YDSTabItemLayout.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/tablayout/YDSTabItemLayout.kt new file mode 100644 index 00000000..13838e97 --- /dev/null +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/tablayout/YDSTabItemLayout.kt @@ -0,0 +1,5 @@ +package com.yapp.presentation.ui.admin.management.components.tablayout + + +internal class YDSTabItemLayout { +} \ No newline at end of file diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/tablayout/YDSTabLayout.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/tablayout/YDSTabLayout.kt new file mode 100644 index 00000000..ee0500a9 --- /dev/null +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/tablayout/YDSTabLayout.kt @@ -0,0 +1,246 @@ +package com.yapp.presentation.ui.admin.management.components.tablayout + +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.layout.Layout +import androidx.compose.ui.layout.layoutId +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.dp +import com.yapp.common.theme.AttendanceTheme +import com.yapp.common.theme.AttendanceTypography +import kotlinx.collections.immutable.persistentListOf +import kotlin.math.max +import kotlin.math.roundToInt + + +private const val LAYOUT_ID_TAB_INDICATOR = "TabIndicator" +private const val LAYOUT_ID_UNSELECTED_TAB_LABEL = "UnSelectedTabLabel" +private const val LAYOUT_ID_SELECTED_TAB_LABEL = "SelectedTabLabel" + +private const val SPACE_TAB_ITEM = 20 + +@Preview(backgroundColor = 0xFFFFFFFF, showBackground = true) +@Composable +private fun YDSTabLayoutPreview() { + var state by remember { + mutableStateOf( + YDSTabLayoutState( + tabItems = persistentListOf( + YDSTabLayoutItemState( + isSelected = true, + label = "팀별" + ), + YDSTabLayoutItemState( + isSelected = false, + label = "직군별" + ) + ) + ) + ) + } + + AttendanceTheme { + Box(modifier = Modifier.fillMaxSize()) { + YDSTabLayout( + modifier = Modifier.align(Alignment.Center), + tabItems = state.tabItems, + selectedIndex = state.selectedIndex, + onTabSelected = { + state = state.select(it) + } + ) + } + } +} + +@Composable +internal fun YDSTabLayout( + modifier: Modifier = Modifier, + tabItems: List, + selectedIndex: Int, + itemSpace: Int = SPACE_TAB_ITEM, + onTabSelected: (tabItemIndex: Int) -> Unit, +) { + require(tabItems.size >= 2) { "최소 2개의 Tab Item을 가지고 있어야 합니다." } + require(selectedIndex >= 0) { "Invalid selected option [${selectedIndex}" } + + var indicatorPosition by remember { mutableStateOf(0f) } + val indicatorPositionAnim by animateFloatAsState(targetValue = indicatorPosition) + + Layout( + modifier = modifier, + content = { + tabItems.forEachIndexed { index, item -> + YDSTabLayoutItem( + modifier = Modifier + .layoutId( + if (item.isSelected) { + LAYOUT_ID_SELECTED_TAB_LABEL + } else { + LAYOUT_ID_UNSELECTED_TAB_LABEL + } + ) + .clickable( + enabled = item.isSelected.not(), + onClick = { onTabSelected(index) }, + interactionSource = remember { MutableInteractionSource() }, + indication = null + ), + isSelected = item.isSelected, + label = item.label + ) + + Box( + modifier = Modifier + .layoutId(LAYOUT_ID_TAB_INDICATOR) + .height(4.dp) + .background(color = AttendanceTheme.colors.mainColors.YappOrange) + ) + } + }, + measurePolicy = { measurables, constraints -> + var tabLabelMaxHeight = 0 + var indicatorWidth = 0 + + val widthList = mutableListOf() + + val tabLabelPlaceables = measurables.filter { it.layoutId == LAYOUT_ID_SELECTED_TAB_LABEL || it.layoutId == LAYOUT_ID_UNSELECTED_TAB_LABEL } + .mapIndexed { index, tabLabelMeasurable -> + tabLabelMeasurable.measure(constraints).also { placeable -> + tabLabelMaxHeight = max(tabLabelMaxHeight, placeable.height) + + widthList.add(placeable.width) + + if (tabLabelMeasurable.layoutId == LAYOUT_ID_SELECTED_TAB_LABEL) { + indicatorWidth = placeable.width + } + } + } + + val indicatorPlaceable = measurables.first { it.layoutId == LAYOUT_ID_TAB_INDICATOR } + .measure( + Constraints.fixed( + width = indicatorWidth, + height = 4.dp.toPx().roundToInt(), + ) + ) + + layout( + width = (widthList.sum()) + (itemSpace.dp.toPx().roundToInt() * (tabItems.size - 1)), + height = tabLabelMaxHeight + indicatorPlaceable.height, + ) { + var indicationLocation = 0 + tabLabelPlaceables.forEachIndexed { index, placable -> + placable.placeRelative( + x = if (index == 0) { + 0 + } else { + val positionX = widthList.take(index).sum() + (itemSpace.dp.toPx().roundToInt() * index) + if (index == selectedIndex) { + indicationLocation = positionX + } + + positionX + }, + y = 0 + ) + } + + indicatorPosition = indicationLocation.toFloat() + indicatorPlaceable.placeRelative( + x = indicatorPositionAnim.roundToInt(), + y = tabLabelMaxHeight + ) + } + } + ) +} + +@Composable +private fun YDSTabLayoutItem( + modifier: Modifier, + isSelected: Boolean, + label: String, +) { + val textColor by animateColorAsState( + targetValue = if (isSelected) { + AttendanceTheme.colors.grayScale.Gray1200 + } else { + AttendanceTheme.colors.grayScale.Gray400 + } + ) + + Column(modifier = modifier) { + Text( + text = label, + style = AttendanceTypography.h3, + color = textColor + ) + Spacer(modifier = Modifier.height(4.dp)) + } +} + +data class YDSTabLayoutState( + val tabItems: List, +) { + + init { + require(tabItems.count { it.isSelected } == 1) + } + + val selectedIndex: Int + get() = tabItems.indexOfFirst { it.isSelected } + + fun select(targetIndex: Int): YDSTabLayoutState { + return this.copy( + tabItems = tabItems.mapIndexed { index, itemState -> + if (itemState.isSelected) { + return@mapIndexed itemState.unSelect() + } + + itemState + }.mapIndexed { index, itemState -> + if (index == targetIndex) { + return@mapIndexed itemState.select() + } + + itemState + } + ) + } + +} + +data class YDSTabLayoutItemState( + val isSelected: Boolean, + val label: String, +) { + + fun select(): YDSTabLayoutItemState { + return this.copy(isSelected = true) + } + + fun unSelect(): YDSTabLayoutItemState { + return this.copy(isSelected = false) + } + +} \ No newline at end of file diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/tablayout/YDSTabLayoutItemState.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/tablayout/YDSTabLayoutItemState.kt new file mode 100644 index 00000000..828f7593 --- /dev/null +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/tablayout/YDSTabLayoutItemState.kt @@ -0,0 +1,33 @@ +package com.yapp.presentation.ui.admin.management.components.tablayout + + +data class YDSTabLayoutState( + val tabItems: List, +) { + + init { + require(tabItems.count { it.isSelected } == 1) + } + + val selectedIndex: Int + get() = tabItems.indexOfFirst { it.isSelected } + + fun select(targetIndex: Int): YDSTabLayoutState { + return this.copy( + tabItems = tabItems.mapIndexed { index, itemState -> + if (itemState.isSelected) { + return@mapIndexed itemState.unSelect() + } + + itemState + }.mapIndexed { index, itemState -> + if (index == targetIndex) { + return@mapIndexed itemState.select() + } + + itemState + } + ) + } + +} \ No newline at end of file diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/tablayout/YDSTabLayoutState.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/tablayout/YDSTabLayoutState.kt new file mode 100644 index 00000000..828f7593 --- /dev/null +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/tablayout/YDSTabLayoutState.kt @@ -0,0 +1,33 @@ +package com.yapp.presentation.ui.admin.management.components.tablayout + + +data class YDSTabLayoutState( + val tabItems: List, +) { + + init { + require(tabItems.count { it.isSelected } == 1) + } + + val selectedIndex: Int + get() = tabItems.indexOfFirst { it.isSelected } + + fun select(targetIndex: Int): YDSTabLayoutState { + return this.copy( + tabItems = tabItems.mapIndexed { index, itemState -> + if (itemState.isSelected) { + return@mapIndexed itemState.unSelect() + } + + itemState + }.mapIndexed { index, itemState -> + if (index == targetIndex) { + return@mapIndexed itemState.select() + } + + itemState + } + ) + } + +} \ No newline at end of file From 85a6edb5dd326847d2bda434ccbd9e1caa8857a9 Mon Sep 17 00:00:00 2001 From: YeongSang Jeon Date: Tue, 30 May 2023 21:30:08 +0900 Subject: [PATCH 08/18] =?UTF-8?q?chore:=20=ED=81=B4=EB=9E=98=EC=8A=A4=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC=20=EB=B0=8F=20=EC=BD=94=EB=93=9C=EC=A0=95?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/tablayout/YDSTabItemLayout.kt | 35 ++++++++- .../components/tablayout/YDSTabLayout.kt | 74 +------------------ .../tablayout/YDSTabLayoutItemState.kt | 30 ++------ 3 files changed, 42 insertions(+), 97 deletions(-) diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/tablayout/YDSTabItemLayout.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/tablayout/YDSTabItemLayout.kt index 13838e97..e8173e01 100644 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/tablayout/YDSTabItemLayout.kt +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/tablayout/YDSTabItemLayout.kt @@ -1,5 +1,38 @@ package com.yapp.presentation.ui.admin.management.components.tablayout +import androidx.compose.animation.animateColorAsState +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.yapp.common.theme.AttendanceTheme +import com.yapp.common.theme.AttendanceTypography -internal class YDSTabItemLayout { + +@Composable +internal fun YDSTabItemLayout( + modifier: Modifier, + isSelected: Boolean, + label: String, +) { + val textColor by animateColorAsState( + targetValue = if (isSelected) { + AttendanceTheme.colors.grayScale.Gray1200 + } else { + AttendanceTheme.colors.grayScale.Gray400 + } + ) + + Column(modifier = modifier) { + Text( + text = label, + style = AttendanceTypography.h3, + color = textColor + ) + Spacer(modifier = Modifier.height(4.dp)) + } } \ No newline at end of file diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/tablayout/YDSTabLayout.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/tablayout/YDSTabLayout.kt index ee0500a9..6d5ceed3 100644 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/tablayout/YDSTabLayout.kt +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/tablayout/YDSTabLayout.kt @@ -1,32 +1,25 @@ package com.yapp.presentation.ui.admin.management.components.tablayout -import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height -import androidx.compose.material.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.layoutId import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.dp import com.yapp.common.theme.AttendanceTheme -import com.yapp.common.theme.AttendanceTypography import kotlinx.collections.immutable.persistentListOf import kotlin.math.max import kotlin.math.roundToInt @@ -90,7 +83,7 @@ internal fun YDSTabLayout( modifier = modifier, content = { tabItems.forEachIndexed { index, item -> - YDSTabLayoutItem( + YDSTabItemLayout( modifier = Modifier .layoutId( if (item.isSelected) { @@ -175,72 +168,7 @@ internal fun YDSTabLayout( ) } -@Composable -private fun YDSTabLayoutItem( - modifier: Modifier, - isSelected: Boolean, - label: String, -) { - val textColor by animateColorAsState( - targetValue = if (isSelected) { - AttendanceTheme.colors.grayScale.Gray1200 - } else { - AttendanceTheme.colors.grayScale.Gray400 - } - ) - - Column(modifier = modifier) { - Text( - text = label, - style = AttendanceTypography.h3, - color = textColor - ) - Spacer(modifier = Modifier.height(4.dp)) - } -} - -data class YDSTabLayoutState( - val tabItems: List, -) { - - init { - require(tabItems.count { it.isSelected } == 1) - } - - val selectedIndex: Int - get() = tabItems.indexOfFirst { it.isSelected } - fun select(targetIndex: Int): YDSTabLayoutState { - return this.copy( - tabItems = tabItems.mapIndexed { index, itemState -> - if (itemState.isSelected) { - return@mapIndexed itemState.unSelect() - } - itemState - }.mapIndexed { index, itemState -> - if (index == targetIndex) { - return@mapIndexed itemState.select() - } - itemState - } - ) - } - -} - -data class YDSTabLayoutItemState( - val isSelected: Boolean, - val label: String, -) { - - fun select(): YDSTabLayoutItemState { - return this.copy(isSelected = true) - } - - fun unSelect(): YDSTabLayoutItemState { - return this.copy(isSelected = false) - } -} \ No newline at end of file diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/tablayout/YDSTabLayoutItemState.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/tablayout/YDSTabLayoutItemState.kt index 828f7593..fa8a866d 100644 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/tablayout/YDSTabLayoutItemState.kt +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/tablayout/YDSTabLayoutItemState.kt @@ -1,33 +1,17 @@ package com.yapp.presentation.ui.admin.management.components.tablayout -data class YDSTabLayoutState( - val tabItems: List, +data class YDSTabLayoutItemState( + val isSelected: Boolean, + val label: String, ) { - init { - require(tabItems.count { it.isSelected } == 1) + fun select(): YDSTabLayoutItemState { + return this.copy(isSelected = true) } - val selectedIndex: Int - get() = tabItems.indexOfFirst { it.isSelected } - - fun select(targetIndex: Int): YDSTabLayoutState { - return this.copy( - tabItems = tabItems.mapIndexed { index, itemState -> - if (itemState.isSelected) { - return@mapIndexed itemState.unSelect() - } - - itemState - }.mapIndexed { index, itemState -> - if (index == targetIndex) { - return@mapIndexed itemState.select() - } - - itemState - } - ) + fun unSelect(): YDSTabLayoutItemState { + return this.copy(isSelected = false) } } \ No newline at end of file From ba08d5449a9c314b804486d115b9fe3f263dffca Mon Sep 17 00:00:00 2001 From: YeongSang Jeon Date: Wed, 31 May 2023 22:21:59 +0900 Subject: [PATCH 09/18] =?UTF-8?q?feat:=20=EB=A7=8C=EB=93=A0=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=EB=93=A4=20=ED=99=94=EB=A9=B4?= =?UTF-8?q?=EC=97=90=20=EC=A0=81=EC=9A=A9=20(WIP)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 만든 컴포넌트들을 화면에 적용합니다. - ViewModel의 Init까지 함 # TODO - 탭 눌렀을때 이벤트 처리 - 출석 변경 눌렀을때 처리 - 유저 삭제 기능 추가 (가능하면) - 코드 정리 --- .../ui/admin/management/Management.kt | 78 ++++++---- .../ui/admin/management/ManagementContract.kt | 63 ++++++-- .../admin/management/ManagementViewModel.kt | 143 +++++++++++++++++- .../AttendanceBottomSheetItemLayout.kt | 3 +- .../AttendanceTypeButton.kt | 7 +- .../StatisticalTableLayout.kt | 2 +- .../StatisticalTableLayoutState.kt | 8 +- .../components/tablayout/YDSTabLayout.kt | 6 +- ...tState.kt => YDSTabLayoutItemStateList.kt} | 12 +- 9 files changed, 257 insertions(+), 65 deletions(-) rename presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/tablayout/{YDSTabLayoutState.kt => YDSTabLayoutItemStateList.kt} (61%) diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/Management.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/Management.kt index 679ad3a0..d34d594d 100644 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/Management.kt +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/Management.kt @@ -1,6 +1,8 @@ package com.yapp.presentation.ui.admin.management +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -13,7 +15,6 @@ import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.ModalBottomSheetLayout -import androidx.compose.material.ModalBottomSheetState import androidx.compose.material.ModalBottomSheetValue import androidx.compose.material.Scaffold import androidx.compose.material.rememberModalBottomSheetState @@ -21,7 +22,10 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color @@ -46,6 +50,7 @@ import com.yapp.presentation.ui.admin.management.components.attendanceBottomShee import com.yapp.presentation.ui.admin.management.components.attendanceBottomSheet.AttendanceBottomSheetItemLayoutState import com.yapp.presentation.ui.admin.management.components.foldableItem.FoldableItemLayout import com.yapp.presentation.ui.admin.management.components.statisticalTable.StatisticalTableLayout +import com.yapp.presentation.ui.admin.management.components.tablayout.YDSTabLayout import kotlinx.coroutines.launch @@ -58,25 +63,14 @@ fun AttendanceManagement( ) { val uiState by viewModel.uiState.collectAsState() - val coroutineScope = rememberCoroutineScope() - - val sheetState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden) - when (uiState.loadState) { Loading -> YDSProgressBar() Idle -> { ManagementScreen( uiState = uiState, - sheetState = sheetState, - onBackButtonClicked = { onBackButtonClicked?.invoke() }, - onBottomSheetDialogItemClicked = { attendanceType -> - viewModel.setEvent(ManagementEvent.OnAttendanceTypeChanged(attendanceType)) - coroutineScope.launch { sheetState.hide() } - }, - onDropDownClicked = { changedMember -> - viewModel.setEvent(ManagementEvent.OnDropDownButtonClicked(changedMember)) - } + onEvent = { viewModel.setEvent(it) }, + onBackButtonClicked = { onBackButtonClicked?.invoke() } ) } @@ -87,31 +81,37 @@ fun AttendanceManagement( LaunchedEffect(key1 = viewModel.effect, key2 = lifeCycleOwner) { lifeCycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.effect.collect { effect -> - when (effect) { - is ManagementContract.ManagementSideEffect.OpenBottomSheetDialog -> sheetState.show() - } } } } } +@OptIn(ExperimentalFoundationApi::class) @ExperimentalMaterialApi @Composable internal fun ManagementScreen( uiState: ManagementContract.ManagementState, - sheetState: ModalBottomSheetState, - onBackButtonClicked: (() -> Unit), - onBottomSheetDialogItemClicked: (Attendance.Status) -> Unit, - onDropDownClicked: ((memberId: Long) -> Unit), + onEvent: (ManagementEvent) -> Unit, + onBackButtonClicked: () -> Unit, ) { + val coroutineScope = rememberCoroutineScope() + + val sheetState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden) + + var selectedMemberId by remember { mutableStateOf(null) } + + LaunchedEffect(key1 = sheetState) { + if (sheetState.isVisible.not()) { + selectedMemberId = null + } + } + ModalBottomSheetLayout( sheetContent = { BottomSheetDialog( itemStates = uiState.bottomSheetDialogState, onClickItem = { attendanceType -> - onBottomSheetDialogItemClicked.invoke( - attendanceType - ) + onEvent(ManagementEvent.OnAttendanceTypeChanged(selectedMemberId!!, attendanceType)) } ) }, @@ -140,10 +140,26 @@ internal fun ManagementScreen( .background(AttendanceTheme.colors.backgroundColors.background) .padding(innerPadding) ) { + + stickyHeader { + Column(modifier = Modifier.fillMaxWidth() + .background(color = AttendanceTheme.colors.backgroundColors.background)) { + Spacer( + modifier = Modifier.fillMaxWidth().height(20.dp) + ) + YDSTabLayout( + modifier = Modifier.padding(horizontal = 24.dp), + tabItems = uiState.tabLayoutState.items, + selectedIndex = uiState.tabLayoutState.selectedIndex, + onTabSelected = { selectedTabIndex -> + onEvent(ManagementEvent.OnTabItemSelected(selectedTabIndex)) + } + ) + } + } + item { - Column( - modifier = Modifier.padding(24.dp) - ) { + Column(modifier = Modifier.padding(top = 24.dp, start = 24.dp, end = 24.dp, bottom = 28.dp)) { StatisticalTableLayout(state = uiState.attendanceStatisticalTableState) } } @@ -155,9 +171,8 @@ internal fun ManagementScreen( FoldableItemLayout( state = team, onDropDownClicked = { changedMember -> - onDropDownClicked.invoke( - changedMember - ) + selectedMemberId = changedMember + coroutineScope.launch { sheetState.show() } } ) } @@ -167,6 +182,7 @@ internal fun ManagementScreen( } } } + } } @@ -205,5 +221,3 @@ private fun BottomSheetDialog( ) } } - - diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementContract.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementContract.kt index c50c847a..08f13c41 100644 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementContract.kt +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementContract.kt @@ -7,6 +7,8 @@ import com.yapp.domain.model.Attendance import com.yapp.presentation.ui.admin.management.components.attendanceBottomSheet.AttendanceBottomSheetItemLayoutState import com.yapp.presentation.ui.admin.management.components.foldableItem.FoldableItemLayoutState import com.yapp.presentation.ui.admin.management.components.statisticalTable.StatisticalTableLayoutState +import com.yapp.presentation.ui.admin.management.components.tablayout.YDSTabLayoutItemState +import com.yapp.presentation.ui.admin.management.components.tablayout.YDSTabLayoutItemStateList import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -18,18 +20,62 @@ class ManagementContract { } data class ManagementState( - val loadState: LoadState = LoadState.Idle, + val loadState: LoadState = LoadState.Loading, val shared: Shared = Shared(), - val topBarState: TopBarLayoutState = TopBarLayoutState(), val attendanceStatisticalTableState: StatisticalTableLayoutState = StatisticalTableLayoutState(), val foldableItemStates: ImmutableList = persistentListOf(), + val topBarState: TopBarLayoutState = TopBarLayoutState(), + val tabLayoutState: ManagementTabLayoutState = ManagementTabLayoutState.init(), val bottomSheetDialogState: ImmutableList = persistentListOf(), ) : UiState { - data class Shared( - val sessionId: Int = 0, - val selectedMemberId: Long = NOT_SELECTED, - ) + data class ManagementTabLayoutState( + private val itemsList: YDSTabLayoutItemStateList = YDSTabLayoutItemStateList(), + ) { + val items: List + get() = itemsList.value + + val selectedIndex: Int + get() = itemsList.selectedIndex + + fun select(index: Int): ManagementTabLayoutState { + return this.copy(itemsList = itemsList.select(index)) + } + + companion object { + const val LABEL_TEAM = "팀별" + const val LABEL_POSITION = "직군별" + + const val INDEX_TEAM = 0 + const val INDEX_POSITION = 1 + + fun init(): ManagementTabLayoutState { + return ManagementTabLayoutState( + itemsList = YDSTabLayoutItemStateList( + buildList { + add( + INDEX_TEAM, + YDSTabLayoutItemState( + isSelected = true, + label = LABEL_TEAM + ) + ) + + add( + index = INDEX_POSITION, + element = YDSTabLayoutItemState( + isSelected = false, + label = LABEL_POSITION + ) + ) + } + ) + ) + } + } + } + + data class Shared(val sessionId: Int = 0) data class TopBarLayoutState(val sessionTitle: String = "") @@ -39,11 +85,10 @@ class ManagementContract { } sealed class ManagementEvent : UiEvent { - data class OnDropDownButtonClicked(val memberId: Long) : ManagementEvent() - data class OnAttendanceTypeChanged(val attendanceType: Attendance.Status) : ManagementEvent() + data class OnTabItemSelected(val tabIndex: Int) : ManagementEvent() + data class OnAttendanceTypeChanged(val memberId: Long, val attendanceType: Attendance.Status) : ManagementEvent() } sealed class ManagementSideEffect : UiSideEffect { - object OpenBottomSheetDialog : ManagementSideEffect() } } diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementViewModel.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementViewModel.kt index 39187f3d..5847ee5d 100644 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementViewModel.kt +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementViewModel.kt @@ -1,7 +1,12 @@ package com.yapp.presentation.ui.admin.management import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.viewModelScope import com.yapp.common.base.BaseViewModel +import com.yapp.domain.model.Attendance +import com.yapp.domain.model.Member +import com.yapp.domain.model.Team +import com.yapp.domain.model.types.PositionType import com.yapp.domain.usecases.GetAllMemberUseCase import com.yapp.domain.usecases.SetMemberAttendanceUseCase import com.yapp.presentation.ui.admin.AdminConstants.KEY_SESSION_ID @@ -9,16 +14,29 @@ import com.yapp.presentation.ui.admin.AdminConstants.KEY_SESSION_TITLE import com.yapp.presentation.ui.admin.management.ManagementContract.ManagementEvent import com.yapp.presentation.ui.admin.management.ManagementContract.ManagementSideEffect import com.yapp.presentation.ui.admin.management.ManagementContract.ManagementState +import com.yapp.presentation.ui.admin.management.components.attendanceBottomSheet.AttendanceBottomSheetItemLayoutState +import com.yapp.presentation.ui.admin.management.components.attendanceTypeButton.AttendanceTypeButtonState +import com.yapp.presentation.ui.admin.management.components.foldableContentItem.FoldableItemContentLayoutState +import com.yapp.presentation.ui.admin.management.components.foldableHeaderItem.FoldableItemHeaderLayoutState +import com.yapp.presentation.ui.admin.management.components.foldableItem.FoldableItemLayoutState +import com.yapp.presentation.ui.admin.management.components.statisticalTable.StatisticalTableLayoutState +import com.yapp.presentation.ui.admin.management.components.tablayout.YDSTabLayoutItemState +import com.yapp.presentation.ui.admin.management.components.tablayout.YDSTabLayoutItemStateList import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel class ManagementViewModel @Inject constructor( - private val getAllMemberUseCase: GetAllMemberUseCase, - private val setMemberAttendanceUseCase: SetMemberAttendanceUseCase, savedStateHandle: SavedStateHandle, - - ) : BaseViewModel(ManagementState()) { + private val getAllMemberUseCase: GetAllMemberUseCase, + private val setMemberAttendanceUseCase: SetMemberAttendanceUseCase +) : BaseViewModel(ManagementState()) { companion object { const val DEFAULT_SESSION_ID = 1 @@ -28,10 +46,127 @@ class ManagementViewModel @Inject constructor( init { val sessionId = savedStateHandle.get(KEY_SESSION_ID) ?: error("세션 아이디를 불러올 수 없습니다.") val sessionTitle = savedStateHandle.get(KEY_SESSION_TITLE) ?: TEXT_LOAD_SESSION_TITLE_FAILED + + viewModelScope.launch { + getAllMemberUseCase().stateIn( + this, + started = SharingStarted.WhileSubscribed(3000L), + initialValue = currentState + ) + initializeState(sessionId, sessionTitle) + } } override suspend fun handleEvent(event: ManagementEvent) { + when(event) { + is ManagementEvent.OnAttendanceTypeChanged -> TODO() + is ManagementEvent.OnTabItemSelected -> { + setState { + this.copy(tabLayoutState = tabLayoutState.select(event.tabIndex)) + } + } + } + } + + private suspend fun initializeState(sessionId: Int, sessionTitle: String) { + getAllMemberUseCase().collectLatest { result -> + result.onSuccess { memberInfoList -> + val foldableItemStateList = when(uiState.value.tabLayoutState.selectedIndex) { + ManagementState.ManagementTabLayoutState.INDEX_TEAM -> { + memberInfoList.groupBy { it.team } + .map { (team, members) -> + FoldableItemLayoutState( + headerState = team.toFoldableItemHeaderState(sessionId, members), + contentStates = members.map { member -> + member.toFoldableItemContentState(sessionId = sessionId) + } + ) + } + } + + ManagementState.ManagementTabLayoutState.INDEX_POSITION -> { + memberInfoList.groupBy { it.position } + .map { (position, members) -> + FoldableItemLayoutState( + headerState = position.toFoldableItemHeaderState(sessionId, members), + contentStates = members.map { member -> + member.toFoldableItemContentState(sessionId = sessionId) + } + ) + } + } + + else -> error("${uiState.value.tabLayoutState.selectedIndex}는 잘못된 Tablayout Index입니다.") + }.toImmutableList() + + ManagementState( + loadState = ManagementState.LoadState.Idle, + shared = ManagementState.Shared(sessionId = sessionId), + attendanceStatisticalTableState = StatisticalTableLayoutState( + totalCount = memberInfoList.size, + attendCount = memberInfoList.count { it.attendances[sessionId].status == Attendance.Status.LATE }, + tardyCount = memberInfoList.count { it.attendances[sessionId].status == Attendance.Status.LATE }, + absentCount = memberInfoList.count { it.attendances[sessionId].status == Attendance.Status.ABSENT }, + admitCount = memberInfoList.count { it.attendances[sessionId].status == Attendance.Status.ADMIT }, + ), + foldableItemStates = foldableItemStateList, + tabLayoutState = ManagementState.ManagementTabLayoutState.init(), + bottomSheetDialogState = initBottomSheetDialogState(), + topBarState = ManagementState.TopBarLayoutState(sessionTitle = sessionTitle) + ).also { initialState -> + setState { initialState } + } + + }.onFailure { + setState { this.copy(loadState = ManagementState.LoadState.Error) } + } + } + } + + private fun initBottomSheetDialogState(): ImmutableList { + return buildList { + add(AttendanceBottomSheetItemLayoutState(label = Attendance.Status.NORMAL.text, iconType = AttendanceBottomSheetItemLayoutState.IconType.ATTEND)) + add(AttendanceBottomSheetItemLayoutState(label = Attendance.Status.LATE.text, iconType = AttendanceBottomSheetItemLayoutState.IconType.TARDY)) + add(AttendanceBottomSheetItemLayoutState(label = Attendance.Status.ABSENT.text, iconType = AttendanceBottomSheetItemLayoutState.IconType.ABSENT)) + add(AttendanceBottomSheetItemLayoutState(label = Attendance.Status.ADMIT.text, iconType = AttendanceBottomSheetItemLayoutState.IconType.ADMIT)) + }.toImmutableList() + } + + private fun PositionType.toFoldableItemHeaderState(sessionId: Int, members: List): FoldableItemHeaderLayoutState { + return FoldableItemHeaderLayoutState( + label = value, + attendMemberCount = members.count { it.attendances[sessionId].status == Attendance.Status.NORMAL }, + allTeamMemberCount = members.size + ) + } + + private fun Team.toFoldableItemHeaderState(sessionId: Int, members: List): FoldableItemHeaderLayoutState { + return FoldableItemHeaderLayoutState( + label = "${type.value} $number", + attendMemberCount = members.count { it.attendances[sessionId].status == Attendance.Status.NORMAL }, + allTeamMemberCount = members.size + ) + } + + private fun Member.toFoldableItemContentState(sessionId: Int): FoldableItemContentLayoutState { + val attendance = attendances[sessionId].status + + val buttonState = AttendanceTypeButtonState( + label = attendance.text, + iconType = when(attendance) { + Attendance.Status.ABSENT -> AttendanceTypeButtonState.IconType.ABSENT + Attendance.Status.LATE -> AttendanceTypeButtonState.IconType.TARDY + Attendance.Status.ADMIT -> AttendanceTypeButtonState.IconType.ADMIT + Attendance.Status.NORMAL -> AttendanceTypeButtonState.IconType.ATTEND + } + ) + return FoldableItemContentLayoutState( + id = id, + label = name, + subLabel = position.value, + attendanceTypeButtonState = buttonState + ) } } diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceBottomSheet/AttendanceBottomSheetItemLayout.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceBottomSheet/AttendanceBottomSheetItemLayout.kt index bfd11d58..fa127fd1 100644 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceBottomSheet/AttendanceBottomSheetItemLayout.kt +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceBottomSheet/AttendanceBottomSheetItemLayout.kt @@ -21,6 +21,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview @@ -76,13 +77,13 @@ fun AttendanceBottomSheetItemLayout( horizontalArrangement = Arrangement.Center ) { Icon( - modifier = Modifier.size(20.dp), painter = when (state.iconType) { AttendanceBottomSheetItemLayoutState.IconType.ATTEND -> painterResource(id = R.drawable.icon_attend) AttendanceBottomSheetItemLayoutState.IconType.ADMIT -> painterResource(id = R.drawable.icon_attend) AttendanceBottomSheetItemLayoutState.IconType.TARDY -> painterResource(id = R.drawable.icon_tardy) AttendanceBottomSheetItemLayoutState.IconType.ABSENT -> painterResource(id = R.drawable.icon_absent) }, + tint= Color.Unspecified, contentDescription = "attendance_type_icon" ) diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceTypeButton/AttendanceTypeButton.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceTypeButton/AttendanceTypeButton.kt index d5b0975f..93103dbc 100644 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceTypeButton/AttendanceTypeButton.kt +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceTypeButton/AttendanceTypeButton.kt @@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentSize @@ -21,6 +22,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -72,15 +74,14 @@ internal fun AttendanceTypeButton( verticalAlignment = Alignment.CenterVertically ) { Icon( - modifier = Modifier.size(20.dp), painter = when (state.iconType) { AttendanceTypeButtonState.IconType.ATTEND -> painterResource(id = R.drawable.icon_attend) AttendanceTypeButtonState.IconType.ADMIT -> painterResource(id = R.drawable.icon_attend) AttendanceTypeButtonState.IconType.TARDY -> painterResource(id = R.drawable.icon_tardy) AttendanceTypeButtonState.IconType.ABSENT -> painterResource(id = R.drawable.icon_absent) }, - contentDescription = "drop down", - tint = AttendanceTheme.colors.grayScale.Gray800 + tint= Color.Unspecified, + contentDescription = "drop down" ) Spacer(modifier = Modifier.width(4.dp)) diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/statisticalTable/StatisticalTableLayout.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/statisticalTable/StatisticalTableLayout.kt index 7c942ed6..67faf304 100644 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/statisticalTable/StatisticalTableLayout.kt +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/statisticalTable/StatisticalTableLayout.kt @@ -86,7 +86,7 @@ internal fun StatisticalTableLayout( verticalAlignment = Alignment.CenterVertically ) { AnimatedCounterText( - count = state.attendanceCount, + count = state.attendCount, style = AttendanceTypography.subtitle2.copy(color = AttendanceTheme.colors.etcColors.EtcGreen), prefix = { Text( diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/statisticalTable/StatisticalTableLayoutState.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/statisticalTable/StatisticalTableLayoutState.kt index ce035d30..76b8c86f 100644 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/statisticalTable/StatisticalTableLayoutState.kt +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/statisticalTable/StatisticalTableLayoutState.kt @@ -3,12 +3,8 @@ package com.yapp.presentation.ui.admin.management.components.statisticalTable data class StatisticalTableLayoutState( val totalCount: Int = 0, + val attendCount: Int = 0, val tardyCount: Int = 0, val absentCount: Int = 0, val admitCount: Int = 0, -) { - - val attendanceCount: Int - get() = totalCount - absentCount - -} \ No newline at end of file +) \ No newline at end of file diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/tablayout/YDSTabLayout.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/tablayout/YDSTabLayout.kt index 6d5ceed3..bd07b3ff 100644 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/tablayout/YDSTabLayout.kt +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/tablayout/YDSTabLayout.kt @@ -36,8 +36,8 @@ private const val SPACE_TAB_ITEM = 20 private fun YDSTabLayoutPreview() { var state by remember { mutableStateOf( - YDSTabLayoutState( - tabItems = persistentListOf( + YDSTabLayoutItemStateList( + value = persistentListOf( YDSTabLayoutItemState( isSelected = true, label = "팀별" @@ -55,7 +55,7 @@ private fun YDSTabLayoutPreview() { Box(modifier = Modifier.fillMaxSize()) { YDSTabLayout( modifier = Modifier.align(Alignment.Center), - tabItems = state.tabItems, + tabItems = state.value, selectedIndex = state.selectedIndex, onTabSelected = { state = state.select(it) diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/tablayout/YDSTabLayoutState.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/tablayout/YDSTabLayoutItemStateList.kt similarity index 61% rename from presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/tablayout/YDSTabLayoutState.kt rename to presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/tablayout/YDSTabLayoutItemStateList.kt index 828f7593..65b4aa40 100644 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/tablayout/YDSTabLayoutState.kt +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/tablayout/YDSTabLayoutItemStateList.kt @@ -1,20 +1,20 @@ package com.yapp.presentation.ui.admin.management.components.tablayout -data class YDSTabLayoutState( - val tabItems: List, +data class YDSTabLayoutItemStateList( + val value: List = emptyList(), ) { init { - require(tabItems.count { it.isSelected } == 1) + require(value.count { it.isSelected } == 1) } val selectedIndex: Int - get() = tabItems.indexOfFirst { it.isSelected } + get() = value.indexOfFirst { it.isSelected } - fun select(targetIndex: Int): YDSTabLayoutState { + fun select(targetIndex: Int): YDSTabLayoutItemStateList { return this.copy( - tabItems = tabItems.mapIndexed { index, itemState -> + value = value.mapIndexed { index, itemState -> if (itemState.isSelected) { return@mapIndexed itemState.unSelect() } From 0f894ae164a02de7ebfa8f1ec8815ebe38cf83ed Mon Sep 17 00:00:00 2001 From: YeongSang Jeon Date: Fri, 2 Jun 2023 00:35:47 +0900 Subject: [PATCH 10/18] =?UTF-8?q?feat:=20=ED=8C=80=EB=B3=84=20/=20?= =?UTF-8?q?=EC=A7=81=EA=B5=B0=EB=B3=84=20=ED=83=AD=20=ED=81=B4=EB=A6=AD=20?= =?UTF-8?q?=EC=8B=9C=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=EA=B0=80=20=EB=B3=B4?= =?UTF-8?q?=EC=97=AC=EC=A7=80=EA=B2=8C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 팀 정렬 / AttendanceType별 정렬도 추가적으로 구현합니다. --- .../data/datasource/MemberRemoteDataSource.kt | 2 +- .../datasource/MemberRemoteDataSourceImpl.kt | 2 +- .../data/repository/MemberRepositoryImpl.kt | 4 +- .../domain/repository/MemberRepository.kt | 2 +- .../domain/usecases/GetAllMemberUseCase.kt | 2 +- .../admin/management/ManagementViewModel.kt | 148 ++++++++++-------- .../totalscore/AdminTotalScoreViewModel.kt | 9 +- 7 files changed, 97 insertions(+), 72 deletions(-) diff --git a/data/src/main/java/com/yapp/data/datasource/MemberRemoteDataSource.kt b/data/src/main/java/com/yapp/data/datasource/MemberRemoteDataSource.kt index 0ac83b05..3f3f9648 100644 --- a/data/src/main/java/com/yapp/data/datasource/MemberRemoteDataSource.kt +++ b/data/src/main/java/com/yapp/data/datasource/MemberRemoteDataSource.kt @@ -9,5 +9,5 @@ interface MemberRemoteDataSource { suspend fun getMember(id: Long): MemberEntity? suspend fun getMemberWithFlow(id: Long): Flow suspend fun deleteMember(id: Long) - suspend fun getAllMember(): Flow> + fun getAllMember(): Flow> } diff --git a/data/src/main/java/com/yapp/data/datasource/MemberRemoteDataSourceImpl.kt b/data/src/main/java/com/yapp/data/datasource/MemberRemoteDataSourceImpl.kt index 67791db2..f1a426df 100644 --- a/data/src/main/java/com/yapp/data/datasource/MemberRemoteDataSourceImpl.kt +++ b/data/src/main/java/com/yapp/data/datasource/MemberRemoteDataSourceImpl.kt @@ -87,7 +87,7 @@ class MemberRemoteDataSourceImpl @Inject constructor( } @OptIn(ExperimentalCoroutinesApi::class) - override suspend fun getAllMember(): Flow> { + override fun getAllMember(): Flow> { return callbackFlow { val fsRef = fireStore.memberRef() val listener = fsRef.addSnapshotListener { snapshot, error -> diff --git a/data/src/main/java/com/yapp/data/repository/MemberRepositoryImpl.kt b/data/src/main/java/com/yapp/data/repository/MemberRepositoryImpl.kt index ef8f20b5..a4795050 100644 --- a/data/src/main/java/com/yapp/data/repository/MemberRepositoryImpl.kt +++ b/data/src/main/java/com/yapp/data/repository/MemberRepositoryImpl.kt @@ -60,10 +60,10 @@ class MemberRepositoryImpl @Inject constructor( ) } - override suspend fun getAllMember(): Flow>> { + override fun getAllMember(): Flow> { return memberRemoteDataSource.getAllMember() .map { entities -> - Result.success(entities.map { it.toDomain() }) + entities.map { it.toDomain() } } } diff --git a/domain/src/main/java/com/yapp/domain/repository/MemberRepository.kt b/domain/src/main/java/com/yapp/domain/repository/MemberRepository.kt index 2984a840..1c672d4a 100644 --- a/domain/src/main/java/com/yapp/domain/repository/MemberRepository.kt +++ b/domain/src/main/java/com/yapp/domain/repository/MemberRepository.kt @@ -9,5 +9,5 @@ interface MemberRepository { suspend fun getMember(id: Long): Result suspend fun getMemberWithFlow(id: Long): Flow> suspend fun deleteMember(id: Long): Result - suspend fun getAllMember(): Flow>> + fun getAllMember(): Flow> } diff --git a/domain/src/main/java/com/yapp/domain/usecases/GetAllMemberUseCase.kt b/domain/src/main/java/com/yapp/domain/usecases/GetAllMemberUseCase.kt index 8b83667d..607d986f 100644 --- a/domain/src/main/java/com/yapp/domain/usecases/GetAllMemberUseCase.kt +++ b/domain/src/main/java/com/yapp/domain/usecases/GetAllMemberUseCase.kt @@ -10,7 +10,7 @@ class GetAllMemberUseCase @Inject constructor( private val memberRepository: MemberRepository, ) { - suspend operator fun invoke(): Flow>> { + operator fun invoke(): Flow> { return memberRepository.getAllMember() } diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementViewModel.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementViewModel.kt index 5847ee5d..0a6246c0 100644 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementViewModel.kt +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementViewModel.kt @@ -25,8 +25,14 @@ import com.yapp.presentation.ui.admin.management.components.tablayout.YDSTabLayo import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import javax.inject.Inject @@ -35,7 +41,7 @@ import javax.inject.Inject class ManagementViewModel @Inject constructor( savedStateHandle: SavedStateHandle, private val getAllMemberUseCase: GetAllMemberUseCase, - private val setMemberAttendanceUseCase: SetMemberAttendanceUseCase + private val setMemberAttendanceUseCase: SetMemberAttendanceUseCase, ) : BaseViewModel(ManagementState()) { companion object { @@ -43,83 +49,57 @@ class ManagementViewModel @Inject constructor( const val TEXT_LOAD_SESSION_TITLE_FAILED = "LOAD FAILED" } + private val members: StateFlow> = getAllMemberUseCase() + .catch { setState { this.copy(loadState = ManagementState.LoadState.Error) } } + .stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(3000L), + initialValue = emptyList() + ) + + private val tabIndex: MutableStateFlow = MutableStateFlow(uiState.value.tabLayoutState.selectedIndex) + init { val sessionId = savedStateHandle.get(KEY_SESSION_ID) ?: error("세션 아이디를 불러올 수 없습니다.") val sessionTitle = savedStateHandle.get(KEY_SESSION_TITLE) ?: TEXT_LOAD_SESSION_TITLE_FAILED + initializeState(sessionId, sessionTitle) + viewModelScope.launch { - getAllMemberUseCase().stateIn( - this, - started = SharingStarted.WhileSubscribed(3000L), - initialValue = currentState - ) - initializeState(sessionId, sessionTitle) + members.combine(tabIndex, transform = { members, tabIndex -> + members to tabIndex + }).collect { (members, tabIndex) -> + setState { + this.copy( + attendanceStatisticalTableState = members.toStatisticalTableState(sessionId = sessionId), + foldableItemStates = members.toFoldableItemContentState(sessionId, tabIndex) + ) + } + } } } override suspend fun handleEvent(event: ManagementEvent) { - when(event) { + when (event) { is ManagementEvent.OnAttendanceTypeChanged -> TODO() is ManagementEvent.OnTabItemSelected -> { setState { + tabIndex.value = event.tabIndex this.copy(tabLayoutState = tabLayoutState.select(event.tabIndex)) } } } } - private suspend fun initializeState(sessionId: Int, sessionTitle: String) { - getAllMemberUseCase().collectLatest { result -> - result.onSuccess { memberInfoList -> - val foldableItemStateList = when(uiState.value.tabLayoutState.selectedIndex) { - ManagementState.ManagementTabLayoutState.INDEX_TEAM -> { - memberInfoList.groupBy { it.team } - .map { (team, members) -> - FoldableItemLayoutState( - headerState = team.toFoldableItemHeaderState(sessionId, members), - contentStates = members.map { member -> - member.toFoldableItemContentState(sessionId = sessionId) - } - ) - } - } - - ManagementState.ManagementTabLayoutState.INDEX_POSITION -> { - memberInfoList.groupBy { it.position } - .map { (position, members) -> - FoldableItemLayoutState( - headerState = position.toFoldableItemHeaderState(sessionId, members), - contentStates = members.map { member -> - member.toFoldableItemContentState(sessionId = sessionId) - } - ) - } - } - - else -> error("${uiState.value.tabLayoutState.selectedIndex}는 잘못된 Tablayout Index입니다.") - }.toImmutableList() - - ManagementState( - loadState = ManagementState.LoadState.Idle, - shared = ManagementState.Shared(sessionId = sessionId), - attendanceStatisticalTableState = StatisticalTableLayoutState( - totalCount = memberInfoList.size, - attendCount = memberInfoList.count { it.attendances[sessionId].status == Attendance.Status.LATE }, - tardyCount = memberInfoList.count { it.attendances[sessionId].status == Attendance.Status.LATE }, - absentCount = memberInfoList.count { it.attendances[sessionId].status == Attendance.Status.ABSENT }, - admitCount = memberInfoList.count { it.attendances[sessionId].status == Attendance.Status.ADMIT }, - ), - foldableItemStates = foldableItemStateList, - tabLayoutState = ManagementState.ManagementTabLayoutState.init(), - bottomSheetDialogState = initBottomSheetDialogState(), - topBarState = ManagementState.TopBarLayoutState(sessionTitle = sessionTitle) - ).also { initialState -> - setState { initialState } - } - - }.onFailure { - setState { this.copy(loadState = ManagementState.LoadState.Error) } - } + private fun initializeState(sessionId: Int, sessionTitle: String) { + setState { + this.copy( + loadState = ManagementState.LoadState.Idle, + shared = ManagementState.Shared(sessionId = sessionId), + tabLayoutState = ManagementState.ManagementTabLayoutState.init(), + bottomSheetDialogState = initBottomSheetDialogState(), + topBarState = ManagementState.TopBarLayoutState(sessionTitle = sessionTitle) + ) } } @@ -132,6 +112,52 @@ class ManagementViewModel @Inject constructor( }.toImmutableList() } + private fun List.toStatisticalTableState(sessionId: Int): StatisticalTableLayoutState { + return StatisticalTableLayoutState( + totalCount = size, + attendCount = count { it.attendances[sessionId].status == Attendance.Status.NORMAL }, + tardyCount = count { it.attendances[sessionId].status == Attendance.Status.LATE }, + absentCount = count { it.attendances[sessionId].status == Attendance.Status.ABSENT }, + admitCount = count { it.attendances[sessionId].status == Attendance.Status.ADMIT }, + ) + } + + private fun List.toFoldableItemContentState(sessionId: Int, tabIndex: Int): ImmutableList { + return when (tabIndex) { + ManagementState.ManagementTabLayoutState.INDEX_TEAM -> { + this.groupBy { it.team } + .entries.sortedWith(comparator = compareBy({it.key.type}, {it.key.number})) + .map { (team, members) -> + FoldableItemLayoutState( + headerState = team.toFoldableItemHeaderState(sessionId, members), + contentStates = members + .sortedBy { it.attendances[sessionId].status } + .map { member -> + member.toFoldableItemContentState(sessionId = sessionId) + } + ) + } + } + + ManagementState.ManagementTabLayoutState.INDEX_POSITION -> { + this.groupBy { it.position } + .entries.sortedBy { it.key.value } + .map { (position, members) -> + FoldableItemLayoutState( + headerState = position.toFoldableItemHeaderState(sessionId, members), + contentStates = members + .sortedBy { it.attendances[sessionId].status } + .map { member -> + member.toFoldableItemContentState(sessionId = sessionId) + } + ) + } + } + + else -> error("${uiState.value.tabLayoutState.selectedIndex}는 잘못된 Tablayout Index입니다.") + }.toImmutableList() + } + private fun PositionType.toFoldableItemHeaderState(sessionId: Int, members: List): FoldableItemHeaderLayoutState { return FoldableItemHeaderLayoutState( label = value, @@ -153,7 +179,7 @@ class ManagementViewModel @Inject constructor( val buttonState = AttendanceTypeButtonState( label = attendance.text, - iconType = when(attendance) { + iconType = when (attendance) { Attendance.Status.ABSENT -> AttendanceTypeButtonState.IconType.ABSENT Attendance.Status.LATE -> AttendanceTypeButtonState.IconType.TARDY Attendance.Status.ADMIT -> AttendanceTypeButtonState.IconType.ADMIT diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/totalscore/AdminTotalScoreViewModel.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/totalscore/AdminTotalScoreViewModel.kt index 4878a4a9..1b1b8aeb 100644 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/totalscore/AdminTotalScoreViewModel.kt +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/totalscore/AdminTotalScoreViewModel.kt @@ -12,6 +12,7 @@ import com.yapp.presentation.ui.admin.totalscore.AdminTotalScoreContract.AdminTo import com.yapp.presentation.ui.admin.totalscore.AdminTotalScoreContract.AdminTotalScoreUiState import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import javax.inject.Inject @@ -72,8 +73,9 @@ class AdminTotalScoreViewModel @Inject constructor( sectionNameFunction: (T) -> String, ) { updateLoadState(AdminTotalScoreUiState.LoadState.Loading) - getAllMemberUseCase().collectLatest { result -> - result.onSuccess { members -> + getAllMemberUseCase() + .catch { setState { copy(loadState = AdminTotalScoreUiState.LoadState.Error) } } + .collectLatest { members -> val memberByGroup = members.groupBy(groupKey) val sectionItemStates = getSectionItemStates( memberBySection = memberByGroup, @@ -85,9 +87,6 @@ class AdminTotalScoreViewModel @Inject constructor( sectionItemStates = sectionItemStates.toImmutableList() ) } - }.onFailure { - setState { copy(loadState = AdminTotalScoreUiState.LoadState.Error) } - } } } From dc88402f776d3e4ac31cf937b58aecfec713050d Mon Sep 17 00:00:00 2001 From: YeongSang Jeon Date: Fri, 2 Jun 2023 00:44:10 +0900 Subject: [PATCH 11/18] =?UTF-8?q?fix:=20ManagementState=EB=A5=BC=20Stable?= =?UTF-8?q?=ED=95=98=EA=B2=8C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/admin/management/ManagementContract.kt | 3 ++- .../ui/admin/management/ManagementViewModel.kt | 12 +++++------- .../components/foldableItem/FoldableItemLayout.kt | 3 ++- .../foldableItem/FoldableItemLayoutState.kt | 5 ++++- .../tablayout/YDSTabLayoutItemStateList.kt | 8 ++++++-- 5 files changed, 19 insertions(+), 12 deletions(-) diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementContract.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementContract.kt index 08f13c41..ed5e25f8 100644 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementContract.kt +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementContract.kt @@ -11,6 +11,7 @@ import com.yapp.presentation.ui.admin.management.components.tablayout.YDSTabLayo import com.yapp.presentation.ui.admin.management.components.tablayout.YDSTabLayoutItemStateList import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList class ManagementContract { @@ -68,7 +69,7 @@ class ManagementContract { label = LABEL_POSITION ) ) - } + }.toImmutableList() ) ) } diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementViewModel.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementViewModel.kt index 0a6246c0..9821657f 100644 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementViewModel.kt +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementViewModel.kt @@ -126,15 +126,14 @@ class ManagementViewModel @Inject constructor( return when (tabIndex) { ManagementState.ManagementTabLayoutState.INDEX_TEAM -> { this.groupBy { it.team } - .entries.sortedWith(comparator = compareBy({it.key.type}, {it.key.number})) + .entries.sortedWith(comparator = compareBy({ it.key.type }, { it.key.number })) .map { (team, members) -> FoldableItemLayoutState( headerState = team.toFoldableItemHeaderState(sessionId, members), contentStates = members .sortedBy { it.attendances[sessionId].status } - .map { member -> - member.toFoldableItemContentState(sessionId = sessionId) - } + .map { member -> member.toFoldableItemContentState(sessionId = sessionId) } + .toImmutableList() ) } } @@ -147,9 +146,8 @@ class ManagementViewModel @Inject constructor( headerState = position.toFoldableItemHeaderState(sessionId, members), contentStates = members .sortedBy { it.attendances[sessionId].status } - .map { member -> - member.toFoldableItemContentState(sessionId = sessionId) - } + .map { member -> member.toFoldableItemContentState(sessionId = sessionId) } + .toImmutableList() ) } } diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/FoldableItemLayout.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/FoldableItemLayout.kt index 3d09346a..b042aca4 100644 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/FoldableItemLayout.kt +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/FoldableItemLayout.kt @@ -33,6 +33,7 @@ import com.yapp.presentation.ui.admin.management.components.foldableContentItem. import com.yapp.presentation.ui.admin.management.components.foldableContentItem.FoldableItemContentLayoutState import com.yapp.presentation.ui.admin.management.components.foldableHeaderItem.FoldableItemHeaderLayout import com.yapp.presentation.ui.admin.management.components.foldableHeaderItem.FoldableItemHeaderLayoutState +import kotlinx.collections.immutable.persistentListOf @Preview @@ -46,7 +47,7 @@ private fun FoldableItemLayoutPreview() { attendMemberCount = 4, allTeamMemberCount = 6 ), - contentStates = listOf( + contentStates = persistentListOf( FoldableItemContentLayoutState( id = 0L, label = "김철수", diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/FoldableItemLayoutState.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/FoldableItemLayoutState.kt index fd3700cb..2e7366e5 100644 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/FoldableItemLayoutState.kt +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/FoldableItemLayoutState.kt @@ -1,10 +1,13 @@ package com.yapp.presentation.ui.admin.management.components.foldableItem +import androidx.compose.runtime.Stable import com.yapp.presentation.ui.admin.management.components.foldableContentItem.FoldableItemContentLayoutState import com.yapp.presentation.ui.admin.management.components.foldableHeaderItem.FoldableItemHeaderLayoutState +import kotlinx.collections.immutable.ImmutableList +@Stable data class FoldableItemLayoutState( val headerState: FoldableItemHeaderLayoutState, - val contentStates: List, + val contentStates: ImmutableList ) \ No newline at end of file diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/tablayout/YDSTabLayoutItemStateList.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/tablayout/YDSTabLayoutItemStateList.kt index 65b4aa40..88337c0b 100644 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/tablayout/YDSTabLayoutItemStateList.kt +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/tablayout/YDSTabLayoutItemStateList.kt @@ -1,8 +1,12 @@ package com.yapp.presentation.ui.admin.management.components.tablayout +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList + data class YDSTabLayoutItemStateList( - val value: List = emptyList(), + val value: ImmutableList = persistentListOf(), ) { init { @@ -26,7 +30,7 @@ data class YDSTabLayoutItemStateList( } itemState - } + }.toImmutableList() ) } From 30cc3c4f3c66185e307e43d7ce2cbbadaff291fe Mon Sep 17 00:00:00 2001 From: YeongSang Jeon Date: Sat, 3 Jun 2023 20:08:27 +0900 Subject: [PATCH 12/18] =?UTF-8?q?[=20Feature=20]=20:=20=EC=B6=9C=EC=84=9D?= =?UTF-8?q?=EC=9D=B8=EC=A0=95=20=EC=95=84=EC=9D=B4=EC=BD=98=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 패턴이 들어간 svg파일은 IDE에서 표현이 불가능 하기 때문에 png파일로 아이콘 이미지를 추가합니다. --- common/src/main/res/drawable-hdpi/icon_admit.png | Bin 0 -> 566 bytes common/src/main/res/drawable-mdpi/icon_admit.png | Bin 0 -> 360 bytes .../src/main/res/drawable-xhdpi/icon_admit.png | Bin 0 -> 661 bytes .../src/main/res/drawable-xxhdpi/icon_admit.png | Bin 0 -> 1007 bytes .../src/main/res/drawable-xxxhdpi/icon_admit.png | Bin 0 -> 1218 bytes 5 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 common/src/main/res/drawable-hdpi/icon_admit.png create mode 100644 common/src/main/res/drawable-mdpi/icon_admit.png create mode 100644 common/src/main/res/drawable-xhdpi/icon_admit.png create mode 100644 common/src/main/res/drawable-xxhdpi/icon_admit.png create mode 100644 common/src/main/res/drawable-xxxhdpi/icon_admit.png diff --git a/common/src/main/res/drawable-hdpi/icon_admit.png b/common/src/main/res/drawable-hdpi/icon_admit.png new file mode 100644 index 0000000000000000000000000000000000000000..18ccfc2fb0a3cf0b10fdaf70e5727abc83ed18d5 GIT binary patch literal 566 zcmV-60?GY}P)yf{g(+ zvy@xlI`&Wk^`Vr$kAbh%_Hj3PE;7p&d0s~>d<~wxWOp=>wap=-(y%!P7 z$Ql)U$%h%POeUxKDDGIQ#qxXbVd57NSlefJ&N@mW2RIJM z^-UUz6t`SUdH5GNwPhp8O}`;0*44*D+2ts%()bBEzlbC`@!wbk34viAU>#sZL9x+F zl9Tz4H-#jN$KlEiM>#~~JOfUyJDU!!KSOF+WKAje_GszOE!9U(u$6DX6_yw;F5&(h5vc|l?YlLE*i9Y#f)y&bk6A0s@#y8%AOPyhe`07*qoM6N<$ Eg0iXc-T(jq literal 0 HcmV?d00001 diff --git a/common/src/main/res/drawable-mdpi/icon_admit.png b/common/src/main/res/drawable-mdpi/icon_admit.png new file mode 100644 index 0000000000000000000000000000000000000000..cb5b30d7b4e7ed09b3719c5c430d0f269535dc01 GIT binary patch literal 360 zcmV-u0hj)XP)4qck2PgOb0DL!=$W10=hp$M;$DTSpNQJDKU z{12PKTjmkMtFN`{gbr!rfNfoySLm;FdLb>d#N<4$Y?%+jDlEL<0eJA+C4MC#X_Dg$ zl(npEnM?2RTfra%#HWh#Mt_h5~vp28+US|%EpoF+<8Czf`1=ZI|!Y8c?ZMfPZf$wE+c#HfRJsba{%Yn%IW5ea}gSY{xQe-22tE4)ZopW-RYPg>dKZbd7K1hc@r5y1wGf+kfUmm_fno;)9k1e21zJb5x8U*=`7C7JiCS zfy}J_62ddC8oyNF3i>(x_eer5U7$8-Cc1k3_Vis1ze}hVMeUMmtjoR5yI7meiQ&BE zXRsull%JE~#k%(}XL>C-8CLqmG-y7C37!mN;k9v4&?(w9!v$5DUY|N>*dpB3iI20H zq((s;BC9;5lfa4DG=1EJ^##@66=fN-h}XFo^XC9(j`AkLtN(H+X1SQ1S*Ty@S(SL^ zNb+Y6JS@=+p5jIBx`zfwgC vzjaQ8uf-2$7P7V|_4llOb)li5;h*ybtRL3r+sCY400000NkvXXu0mjf8`~~i literal 0 HcmV?d00001 diff --git a/common/src/main/res/drawable-xxhdpi/icon_admit.png b/common/src/main/res/drawable-xxhdpi/icon_admit.png new file mode 100644 index 0000000000000000000000000000000000000000..9e80b78cb84e10c829edb94fc78d71b62f8e55a7 GIT binary patch literal 1007 zcmV>H6 z09`up6L2n5B!(vdx^!`qiB07f$i#Fc<4D<+?2HhnkCV=Ffcwob7N@hddw09Hw=2M6 zu~;k?i=`q2DrT+c?!iZ!F6*;4ALx@LXV*=6ggH~G!moSof$(vd*ulsCuq970M;ufL zDZ8EC0ehG;)hZ0sGP;a0XO>}z^-r$rIoKej#`M=2H09`CCm&tX`XK2yXa$_B+TZlt z#sqjrwqA2QTn$=Mq98_Wm~8WugmoiL<7I)ZZ%x^c_g(w!vW>=8w9-5T0;i=bkRrhElpTh*c z+U|vbOM`I?O$sYq@42QLDIS*tiQhmW8;f)6fNj2y z0&=J*Wy9ovej@h3Kv{BFqoy>r#qcaX^Kn6!jo7Ub`H5H1pp*@>C+3IF{-@EqG_5Sd zzxOMAR&=I$Cul;G(q5WJpO#f)i3Ct>L#Cl^YLh>2NqN%)Yq2~)v~4)BA&<_o4n`C& zUt+=N%A@6_IkKXV?!p&1<FDCFa zMMQsnaa@_g2Dz-HbL*6o;8C3M`Ip^5G+)*1Ujb7gK|j+X`!$Hch|-fN}wd*(Ij+MQRmnl z>_`^{F!qiIIS-dUHfRan&~_nJ+eOwp9zpQ0n6+t*xJ`q|70IhI2bM}-YN3_Dh3&Ky zjCQR^mnZj*N2Ll=!mpEdKj|G0lPsY^ethhIdGWR(XsUUpU(VVES0KrLhb{R57K_DV du~;mRg})cXTsZX0!Po!*002ovPDHLkV1iRp(X;>n literal 0 HcmV?d00001 diff --git a/common/src/main/res/drawable-xxxhdpi/icon_admit.png b/common/src/main/res/drawable-xxxhdpi/icon_admit.png new file mode 100644 index 0000000000000000000000000000000000000000..af21699b6e875985e7ed6b9098d5f859fd48a610 GIT binary patch literal 1218 zcmV;z1U>tSP)^3bV)=(RCodHo3T>cKoExiQ<#L36zP?QWF`%H1MvnplOi2jZq+@A^rCOB95g zLUgO}I_iWYG>C`x@d0}A7=Ja!E*hqF0+RJ@7`Ii}M#HpDK$=t5W@z5%@MTu80C--* zcvwcdiJumU3;MGI1IPl0x;DHZts=GR$i<@LbP>rGgkyRcOs>(74u1lBWt0Cbi-=O# zEE9!=djk6I1Y5bw1A0%-rjX8ta88IIf1O>ECkgsx@8ciG!qAf)l(kV~AvUXoAgenl zh{punr))%6!HET$SWzax02YkUQr%+Iy3Dq!|R1B0efx+QbOjfZoXxi~>xE3|EazDXlmiT7OY zV|l349JgoK+(!+p3V5TpD*j=c*PZYjj-dw>uFzf?dP|H@1FHh2le|KyI4g!LuPr`x z!XZ7dPT76-N$ZXT>x;-O&?DdIq;Ppe{$G!O5EcwSuw*P^l)uitBs=t|iL;#u`(hJ! zf+zK+!%n9%eFK@bE%Xp&m1a6WsMu*2G-Y?%pZ;Ddv=6!x08|8omi z5p}T^Dso9x|7Xee)p4ZDvS(JJ9ImN|8qJjLn_ECl_AEgA3CeEFlI^QwftBe;o*&Ut z4EpB75NA1&rnhcY*z8*yD-)IqX96zy5H@CR0hJ~Yx#UCGIP5mR1tt$-e)m-6bX&xr z?1o9j!-Tm7q!`~Jrdwe|3ii-))VPn)lv@$+MqtPz%*p6ccmHTlz92NUdThgOj552FQ@H+%S5Cp+B&eNK4f|2(K!=_i}v{E(B>IE&PHLVMn zR)&rXD1+SPD2cIm{nmX+$28W@dfN&K`>pp|I9;WzDc1MGX=UicSevgr^i!)`Ft7UE z4H7Rzp?E#5l#S~Leyv(y7AF5z(PYH+F^ekk$SPHNG?WGpD1864jukGtylw4Qf~)n5 zp7Zk^>mR#YSiRhV$oFD4O!(RW{)^gV_9qH2J z4(x+ncfy|Ly?>o>Dxf`a^x~|0S@M3XiGOD~k3 zaGDio7MN#p*pQ3Rbv_rhJx<0a)lE41@+RCh`ED%ZVZ(W#p?3vy-z*{lpQAzKyl_q0 zmsqdJ_C08-+?{qZrF8;UWc!NDls$7r>jcb>X>DdJ=mQJmgg!-5_PK^{SqeD?K@bE% g5ClOG1mR!eH{gxmKa@8%a{vGU07*qoM6N<$fOV literal 0 HcmV?d00001 From 61c8e555a0125031d76e24fc98c32d8b97a5ef2d Mon Sep 17 00:00:00 2001 From: YeongSang Jeon Date: Sat, 3 Jun 2023 20:09:26 +0900 Subject: [PATCH 13/18] =?UTF-8?q?[=20Feature=20]=20:=20=EC=84=B8=EC=85=98?= =?UTF-8?q?=20=EB=B3=84=20=EB=A9=A4=EB=B2=84=20=EC=B6=9C=EC=84=9D=ED=98=84?= =?UTF-8?q?=ED=99=A9=20=ED=99=94=EB=A9=B4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 새롭게 바뀐 디자인대로 화면을 구현합니다. - 화면에 존재하는 컴포넌트들을 파일들로 분리합니다. - 멤버 제거 기능을 추가합니다. (임시) - 멤버를 꾹 누르면 다이얼로그가 띄워지고 확인을 누르면 해당 유저를 삭제합니다. --- .../model/collections/AttendanceList.kt | 4 +- .../domain/usecases/MarkAttendanceUseCase.kt | 7 +- .../usecases/SetMemberAttendanceUseCase.kt | 10 +- .../ui/admin/management/Management.kt | 98 +++++++++++++++---- .../ui/admin/management/ManagementContract.kt | 70 ++----------- .../admin/management/ManagementViewModel.kt | 52 +++++----- .../AttendanceBottomSheetItemLayout.kt | 5 +- .../AttendanceBottomSheetItemLayoutState.kt | 13 +++ .../AttendanceTypeButton.kt | 69 ++++++------- .../FoldableItemContentLayout.kt | 10 ++ .../foldableItem/FoldableItemLayout.kt | 24 ++--- .../StatisticalTableLayout.kt | 19 ++-- .../StatisticalTableLayoutState.kt | 11 ++- .../management/dto/ManagementSharedData.kt | 3 + .../dto/ManagementTabLayoutState.kt | 51 ++++++++++ .../dto/ManagementTopBarLayoutState.kt | 3 + 16 files changed, 274 insertions(+), 175 deletions(-) create mode 100644 presentation/src/main/java/com/yapp/presentation/ui/admin/management/dto/ManagementSharedData.kt create mode 100644 presentation/src/main/java/com/yapp/presentation/ui/admin/management/dto/ManagementTabLayoutState.kt create mode 100644 presentation/src/main/java/com/yapp/presentation/ui/admin/management/dto/ManagementTopBarLayoutState.kt diff --git a/domain/src/main/java/com/yapp/domain/model/collections/AttendanceList.kt b/domain/src/main/java/com/yapp/domain/model/collections/AttendanceList.kt index b7f6e51f..8fca2fe3 100644 --- a/domain/src/main/java/com/yapp/domain/model/collections/AttendanceList.kt +++ b/domain/src/main/java/com/yapp/domain/model/collections/AttendanceList.kt @@ -39,10 +39,10 @@ class AttendanceList private constructor( } // 데이터를 변경하는 행위는 오로지 Domain에서만 가능하게끔 interal을 붙여주었다. - internal fun changeAttendanceType(sessionId: Int, changingAttendance: Attendance): AttendanceList { + internal fun changeAttendanceType(sessionId: Int, changingAttendance: Attendance.Status): AttendanceList { return this.value.map { if (it.sessionId == sessionId) { - return@map changingAttendance + return@map it.copy(status = changingAttendance) } return@map it diff --git a/domain/src/main/java/com/yapp/domain/usecases/MarkAttendanceUseCase.kt b/domain/src/main/java/com/yapp/domain/usecases/MarkAttendanceUseCase.kt index ca83da2b..27877df0 100644 --- a/domain/src/main/java/com/yapp/domain/usecases/MarkAttendanceUseCase.kt +++ b/domain/src/main/java/com/yapp/domain/usecases/MarkAttendanceUseCase.kt @@ -16,16 +16,11 @@ class MarkAttendanceUseCase @Inject constructor( return localRepository.getMemberId().mapCatching { currentUserId: Long? -> require(currentUserId != null) - val markedAttendanceState = Attendance( - sessionId = checkedSession.sessionId, - status = checkAttendanceState(checkedSession.date) - ) - val currentMemberInfo = memberRepository.getMember(currentUserId).getOrThrow() currentMemberInfo!!.attendances.changeAttendanceType( sessionId = checkedSession.sessionId, - changingAttendance = markedAttendanceState + changingAttendance = checkAttendanceState(checkedSession.date) ).also { updatedAttendanceList -> memberRepository.setMember(member = currentMemberInfo.copy(attendances = updatedAttendanceList)) } diff --git a/domain/src/main/java/com/yapp/domain/usecases/SetMemberAttendanceUseCase.kt b/domain/src/main/java/com/yapp/domain/usecases/SetMemberAttendanceUseCase.kt index 96d4c9fb..3ecf8293 100644 --- a/domain/src/main/java/com/yapp/domain/usecases/SetMemberAttendanceUseCase.kt +++ b/domain/src/main/java/com/yapp/domain/usecases/SetMemberAttendanceUseCase.kt @@ -13,12 +13,14 @@ class SetMemberAttendanceUseCase @Inject constructor( return memberRepository.getMember(params.memberId).mapCatching { targetMemeber -> require(targetMemeber != null) + if (targetMemeber.attendances[params.sessionId].status == params.changedAttendance) { + return@mapCatching + } + val changedAttendances = targetMemeber.attendances.changeAttendanceType( sessionId = params.sessionId, changingAttendance = params.changedAttendance - ).also { - - } + ) memberRepository.setMember(member = targetMemeber.copy(attendances = changedAttendances)) } @@ -32,7 +34,7 @@ class SetMemberAttendanceUseCase @Inject constructor( class Params( val memberId: Long, val sessionId: Int, - val changedAttendance: Attendance, + val changedAttendance: Attendance.Status, ) } \ No newline at end of file diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/Management.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/Management.kt index d34d594d..16e12eec 100644 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/Management.kt +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/Management.kt @@ -2,7 +2,6 @@ package com.yapp.presentation.ui.admin.management import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -19,17 +18,20 @@ import androidx.compose.material.ModalBottomSheetValue import androidx.compose.material.Scaffold import androidx.compose.material.rememberModalBottomSheetState import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue +import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalLifecycleOwner +import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel @@ -37,11 +39,14 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.google.accompanist.insets.navigationBarsWithImePadding import com.google.accompanist.insets.systemBarsPadding +import com.yapp.common.flow.collectAsStateWithLifecycle import com.yapp.common.theme.AttendanceTheme import com.yapp.common.yds.YDSAppBar import com.yapp.common.yds.YDSEmptyScreen +import com.yapp.common.yds.YDSPopupDialog import com.yapp.common.yds.YDSProgressBar import com.yapp.domain.model.Attendance +import com.yapp.presentation.R import com.yapp.presentation.ui.admin.management.ManagementContract.ManagementEvent import com.yapp.presentation.ui.admin.management.ManagementContract.ManagementState.LoadState.Error import com.yapp.presentation.ui.admin.management.ManagementContract.ManagementState.LoadState.Idle @@ -59,9 +64,9 @@ import kotlinx.coroutines.launch @Composable fun AttendanceManagement( viewModel: ManagementViewModel = hiltViewModel(), - onBackButtonClicked: (() -> Unit)? = null, + onBackButtonClicked: (() -> Unit)? = null ) { - val uiState by viewModel.uiState.collectAsState() + val uiState by viewModel.uiState.collectAsStateWithLifecycle() when (uiState.loadState) { Loading -> YDSProgressBar() @@ -86,13 +91,17 @@ fun AttendanceManagement( } } -@OptIn(ExperimentalFoundationApi::class) +internal typealias MemberItemLongPressCallback = (memberId: Long) -> Unit + +internal val LocalMemberItemLongPressCallback = compositionLocalOf(defaultFactory = { null }) + +@OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class) @ExperimentalMaterialApi @Composable internal fun ManagementScreen( uiState: ManagementContract.ManagementState, onEvent: (ManagementEvent) -> Unit, - onBackButtonClicked: () -> Unit, + onBackButtonClicked: () -> Unit ) { val coroutineScope = rememberCoroutineScope() @@ -100,6 +109,37 @@ internal fun ManagementScreen( var selectedMemberId by remember { mutableStateOf(null) } + var showDialog by remember { mutableStateOf(false) } + + val itemLongPressCallback by remember { + mutableStateOf({ memberId -> + selectedMemberId = memberId + showDialog = true + }) + } + + /** + * 임시로 추가한 회원삭제 다이얼로그로 + * (기획 / 디자인)이 변경될 시 수정 예정 + */ + if (showDialog) { + YDSPopupDialog( + title = "해당 회원을 삭제하시겠습니까?", + content = "삭제하면 모든 세션에서 해당 회원이 사라져요", + negativeButtonText = stringResource(id = R.string.member_setting_withdraw_dialog_negative_button), + positiveButtonText = "삭제하기", + onClickPositiveButton = { + selectedMemberId?.let { onEvent(ManagementEvent.OnDeleteMemberClicked(it)) } + showDialog = !showDialog + }, + onClickNegativeButton = { + selectedMemberId = null + showDialog = !showDialog + }, + onDismiss = { showDialog = !showDialog } + ) + } + LaunchedEffect(key1 = sheetState) { if (sheetState.isVisible.not()) { selectedMemberId = null @@ -111,7 +151,13 @@ internal fun ManagementScreen( BottomSheetDialog( itemStates = uiState.bottomSheetDialogState, onClickItem = { attendanceType -> - onEvent(ManagementEvent.OnAttendanceTypeChanged(selectedMemberId!!, attendanceType)) + coroutineScope.launch { sheetState.hide() } + onEvent( + ManagementEvent.OnAttendanceTypeChanged( + selectedMemberId!!, + attendanceType + ) + ) } ) }, @@ -142,10 +188,15 @@ internal fun ManagementScreen( ) { stickyHeader { - Column(modifier = Modifier.fillMaxWidth() - .background(color = AttendanceTheme.colors.backgroundColors.background)) { + Column( + modifier = Modifier + .fillMaxWidth() + .background(color = AttendanceTheme.colors.backgroundColors.background) + ) { Spacer( - modifier = Modifier.fillMaxWidth().height(20.dp) + modifier = Modifier + .fillMaxWidth() + .height(20.dp) ) YDSTabLayout( modifier = Modifier.padding(horizontal = 24.dp), @@ -159,7 +210,14 @@ internal fun ManagementScreen( } item { - Column(modifier = Modifier.padding(top = 24.dp, start = 24.dp, end = 24.dp, bottom = 28.dp)) { + Column( + modifier = Modifier.padding( + top = 24.dp, + start = 24.dp, + end = 24.dp, + bottom = 28.dp + ) + ) { StatisticalTableLayout(state = uiState.attendanceStatisticalTableState) } } @@ -168,13 +226,15 @@ internal fun ManagementScreen( items = uiState.foldableItemStates, key = { _, team -> team.headerState.label } ) { _, team -> - FoldableItemLayout( - state = team, - onDropDownClicked = { changedMember -> - selectedMemberId = changedMember - coroutineScope.launch { sheetState.show() } - } - ) + CompositionLocalProvider(LocalMemberItemLongPressCallback provides itemLongPressCallback) { + FoldableItemLayout( + state = team, + onDropDownClicked = { changedMember -> + selectedMemberId = changedMember + coroutineScope.launch { sheetState.show() } + } + ) + } } item { @@ -190,7 +250,7 @@ internal fun ManagementScreen( private fun BottomSheetDialog( modifier: Modifier = Modifier, itemStates: List, - onClickItem: (Attendance.Status) -> Unit = {}, + onClickItem: (Attendance.Status) -> Unit = {} ) { Column( modifier = modifier diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementContract.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementContract.kt index ed5e25f8..7b3547c2 100644 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementContract.kt +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementContract.kt @@ -9,6 +9,9 @@ import com.yapp.presentation.ui.admin.management.components.foldableItem.Foldabl import com.yapp.presentation.ui.admin.management.components.statisticalTable.StatisticalTableLayoutState import com.yapp.presentation.ui.admin.management.components.tablayout.YDSTabLayoutItemState import com.yapp.presentation.ui.admin.management.components.tablayout.YDSTabLayoutItemStateList +import com.yapp.presentation.ui.admin.management.dto.ManagementSharedData +import com.yapp.presentation.ui.admin.management.dto.ManagementTabLayoutState +import com.yapp.presentation.ui.admin.management.dto.ManagementTopBarLayoutState import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList @@ -16,80 +19,27 @@ import kotlinx.collections.immutable.toImmutableList class ManagementContract { - private companion object { - const val NOT_SELECTED = -1L - } - data class ManagementState( + val shared: ManagementSharedData = ManagementSharedData(), val loadState: LoadState = LoadState.Loading, - val shared: Shared = Shared(), + val topBarState: ManagementTopBarLayoutState = ManagementTopBarLayoutState(), val attendanceStatisticalTableState: StatisticalTableLayoutState = StatisticalTableLayoutState(), - val foldableItemStates: ImmutableList = persistentListOf(), - val topBarState: TopBarLayoutState = TopBarLayoutState(), val tabLayoutState: ManagementTabLayoutState = ManagementTabLayoutState.init(), - val bottomSheetDialogState: ImmutableList = persistentListOf(), + val foldableItemStates: ImmutableList = persistentListOf(), + val bottomSheetDialogState: ImmutableList = persistentListOf() ) : UiState { - data class ManagementTabLayoutState( - private val itemsList: YDSTabLayoutItemStateList = YDSTabLayoutItemStateList(), - ) { - val items: List - get() = itemsList.value - - val selectedIndex: Int - get() = itemsList.selectedIndex - - fun select(index: Int): ManagementTabLayoutState { - return this.copy(itemsList = itemsList.select(index)) - } - - companion object { - const val LABEL_TEAM = "팀별" - const val LABEL_POSITION = "직군별" - - const val INDEX_TEAM = 0 - const val INDEX_POSITION = 1 - - fun init(): ManagementTabLayoutState { - return ManagementTabLayoutState( - itemsList = YDSTabLayoutItemStateList( - buildList { - add( - INDEX_TEAM, - YDSTabLayoutItemState( - isSelected = true, - label = LABEL_TEAM - ) - ) - - add( - index = INDEX_POSITION, - element = YDSTabLayoutItemState( - isSelected = false, - label = LABEL_POSITION - ) - ) - }.toImmutableList() - ) - ) - } - } - } - - data class Shared(val sessionId: Int = 0) - - data class TopBarLayoutState(val sessionTitle: String = "") - enum class LoadState { Loading, Idle, Error } + } sealed class ManagementEvent : UiEvent { data class OnTabItemSelected(val tabIndex: Int) : ManagementEvent() data class OnAttendanceTypeChanged(val memberId: Long, val attendanceType: Attendance.Status) : ManagementEvent() + data class OnDeleteMemberClicked(val memberId: Long) : ManagementEvent() } - sealed class ManagementSideEffect : UiSideEffect { - } + sealed class ManagementSideEffect : UiSideEffect } diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementViewModel.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementViewModel.kt index 9821657f..18c47392 100644 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementViewModel.kt +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementViewModel.kt @@ -7,6 +7,7 @@ import com.yapp.domain.model.Attendance import com.yapp.domain.model.Member import com.yapp.domain.model.Team import com.yapp.domain.model.types.PositionType +import com.yapp.domain.usecases.DeleteMemberInfoUseCase import com.yapp.domain.usecases.GetAllMemberUseCase import com.yapp.domain.usecases.SetMemberAttendanceUseCase import com.yapp.presentation.ui.admin.AdminConstants.KEY_SESSION_ID @@ -20,8 +21,9 @@ import com.yapp.presentation.ui.admin.management.components.foldableContentItem. import com.yapp.presentation.ui.admin.management.components.foldableHeaderItem.FoldableItemHeaderLayoutState import com.yapp.presentation.ui.admin.management.components.foldableItem.FoldableItemLayoutState import com.yapp.presentation.ui.admin.management.components.statisticalTable.StatisticalTableLayoutState -import com.yapp.presentation.ui.admin.management.components.tablayout.YDSTabLayoutItemState -import com.yapp.presentation.ui.admin.management.components.tablayout.YDSTabLayoutItemStateList +import com.yapp.presentation.ui.admin.management.dto.ManagementSharedData +import com.yapp.presentation.ui.admin.management.dto.ManagementTabLayoutState +import com.yapp.presentation.ui.admin.management.dto.ManagementTopBarLayoutState import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList @@ -29,10 +31,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import javax.inject.Inject @@ -40,12 +39,12 @@ import javax.inject.Inject @HiltViewModel class ManagementViewModel @Inject constructor( savedStateHandle: SavedStateHandle, - private val getAllMemberUseCase: GetAllMemberUseCase, + getAllMemberUseCase: GetAllMemberUseCase, private val setMemberAttendanceUseCase: SetMemberAttendanceUseCase, + private val deleteMemberInfoUseCase: DeleteMemberInfoUseCase ) : BaseViewModel(ManagementState()) { companion object { - const val DEFAULT_SESSION_ID = 1 const val TEXT_LOAD_SESSION_TITLE_FAILED = "LOAD FAILED" } @@ -65,6 +64,9 @@ class ManagementViewModel @Inject constructor( initializeState(sessionId, sessionTitle) + /** + * members변경 이외에도 tabIndex가 변경 될 시 UiState를 Update해준다. + */ viewModelScope.launch { members.combine(tabIndex, transform = { members, tabIndex -> members to tabIndex @@ -81,13 +83,26 @@ class ManagementViewModel @Inject constructor( override suspend fun handleEvent(event: ManagementEvent) { when (event) { - is ManagementEvent.OnAttendanceTypeChanged -> TODO() + is ManagementEvent.OnAttendanceTypeChanged -> { + setMemberAttendanceUseCase( + params = SetMemberAttendanceUseCase.Params( + memberId = event.memberId, + sessionId = uiState.value.shared.sessionId, + changedAttendance = event.attendanceType + ) + ) + } + is ManagementEvent.OnTabItemSelected -> { setState { tabIndex.value = event.tabIndex this.copy(tabLayoutState = tabLayoutState.select(event.tabIndex)) } } + + is ManagementEvent.OnDeleteMemberClicked -> { + deleteMemberInfoUseCase(event.memberId) + } } } @@ -95,23 +110,14 @@ class ManagementViewModel @Inject constructor( setState { this.copy( loadState = ManagementState.LoadState.Idle, - shared = ManagementState.Shared(sessionId = sessionId), - tabLayoutState = ManagementState.ManagementTabLayoutState.init(), - bottomSheetDialogState = initBottomSheetDialogState(), - topBarState = ManagementState.TopBarLayoutState(sessionTitle = sessionTitle) + shared = ManagementSharedData(sessionId = sessionId), + topBarState = ManagementTopBarLayoutState(sessionTitle = sessionTitle), + tabLayoutState = ManagementTabLayoutState.init(), + bottomSheetDialogState = AttendanceBottomSheetItemLayoutState.init() ) } } - private fun initBottomSheetDialogState(): ImmutableList { - return buildList { - add(AttendanceBottomSheetItemLayoutState(label = Attendance.Status.NORMAL.text, iconType = AttendanceBottomSheetItemLayoutState.IconType.ATTEND)) - add(AttendanceBottomSheetItemLayoutState(label = Attendance.Status.LATE.text, iconType = AttendanceBottomSheetItemLayoutState.IconType.TARDY)) - add(AttendanceBottomSheetItemLayoutState(label = Attendance.Status.ABSENT.text, iconType = AttendanceBottomSheetItemLayoutState.IconType.ABSENT)) - add(AttendanceBottomSheetItemLayoutState(label = Attendance.Status.ADMIT.text, iconType = AttendanceBottomSheetItemLayoutState.IconType.ADMIT)) - }.toImmutableList() - } - private fun List.toStatisticalTableState(sessionId: Int): StatisticalTableLayoutState { return StatisticalTableLayoutState( totalCount = size, @@ -124,7 +130,7 @@ class ManagementViewModel @Inject constructor( private fun List.toFoldableItemContentState(sessionId: Int, tabIndex: Int): ImmutableList { return when (tabIndex) { - ManagementState.ManagementTabLayoutState.INDEX_TEAM -> { + ManagementTabLayoutState.INDEX_TEAM -> { this.groupBy { it.team } .entries.sortedWith(comparator = compareBy({ it.key.type }, { it.key.number })) .map { (team, members) -> @@ -138,7 +144,7 @@ class ManagementViewModel @Inject constructor( } } - ManagementState.ManagementTabLayoutState.INDEX_POSITION -> { + ManagementTabLayoutState.INDEX_POSITION -> { this.groupBy { it.position } .entries.sortedBy { it.key.value } .map { (position, members) -> diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceBottomSheet/AttendanceBottomSheetItemLayout.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceBottomSheet/AttendanceBottomSheetItemLayout.kt index fa127fd1..3a5590a3 100644 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceBottomSheet/AttendanceBottomSheetItemLayout.kt +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceBottomSheet/AttendanceBottomSheetItemLayout.kt @@ -10,7 +10,6 @@ import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.material.Icon import androidx.compose.material.Text @@ -79,11 +78,11 @@ fun AttendanceBottomSheetItemLayout( Icon( painter = when (state.iconType) { AttendanceBottomSheetItemLayoutState.IconType.ATTEND -> painterResource(id = R.drawable.icon_attend) - AttendanceBottomSheetItemLayoutState.IconType.ADMIT -> painterResource(id = R.drawable.icon_attend) + AttendanceBottomSheetItemLayoutState.IconType.ADMIT -> painterResource(id = R.drawable.icon_admit) AttendanceBottomSheetItemLayoutState.IconType.TARDY -> painterResource(id = R.drawable.icon_tardy) AttendanceBottomSheetItemLayoutState.IconType.ABSENT -> painterResource(id = R.drawable.icon_absent) }, - tint= Color.Unspecified, + tint = Color.Unspecified, contentDescription = "attendance_type_icon" ) diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceBottomSheet/AttendanceBottomSheetItemLayoutState.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceBottomSheet/AttendanceBottomSheetItemLayoutState.kt index 6f1089f8..d059bf10 100644 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceBottomSheet/AttendanceBottomSheetItemLayoutState.kt +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceBottomSheet/AttendanceBottomSheetItemLayoutState.kt @@ -1,6 +1,8 @@ package com.yapp.presentation.ui.admin.management.components.attendanceBottomSheet import com.yapp.domain.model.Attendance +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList data class AttendanceBottomSheetItemLayoutState( @@ -20,4 +22,15 @@ data class AttendanceBottomSheetItemLayoutState( ATTEND, TARDY, ADMIT, ABSENT } + companion object { + fun init(): ImmutableList { + return buildList { + add(AttendanceBottomSheetItemLayoutState(label = Attendance.Status.NORMAL.text, iconType = AttendanceBottomSheetItemLayoutState.IconType.ATTEND)) + add(AttendanceBottomSheetItemLayoutState(label = Attendance.Status.LATE.text, iconType = AttendanceBottomSheetItemLayoutState.IconType.TARDY)) + add(AttendanceBottomSheetItemLayoutState(label = Attendance.Status.ABSENT.text, iconType = AttendanceBottomSheetItemLayoutState.IconType.ABSENT)) + add(AttendanceBottomSheetItemLayoutState(label = Attendance.Status.ADMIT.text, iconType = AttendanceBottomSheetItemLayoutState.IconType.ADMIT)) + }.toImmutableList() + } + } + } \ No newline at end of file diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceTypeButton/AttendanceTypeButton.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceTypeButton/AttendanceTypeButton.kt index 93103dbc..5c2e1539 100644 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceTypeButton/AttendanceTypeButton.kt +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceTypeButton/AttendanceTypeButton.kt @@ -1,18 +1,15 @@ package com.yapp.presentation.ui.admin.management.components.attendanceTypeButton import androidx.compose.animation.Crossfade +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width -import androidx.compose.foundation.layout.wrapContentSize -import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.Button -import androidx.compose.material.ButtonDefaults import androidx.compose.material.Icon import androidx.compose.material.Text import androidx.compose.runtime.Composable @@ -22,6 +19,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview @@ -59,43 +57,38 @@ internal fun AttendanceTypeButton( state: AttendanceTypeButtonState, onClick: () -> Unit, ) { - Button( - onClick = onClick, + Row( modifier = modifier - .wrapContentWidth(), - shape = RoundedCornerShape(8.dp), - colors = ButtonDefaults.buttonColors(backgroundColor = AttendanceTheme.colors.grayScale.Gray200), - elevation = null + .clip(RoundedCornerShape(8.dp)) + .background(AttendanceTheme.colors.grayScale.Gray200) + .clickable { + onClick.invoke() + } + .padding(start = 8.dp, end = 12.dp, top = 8.dp, bottom = 8.dp), + verticalAlignment = Alignment.CenterVertically, ) { - Row( - modifier = Modifier - .align(Alignment.CenterVertically) - .wrapContentSize(), - verticalAlignment = Alignment.CenterVertically - ) { - Icon( - painter = when (state.iconType) { - AttendanceTypeButtonState.IconType.ATTEND -> painterResource(id = R.drawable.icon_attend) - AttendanceTypeButtonState.IconType.ADMIT -> painterResource(id = R.drawable.icon_attend) - AttendanceTypeButtonState.IconType.TARDY -> painterResource(id = R.drawable.icon_tardy) - AttendanceTypeButtonState.IconType.ABSENT -> painterResource(id = R.drawable.icon_absent) - }, - tint= Color.Unspecified, - contentDescription = "drop down" - ) + Icon( + painter = when (state.iconType) { + AttendanceTypeButtonState.IconType.ATTEND -> painterResource(id = R.drawable.icon_attend) + AttendanceTypeButtonState.IconType.ADMIT -> painterResource(id = R.drawable.icon_admit) + AttendanceTypeButtonState.IconType.TARDY -> painterResource(id = R.drawable.icon_tardy) + AttendanceTypeButtonState.IconType.ABSENT -> painterResource(id = R.drawable.icon_absent) + }, + tint = Color.Unspecified, + contentDescription = "drop down" + ) - Spacer(modifier = Modifier.width(4.dp)) + Spacer(modifier = Modifier.width(4.dp)) - Crossfade(targetState = state.label) { - Text( - text = it, - modifier = Modifier - .height(20.dp) - .align(alignment = Alignment.CenterVertically), - style = AttendanceTypography.subtitle2, - color = AttendanceTheme.colors.grayScale.Gray800 - ) - } + Crossfade(targetState = state.label, label = "") { + Text( + text = it, + modifier = Modifier + .height(20.dp) + .align(alignment = Alignment.CenterVertically), + style = AttendanceTypography.subtitle2, + color = AttendanceTheme.colors.grayScale.Gray800 + ) } } } \ No newline at end of file diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableContentItem/FoldableItemContentLayout.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableContentItem/FoldableItemContentLayout.kt index 326cb046..c774efee 100644 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableContentItem/FoldableItemContentLayout.kt +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableContentItem/FoldableItemContentLayout.kt @@ -1,6 +1,7 @@ package com.yapp.presentation.ui.admin.management.components.foldableContentItem import androidx.compose.animation.animateContentSize +import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -17,12 +18,14 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.constraintlayout.compose.ConstraintLayout import com.yapp.common.theme.AttendanceTheme import com.yapp.common.theme.AttendanceTypography +import com.yapp.presentation.ui.admin.management.LocalMemberItemLongPressCallback import com.yapp.presentation.ui.admin.management.components.attendanceTypeButton.AttendanceTypeButton import com.yapp.presentation.ui.admin.management.components.attendanceTypeButton.AttendanceTypeButtonState @@ -57,10 +60,17 @@ internal fun FoldableItemContentLayout( state: FoldableItemContentLayoutState, onDropDownClicked: (memberId: Long) -> Unit = {}, ) { + val longPressCallback = LocalMemberItemLongPressCallback.current + ConstraintLayout( modifier = modifier .fillMaxWidth() .height(62.dp) + .pointerInput(Unit) { + detectTapGestures(onLongPress = { + longPressCallback?.invoke(state.id) + }) + } ) { val (ydsDropdownButton, textContents) = createRefs() diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/FoldableItemLayout.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/FoldableItemLayout.kt index b042aca4..141bcde8 100644 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/FoldableItemLayout.kt +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/FoldableItemLayout.kt @@ -81,7 +81,7 @@ private fun FoldableItemLayoutPreview() { internal fun FoldableItemLayout( modifier: Modifier = Modifier, state: FoldableItemLayoutState, - onDropDownClicked: ((memberId: Long) -> Unit), + onDropDownClicked: ((memberId: Long) -> Unit) ) { var expanded by rememberSaveable { mutableStateOf(false) } @@ -109,19 +109,19 @@ internal fun FoldableItemLayout( AnimatedVisibility( visible = expanded, enter = fadeIn(animationSpec = tween(50)) + - expandVertically( - animationSpec = spring( - dampingRatio = Spring.DampingRatioLowBouncy, - stiffness = Spring.StiffnessLow - ) - ), + expandVertically( + animationSpec = spring( + dampingRatio = Spring.DampingRatioLowBouncy, + stiffness = Spring.StiffnessLow + ) + ), exit = fadeOut(animationSpec = tween(50)) + - shrinkVertically( - animationSpec = spring( - dampingRatio = Spring.DampingRatioLowBouncy, - stiffness = Spring.StiffnessLow - ) + shrinkVertically( + animationSpec = spring( + dampingRatio = Spring.DampingRatioLowBouncy, + stiffness = Spring.StiffnessLow ) + ) ) { Column { Divider( diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/statisticalTable/StatisticalTableLayout.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/statisticalTable/StatisticalTableLayout.kt index 67faf304..e2852a78 100644 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/statisticalTable/StatisticalTableLayout.kt +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/statisticalTable/StatisticalTableLayout.kt @@ -55,7 +55,7 @@ private fun StatisticalTableLayoutPreview() { ) { Button( modifier = Modifier.align(Alignment.Center), - onClick = { state = state.copy(absentCount = state.absentCount + 1) } + onClick = { state = state.copy(totalCount = state.totalCount + 1, absentCount = state.absentCount + 1) } ) { Text(text = "dd") } @@ -68,7 +68,7 @@ private fun StatisticalTableLayoutPreview() { @Composable internal fun StatisticalTableLayout( - state: StatisticalTableLayoutState, + state: StatisticalTableLayoutState ) { Column( modifier = Modifier @@ -86,15 +86,20 @@ internal fun StatisticalTableLayout( verticalAlignment = Alignment.CenterVertically ) { AnimatedCounterText( - count = state.attendCount, - style = AttendanceTypography.subtitle2.copy(color = AttendanceTheme.colors.etcColors.EtcGreen), - prefix = { + count = state.totalCount, + style = AttendanceTypography.body2.copy(color = AttendanceTheme.colors.grayScale.Gray1200), + suffix = { Text( style = AttendanceTypography.body2, - text = "${state.totalCount}명 중 ", + text = "명 중 ", color = AttendanceTheme.colors.grayScale.Gray1200 ) - }, + } + ) + + AnimatedCounterText( + count = state.currentAttendCount, + style = AttendanceTypography.subtitle2.copy(color = AttendanceTheme.colors.etcColors.EtcGreen), suffix = { Text( style = AttendanceTypography.subtitle2, diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/statisticalTable/StatisticalTableLayoutState.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/statisticalTable/StatisticalTableLayoutState.kt index 76b8c86f..ce487ad3 100644 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/statisticalTable/StatisticalTableLayoutState.kt +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/statisticalTable/StatisticalTableLayoutState.kt @@ -7,4 +7,13 @@ data class StatisticalTableLayoutState( val tardyCount: Int = 0, val absentCount: Int = 0, val admitCount: Int = 0, -) \ No newline at end of file +) { + + /** + * 출석 인정, 지각, 출석 3개는 현재 **출석**한 인원이기 때문에 + * 현재 출석한 인원에 포함한다. + */ + val currentAttendCount: Int + get() = attendCount + tardyCount + admitCount + +} \ No newline at end of file diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/dto/ManagementSharedData.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/dto/ManagementSharedData.kt new file mode 100644 index 00000000..9c586a5e --- /dev/null +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/dto/ManagementSharedData.kt @@ -0,0 +1,3 @@ +package com.yapp.presentation.ui.admin.management.dto + +data class ManagementSharedData(val sessionId: Int = 0) \ No newline at end of file diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/dto/ManagementTabLayoutState.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/dto/ManagementTabLayoutState.kt new file mode 100644 index 00000000..62264d04 --- /dev/null +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/dto/ManagementTabLayoutState.kt @@ -0,0 +1,51 @@ +package com.yapp.presentation.ui.admin.management.dto + +import com.yapp.presentation.ui.admin.management.components.tablayout.YDSTabLayoutItemState +import com.yapp.presentation.ui.admin.management.components.tablayout.YDSTabLayoutItemStateList +import kotlinx.collections.immutable.toImmutableList + +data class ManagementTabLayoutState( + private val itemsList: YDSTabLayoutItemStateList = YDSTabLayoutItemStateList(), +) { + val items: List + get() = itemsList.value + + val selectedIndex: Int + get() = itemsList.selectedIndex + + fun select(index: Int): ManagementTabLayoutState { + return this.copy(itemsList = itemsList.select(index)) + } + + companion object { + private const val LABEL_TEAM = "팀별" + private const val LABEL_POSITION = "직군별" + + const val INDEX_TEAM = 0 + const val INDEX_POSITION = 1 + + fun init(): ManagementTabLayoutState { + return ManagementTabLayoutState( + itemsList = YDSTabLayoutItemStateList( + buildList { + add( + INDEX_TEAM, + YDSTabLayoutItemState( + isSelected = true, + label = LABEL_TEAM + ) + ) + + add( + index = INDEX_POSITION, + element = YDSTabLayoutItemState( + isSelected = false, + label = LABEL_POSITION + ) + ) + }.toImmutableList() + ) + ) + } + } +} \ No newline at end of file diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/dto/ManagementTopBarLayoutState.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/dto/ManagementTopBarLayoutState.kt new file mode 100644 index 00000000..1e0adc91 --- /dev/null +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/dto/ManagementTopBarLayoutState.kt @@ -0,0 +1,3 @@ +package com.yapp.presentation.ui.admin.management.dto + +data class ManagementTopBarLayoutState(val sessionTitle: String = "") \ No newline at end of file From 6775d39d3199e56f6feaaeb6bd088f5949b54662 Mon Sep 17 00:00:00 2001 From: YeongSang Jeon Date: Sat, 15 Jul 2023 23:23:48 +0900 Subject: [PATCH 14/18] =?UTF-8?q?[Feature]:=20Management=EC=9D=98=20?= =?UTF-8?q?=EC=A0=84=EC=B2=B4=EC=A0=81=20=EA=B5=AC=EC=A1=B0=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20&=20=EC=95=A0=EB=8B=88=EB=A9=94=EC=9D=B4=EC=85=98?= =?UTF-8?q?=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Management를 Map<[헤더] - [멤버리스트]> 의 구조의 State에서 Flat한 형태로 변경합니다. - 헤더와 Content는 모두 공통의 FoldableItem으로 interface로 묶습니다. - 기존은 for문으로 멤버 컴포저블을 생성하는 방식을 Flat하게 변경하고 Type에 따라 LazyColumn에서 표현하게끔 수정합니다. - 위의 flat한 구조 변경으로 각 아이템을을 Plcaement 애니메이션을 적용합니다 (for문으로 한번에 컴포저블 생성하면 Placement애니메이션 적용불가) - LazyColumn의 아이템의 enter 애니메이션을 추가합니다. - 탭의 Index가 바뀌지 않은이상 멤버의 출결현황이 바뀌어도 Header가 초기화(닫히지)지 않도록 수정합니다. --- .../ui/admin/management/Management.kt | 84 +++++++--- .../ui/admin/management/ManagementContract.kt | 15 +- .../admin/management/ManagementViewModel.kt | 135 ++++++++++++---- .../components/AnimatedCounterText.kt | 3 + .../FoldableItemContentLayoutState.kt | 11 -- .../FoldableItemHeaderLayoutState.kt | 8 - .../components/foldableItem/FoldableItem.kt | 7 + .../foldableItem/FoldableItemLayout.kt | 146 ------------------ .../foldableItem/FoldableItemLayoutState.kt | 13 -- .../foldableItem/FoldableItemState.kt | 77 +++++++++ .../FoldableItemContentLayout.kt | 93 +++++++++-- .../FoldableItemContentLayoutState.kt | 47 ++++++ .../FoldableItemHeaderLayout.kt | 103 +++++++++--- .../FoldableItemHeaderLayoutState.kt | 35 +++++ .../StatisticalTableLayout.kt | 9 +- 15 files changed, 506 insertions(+), 280 deletions(-) delete mode 100644 presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableContentItem/FoldableItemContentLayoutState.kt delete mode 100644 presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableHeaderItem/FoldableItemHeaderLayoutState.kt create mode 100644 presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/FoldableItem.kt delete mode 100644 presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/FoldableItemLayout.kt delete mode 100644 presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/FoldableItemLayoutState.kt create mode 100644 presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/FoldableItemState.kt rename presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/{ => foldableItem}/foldableContentItem/FoldableItemContentLayout.kt (57%) create mode 100644 presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/foldableContentItem/FoldableItemContentLayoutState.kt rename presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/{ => foldableItem}/foldableHeaderItem/FoldableItemHeaderLayout.kt (54%) create mode 100644 presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/foldableHeaderItem/FoldableItemHeaderLayoutState.kt diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/Management.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/Management.kt index 16e12eec..e66b316a 100644 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/Management.kt +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/Management.kt @@ -1,5 +1,6 @@ package com.yapp.presentation.ui.admin.management +import FoldableHeaderItemState import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column @@ -53,7 +54,9 @@ import com.yapp.presentation.ui.admin.management.ManagementContract.ManagementSt import com.yapp.presentation.ui.admin.management.ManagementContract.ManagementState.LoadState.Loading import com.yapp.presentation.ui.admin.management.components.attendanceBottomSheet.AttendanceBottomSheetItemLayout import com.yapp.presentation.ui.admin.management.components.attendanceBottomSheet.AttendanceBottomSheetItemLayoutState -import com.yapp.presentation.ui.admin.management.components.foldableItem.FoldableItemLayout +import com.yapp.presentation.ui.admin.management.components.foldableItem.foldableContentItem.FoldableContentItemState +import com.yapp.presentation.ui.admin.management.components.foldableItem.foldableContentItem.FoldableItemContentLayout +import com.yapp.presentation.ui.admin.management.components.foldableItem.foldableHeaderItem.FoldableHeaderItem import com.yapp.presentation.ui.admin.management.components.statisticalTable.StatisticalTableLayout import com.yapp.presentation.ui.admin.management.components.tablayout.YDSTabLayout import kotlinx.coroutines.launch @@ -167,7 +170,9 @@ internal fun ManagementScreen( Scaffold( modifier = Modifier .fillMaxSize() - .systemBarsPadding(), + .systemBarsPadding() + .background(AttendanceTheme.colors.backgroundColors.background) + .padding(horizontal = 24.dp), topBar = { YDSAppBar( modifier = Modifier @@ -193,13 +198,8 @@ internal fun ManagementScreen( .fillMaxWidth() .background(color = AttendanceTheme.colors.backgroundColors.background) ) { - Spacer( - modifier = Modifier - .fillMaxWidth() - .height(20.dp) - ) + Spacer(modifier = Modifier.height(20.dp)) YDSTabLayout( - modifier = Modifier.padding(horizontal = 24.dp), tabItems = uiState.tabLayoutState.items, selectedIndex = uiState.tabLayoutState.selectedIndex, onTabSelected = { selectedTabIndex -> @@ -210,30 +210,64 @@ internal fun ManagementScreen( } item { - Column( - modifier = Modifier.padding( - top = 24.dp, - start = 24.dp, - end = 24.dp, - bottom = 28.dp - ) - ) { + Column(modifier = Modifier.background(AttendanceTheme.colors.backgroundColors.background)) { + Spacer(modifier = Modifier.height(24.dp)) StatisticalTableLayout(state = uiState.attendanceStatisticalTableState) + Spacer(modifier = Modifier.height(28.dp)) } } itemsIndexed( - items = uiState.foldableItemStates, - key = { _, team -> team.headerState.label } - ) { _, team -> + items = uiState.foldableItemStates.flatMap { it.flatten }, + key = { _, itemState -> + when (itemState) { + is FoldableHeaderItemState -> { + itemState.label + } + + is FoldableContentItemState -> { + itemState.memberId + } + + else -> itemState.hashCode() + } + } + ) { _, itemState -> CompositionLocalProvider(LocalMemberItemLongPressCallback provides itemLongPressCallback) { - FoldableItemLayout( - state = team, - onDropDownClicked = { changedMember -> - selectedMemberId = changedMember - coroutineScope.launch { sheetState.show() } + when (itemState) { + is FoldableHeaderItemState.TeamType -> { + FoldableHeaderItem( + modifier = Modifier.animateItemPlacement(), + state = itemState, + onExpandClicked = { isExpanded, teamName, teamNumber -> + onEvent(ManagementEvent.OnTeamTypeHeaderItemClicked(teamName, teamNumber)) + } + ) } - ) + + is FoldableHeaderItemState.PositionType -> { + FoldableHeaderItem( + modifier = Modifier.animateItemPlacement(), + state = itemState, + onExpandClicked = { isExpanded, position -> + onEvent(ManagementEvent.OnPositionTypeHeaderItemClicked(positionName = position)) + } + ) + } + + is FoldableContentItemState -> { + FoldableItemContentLayout( + modifier = Modifier.animateItemPlacement(), + state = itemState, + onDropDownClicked = { + selectedMemberId = it + coroutineScope.launch { + sheetState.show() + } + } + ) + } + } } } diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementContract.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementContract.kt index 7b3547c2..dc66e726 100644 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementContract.kt +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementContract.kt @@ -5,16 +5,13 @@ import com.yapp.common.base.UiSideEffect import com.yapp.common.base.UiState import com.yapp.domain.model.Attendance import com.yapp.presentation.ui.admin.management.components.attendanceBottomSheet.AttendanceBottomSheetItemLayoutState -import com.yapp.presentation.ui.admin.management.components.foldableItem.FoldableItemLayoutState +import com.yapp.presentation.ui.admin.management.components.foldableItem.FoldableItemState import com.yapp.presentation.ui.admin.management.components.statisticalTable.StatisticalTableLayoutState -import com.yapp.presentation.ui.admin.management.components.tablayout.YDSTabLayoutItemState -import com.yapp.presentation.ui.admin.management.components.tablayout.YDSTabLayoutItemStateList import com.yapp.presentation.ui.admin.management.dto.ManagementSharedData import com.yapp.presentation.ui.admin.management.dto.ManagementTabLayoutState import com.yapp.presentation.ui.admin.management.dto.ManagementTopBarLayoutState import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.toImmutableList class ManagementContract { @@ -25,7 +22,7 @@ class ManagementContract { val topBarState: ManagementTopBarLayoutState = ManagementTopBarLayoutState(), val attendanceStatisticalTableState: StatisticalTableLayoutState = StatisticalTableLayoutState(), val tabLayoutState: ManagementTabLayoutState = ManagementTabLayoutState.init(), - val foldableItemStates: ImmutableList = persistentListOf(), + val foldableItemStates: ImmutableList = persistentListOf(), val bottomSheetDialogState: ImmutableList = persistentListOf() ) : UiState { @@ -36,9 +33,11 @@ class ManagementContract { } sealed class ManagementEvent : UiEvent { - data class OnTabItemSelected(val tabIndex: Int) : ManagementEvent() - data class OnAttendanceTypeChanged(val memberId: Long, val attendanceType: Attendance.Status) : ManagementEvent() - data class OnDeleteMemberClicked(val memberId: Long) : ManagementEvent() + class OnTabItemSelected(val tabIndex: Int) : ManagementEvent() + class OnAttendanceTypeChanged(val memberId: Long, val attendanceType: Attendance.Status) : ManagementEvent() + class OnDeleteMemberClicked(val memberId: Long) : ManagementEvent() + class OnPositionTypeHeaderItemClicked(val positionName: String) : ManagementEvent() + class OnTeamTypeHeaderItemClicked(val teamName: String, val teamNumber: Int) : ManagementEvent() } sealed class ManagementSideEffect : UiSideEffect diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementViewModel.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementViewModel.kt index 18c47392..d5de4f28 100644 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementViewModel.kt +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementViewModel.kt @@ -1,5 +1,6 @@ package com.yapp.presentation.ui.admin.management +import FoldableHeaderItemState import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope import com.yapp.common.base.BaseViewModel @@ -17,9 +18,10 @@ import com.yapp.presentation.ui.admin.management.ManagementContract.ManagementSi import com.yapp.presentation.ui.admin.management.ManagementContract.ManagementState import com.yapp.presentation.ui.admin.management.components.attendanceBottomSheet.AttendanceBottomSheetItemLayoutState import com.yapp.presentation.ui.admin.management.components.attendanceTypeButton.AttendanceTypeButtonState -import com.yapp.presentation.ui.admin.management.components.foldableContentItem.FoldableItemContentLayoutState -import com.yapp.presentation.ui.admin.management.components.foldableHeaderItem.FoldableItemHeaderLayoutState -import com.yapp.presentation.ui.admin.management.components.foldableItem.FoldableItemLayoutState +import com.yapp.presentation.ui.admin.management.components.foldableItem.FoldableItemState +import com.yapp.presentation.ui.admin.management.components.foldableItem.PositionItemState +import com.yapp.presentation.ui.admin.management.components.foldableItem.TeamItemState +import com.yapp.presentation.ui.admin.management.components.foldableItem.foldableContentItem.FoldableContentItemState import com.yapp.presentation.ui.admin.management.components.statisticalTable.StatisticalTableLayoutState import com.yapp.presentation.ui.admin.management.dto.ManagementSharedData import com.yapp.presentation.ui.admin.management.dto.ManagementTabLayoutState @@ -74,7 +76,7 @@ class ManagementViewModel @Inject constructor( setState { this.copy( attendanceStatisticalTableState = members.toStatisticalTableState(sessionId = sessionId), - foldableItemStates = members.toFoldableItemContentState(sessionId, tabIndex) + foldableItemStates = members.toFoldableItemGroup(sessionId, tabIndex) ) } } @@ -103,6 +105,48 @@ class ManagementViewModel @Inject constructor( is ManagementEvent.OnDeleteMemberClicked -> { deleteMemberInfoUseCase(event.memberId) } + + is ManagementEvent.OnPositionTypeHeaderItemClicked -> { + setState { + this.copy( + foldableItemStates = currentState.foldableItemStates + .filterIsInstance() + .map { itemGroup -> + if (itemGroup.position == event.positionName) { + return@map if (itemGroup.headerItem.isExpanded) { + itemGroup.collapse() + } else { + itemGroup.expand() + } + } + + itemGroup + }.toImmutableList() + ) + + } + } + + is ManagementEvent.OnTeamTypeHeaderItemClicked -> { + setState { + this.copy( + foldableItemStates = currentState.foldableItemStates + .filterIsInstance() + .map { itemGroup -> + if (itemGroup.teamName == event.teamName && itemGroup.teamNumber == event.teamNumber) { + return@map if (itemGroup.headerItem.isExpanded) { + itemGroup.collapse() + } else { + itemGroup.expand() + } + } + + itemGroup + }.toImmutableList() + ) + + } + } } } @@ -128,17 +172,22 @@ class ManagementViewModel @Inject constructor( ) } - private fun List.toFoldableItemContentState(sessionId: Int, tabIndex: Int): ImmutableList { + private fun List.toFoldableItemGroup(sessionId: Int, tabIndex: Int): ImmutableList { return when (tabIndex) { ManagementTabLayoutState.INDEX_TEAM -> { this.groupBy { it.team } .entries.sortedWith(comparator = compareBy({ it.key.type }, { it.key.number })) .map { (team, members) -> - FoldableItemLayoutState( - headerState = team.toFoldableItemHeaderState(sessionId, members), - contentStates = members + TeamItemState( + headerItem = toTeamTypeHeaderItem(team = team, sessionId = sessionId, members = members), + contentItems = members .sortedBy { it.attendances[sessionId].status } - .map { member -> member.toFoldableItemContentState(sessionId = sessionId) } + .map { member -> + toTeamTypeContentItem( + member = member, + buttonState = toButtonState(member, sessionId) + ) + } .toImmutableList() ) } @@ -148,11 +197,16 @@ class ManagementViewModel @Inject constructor( this.groupBy { it.position } .entries.sortedBy { it.key.value } .map { (position, members) -> - FoldableItemLayoutState( - headerState = position.toFoldableItemHeaderState(sessionId, members), - contentStates = members + PositionItemState( + headerItem = toPositionTypeHeaderItem(positionType = position, sessionId = sessionId, members = members), + contentItems = members .sortedBy { it.attendances[sessionId].status } - .map { member -> member.toFoldableItemContentState(sessionId = sessionId) } + .map { member -> + toPositionTypeContentItem( + member = member, + buttonState = toButtonState(member = member, sessionId = sessionId) + ) + } .toImmutableList() ) } @@ -162,26 +216,52 @@ class ManagementViewModel @Inject constructor( }.toImmutableList() } - private fun PositionType.toFoldableItemHeaderState(sessionId: Int, members: List): FoldableItemHeaderLayoutState { - return FoldableItemHeaderLayoutState( - label = value, - attendMemberCount = members.count { it.attendances[sessionId].status == Attendance.Status.NORMAL }, + private fun toPositionTypeHeaderItem(positionType: PositionType, sessionId: Int, members: List): FoldableHeaderItemState.PositionType { + return FoldableHeaderItemState.PositionType( + position = positionType.value, + isExpanded = currentState.foldableItemStates + .filterIsInstance() + .find { it.position == positionType.value }?.headerItem?.isExpanded ?: false, + attendMemberCount = members.count { it.attendances[sessionId].status != Attendance.Status.ABSENT }, allTeamMemberCount = members.size ) } - private fun Team.toFoldableItemHeaderState(sessionId: Int, members: List): FoldableItemHeaderLayoutState { - return FoldableItemHeaderLayoutState( - label = "${type.value} $number", - attendMemberCount = members.count { it.attendances[sessionId].status == Attendance.Status.NORMAL }, + private fun toTeamTypeHeaderItem(team: Team, sessionId: Int, members: List): FoldableHeaderItemState.TeamType { + return FoldableHeaderItemState.TeamType( + teamName = team.type.value, + teamNumber = team.number, + isExpanded = currentState.foldableItemStates + .filterIsInstance() + .find { it.teamName == team.type.value && it.teamNumber == team.number }?.headerItem?.isExpanded ?: false, + attendMemberCount = members.count { (it.attendances[sessionId].status != Attendance.Status.ABSENT) }, allTeamMemberCount = members.size ) } - private fun Member.toFoldableItemContentState(sessionId: Int): FoldableItemContentLayoutState { - val attendance = attendances[sessionId].status + private fun toPositionTypeContentItem(member: Member, buttonState: AttendanceTypeButtonState): FoldableContentItemState.PositionType { + return FoldableContentItemState.PositionType( + memberId = member.id, + memberName = member.name, + teamType = member.team.type.value, + teamNumber = member.team.number, + attendanceTypeButtonState = buttonState + ) + } - val buttonState = AttendanceTypeButtonState( + private fun toTeamTypeContentItem(member: Member, buttonState: AttendanceTypeButtonState): FoldableContentItemState.TeamType { + return FoldableContentItemState.TeamType( + memberId = member.id, + memberName = member.name, + position = member.position.value, + attendanceTypeButtonState = buttonState + ) + } + + private fun toButtonState(member: Member, sessionId: Int): AttendanceTypeButtonState { + val attendance = member.attendances[sessionId].status + + return AttendanceTypeButtonState( label = attendance.text, iconType = when (attendance) { Attendance.Status.ABSENT -> AttendanceTypeButtonState.IconType.ABSENT @@ -190,13 +270,6 @@ class ManagementViewModel @Inject constructor( Attendance.Status.NORMAL -> AttendanceTypeButtonState.IconType.ATTEND } ) - - return FoldableItemContentLayoutState( - id = id, - label = name, - subLabel = position.value, - attendanceTypeButtonState = buttonState - ) } } diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/AnimatedCounterText.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/AnimatedCounterText.kt index 558a4b9b..a6501296 100644 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/AnimatedCounterText.kt +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/AnimatedCounterText.kt @@ -14,6 +14,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.TextStyle @@ -23,6 +24,7 @@ internal fun AnimatedCounterText( count: Int, modifier: Modifier = Modifier, style: TextStyle, + color: Color, prefix: @Composable () -> Unit = {}, suffix: @Composable () -> Unit = {}, ) { @@ -55,6 +57,7 @@ internal fun AnimatedCounterText( Text( text = char.toString(), style = style, + color = color, softWrap = false ) diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableContentItem/FoldableItemContentLayoutState.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableContentItem/FoldableItemContentLayoutState.kt deleted file mode 100644 index 4a1da89b..00000000 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableContentItem/FoldableItemContentLayoutState.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.yapp.presentation.ui.admin.management.components.foldableContentItem - -import com.yapp.presentation.ui.admin.management.components.attendanceTypeButton.AttendanceTypeButtonState - - -data class FoldableItemContentLayoutState( - val id: Long, - val label: String, - val subLabel: String, - val attendanceTypeButtonState: AttendanceTypeButtonState, -) \ No newline at end of file diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableHeaderItem/FoldableItemHeaderLayoutState.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableHeaderItem/FoldableItemHeaderLayoutState.kt deleted file mode 100644 index 898e4ea0..00000000 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableHeaderItem/FoldableItemHeaderLayoutState.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.yapp.presentation.ui.admin.management.components.foldableHeaderItem - - -data class FoldableItemHeaderLayoutState( - val label: String = "", - val attendMemberCount: Int = 0, - val allTeamMemberCount: Int = 0, -) \ No newline at end of file diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/FoldableItem.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/FoldableItem.kt new file mode 100644 index 00000000..ed0ac783 --- /dev/null +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/FoldableItem.kt @@ -0,0 +1,7 @@ +package com.yapp.presentation.ui.admin.management.components.foldableItem + +import androidx.compose.runtime.Stable + + +@Stable +interface FoldableItem \ No newline at end of file diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/FoldableItemLayout.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/FoldableItemLayout.kt deleted file mode 100644 index 141bcde8..00000000 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/FoldableItemLayout.kt +++ /dev/null @@ -1,146 +0,0 @@ -package com.yapp.presentation.ui.admin.management.components.foldableItem - -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.Spring -import androidx.compose.animation.core.spring -import androidx.compose.animation.core.tween -import androidx.compose.animation.expandVertically -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.shrinkVertically -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.material.Divider -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import com.yapp.common.theme.AttendanceTheme -import com.yapp.presentation.ui.admin.management.components.attendanceTypeButton.AttendanceTypeButtonState -import com.yapp.presentation.ui.admin.management.components.foldableContentItem.FoldableItemContentLayout -import com.yapp.presentation.ui.admin.management.components.foldableContentItem.FoldableItemContentLayoutState -import com.yapp.presentation.ui.admin.management.components.foldableHeaderItem.FoldableItemHeaderLayout -import com.yapp.presentation.ui.admin.management.components.foldableHeaderItem.FoldableItemHeaderLayoutState -import kotlinx.collections.immutable.persistentListOf - - -@Preview -@Composable -private fun FoldableItemLayoutPreview() { - var state by remember { - mutableStateOf( - FoldableItemLayoutState( - headerState = FoldableItemHeaderLayoutState( - label = "Android 1", - attendMemberCount = 4, - allTeamMemberCount = 6 - ), - contentStates = persistentListOf( - FoldableItemContentLayoutState( - id = 0L, - label = "김철수", - subLabel = "ProducDesign", - attendanceTypeButtonState = AttendanceTypeButtonState() - ), - FoldableItemContentLayoutState( - id = 0L, - label = "", - subLabel = "", - attendanceTypeButtonState = AttendanceTypeButtonState() - ) - ) - ) - ) - } - - AttendanceTheme { - Box(modifier = Modifier) { - FoldableItemLayout( - state = state, - onDropDownClicked = { - - } - ) - } - } -} - -@Composable -internal fun FoldableItemLayout( - modifier: Modifier = Modifier, - state: FoldableItemLayoutState, - onDropDownClicked: ((memberId: Long) -> Unit) -) { - var expanded by rememberSaveable { mutableStateOf(false) } - - Column( - modifier = modifier - .fillMaxWidth() - .wrapContentHeight() - .padding(start = 24.dp, end = 24.dp) - .background(AttendanceTheme.colors.backgroundColors.background) - .animateContentSize( - animationSpec = spring( - dampingRatio = Spring.DampingRatioLowBouncy, - stiffness = Spring.StiffnessLow - ) - ), - horizontalAlignment = Alignment.CenterHorizontally - ) { - - FoldableItemHeaderLayout( - state = state.headerState, - expanded = expanded, - onExpandClicked = { changedState -> expanded = changedState } - ) - - AnimatedVisibility( - visible = expanded, - enter = fadeIn(animationSpec = tween(50)) + - expandVertically( - animationSpec = spring( - dampingRatio = Spring.DampingRatioLowBouncy, - stiffness = Spring.StiffnessLow - ) - ), - exit = fadeOut(animationSpec = tween(50)) + - shrinkVertically( - animationSpec = spring( - dampingRatio = Spring.DampingRatioLowBouncy, - stiffness = Spring.StiffnessLow - ) - ) - ) { - Column { - Divider( - modifier = Modifier.height(1.dp), - color = AttendanceTheme.colors.grayScale.Gray300 - ) - for (contentItem in state.contentStates) { - FoldableItemContentLayout( - state = contentItem, - onDropDownClicked = { clickedMemberId -> - onDropDownClicked.invoke(clickedMemberId) - } - ) - } - Divider( - modifier = Modifier.height(1.dp), - color = AttendanceTheme.colors.grayScale.Gray300 - ) - } - } - } -} \ No newline at end of file diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/FoldableItemLayoutState.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/FoldableItemLayoutState.kt deleted file mode 100644 index 2e7366e5..00000000 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/FoldableItemLayoutState.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.yapp.presentation.ui.admin.management.components.foldableItem - -import androidx.compose.runtime.Stable -import com.yapp.presentation.ui.admin.management.components.foldableContentItem.FoldableItemContentLayoutState -import com.yapp.presentation.ui.admin.management.components.foldableHeaderItem.FoldableItemHeaderLayoutState -import kotlinx.collections.immutable.ImmutableList - - -@Stable -data class FoldableItemLayoutState( - val headerState: FoldableItemHeaderLayoutState, - val contentStates: ImmutableList -) \ No newline at end of file diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/FoldableItemState.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/FoldableItemState.kt new file mode 100644 index 00000000..c6261904 --- /dev/null +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/FoldableItemState.kt @@ -0,0 +1,77 @@ +package com.yapp.presentation.ui.admin.management.components.foldableItem + +import FoldableHeaderItemState +import androidx.compose.runtime.Stable +import com.yapp.presentation.ui.admin.management.components.foldableItem.foldableContentItem.FoldableContentItemState +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList + + +@Stable +interface FoldableItemState { + val headerItem: FoldableHeaderItemState + val contentItems: ImmutableList + + val flatten: ImmutableList + get() { + return buildList { + add(headerItem) + if (headerItem.isExpanded) { + addAll(contentItems) + } + }.toImmutableList() + } + + fun expand(): FoldableItemState + + fun collapse(): FoldableItemState +} + +data class PositionItemState( + override val headerItem: FoldableHeaderItemState.PositionType, + override val contentItems: ImmutableList +) : FoldableItemState { + + val position: String + get() = headerItem.position + + override fun expand(): PositionItemState { + return this.copy( + headerItem = headerItem.copy(isExpanded = true), + contentItems = contentItems + ) + } + + override fun collapse(): PositionItemState { + return this.copy( + headerItem = headerItem.copy(isExpanded = false) + ) + } + +} + +data class TeamItemState( + override val headerItem: FoldableHeaderItemState.TeamType, + override val contentItems: ImmutableList +) : FoldableItemState { + + val teamName: String + get() = headerItem.teamName + + val teamNumber: Int + get() = headerItem.teamNumber + + override fun expand(): TeamItemState { + return this.copy( + headerItem = headerItem.copy(isExpanded = true), + contentItems = contentItems + ) + } + + override fun collapse(): TeamItemState { + return this.copy( + headerItem = headerItem.copy(isExpanded = false) + ) + } + +} \ No newline at end of file diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableContentItem/FoldableItemContentLayout.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/foldableContentItem/FoldableItemContentLayout.kt similarity index 57% rename from presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableContentItem/FoldableItemContentLayout.kt rename to presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/foldableContentItem/FoldableItemContentLayout.kt index c774efee..6752128f 100644 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableContentItem/FoldableItemContentLayout.kt +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/foldableContentItem/FoldableItemContentLayout.kt @@ -1,8 +1,11 @@ -package com.yapp.presentation.ui.admin.management.components.foldableContentItem +package com.yapp.presentation.ui.admin.management.components.foldableItem.foldableContentItem import androidx.compose.animation.animateContentSize +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.tween +import androidx.compose.foundation.background import androidx.compose.foundation.gestures.detectTapGestures -import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth @@ -12,12 +15,14 @@ import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview @@ -33,12 +38,12 @@ import com.yapp.presentation.ui.admin.management.components.attendanceTypeButton @Preview(backgroundColor = 0xFFFFFFFF, showBackground = true) @Composable private fun FoldableItemContentLayoutPreview() { - var state by remember { + var teamState by remember { mutableStateOf( - FoldableItemContentLayoutState( - id = 0L, - label = "장덕철", - subLabel = "Android", + FoldableContentItemState.TeamType( + memberId = 0L, + memberName = "장덕철", + position = "Android", attendanceTypeButtonState = AttendanceTypeButtonState( label = "출석", iconType = AttendanceTypeButtonState.IconType.ATTEND @@ -47,9 +52,32 @@ private fun FoldableItemContentLayoutPreview() { ) } + val positionState by remember { + mutableStateOf( + FoldableContentItemState.PositionType( + memberId = 0L, + memberName = "장덕철", + attendanceTypeButtonState = AttendanceTypeButtonState( + label = "결석", + iconType = AttendanceTypeButtonState.IconType.ABSENT + ), + teamNumber = 1, + teamType = "Android" + ) + ) + } + AttendanceTheme { - Box(modifier = Modifier) { - FoldableItemContentLayout(state = state) + Column(modifier = Modifier) { + FoldableItemContentLayout( + state = teamState, + onDropDownClicked = {} + ) + + FoldableItemContentLayout( + state = positionState, + onDropDownClicked = {} + ) } } } @@ -57,18 +85,51 @@ private fun FoldableItemContentLayoutPreview() { @Composable internal fun FoldableItemContentLayout( modifier: Modifier = Modifier, - state: FoldableItemContentLayoutState, - onDropDownClicked: (memberId: Long) -> Unit = {}, + state: FoldableContentItemState, + onDropDownClicked: (memberId: Long) -> Unit ) { val longPressCallback = LocalMemberItemLongPressCallback.current + FoldableItemContentLayout( + modifier = modifier, + label = state.label, + subLabel = state.subLabel, + attendanceTypeButtonState = state.attendanceTypeButtonState, + onDropDownClicked = { onDropDownClicked(state.memberId) }, + onLongPressed = { longPressCallback?.invoke(state.memberId) } + ) +} + +@Composable +private fun FoldableItemContentLayout( + modifier: Modifier = Modifier, + label: String, + subLabel: String, + attendanceTypeButtonState: AttendanceTypeButtonState, + onDropDownClicked: () -> Unit = {}, + onLongPressed: () -> Unit = {} +) { + var visibilityProgress by remember { mutableStateOf(0F) } + val progressAnimatable = remember { Animatable(0F) } + + LaunchedEffect(key1 = true) { + progressAnimatable.animateTo(1F, animationSpec = tween()) { + visibilityProgress = value + } + } + ConstraintLayout( modifier = modifier .fillMaxWidth() .height(62.dp) + .background(AttendanceTheme.colors.backgroundColors.background) + .graphicsLayer { + alpha = visibilityProgress + scaleY = visibilityProgress + } .pointerInput(Unit) { detectTapGestures(onLongPress = { - longPressCallback?.invoke(state.id) + onLongPressed() }) } ) { @@ -85,7 +146,7 @@ internal fun FoldableItemContentLayout( verticalAlignment = Alignment.CenterVertically ) { Text( - text = state.label, + text = label, textAlign = TextAlign.Start, style = AttendanceTypography.body1, color = AttendanceTheme.colors.grayScale.Gray1000 @@ -94,7 +155,7 @@ internal fun FoldableItemContentLayout( Spacer(modifier = Modifier.width(6.dp)) Text( - text = state.subLabel, + text = subLabel, textAlign = TextAlign.Start, style = AttendanceTypography.caption, color = AttendanceTheme.colors.grayScale.Gray600 @@ -110,8 +171,8 @@ internal fun FoldableItemContentLayout( top.linkTo(parent.top, margin = 13.dp) bottom.linkTo(parent.bottom, margin = 13.dp) }, - state = state.attendanceTypeButtonState, - onClick = { onDropDownClicked.invoke(state.id) } + state = attendanceTypeButtonState, + onClick = { onDropDownClicked.invoke() } ) } diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/foldableContentItem/FoldableItemContentLayoutState.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/foldableContentItem/FoldableItemContentLayoutState.kt new file mode 100644 index 00000000..017ed058 --- /dev/null +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/foldableContentItem/FoldableItemContentLayoutState.kt @@ -0,0 +1,47 @@ +package com.yapp.presentation.ui.admin.management.components.foldableItem.foldableContentItem + +import androidx.compose.runtime.Stable +import com.yapp.presentation.ui.admin.management.components.attendanceTypeButton.AttendanceTypeButtonState +import com.yapp.presentation.ui.admin.management.components.foldableItem.FoldableItem + + +@Stable +interface FoldableContentItemState : FoldableItem { + val memberId: Long + val label: String + val subLabel: String + val attendanceTypeButtonState: AttendanceTypeButtonState + + data class TeamType( + override val attendanceTypeButtonState: AttendanceTypeButtonState, + override val memberId: Long, + val memberName: String, + val position: String + ) : FoldableContentItemState { + + override val label: String + get() = memberName + + override val subLabel: String + get() = position + + } + + data class PositionType( + override val attendanceTypeButtonState: AttendanceTypeButtonState, + override val memberId: Long, + val memberName: String, + val teamType: String, + val teamNumber: Int + ) : FoldableContentItemState { + + override val label: String + get() = memberName + + override val subLabel: String + get() = "$teamType $teamNumber" + + } +} + + diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableHeaderItem/FoldableItemHeaderLayout.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/foldableHeaderItem/FoldableItemHeaderLayout.kt similarity index 54% rename from presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableHeaderItem/FoldableItemHeaderLayout.kt rename to presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/foldableHeaderItem/FoldableItemHeaderLayout.kt index 660f9ecf..6bb08d0a 100644 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableHeaderItem/FoldableItemHeaderLayout.kt +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/foldableHeaderItem/FoldableItemHeaderLayout.kt @@ -1,9 +1,13 @@ -package com.yapp.presentation.ui.admin.management.components.foldableHeaderItem +package com.yapp.presentation.ui.admin.management.components.foldableItem.foldableHeaderItem +import FoldableHeaderItemState +import androidx.compose.animation.animateContentSize +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -15,12 +19,14 @@ import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview @@ -34,28 +40,87 @@ import com.yapp.presentation.ui.admin.management.components.AnimatedCounterText @Preview(backgroundColor = 0xFFFFFFFF, showBackground = true) @Composable private fun FoldableItemHeaderLayoutPreview() { - var state by remember { + val teamState by remember { mutableStateOf( - FoldableItemHeaderLayoutState( - label = "Android 1", - attendMemberCount = 4, - allTeamMemberCount = 6 + FoldableHeaderItemState.TeamType( + attendMemberCount = 6, + allTeamMemberCount = 9, + isExpanded = false, + teamNumber = 1, + teamName = "Android" + ) + ) + } + + val positionState by remember { + mutableStateOf( + FoldableHeaderItemState.PositionType( + attendMemberCount = 6, + allTeamMemberCount = 9, + isExpanded = false, + position = "Android" ) ) } AttendanceTheme { - Box(modifier = Modifier) { - FoldableItemHeaderLayout(state = state) + Column(modifier = Modifier) { + FoldableHeaderItem( + state = teamState, + onExpandClicked = { a, b, c -> + + } + ) + + FoldableHeaderItem( + state = positionState, + onExpandClicked = { a, b -> + + } + ) } } } @Composable -internal fun FoldableItemHeaderLayout( +internal fun FoldableHeaderItem( modifier: Modifier = Modifier, - state: FoldableItemHeaderLayoutState, - expanded: Boolean = false, + state: FoldableHeaderItemState.PositionType, + onExpandClicked: (isExpanded: Boolean, position: String) -> Unit +) { + FoldableHeaderItem( + modifier = modifier, + label = state.label, + attendMemberCount = state.attendMemberCount, + allTeamMemberCount = state.allTeamMemberCount, + expanded = state.isExpanded, + onExpandClicked = { onExpandClicked(it, state.position) } + ) +} + +@Composable +internal fun FoldableHeaderItem( + modifier: Modifier = Modifier, + state: FoldableHeaderItemState.TeamType, + onExpandClicked: (isExpanded: Boolean, teamName: String, teamNumber: Int) -> Unit +) { + FoldableHeaderItem( + modifier = modifier, + label = state.label, + attendMemberCount = state.attendMemberCount, + allTeamMemberCount = state.allTeamMemberCount, + expanded = state.isExpanded, + onExpandClicked = { onExpandClicked(it, state.teamName, state.teamNumber) } + ) +} + +@Composable +internal fun FoldableHeaderItem( + modifier: Modifier = Modifier, + label: String, + attendMemberCount: Int, + allTeamMemberCount: Int, + expanded: Boolean, onExpandClicked: (Boolean) -> Unit = {}, ) { Row( @@ -74,8 +139,7 @@ internal fun FoldableItemHeaderLayout( verticalAlignment = Alignment.CenterVertically ) { Text( - modifier = Modifier, - text = state.label, + text = label, textAlign = TextAlign.Start, style = AttendanceTypography.h3, color = AttendanceTheme.colors.grayScale.Gray1200 @@ -84,8 +148,9 @@ internal fun FoldableItemHeaderLayout( Spacer(modifier = Modifier.width(6.dp)) AnimatedCounterText( - count = state.attendMemberCount, - style = AttendanceTypography.subtitle2.copy(color = AttendanceTheme.colors.mainColors.YappOrange) + count = attendMemberCount, + style = AttendanceTypography.subtitle2, + color = AttendanceTheme.colors.mainColors.YappOrange ) Text( @@ -96,12 +161,12 @@ internal fun FoldableItemHeaderLayout( ) AnimatedCounterText( - count = state.allTeamMemberCount, - style = AttendanceTypography.subtitle2.copy(color = AttendanceTheme.colors.mainColors.YappOrange) + count = allTeamMemberCount, + style = AttendanceTypography.subtitle2, + color = AttendanceTheme.colors.mainColors.YappOrange ) } - IconButton( modifier = Modifier .width(0.dp) diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/foldableHeaderItem/FoldableItemHeaderLayoutState.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/foldableHeaderItem/FoldableItemHeaderLayoutState.kt new file mode 100644 index 00000000..7e2ef0fb --- /dev/null +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/foldableHeaderItem/FoldableItemHeaderLayoutState.kt @@ -0,0 +1,35 @@ +import androidx.compose.runtime.Stable +import com.yapp.presentation.ui.admin.management.components.foldableItem.FoldableItem + +@Stable +interface FoldableHeaderItemState : FoldableItem { + val label: String + val attendMemberCount: Int + val allTeamMemberCount: Int + val isExpanded: Boolean + + data class PositionType( + override val attendMemberCount: Int, + override val allTeamMemberCount: Int, + override val isExpanded: Boolean, + val position: String, + ) : FoldableHeaderItemState { + + override val label: String + get() = position + + } + + data class TeamType( + override val attendMemberCount: Int, + override val allTeamMemberCount: Int, + override val isExpanded: Boolean, + val teamName: String, + val teamNumber: Int + ) : FoldableHeaderItemState { + + override val label: String + get() = "$teamName $teamNumber" + + } +} \ No newline at end of file diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/statisticalTable/StatisticalTableLayout.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/statisticalTable/StatisticalTableLayout.kt index e2852a78..77f143f7 100644 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/statisticalTable/StatisticalTableLayout.kt +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/statisticalTable/StatisticalTableLayout.kt @@ -87,7 +87,8 @@ internal fun StatisticalTableLayout( ) { AnimatedCounterText( count = state.totalCount, - style = AttendanceTypography.body2.copy(color = AttendanceTheme.colors.grayScale.Gray1200), + style = AttendanceTypography.body2, + color = AttendanceTheme.colors.grayScale.Gray1200, suffix = { Text( style = AttendanceTypography.body2, @@ -99,7 +100,8 @@ internal fun StatisticalTableLayout( AnimatedCounterText( count = state.currentAttendCount, - style = AttendanceTypography.subtitle2.copy(color = AttendanceTheme.colors.etcColors.EtcGreen), + style = AttendanceTypography.subtitle2, + color = AttendanceTheme.colors.etcColors.EtcGreen, suffix = { Text( style = AttendanceTypography.subtitle2, @@ -180,7 +182,8 @@ private fun TableItemText( AnimatedCounterText( count = count, - style = AttendanceTypography.subtitle2.copy(color = AttendanceTheme.colors.grayScale.Gray1200), + style = AttendanceTypography.subtitle2, + color = AttendanceTheme.colors.grayScale.Gray1200, suffix = { Text( text = "명", From 41cbe1c70550f2b83380aea48cdb646f0c027998 Mon Sep 17 00:00:00 2001 From: YeongSang Jeon Date: Sun, 16 Jul 2023 00:23:16 +0900 Subject: [PATCH 15/18] =?UTF-8?q?[=20Feature=20]:=20Header=20=ED=95=98?= =?UTF-8?q?=EB=8B=A8=EC=97=90=20Divider=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yapp/presentation/ui/admin/management/Management.kt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/Management.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/Management.kt index e66b316a..5de21618 100644 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/Management.kt +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/Management.kt @@ -13,6 +13,7 @@ import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Divider import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.ModalBottomSheetLayout import androidx.compose.material.ModalBottomSheetValue @@ -31,6 +32,7 @@ import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.Path import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview @@ -243,6 +245,9 @@ internal fun ManagementScreen( onEvent(ManagementEvent.OnTeamTypeHeaderItemClicked(teamName, teamNumber)) } ) + if (itemState.isExpanded) { + Divider(color = AttendanceTheme.colors.grayScale.Gray300, thickness = 1.dp) + } } is FoldableHeaderItemState.PositionType -> { @@ -253,6 +258,9 @@ internal fun ManagementScreen( onEvent(ManagementEvent.OnPositionTypeHeaderItemClicked(positionName = position)) } ) + if (itemState.isExpanded) { + Divider(color = AttendanceTheme.colors.grayScale.Gray300, thickness = 1.dp) + } } is FoldableContentItemState -> { From e2b8a0263d341b9ec145307653f53d1f4e8a354d Mon Sep 17 00:00:00 2001 From: YeongSang Jeon Date: Sun, 16 Jul 2023 01:24:00 +0900 Subject: [PATCH 16/18] =?UTF-8?q?[=20Fix=20]:=20InnerPadding=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=9D=B8=ED=95=B4=20=EC=9E=98=EB=AA=BB=EC=9E=85?= =?UTF-8?q?=EB=A0=A5=EB=90=9C=20AppBar=20Padding=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/ui/admin/management/Management.kt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/Management.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/Management.kt index 5de21618..c74d24a2 100644 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/Management.kt +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/Management.kt @@ -1,10 +1,12 @@ package com.yapp.presentation.ui.admin.management import FoldableHeaderItemState +import android.annotation.SuppressLint import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.calculateEndPadding import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -100,6 +102,7 @@ internal typealias MemberItemLongPressCallback = (memberId: Long) -> Unit internal val LocalMemberItemLongPressCallback = compositionLocalOf(defaultFactory = { null }) +@SuppressLint("UnusedMaterialScaffoldPaddingParameter") @OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class) @ExperimentalMaterialApi @Composable @@ -173,8 +176,7 @@ internal fun ManagementScreen( modifier = Modifier .fillMaxSize() .systemBarsPadding() - .background(AttendanceTheme.colors.backgroundColors.background) - .padding(horizontal = 24.dp), + .background(AttendanceTheme.colors.backgroundColors.background), topBar = { YDSAppBar( modifier = Modifier @@ -185,15 +187,14 @@ internal fun ManagementScreen( ) }, backgroundColor = AttendanceTheme.colors.backgroundColors.backgroundBase - ) { innerPadding -> + ) { _ -> LazyColumn( modifier = Modifier .fillMaxSize() .background(AttendanceTheme.colors.backgroundColors.background) - .padding(innerPadding) + .padding(horizontal = 24.dp) ) { - stickyHeader { Column( modifier = Modifier From 92697db2cd8107079cce4ecd7025415d8f4aafdb Mon Sep 17 00:00:00 2001 From: YeongSang Jeon Date: Sun, 16 Jul 2023 20:52:54 +0900 Subject: [PATCH 17/18] =?UTF-8?q?[=20Fix=20]:=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0=20?= =?UTF-8?q?=EB=B0=8F=20=EC=BD=94=EB=93=9C=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../attendanceTypeButton/AttendanceTypeButton.kt | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceTypeButton/AttendanceTypeButton.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceTypeButton/AttendanceTypeButton.kt index 5c2e1539..31105e46 100644 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceTypeButton/AttendanceTypeButton.kt +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/attendanceTypeButton/AttendanceTypeButton.kt @@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape @@ -22,6 +23,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.yapp.common.R @@ -55,15 +57,13 @@ private fun AttendanceTypeButtonPreview() { internal fun AttendanceTypeButton( modifier: Modifier = Modifier, state: AttendanceTypeButtonState, - onClick: () -> Unit, + onClick: () -> Unit ) { Row( modifier = modifier .clip(RoundedCornerShape(8.dp)) .background(AttendanceTheme.colors.grayScale.Gray200) - .clickable { - onClick.invoke() - } + .clickable(onClick = onClick) .padding(start = 8.dp, end = 12.dp, top = 8.dp, bottom = 8.dp), verticalAlignment = Alignment.CenterVertically, ) { @@ -83,11 +83,8 @@ internal fun AttendanceTypeButton( Crossfade(targetState = state.label, label = "") { Text( text = it, - modifier = Modifier - .height(20.dp) - .align(alignment = Alignment.CenterVertically), style = AttendanceTypography.subtitle2, - color = AttendanceTheme.colors.grayScale.Gray800 + color = AttendanceTheme.colors.grayScale.Gray800, ) } } From 1f6ca9afc7feddb4b8ffe70a7e3875beba50196d Mon Sep 17 00:00:00 2001 From: YeongSang Jeon Date: Sun, 16 Jul 2023 21:04:26 +0900 Subject: [PATCH 18/18] =?UTF-8?q?[=20Fix=20]:=20FoldableItemState=EB=A5=BC?= =?UTF-8?q?=20UI=EA=B4=80=EC=A0=90=EC=9D=B4=20=EC=95=84=EB=8B=8C=20State?= =?UTF-8?q?=EC=9D=98=20=EA=B4=80=EC=A0=90=EC=9C=BC=EB=A1=9C=EC=84=9C=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/management/ManagementViewModel.kt | 26 ++++++------------- .../foldableItem/FoldableItemState.kt | 25 ++++-------------- 2 files changed, 13 insertions(+), 38 deletions(-) diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementViewModel.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementViewModel.kt index d5de4f28..c3292867 100644 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementViewModel.kt +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/ManagementViewModel.kt @@ -111,19 +111,14 @@ class ManagementViewModel @Inject constructor( this.copy( foldableItemStates = currentState.foldableItemStates .filterIsInstance() - .map { itemGroup -> - if (itemGroup.position == event.positionName) { - return@map if (itemGroup.headerItem.isExpanded) { - itemGroup.collapse() - } else { - itemGroup.expand() - } + .map { itemState -> + if (itemState.position == event.positionName) { + return@map itemState.setHeaderItemExpandable(isExpand = itemState.headerItem.isExpanded.not()) } - itemGroup + itemState }.toImmutableList() ) - } } @@ -132,19 +127,14 @@ class ManagementViewModel @Inject constructor( this.copy( foldableItemStates = currentState.foldableItemStates .filterIsInstance() - .map { itemGroup -> - if (itemGroup.teamName == event.teamName && itemGroup.teamNumber == event.teamNumber) { - return@map if (itemGroup.headerItem.isExpanded) { - itemGroup.collapse() - } else { - itemGroup.expand() - } + .map { itemState -> + if (itemState.teamName == event.teamName && itemState.teamNumber == event.teamNumber) { + return@map itemState.setHeaderItemExpandable(isExpand = itemState.headerItem.isExpanded.not()) } - itemGroup + itemState }.toImmutableList() ) - } } } diff --git a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/FoldableItemState.kt b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/FoldableItemState.kt index c6261904..2a931a94 100644 --- a/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/FoldableItemState.kt +++ b/presentation/src/main/java/com/yapp/presentation/ui/admin/management/components/foldableItem/FoldableItemState.kt @@ -22,9 +22,8 @@ interface FoldableItemState { }.toImmutableList() } - fun expand(): FoldableItemState + fun setHeaderItemExpandable(isExpand: Boolean): FoldableItemState - fun collapse(): FoldableItemState } data class PositionItemState( @@ -35,16 +34,9 @@ data class PositionItemState( val position: String get() = headerItem.position - override fun expand(): PositionItemState { + override fun setHeaderItemExpandable(isExpand: Boolean): FoldableItemState { return this.copy( - headerItem = headerItem.copy(isExpanded = true), - contentItems = contentItems - ) - } - - override fun collapse(): PositionItemState { - return this.copy( - headerItem = headerItem.copy(isExpanded = false) + headerItem = headerItem.copy(isExpanded = isExpand) ) } @@ -61,16 +53,9 @@ data class TeamItemState( val teamNumber: Int get() = headerItem.teamNumber - override fun expand(): TeamItemState { - return this.copy( - headerItem = headerItem.copy(isExpanded = true), - contentItems = contentItems - ) - } - - override fun collapse(): TeamItemState { + override fun setHeaderItemExpandable(isExpand: Boolean): FoldableItemState { return this.copy( - headerItem = headerItem.copy(isExpanded = false) + headerItem = headerItem.copy(isExpanded = isExpand) ) }