From 8711287818dd35a0b62528ac75d13e4ad925840b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=ED=98=B8=EC=84=B1?= Date: Fri, 10 May 2024 16:31:25 +0900 Subject: [PATCH 1/8] =?UTF-8?q?feat=20#244:=20=ED=95=99=EC=83=9D=20?= =?UTF-8?q?=EC=95=84=EC=9D=B4=EC=BD=98=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/res/drawable/ic_close_filled.xml | 9 ++ .../student/group/component/MemberIcon.kt | 101 ++++++++++++++++++ .../student/group/create/CreateGroupRoute.kt | 10 +- 3 files changed, 118 insertions(+), 2 deletions(-) create mode 100644 android/core/designsystem/src/main/res/drawable/ic_close_filled.xml create mode 100644 android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/component/MemberIcon.kt diff --git a/android/core/designsystem/src/main/res/drawable/ic_close_filled.xml b/android/core/designsystem/src/main/res/drawable/ic_close_filled.xml new file mode 100644 index 00000000..e1a18e26 --- /dev/null +++ b/android/core/designsystem/src/main/res/drawable/ic_close_filled.xml @@ -0,0 +1,9 @@ + + + diff --git a/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/component/MemberIcon.kt b/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/component/MemberIcon.kt new file mode 100644 index 00000000..5bec7d56 --- /dev/null +++ b/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/component/MemberIcon.kt @@ -0,0 +1,101 @@ +package com.sixkids.student.group.component + +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.size +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import coil.compose.AsyncImage +import com.sixkids.designsystem.R as DesignSystemR + +@Composable +fun MemberIcon( + modifier: Modifier = Modifier, + memberId: Long, + name: String, + photo: String, + onIconClick: (Long) -> Unit, + onRemoveClick: (Long) -> Unit, + showX: Boolean = false, + isActive: Boolean = false +) { + Card( + modifier = modifier + .wrapContentSize() + .background(if (isActive) Color.Transparent else Color.Gray) + .graphicsLayer { + alpha = if (isActive) 1f else 0.5f + }, + colors = CardDefaults.cardColors(containerColor = Color.Transparent), + shape = RoundedCornerShape(8.dp), + onClick = { onIconClick(memberId) } + ) { + Box(modifier.wrapContentSize()) { + + Column( + modifier = modifier.wrapContentSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Box( + modifier = modifier.wrapContentSize(), + ) { +// Image( +// modifier = modifier.align(Alignment.Center), +// painter = painterResource(id = DesignSystemR.drawable.student_boy), +// contentDescription = null +// ) + AsyncImage( + model = photo, + contentDescription = null, + modifier = modifier.size(48.dp) + ) + if (showX) { + Icon( + imageVector = ImageVector.vectorResource(DesignSystemR.drawable.ic_close_filled), + contentDescription = "Close icon", + tint = Color.Red, + modifier = Modifier + .align(Alignment.TopEnd) + .size(24.dp) + .clickable { + onRemoveClick(memberId) + } + ) + } + } + Text(text = name) + } + } + } + + +} + +@Preview(showBackground = true) +@Composable +fun MemberIconPreview() { + MemberIcon( + memberId = 1, + name = "홍길동", + photo = "https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885__480.jpg", + onIconClick = {}, + onRemoveClick = {} + ) +} diff --git a/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/create/CreateGroupRoute.kt b/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/create/CreateGroupRoute.kt index 537a4b75..9ce9c401 100644 --- a/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/create/CreateGroupRoute.kt +++ b/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/create/CreateGroupRoute.kt @@ -1,7 +1,9 @@ package com.sixkids.student.group.create -import androidx.compose.material3.Text +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview @Composable @@ -11,7 +13,11 @@ fun CreateGroupRoute() { @Composable fun CreateGroupScreen() { - Text(text = "Create Group Screen") + Column( + modifier = Modifier.fillMaxSize() + ) { + + } } @Preview(showBackground = true) From 9cd110fad22a172e72bfb1cab118fffcb4617ab3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=ED=98=B8=EC=84=B1?= Date: Sat, 11 May 2024 22:18:29 +0900 Subject: [PATCH 2/8] =?UTF-8?q?feat=20#235=20:=20=EC=B1=8C=EB=A6=B0?= =?UTF-8?q?=EC=A7=80=20=EA=B8=B0=EB=A1=9D=20=EB=84=A4=EB=B9=84=EA=B2=8C?= =?UTF-8?q?=EC=9D=B4=EC=85=98=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/sixkids/feature/navigator/MainNavigator.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/android/feature/navigator/src/main/java/com/sixkids/feature/navigator/MainNavigator.kt b/android/feature/navigator/src/main/java/com/sixkids/feature/navigator/MainNavigator.kt index e8a4334b..8d82690a 100644 --- a/android/feature/navigator/src/main/java/com/sixkids/feature/navigator/MainNavigator.kt +++ b/android/feature/navigator/src/main/java/com/sixkids/feature/navigator/MainNavigator.kt @@ -19,6 +19,7 @@ import com.sixkids.student.board.navigation.navigateStudentHome import com.sixkids.student.main.navigation.navigateJoinOrganization import com.sixkids.student.main.navigation.navigateStudentOrganizationList import com.sixkids.student.main.navigation.navigateStudentProfile +import com.sixkids.student.navigation.navigateStudentChallengeHistory import com.sixkids.student.navigation.navigateStudentGroupCreate import com.sixkids.student.navigation.navigateStudentGroupJoin import com.sixkids.teacher.board.navigation.BoardRoute @@ -83,7 +84,7 @@ class MainNavigator( MainNavigationTab.STUDENT_HOME -> navController.navigateStudentHome(studentNavOptions) MainNavigationTab.STUDENT_BOARD -> {} MainNavigationTab.STUDENT_RELAY -> {} - MainNavigationTab.STUDENT_CHALLENGE -> {} + MainNavigationTab.STUDENT_CHALLENGE -> navController.navigateStudentChallengeHistory(studentNavOptions) } } @@ -279,4 +280,4 @@ internal fun studentTab(): State>{ MainNavigationTab.STUDENT_CHALLENGE ) ) -} \ No newline at end of file +} From a427036bdd23ddd0e3c78c964cd02eafe6cf1907 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=ED=98=B8=EC=84=B1?= Date: Sat, 11 May 2024 22:27:42 +0900 Subject: [PATCH 3/8] =?UTF-8?q?feat=20#244=20:=20=EC=B1=8C=EB=A6=B0?= =?UTF-8?q?=EC=A7=80=20=EC=83=9D=EC=84=B1=20UI=20=EB=B2=A0=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/sixkids/model/GroupType.kt | 6 ++ .../student/group/component/GroupWaiting.kt | 101 ++++++++++++++++++ .../student/group/component/MemberIcon.kt | 24 +++-- .../group/component/MultiLayeredCircles.kt | 40 +++++++ .../student/group/create/CreateGroupRoute.kt | 25 ++++- .../student/group/join/JoinGroupScreen.kt | 17 --- .../challenge/src/main/res/values/strings.xml | 2 + 7 files changed, 186 insertions(+), 29 deletions(-) create mode 100644 android/core/model/src/main/java/com/sixkids/model/GroupType.kt create mode 100644 android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/component/GroupWaiting.kt create mode 100644 android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/component/MultiLayeredCircles.kt diff --git a/android/core/model/src/main/java/com/sixkids/model/GroupType.kt b/android/core/model/src/main/java/com/sixkids/model/GroupType.kt new file mode 100644 index 00000000..92e5131e --- /dev/null +++ b/android/core/model/src/main/java/com/sixkids/model/GroupType.kt @@ -0,0 +1,6 @@ +package com.sixkids.model + +enum class GroupType { + FREE, + DESIGN +} diff --git a/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/component/GroupWaiting.kt b/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/component/GroupWaiting.kt new file mode 100644 index 00000000..6b97c594 --- /dev/null +++ b/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/component/GroupWaiting.kt @@ -0,0 +1,101 @@ +package com.sixkids.student.group.component + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.sixkids.designsystem.component.button.UlbanFilledButton +import com.sixkids.designsystem.theme.Cream +import com.sixkids.designsystem.theme.UlbanTypography +import com.sixkids.student.challenge.R + +@Composable +fun GroupWaiting( + memberList: List = emptyList(), + onDoneClick: () -> Unit = {}, + onRemoveClick: (Long) -> Unit = {}, + donButtonEnable: Boolean = false +) { + Card( + modifier = Modifier + .padding(top = 16.dp) + .fillMaxWidth(), + shape = RoundedCornerShape( + topStart = 12.dp, + topEnd = 12.dp, + bottomStart = 0.dp, + bottomEnd = 0.dp + ), + elevation = CardDefaults.cardElevation(16.dp), + colors = CardDefaults.cardColors( + containerColor = Cream + ) + ) { + Column( + modifier = Modifier.padding(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + Text( + modifier = Modifier.padding(top = 16.dp), + text = "2명의 친구들을 더 모아보세요", + style = UlbanTypography.bodyMedium + ) + LazyRow( + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + items(memberList) { item -> + MemberIcon( + memberId = item.memberId, + name = item.name, + photo = item.photo, + showX = item.showX, + isActive = item.isActive, + onRemoveClick = onRemoveClick + ) + } + } + UlbanFilledButton( + modifier = Modifier.fillMaxWidth(), + text = stringResource(R.string.done), + onClick = onDoneClick, + enabled = donButtonEnable + ) + } + } +} + +data class MemberIconItem( + val memberId: Long, + val name: String, + val photo: String, + val showX: Boolean = false, + val isActive: Boolean = false +) + + +@Preview(showBackground = true) +@Composable +fun GroupWaitingPreview() { + GroupWaiting( + memberList = List(4){ + MemberIconItem( + memberId = it.toLong(), + name = "name$it", + photo = "", + showX = false, + isActive = it % 2 == 0 + ) + } + ) +} diff --git a/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/component/MemberIcon.kt b/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/component/MemberIcon.kt index 5bec7d56..b77420da 100644 --- a/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/component/MemberIcon.kt +++ b/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/component/MemberIcon.kt @@ -5,6 +5,7 @@ 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.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.shape.RoundedCornerShape @@ -22,6 +23,7 @@ import androidx.compose.ui.res.vectorResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import coil.compose.AsyncImage +import com.sixkids.designsystem.theme.UlbanTypography import com.sixkids.designsystem.R as DesignSystemR @Composable @@ -30,20 +32,23 @@ fun MemberIcon( memberId: Long, name: String, photo: String, - onIconClick: (Long) -> Unit, - onRemoveClick: (Long) -> Unit, + onIconClick: (Long) -> Unit = {}, + onRemoveClick: (Long) -> Unit = {}, showX: Boolean = false, isActive: Boolean = false ) { Card( modifier = modifier .wrapContentSize() - .background(if (isActive) Color.Transparent else Color.Gray) + .background( + if (isActive) Color.Transparent else Color.Gray, + shape = RoundedCornerShape(8.dp), + ) .graphicsLayer { alpha = if (isActive) 1f else 0.5f }, colors = CardDefaults.cardColors(containerColor = Color.Transparent), - shape = RoundedCornerShape(8.dp), + onClick = { onIconClick(memberId) } ) { Box(modifier.wrapContentSize()) { @@ -56,11 +61,6 @@ fun MemberIcon( Box( modifier = modifier.wrapContentSize(), ) { -// Image( -// modifier = modifier.align(Alignment.Center), -// painter = painterResource(id = DesignSystemR.drawable.student_boy), -// contentDescription = null -// ) AsyncImage( model = photo, contentDescription = null, @@ -80,7 +80,11 @@ fun MemberIcon( ) } } - Text(text = name) + Text( + text = name, + style = UlbanTypography.bodyMedium, + modifier = Modifier.padding(4.dp) + ) } } } diff --git a/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/component/MultiLayeredCircles.kt b/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/component/MultiLayeredCircles.kt new file mode 100644 index 00000000..2e871ef8 --- /dev/null +++ b/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/component/MultiLayeredCircles.kt @@ -0,0 +1,40 @@ +package com.sixkids.student.group.component + +import androidx.compose.foundation.Canvas +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.sixkids.designsystem.theme.Blue +import com.sixkids.designsystem.theme.Green +import com.sixkids.designsystem.theme.Orange +import com.sixkids.designsystem.theme.Red +import com.sixkids.designsystem.theme.UlbanTheme + +@Composable +fun MultiLayeredCircles() { + Canvas(modifier = Modifier ){ + val strokeWidth = 2.dp.toPx() + val radiusIncrement = 46.dp.toPx() // 각 원의 반지름 증가량 + + val colors = listOf(Red, Orange, Green, Blue) // 원의 색상 리스트 + + for (i in colors.indices) { + drawCircle( + color = colors[i], + radius = radiusIncrement * (i + 1), + style = Stroke(width = strokeWidth) + ) + } + } +} + + +@Preview(showBackground = true) +@Composable +fun MultiLayeredCirclesPreview() { + UlbanTheme { + MultiLayeredCircles() + } +} diff --git a/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/create/CreateGroupRoute.kt b/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/create/CreateGroupRoute.kt index 9ce9c401..98f30780 100644 --- a/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/create/CreateGroupRoute.kt +++ b/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/create/CreateGroupRoute.kt @@ -1,10 +1,20 @@ package com.sixkids.student.group.create import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.sixkids.designsystem.theme.UlbanTheme +import com.sixkids.designsystem.theme.UlbanTypography +import com.sixkids.student.challenge.R +import com.sixkids.student.group.component.GroupWaiting @Composable fun CreateGroupRoute() { @@ -13,15 +23,26 @@ fun CreateGroupRoute() { @Composable fun CreateGroupScreen() { + Column( - modifier = Modifier.fillMaxSize() + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally ) { + Text( + text = stringResource(R.string.invite_friend), + style = UlbanTypography.titleSmall, + modifier = Modifier.padding(top = 32.dp) + ) + Spacer(modifier = Modifier.weight(1f)) + GroupWaiting() } } @Preview(showBackground = true) @Composable fun CreateGroupScreenPreview() { - CreateGroupScreen() + UlbanTheme { + CreateGroupScreen() + } } diff --git a/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/join/JoinGroupScreen.kt b/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/join/JoinGroupScreen.kt index f7ca36ba..5ab0bd88 100644 --- a/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/join/JoinGroupScreen.kt +++ b/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/join/JoinGroupScreen.kt @@ -23,23 +23,6 @@ fun JoinGroupScreen( //fun MemberItemPreview() { // MemberItem() //} -//@Composable -//fun MultiLayeredCircles() { -// Canvas(modifier = Modifier ){ -// val strokeWidth = 2.dp.toPx() -// val radiusIncrement = 46.dp.toPx() // 각 원의 반지름 증가량 -// -// val colors = listOf(Red, Orange, Green, Blue) // 원의 색상 리스트 -// -// for (i in colors.indices) { -// drawCircle( -// color = colors[i], -// radius = radiusIncrement * (i + 1), -// style = Stroke(width = strokeWidth) -// ) -// } -// } -//} @Preview(showBackground = true) diff --git a/android/feature/student/challenge/src/main/res/values/strings.xml b/android/feature/student/challenge/src/main/res/values/strings.xml index c4e21261..d3c4c721 100644 --- a/android/feature/student/challenge/src/main/res/values/strings.xml +++ b/android/feature/student/challenge/src/main/res/values/strings.xml @@ -7,4 +7,6 @@ 취소 그룹\n만들기 그룹\n참여하기 + 완료 + 근처에 있는 친구를 초대해 보세요 From 3e89ae430caf9a63555dc1814763b9fa8d1cd064 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=ED=98=B8=EC=84=B1?= Date: Sat, 11 May 2024 22:38:57 +0900 Subject: [PATCH 4/8] =?UTF-8?q?feat=20#244=20:=20=EC=B1=8C=EB=A6=B0?= =?UTF-8?q?=EC=A7=80=20=EA=B8=B0=EB=B3=B8=20=EC=83=81=ED=83=9C=20=EC=84=B8?= =?UTF-8?q?=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../student/group/create/CreateGroupContract.kt | 12 ++++++++++++ .../student/group/create/CreateGroupRoute.kt | 8 ++++++-- .../student/group/create/CreateGroupViewModel.kt | 9 +++++++++ .../sixkids/student/group/join/JoinGroupContract.kt | 13 +++++++++++++ .../student/group/join/JoinGroupViewModel.kt | 10 ++++++++++ 5 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/create/CreateGroupContract.kt create mode 100644 android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/create/CreateGroupViewModel.kt create mode 100644 android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/join/JoinGroupContract.kt create mode 100644 android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/join/JoinGroupViewModel.kt diff --git a/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/create/CreateGroupContract.kt b/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/create/CreateGroupContract.kt new file mode 100644 index 00000000..da833d90 --- /dev/null +++ b/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/create/CreateGroupContract.kt @@ -0,0 +1,12 @@ +package com.sixkids.student.group.create + +import com.sixkids.ui.base.SideEffect +import com.sixkids.ui.base.UiState + +data class CreateGroupState( + val isLoading: Boolean = false, +): UiState + + +sealed interface CreateGroupEffect: SideEffect { +} diff --git a/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/create/CreateGroupRoute.kt b/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/create/CreateGroupRoute.kt index 98f30780..e549741c 100644 --- a/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/create/CreateGroupRoute.kt +++ b/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/create/CreateGroupRoute.kt @@ -11,18 +11,22 @@ import androidx.compose.ui.Modifier 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 import com.sixkids.designsystem.theme.UlbanTheme import com.sixkids.designsystem.theme.UlbanTypography import com.sixkids.student.challenge.R import com.sixkids.student.group.component.GroupWaiting @Composable -fun CreateGroupRoute() { +fun CreateGroupRoute( + viewModel: CreateGroupViewModel = hiltViewModel(), +) { CreateGroupScreen() } @Composable -fun CreateGroupScreen() { +fun CreateGroupScreen( +) { Column( modifier = Modifier.fillMaxSize(), diff --git a/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/create/CreateGroupViewModel.kt b/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/create/CreateGroupViewModel.kt new file mode 100644 index 00000000..3a8a9a23 --- /dev/null +++ b/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/create/CreateGroupViewModel.kt @@ -0,0 +1,9 @@ +package com.sixkids.student.group.create + +import com.sixkids.ui.base.BaseViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject +@HiltViewModel + +class CreateGroupViewModel @Inject constructor(): BaseViewModel(CreateGroupState()){ +} diff --git a/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/join/JoinGroupContract.kt b/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/join/JoinGroupContract.kt new file mode 100644 index 00000000..894e845d --- /dev/null +++ b/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/join/JoinGroupContract.kt @@ -0,0 +1,13 @@ +package com.sixkids.student.group.join + +import com.sixkids.ui.base.SideEffect +import com.sixkids.ui.base.UiState + + +data class JoinGroupState( + val isLoading: Boolean = false, +) : UiState + +sealed interface +JoinGroupEffect : SideEffect { +} diff --git a/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/join/JoinGroupViewModel.kt b/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/join/JoinGroupViewModel.kt new file mode 100644 index 00000000..fe0b96f3 --- /dev/null +++ b/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/join/JoinGroupViewModel.kt @@ -0,0 +1,10 @@ +package com.sixkids.student.group.join + +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject + +@HiltViewModel +class JoinGroupViewModel@Inject constructor( +){ + +} From e6d96eefcfd91f668d04b143d12893fa1951a17a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=ED=98=B8=EC=84=B1?= Date: Sun, 12 May 2024 00:25:02 +0900 Subject: [PATCH 5/8] =?UTF-8?q?feat=20#244=20:=20=EB=B8=94=EB=A3=A8?= =?UTF-8?q?=ED=88=AC=EC=8A=A4=20=EB=AA=A8=EB=93=88=20hilt=20=EC=9D=98?= =?UTF-8?q?=EC=A1=B4=EC=84=B1=20=EC=A3=BC=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/core/bluetooth/build.gradle.kts | 1 + .../core/bluetooth/BluetoothScanner.kt | 4 +++ .../sixkids/core/bluetooth/BluetoothServer.kt | 4 +-- .../core/bluetooth/di/BluetoothModule.kt | 33 +++++++++++++++++++ .../student/challenge/build.gradle.kts | 1 + 5 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 android/core/bluetooth/src/main/kotlin/com/sixkids/core/bluetooth/di/BluetoothModule.kt diff --git a/android/core/bluetooth/build.gradle.kts b/android/core/bluetooth/build.gradle.kts index b0987bd0..656d03eb 100644 --- a/android/core/bluetooth/build.gradle.kts +++ b/android/core/bluetooth/build.gradle.kts @@ -1,5 +1,6 @@ plugins { alias(libs.plugins.sixkids.android.library) + alias(libs.plugins.sixkids.android.hilt) } android { diff --git a/android/core/bluetooth/src/main/kotlin/com/sixkids/core/bluetooth/BluetoothScanner.kt b/android/core/bluetooth/src/main/kotlin/com/sixkids/core/bluetooth/BluetoothScanner.kt index d2c278c9..278b9ae0 100644 --- a/android/core/bluetooth/src/main/kotlin/com/sixkids/core/bluetooth/BluetoothScanner.kt +++ b/android/core/bluetooth/src/main/kotlin/com/sixkids/core/bluetooth/BluetoothScanner.kt @@ -56,4 +56,8 @@ class BluetoothScanner(context: Context) { isScanning.value = false } + fun removeDevice(deviceName: String) { + foundDevices.update { it - deviceName } + } + } diff --git a/android/core/bluetooth/src/main/kotlin/com/sixkids/core/bluetooth/BluetoothServer.kt b/android/core/bluetooth/src/main/kotlin/com/sixkids/core/bluetooth/BluetoothServer.kt index ee5f0b9b..41e04f2e 100644 --- a/android/core/bluetooth/src/main/kotlin/com/sixkids/core/bluetooth/BluetoothServer.kt +++ b/android/core/bluetooth/src/main/kotlin/com/sixkids/core/bluetooth/BluetoothServer.kt @@ -11,7 +11,7 @@ import androidx.annotation.RequiresPermission import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine -class BluetoothServer(context: Context, private val memberId: Long) { +class BluetoothServer(context: Context) { private val bluetooth = context.getSystemService(Context.BLUETOOTH_SERVICE) as? BluetoothManager ?: throw Exception("This device doesn't support Bluetooth") @@ -19,7 +19,7 @@ class BluetoothServer(context: Context, private val memberId: Long) { @RequiresPermission(allOf = [Manifest.permission.BLUETOOTH_ADVERTISE, Manifest.permission.BLUETOOTH_CONNECT]) - suspend fun startAdvertising() { + suspend fun startAdvertising(memberId: Long) { val advertiser: BluetoothLeAdvertiser = bluetooth.adapter.bluetoothLeAdvertiser ?: throw Exception("This device doesn't support Bluetooth advertising") diff --git a/android/core/bluetooth/src/main/kotlin/com/sixkids/core/bluetooth/di/BluetoothModule.kt b/android/core/bluetooth/src/main/kotlin/com/sixkids/core/bluetooth/di/BluetoothModule.kt new file mode 100644 index 00000000..62fc7936 --- /dev/null +++ b/android/core/bluetooth/src/main/kotlin/com/sixkids/core/bluetooth/di/BluetoothModule.kt @@ -0,0 +1,33 @@ +package com.sixkids.core.bluetooth.di + +import android.content.Context +import com.sixkids.core.bluetooth.BluetoothScanner +import com.sixkids.core.bluetooth.BluetoothServer +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object BluetoothModule { + + @Provides + @Singleton + fun provideBluetoothScanner( + @ApplicationContext context: Context + ): BluetoothScanner { + return BluetoothScanner(context) + } + + @Provides + @Singleton + fun provideBluetoothServer( + @ApplicationContext context: Context + ): BluetoothServer { + return BluetoothServer(context) + } + +} diff --git a/android/feature/student/challenge/build.gradle.kts b/android/feature/student/challenge/build.gradle.kts index 1bd0a6e4..9a8795b3 100644 --- a/android/feature/student/challenge/build.gradle.kts +++ b/android/feature/student/challenge/build.gradle.kts @@ -8,4 +8,5 @@ android { dependencies { implementation(libs.bundles.paging) + implementation(projects.core.bluetooth) } From 77c6bf4f61aa12643d716e83033d2bea33e33040 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=ED=98=B8=EC=84=B1?= Date: Sun, 12 May 2024 00:25:49 +0900 Subject: [PATCH 6/8] =?UTF-8?q?feat=20#244=20:=20=EB=B8=94=EB=A3=A8?= =?UTF-8?q?=ED=88=AC=EC=8A=A4=20=EB=AA=A8=EB=93=88=20hilt=20=EC=9D=98?= =?UTF-8?q?=EC=A1=B4=EC=84=B1=20=EC=A3=BC=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../challenge/src/main/AndroidManifest.xml | 3 +- .../student/group/component/GroupWaiting.kt | 11 +-- .../student/group/component/MemberIcon.kt | 25 +++---- .../group/create/CreateGroupContract.kt | 11 ++- .../student/group/create/CreateGroupRoute.kt | 54 ++++++++++++++- .../group/create/CreateGroupViewModel.kt | 67 ++++++++++++++++++- .../student/group/join/JoinGroupViewModel.kt | 6 +- 7 files changed, 152 insertions(+), 25 deletions(-) diff --git a/android/feature/student/challenge/src/main/AndroidManifest.xml b/android/feature/student/challenge/src/main/AndroidManifest.xml index a5918e68..791256fd 100644 --- a/android/feature/student/challenge/src/main/AndroidManifest.xml +++ b/android/feature/student/challenge/src/main/AndroidManifest.xml @@ -1,4 +1,5 @@ - \ No newline at end of file + + diff --git a/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/component/GroupWaiting.kt b/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/component/GroupWaiting.kt index 6b97c594..62f38264 100644 --- a/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/component/GroupWaiting.kt +++ b/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/component/GroupWaiting.kt @@ -18,6 +18,7 @@ import androidx.compose.ui.unit.dp import com.sixkids.designsystem.component.button.UlbanFilledButton import com.sixkids.designsystem.theme.Cream import com.sixkids.designsystem.theme.UlbanTypography +import com.sixkids.model.MemberSimple import com.sixkids.student.challenge.R @Composable @@ -56,9 +57,11 @@ fun GroupWaiting( ) { items(memberList) { item -> MemberIcon( - memberId = item.memberId, - name = item.name, - photo = item.photo, + member = MemberSimple( + id = item.memberId, + name = item.name, + photo = item.photo + ), showX = item.showX, isActive = item.isActive, onRemoveClick = onRemoveClick @@ -88,7 +91,7 @@ data class MemberIconItem( @Composable fun GroupWaitingPreview() { GroupWaiting( - memberList = List(4){ + memberList = List(4) { MemberIconItem( memberId = it.toLong(), name = "name$it", diff --git a/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/component/MemberIcon.kt b/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/component/MemberIcon.kt index b77420da..40148f76 100644 --- a/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/component/MemberIcon.kt +++ b/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/component/MemberIcon.kt @@ -24,18 +24,17 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import coil.compose.AsyncImage import com.sixkids.designsystem.theme.UlbanTypography +import com.sixkids.model.MemberSimple import com.sixkids.designsystem.R as DesignSystemR @Composable fun MemberIcon( modifier: Modifier = Modifier, - memberId: Long, - name: String, - photo: String, - onIconClick: (Long) -> Unit = {}, + member: MemberSimple, + onIconClick: (MemberSimple) -> Unit = {}, onRemoveClick: (Long) -> Unit = {}, showX: Boolean = false, - isActive: Boolean = false + isActive: Boolean = true ) { Card( modifier = modifier @@ -49,7 +48,7 @@ fun MemberIcon( }, colors = CardDefaults.cardColors(containerColor = Color.Transparent), - onClick = { onIconClick(memberId) } + onClick = { onIconClick(member) } ) { Box(modifier.wrapContentSize()) { @@ -62,7 +61,7 @@ fun MemberIcon( modifier = modifier.wrapContentSize(), ) { AsyncImage( - model = photo, + model = member.photo, contentDescription = null, modifier = modifier.size(48.dp) ) @@ -75,13 +74,13 @@ fun MemberIcon( .align(Alignment.TopEnd) .size(24.dp) .clickable { - onRemoveClick(memberId) + onRemoveClick(member.id) } ) } } Text( - text = name, + text = member.name, style = UlbanTypography.bodyMedium, modifier = Modifier.padding(4.dp) ) @@ -96,9 +95,11 @@ fun MemberIcon( @Composable fun MemberIconPreview() { MemberIcon( - memberId = 1, - name = "홍길동", - photo = "https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885__480.jpg", + member = MemberSimple( + id = 1, + name = "홍길동", + photo = "https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885__480.jpg" + ), onIconClick = {}, onRemoveClick = {} ) diff --git a/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/create/CreateGroupContract.kt b/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/create/CreateGroupContract.kt index da833d90..a5ed704f 100644 --- a/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/create/CreateGroupContract.kt +++ b/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/create/CreateGroupContract.kt @@ -1,12 +1,19 @@ package com.sixkids.student.group.create +import com.sixkids.model.MemberSimple import com.sixkids.ui.base.SideEffect import com.sixkids.ui.base.UiState data class CreateGroupState( val isLoading: Boolean = false, -): UiState + val isScanning: Boolean = false, + val foundMembers: List = emptyList(), + val selectedMembers: List = emptyList(), +) : UiState -sealed interface CreateGroupEffect: SideEffect { +sealed interface CreateGroupEffect : SideEffect { + data object FriendScanStart : CreateGroupEffect + data object FriendScanStop : CreateGroupEffect + } diff --git a/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/create/CreateGroupRoute.kt b/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/create/CreateGroupRoute.kt index e549741c..d085b7a0 100644 --- a/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/create/CreateGroupRoute.kt +++ b/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/create/CreateGroupRoute.kt @@ -4,28 +4,54 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.sixkids.designsystem.theme.UlbanTheme import com.sixkids.designsystem.theme.UlbanTypography +import com.sixkids.model.MemberSimple import com.sixkids.student.challenge.R import com.sixkids.student.group.component.GroupWaiting +import com.sixkids.student.group.component.MemberIcon +import com.sixkids.student.group.component.MemberIconItem +import com.sixkids.ui.extension.collectWithLifecycle @Composable fun CreateGroupRoute( viewModel: CreateGroupViewModel = hiltViewModel(), ) { - CreateGroupScreen() + val uiState by viewModel.uiState.collectAsStateWithLifecycle() + + LaunchedEffect(Unit) { + viewModel.startScan() + } + + + viewModel.sideEffect.collectWithLifecycle { + + } + CreateGroupScreen( + uiState = uiState, + onMemberSelect = viewModel::selectMember, + onMemberRemove = viewModel::removeMember + ) } @Composable fun CreateGroupScreen( + uiState: CreateGroupState = CreateGroupState(), + onMemberSelect: (MemberSimple) -> Unit = { }, + onMemberRemove: (Long) -> Unit = { } ) { Column( @@ -39,7 +65,31 @@ fun CreateGroupScreen( ) Spacer(modifier = Modifier.weight(1f)) - GroupWaiting() + if (uiState.foundMembers.isNotEmpty()) { + LazyColumn { + items(uiState.foundMembers) { member -> + MemberIcon( + member = member, + onIconClick = { onMemberSelect(member) }, + ) + } + } + } + + GroupWaiting( + memberList = uiState.selectedMembers.map { + MemberIconItem( + memberId = it.id, + name = it.name, + photo = it.photo, + showX = true, + isActive = true + ) + }, + onRemoveClick = { + onMemberRemove(it) + } + ) } } diff --git a/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/create/CreateGroupViewModel.kt b/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/create/CreateGroupViewModel.kt index 3a8a9a23..21624884 100644 --- a/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/create/CreateGroupViewModel.kt +++ b/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/create/CreateGroupViewModel.kt @@ -1,9 +1,74 @@ package com.sixkids.student.group.create +import android.Manifest +import androidx.annotation.RequiresPermission +import androidx.lifecycle.viewModelScope +import com.sixkids.core.bluetooth.BluetoothScanner +import com.sixkids.model.MemberSimple import com.sixkids.ui.base.BaseViewModel import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch import javax.inject.Inject + @HiltViewModel -class CreateGroupViewModel @Inject constructor(): BaseViewModel(CreateGroupState()){ +class CreateGroupViewModel @Inject constructor( + private val bluetoothScanner: BluetoothScanner, +) : BaseViewModel(CreateGroupState()) { + + @RequiresPermission(Manifest.permission.BLUETOOTH_SCAN) + fun startScan() { + bluetoothScanner.startScanning() + viewModelScope.launch { + bluetoothScanner.foundDevices.collect { devices -> + if (devices.isEmpty()) return@collect + val (name, id) = devices.first().split("-") + if (uiState.value.selectedMembers.none { it.id == id.toLong() }) { + intent { + copy( + foundMembers = foundMembers.toMutableList().apply { + add( + //TODO: 사용자 정보 받아오는 API 구현 필 + MemberSimple( + id = id.toLong(), + name = name, + photo = "https://static01.nyt.com/images/2021/09/14/science/07CAT-STRIPES/07CAT-STRIPES-jumbo.jpg?quality=75&auto=webp" + ) + ) + } + ) + } + } + } + } + } + + @RequiresPermission(Manifest.permission.BLUETOOTH_SCAN) + fun stopScan() { + bluetoothScanner.stopScanning() + } + + fun selectMember(member: MemberSimple) { + intent { + copy( + foundMembers = foundMembers.toMutableList().apply { + remove(member) + }, + selectedMembers = selectedMembers.toMutableList().apply { + add(member) + } + ) + } + } + + fun removeMember(memberId: Long) { + intent { + bluetoothScanner.removeDevice("sixkids-${memberId}") + copy( + selectedMembers = selectedMembers.toMutableList().apply { + removeIf { it.id == memberId } + } + ) + } + } } diff --git a/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/join/JoinGroupViewModel.kt b/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/join/JoinGroupViewModel.kt index fe0b96f3..77aaa2b4 100644 --- a/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/join/JoinGroupViewModel.kt +++ b/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/join/JoinGroupViewModel.kt @@ -1,10 +1,10 @@ package com.sixkids.student.group.join +import com.sixkids.ui.base.BaseViewModel import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject @HiltViewModel -class JoinGroupViewModel@Inject constructor( -){ - +class JoinGroupViewModel @Inject constructor( +) : BaseViewModel(JoinGroupState()) { } From 8567ff66fc7b7ce0bbe87e111291fa89d5a055d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=ED=98=B8=EC=84=B1?= Date: Sun, 12 May 2024 00:58:45 +0900 Subject: [PATCH 7/8] =?UTF-8?q?feat=20#244=20:=20membersimpel=20=EC=9A=94?= =?UTF-8?q?=EC=B2=AD=20api=20=EA=B5=AC=ED=98=84=20=EB=B0=8F=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/sixkids/data/api/MemberService.kt | 7 +++++ .../repository/user/UserRepositoryImpl.kt | 5 ++++ .../user/remote/UserRemoteDataSource.kt | 3 ++ .../user/remote/UserRemoteDataSourceImpl.kt | 4 +++ .../domain/repository/UserRepository.kt | 3 ++ .../usecase/user/GetMemberSimpleUseCase.kt | 12 ++++++++ .../group/create/CreateGroupViewModel.kt | 30 +++++++++---------- 7 files changed, 48 insertions(+), 16 deletions(-) create mode 100644 android/domain/src/main/java/com/sixkids/domain/usecase/user/GetMemberSimpleUseCase.kt diff --git a/android/data/src/main/java/com/sixkids/data/api/MemberService.kt b/android/data/src/main/java/com/sixkids/data/api/MemberService.kt index d13f9b18..9558279a 100644 --- a/android/data/src/main/java/com/sixkids/data/api/MemberService.kt +++ b/android/data/src/main/java/com/sixkids/data/api/MemberService.kt @@ -3,6 +3,7 @@ package com.sixkids.data.api import com.sixkids.data.model.request.FcmRequest import com.sixkids.data.model.response.ApiResponse import com.sixkids.data.model.response.MemberInfoResponse +import com.sixkids.data.model.response.MemberSimpleInfoResponse import com.sixkids.data.model.response.SignInResponse import com.sixkids.data.model.response.UpdateProfilePhotoResponse import com.sixkids.data.network.ApiResult @@ -15,11 +16,17 @@ import retrofit2.http.PATCH import retrofit2.http.POST import retrofit2.http.Part import retrofit2.http.PartMap +import retrofit2.http.Path interface MemberService { @GET("members/") suspend fun getMemberInfo(): ApiResult> + @GET("members/{id}") + suspend fun getMemberInfoById( + @Path("id") id: Long + ): ApiResult> + @Multipart @PATCH("members/photo") suspend fun updateMemberProfilePhoto( diff --git a/android/data/src/main/java/com/sixkids/data/repository/user/UserRepositoryImpl.kt b/android/data/src/main/java/com/sixkids/data/repository/user/UserRepositoryImpl.kt index 73f3eb68..0c499f58 100644 --- a/android/data/src/main/java/com/sixkids/data/repository/user/UserRepositoryImpl.kt +++ b/android/data/src/main/java/com/sixkids/data/repository/user/UserRepositoryImpl.kt @@ -5,6 +5,7 @@ import com.sixkids.data.repository.user.remote.UserRemoteDataSource import com.sixkids.domain.repository.TokenRepository import com.sixkids.domain.repository.UserRepository import com.sixkids.model.JwtToken +import com.sixkids.model.MemberSimple import com.sixkids.model.UserInfo import java.io.File import javax.inject.Inject @@ -56,6 +57,10 @@ class UserRepositoryImpl @Inject constructor( return userRemoteDataSource.getMemberInfo() } + override suspend fun getMemberSimpleInfo(id: Long): MemberSimple { + return userRemoteDataSource.getMemberSimple(id) + } + override suspend fun updateMemberProfilePhoto(file: File?, defaultImage: Int): String { return userRemoteDataSource.updateMemberProfilePhoto(file, defaultImage) } diff --git a/android/data/src/main/java/com/sixkids/data/repository/user/remote/UserRemoteDataSource.kt b/android/data/src/main/java/com/sixkids/data/repository/user/remote/UserRemoteDataSource.kt index f09780ca..44796ec6 100644 --- a/android/data/src/main/java/com/sixkids/data/repository/user/remote/UserRemoteDataSource.kt +++ b/android/data/src/main/java/com/sixkids/data/repository/user/remote/UserRemoteDataSource.kt @@ -1,6 +1,7 @@ package com.sixkids.data.repository.user.remote import com.sixkids.model.JwtToken +import com.sixkids.model.MemberSimple import com.sixkids.model.UserInfo import java.io.File @@ -11,6 +12,8 @@ interface UserRemoteDataSource { suspend fun getMemberInfo(): UserInfo + suspend fun getMemberSimple(id: Long): MemberSimple + suspend fun updateMemberProfilePhoto(file: File?, defaultImage: Int): String suspend fun autoSignIn(): JwtToken diff --git a/android/data/src/main/java/com/sixkids/data/repository/user/remote/UserRemoteDataSourceImpl.kt b/android/data/src/main/java/com/sixkids/data/repository/user/remote/UserRemoteDataSourceImpl.kt index b6219ed6..0b4aacd2 100644 --- a/android/data/src/main/java/com/sixkids/data/repository/user/remote/UserRemoteDataSourceImpl.kt +++ b/android/data/src/main/java/com/sixkids/data/repository/user/remote/UserRemoteDataSourceImpl.kt @@ -7,6 +7,7 @@ import com.sixkids.data.model.request.SignInRequest import com.sixkids.data.model.response.toModel import com.sixkids.data.repository.user.local.UserLocalDataSource import com.sixkids.model.JwtToken +import com.sixkids.model.MemberSimple import com.sixkids.model.UserInfo import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MultipartBody @@ -75,6 +76,9 @@ class UserRemoteDataSourceImpl @Inject constructor( return response.getOrThrow().data.toModel() } + override suspend fun getMemberSimple(id: Long): MemberSimple = + memberService.getMemberInfoById(id).getOrThrow().data.toModel() + override suspend fun updateMemberProfilePhoto(file: File?, defaultImage: Int): String { val data = HashMap() diff --git a/android/domain/src/main/java/com/sixkids/domain/repository/UserRepository.kt b/android/domain/src/main/java/com/sixkids/domain/repository/UserRepository.kt index 18635e92..864af6fb 100644 --- a/android/domain/src/main/java/com/sixkids/domain/repository/UserRepository.kt +++ b/android/domain/src/main/java/com/sixkids/domain/repository/UserRepository.kt @@ -1,6 +1,7 @@ package com.sixkids.domain.repository import com.sixkids.model.JwtToken +import com.sixkids.model.MemberSimple import com.sixkids.model.UserInfo import java.io.File @@ -13,6 +14,8 @@ interface UserRepository { suspend fun getMemberInfo(): UserInfo + suspend fun getMemberSimpleInfo(id: Long): MemberSimple + suspend fun updateMemberProfilePhoto(file: File?, defaultImage: Int): String suspend fun signOut() : Boolean diff --git a/android/domain/src/main/java/com/sixkids/domain/usecase/user/GetMemberSimpleUseCase.kt b/android/domain/src/main/java/com/sixkids/domain/usecase/user/GetMemberSimpleUseCase.kt new file mode 100644 index 00000000..f6fa833b --- /dev/null +++ b/android/domain/src/main/java/com/sixkids/domain/usecase/user/GetMemberSimpleUseCase.kt @@ -0,0 +1,12 @@ +package com.sixkids.domain.usecase.user + +import com.sixkids.domain.repository.UserRepository +import javax.inject.Inject + +class GetMemberSimpleUseCase @Inject constructor( + private val userRepository: UserRepository +){ + suspend operator fun invoke(id: Long) = runCatching { + userRepository.getMemberSimpleInfo(id) + } +} diff --git a/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/create/CreateGroupViewModel.kt b/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/create/CreateGroupViewModel.kt index 21624884..0d1af514 100644 --- a/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/create/CreateGroupViewModel.kt +++ b/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/create/CreateGroupViewModel.kt @@ -4,6 +4,7 @@ import android.Manifest import androidx.annotation.RequiresPermission import androidx.lifecycle.viewModelScope import com.sixkids.core.bluetooth.BluetoothScanner +import com.sixkids.domain.usecase.user.GetMemberSimpleUseCase import com.sixkids.model.MemberSimple import com.sixkids.ui.base.BaseViewModel import dagger.hilt.android.lifecycle.HiltViewModel @@ -14,6 +15,7 @@ import javax.inject.Inject class CreateGroupViewModel @Inject constructor( private val bluetoothScanner: BluetoothScanner, + private val getMemberSimpleUseCase: GetMemberSimpleUseCase, ) : BaseViewModel(CreateGroupState()) { @RequiresPermission(Manifest.permission.BLUETOOTH_SCAN) @@ -22,22 +24,18 @@ class CreateGroupViewModel @Inject constructor( viewModelScope.launch { bluetoothScanner.foundDevices.collect { devices -> if (devices.isEmpty()) return@collect - val (name, id) = devices.first().split("-") - if (uiState.value.selectedMembers.none { it.id == id.toLong() }) { - intent { - copy( - foundMembers = foundMembers.toMutableList().apply { - add( - //TODO: 사용자 정보 받아오는 API 구현 필 - MemberSimple( - id = id.toLong(), - name = name, - photo = "https://static01.nyt.com/images/2021/09/14/science/07CAT-STRIPES/07CAT-STRIPES-jumbo.jpg?quality=75&auto=webp" - ) - ) - } - ) - } +// val id = devices.last().split("-").last() + val id = 9L + if (uiState.value.selectedMembers.none { it.id == id }) { + getMemberSimpleUseCase(id).onSuccess { member -> + intent { + copy( + foundMembers = foundMembers.toMutableList().apply { + add(member) + } + ) + } + }.onFailure { } } } } From fc9b5dfbc5f177bf4e50d464b95fc84e3550847b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=ED=98=B8=EC=84=B1?= Date: Sun, 12 May 2024 02:06:41 +0900 Subject: [PATCH 8/8] =?UTF-8?q?feat=20#244=20:=20=EB=A6=AC=EB=8D=94=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=20=EC=A0=84=EB=8B=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/sixkids/model/MemberSimple.kt | 6 ++-- .../student/group/component/GroupWaiting.kt | 30 ++++++++++++++++--- .../group/create/CreateGroupContract.kt | 6 ++++ .../student/group/create/CreateGroupRoute.kt | 10 +++++++ .../group/create/CreateGroupViewModel.kt | 21 ++++++++++++- .../challenge/src/main/res/values/strings.xml | 2 ++ 6 files changed, 67 insertions(+), 8 deletions(-) diff --git a/android/core/model/src/main/java/com/sixkids/model/MemberSimple.kt b/android/core/model/src/main/java/com/sixkids/model/MemberSimple.kt index 017f2070..4e9dad58 100644 --- a/android/core/model/src/main/java/com/sixkids/model/MemberSimple.kt +++ b/android/core/model/src/main/java/com/sixkids/model/MemberSimple.kt @@ -1,7 +1,7 @@ package com.sixkids.model data class MemberSimple( - val id: Long, - val name: String, - val photo: String + val id: Long = 0L, + val name: String = "", + val photo: String = "", ) diff --git a/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/component/GroupWaiting.kt b/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/component/GroupWaiting.kt index 62f38264..1f37648c 100644 --- a/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/component/GroupWaiting.kt +++ b/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/component/GroupWaiting.kt @@ -23,10 +23,11 @@ import com.sixkids.student.challenge.R @Composable fun GroupWaiting( + leader: MemberSimple = MemberSimple(), memberList: List = emptyList(), onDoneClick: () -> Unit = {}, onRemoveClick: (Long) -> Unit = {}, - donButtonEnable: Boolean = false + groupSize: Int = 0 ) { Card( modifier = Modifier @@ -43,18 +44,33 @@ fun GroupWaiting( containerColor = Cream ) ) { + val remainingMember = groupSize - memberList.size - 1 Column( modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp) ) { Text( modifier = Modifier.padding(top = 16.dp), - text = "2명의 친구들을 더 모아보세요", + text = if(remainingMember != 0) { + stringResource(R.string.friend_waiting_message, remainingMember) + } else { + stringResource(R.string.can_create_group) + }, style = UlbanTypography.bodyMedium ) LazyRow( horizontalArrangement = Arrangement.spacedBy(8.dp) ) { + if(leader.id != 0L) { + item { + MemberIcon( + member = leader, + showX = false, + isActive = true, + onRemoveClick = {} + ) + } + } items(memberList) { item -> MemberIcon( member = MemberSimple( @@ -72,7 +88,7 @@ fun GroupWaiting( modifier = Modifier.fillMaxWidth(), text = stringResource(R.string.done), onClick = onDoneClick, - enabled = donButtonEnable + enabled = remainingMember == 0 ) } } @@ -91,6 +107,11 @@ data class MemberIconItem( @Composable fun GroupWaitingPreview() { GroupWaiting( + leader = MemberSimple( + id = 1, + name = "leader", + photo = "" + ), memberList = List(4) { MemberIconItem( memberId = it.toLong(), @@ -99,6 +120,7 @@ fun GroupWaitingPreview() { showX = false, isActive = it % 2 == 0 ) - } + }, + groupSize = 5 ) } diff --git a/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/create/CreateGroupContract.kt b/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/create/CreateGroupContract.kt index a5ed704f..7fcf625a 100644 --- a/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/create/CreateGroupContract.kt +++ b/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/create/CreateGroupContract.kt @@ -8,6 +8,8 @@ data class CreateGroupState( val isLoading: Boolean = false, val isScanning: Boolean = false, val foundMembers: List = emptyList(), + val groupSize: Int = 0, + val leader: MemberSimple = MemberSimple(), val selectedMembers: List = emptyList(), ) : UiState @@ -15,5 +17,9 @@ data class CreateGroupState( sealed interface CreateGroupEffect : SideEffect { data object FriendScanStart : CreateGroupEffect data object FriendScanStop : CreateGroupEffect + data class HandleException( + val throwable: Throwable, + val retryAction: () -> Unit + ) : CreateGroupEffect } diff --git a/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/create/CreateGroupRoute.kt b/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/create/CreateGroupRoute.kt index d085b7a0..78829ff3 100644 --- a/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/create/CreateGroupRoute.kt +++ b/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/create/CreateGroupRoute.kt @@ -8,6 +8,7 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment @@ -33,9 +34,16 @@ fun CreateGroupRoute( val uiState by viewModel.uiState.collectAsStateWithLifecycle() LaunchedEffect(Unit) { + viewModel.loadUserInfo() viewModel.startScan() } + DisposableEffect(Unit) { + onDispose { + viewModel.stopScan() + } + } + viewModel.sideEffect.collectWithLifecycle { @@ -77,6 +85,8 @@ fun CreateGroupScreen( } GroupWaiting( + groupSize = uiState.groupSize, + leader = uiState.leader, memberList = uiState.selectedMembers.map { MemberIconItem( memberId = it.id, diff --git a/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/create/CreateGroupViewModel.kt b/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/create/CreateGroupViewModel.kt index 0d1af514..cb94b77c 100644 --- a/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/create/CreateGroupViewModel.kt +++ b/android/feature/student/challenge/src/main/kotlin/com/sixkids/student/group/create/CreateGroupViewModel.kt @@ -5,6 +5,7 @@ import androidx.annotation.RequiresPermission import androidx.lifecycle.viewModelScope import com.sixkids.core.bluetooth.BluetoothScanner import com.sixkids.domain.usecase.user.GetMemberSimpleUseCase +import com.sixkids.domain.usecase.user.LoadUserInfoUseCase import com.sixkids.model.MemberSimple import com.sixkids.ui.base.BaseViewModel import dagger.hilt.android.lifecycle.HiltViewModel @@ -16,7 +17,25 @@ import javax.inject.Inject class CreateGroupViewModel @Inject constructor( private val bluetoothScanner: BluetoothScanner, private val getMemberSimpleUseCase: GetMemberSimpleUseCase, + private val loadUserInfoUseCase: LoadUserInfoUseCase, ) : BaseViewModel(CreateGroupState()) { + fun loadUserInfo() { + viewModelScope.launch { + loadUserInfoUseCase().onSuccess { member -> + intent { + copy( + leader = MemberSimple( + id = member.id.toLong(), + name = member.name, + photo = member.photo + ) + ) + } + }.onFailure { + postSideEffect(CreateGroupEffect.HandleException(it, ::loadUserInfo)) + } + } + } @RequiresPermission(Manifest.permission.BLUETOOTH_SCAN) fun startScan() { @@ -35,7 +54,7 @@ class CreateGroupViewModel @Inject constructor( } ) } - }.onFailure { } + }.onFailure { } } } } diff --git a/android/feature/student/challenge/src/main/res/values/strings.xml b/android/feature/student/challenge/src/main/res/values/strings.xml index d3c4c721..7168888e 100644 --- a/android/feature/student/challenge/src/main/res/values/strings.xml +++ b/android/feature/student/challenge/src/main/res/values/strings.xml @@ -9,4 +9,6 @@ 그룹\n참여하기 완료 근처에 있는 친구를 초대해 보세요 + %d명의 친구들을 더 모아보세요 + 그룹을 만들 수 있어요