diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml index 5a5e615c..b268ef36 100644 --- a/.idea/deploymentTargetSelector.xml +++ b/.idea/deploymentTargetSelector.xml @@ -2,7 +2,7 @@ - + diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a776656f..b679eeb7 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,3 +1,5 @@ +import org.gradle.kotlin.dsl.implementation + plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) @@ -50,6 +52,9 @@ dependencies { implementation(libs.androidx.ui.tooling.preview) implementation(libs.androidx.material3) implementation(libs.androidx.navigation.compose) + implementation(libs.androidx.navigation.runtime.android) + implementation(libs.accompanist.pager) + implementation(libs.accompanist.pager.indicators) testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) diff --git a/app/src/main/java/com/texthip/thip/ui/common/buttons/OptionChipButton.kt b/app/src/main/java/com/texthip/thip/ui/common/buttons/OptionChipButton.kt index 22069b2e..1e492a6e 100644 --- a/app/src/main/java/com/texthip/thip/ui/common/buttons/OptionChipButton.kt +++ b/app/src/main/java/com/texthip/thip/ui/common/buttons/OptionChipButton.kt @@ -30,22 +30,24 @@ fun OptionChipButton( modifier: Modifier = Modifier, text: String, isFilled: Boolean = false, + isSelected: Boolean? = null, // 추가 onClick: () -> Unit, ) { var isClicked by remember { mutableStateOf(false) } + val checked = isSelected ?: isClicked val textColor = when { isFilled -> colors.White - isClicked -> colors.Purple + checked -> colors.Purple else -> colors.Grey01 } val backgroundColor = when { - isFilled && isClicked -> colors.Purple + isFilled && checked -> colors.Purple isFilled -> colors.DarkGrey else -> Color.Transparent } val borderColor = when { - !isFilled && isClicked -> colors.Purple + !isFilled && checked -> colors.Purple !isFilled -> colors.Grey02 else -> Color.Transparent } @@ -62,7 +64,7 @@ fun OptionChipButton( shape = RoundedCornerShape(20.dp) ) .clickable { - isClicked = !isClicked + if (isSelected == null) isClicked = !isClicked onClick() } .padding(vertical = 8.dp, horizontal = 12.dp), diff --git a/app/src/main/java/com/texthip/thip/ui/common/cards/CardAlarm.kt b/app/src/main/java/com/texthip/thip/ui/common/cards/CardAlarm.kt index afdeb777..cca99e99 100644 --- a/app/src/main/java/com/texthip/thip/ui/common/cards/CardAlarm.kt +++ b/app/src/main/java/com/texthip/thip/ui/common/cards/CardAlarm.kt @@ -1,5 +1,6 @@ package com.texthip.thip.ui.common.cards +import androidx.compose.ui.graphics.Color import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable @@ -34,12 +35,15 @@ import com.texthip.thip.ui.theme.ThipTheme.typography @Composable -fun NotificationCard( +fun CardAlarm( modifier: Modifier = Modifier, + badgeText: String, title: String, message: String, timeAgo: String, isRead: Boolean = false, + containerColorUnread: Color = colors.DarkGrey, // 안읽음 상태의 배경색 + containerColorRead: Color = colors.DarkGrey02, // 읽음 상태의 배경색 onClick: () -> Unit = {} ) { Card( @@ -47,7 +51,7 @@ fun NotificationCard( .fillMaxWidth() .clickable { onClick() }, colors = CardDefaults.cardColors( - containerColor = if (isRead) colors.DarkGrey else colors.DarkGrey02 + containerColor = if (isRead) containerColorUnread else containerColorRead ), elevation = CardDefaults.cardElevation(defaultElevation = 2.dp), shape = RoundedCornerShape(12.dp) @@ -65,7 +69,6 @@ fun NotificationCard( // 뱃지 Box( modifier = Modifier - .size(width = 40.dp, height = 24.dp) .clip(RoundedCornerShape(13.dp)) .border( width = 1.dp, @@ -75,7 +78,9 @@ fun NotificationCard( contentAlignment = Alignment.Center ) { Text( - text = stringResource(R.string.group), + modifier = Modifier + .padding(horizontal = 10.dp, vertical = 2.dp), + text = badgeText, color = if (isRead) colors.Grey01 else colors.Grey, style = typography.menu_sb600_s12_h20 ) @@ -147,8 +152,9 @@ fun PreviewNotificationCards() { verticalArrangement = Arrangement.spacedBy(8.dp) ) { // 안읽은 알림 - NotificationCard( + CardAlarm( title = "같이 읽기를 시작했어요!", + badgeText = "모임", message = "한줄만 입력이 가능합니다. 한줄만 입력이 가능합니다. 한줄만 입력이 가능합니다.", timeAgo = "12", isRead = isRead @@ -157,12 +163,37 @@ fun PreviewNotificationCards() { } // 읽은 알림 - NotificationCard( + CardAlarm( title = "같이 읽기를 시작했어요!", + badgeText = "모임", message = "한줄만 입력이 가능합니다. 한줄만 입력이 가능합니다. 한줄만 입력이 가능합니다.", timeAgo = "12", isRead = true ) + + CardAlarm( + title = "같이 읽기를 시작했어요!", + badgeText = "피드", + message = "한줄만 입력이 가능합니다. 한줄만 입력이 가능합니다. 한줄만 입력이 가능합니다.", + timeAgo = "12", + isRead = false + ) + + CardAlarm( + title = "같이 읽기를 시작했어요!", + badgeText = "좋아요", + message = "한줄만 입력이 가능합니다. 한줄만 입력이 가능합니다. 한줄만 입력이 가능합니다.", + timeAgo = "12", + isRead = isRead + ) + + CardAlarm( + title = "같이 읽기를 시작했어요!", + badgeText = "댓글", + message = "한줄만 입력이 가능합니다. 한줄만 입력이 가능합니다. 한줄만 입력이 가능합니다.", + timeAgo = "12", + isRead = isRead + ) } } \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/ui/common/cards/CardInputBook.kt b/app/src/main/java/com/texthip/thip/ui/common/cards/CardInputBook.kt index e6b322af..5a59a36f 100644 --- a/app/src/main/java/com/texthip/thip/ui/common/cards/CardInputBook.kt +++ b/app/src/main/java/com/texthip/thip/ui/common/cards/CardInputBook.kt @@ -74,7 +74,7 @@ fun CardInputBook( ) Spacer(modifier = Modifier.height(8.dp)) Text( - text = "$author 저", + text = stringResource(R.string.card_input_author, author), style = typography.view_m500_s12_h20, color = colors.Grey01 ) diff --git a/app/src/main/java/com/texthip/thip/ui/common/cards/CardItemRoom.kt b/app/src/main/java/com/texthip/thip/ui/common/cards/CardItemRoom.kt new file mode 100644 index 00000000..5a7b8137 --- /dev/null +++ b/app/src/main/java/com/texthip/thip/ui/common/cards/CardItemRoom.kt @@ -0,0 +1,218 @@ +package com.texthip.thip.ui.common.cards + +import androidx.compose.foundation.Image +import androidx.compose.foundation.border +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.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +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.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.texthip.thip.R +import com.texthip.thip.ui.theme.ThipTheme +import com.texthip.thip.ui.theme.ThipTheme.colors +import com.texthip.thip.ui.theme.ThipTheme.typography + +@Composable +fun CardItemRoom( + modifier: Modifier = Modifier, + title: String, + participants: Int, + maxParticipants: Int, + isRecruiting: Boolean, + endDate: Int, + imageRes: Int? = R.drawable.bookcover_sample, + hasBorder: Boolean = false, + onClick: () -> Unit = {} +) { + Card( + modifier = modifier + .fillMaxWidth() + .then( + if (hasBorder) + Modifier + .border( + width = 1.dp, + color = colors.Grey02, + shape = RoundedCornerShape(12.dp) + ) + else Modifier + ) + .clickable { onClick() }, + colors = CardDefaults.cardColors( + containerColor = colors.DarkGrey50 + ), + shape = RoundedCornerShape(12.dp) + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(12.dp) + ) { + Row( + modifier = Modifier.fillMaxWidth() + ) { + // 이미지 + Box( + modifier = Modifier + .size(width = 80.dp, height = 107.dp) + ) { + imageRes?.let { + Image( + painter = painterResource(id = it), + contentDescription = null, + modifier = Modifier.fillMaxSize(), + contentScale = ContentScale.Crop + ) + } + } + + Spacer(modifier = Modifier.width(16.dp)) + + Column( + modifier = Modifier.fillMaxWidth() + ) { + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = title, + color = colors.White, + style = typography.smalltitle_sb600_s18_h24, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + Spacer(modifier = Modifier.height(8.dp)) + Row(verticalAlignment = Alignment.CenterVertically) { + Icon( + modifier = Modifier.size(20.dp), + painter = painterResource(id = R.drawable.ic_group), + contentDescription = "그룹 아이콘", + tint = Color.White + ) + Spacer(modifier = Modifier.width(4.dp)) + + if (isRecruiting) { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = stringResource( + R.string.card_item_participant_count, + participants, + ), + style = typography.menu_sb600_s12, + color = colors.White + ) + Spacer(modifier = Modifier.width(2.dp)) + + Text( + text = stringResource( + R.string.card_item_participant_count_max, + maxParticipants + ), + style = typography.info_m500_s12, + color = colors.Grey + ) + } + } else { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = stringResource(R.string.card_item_participant, participants), + style = typography.menu_sb600_s12, + color = colors.White + ) + Spacer(modifier = Modifier.width(2.dp)) + + Text( + text = stringResource(R.string.card_item_participant_string), + style = typography.info_m500_s12, + color = colors.Grey + ) + } + } + } + Spacer(modifier = Modifier.height(5.dp)) + + Text( + text = stringResource( + R.string.card_item_end_date, + endDate + ) + if (isRecruiting) stringResource( + R.string.card_item_end + ) else stringResource(R.string.card_item_finish), + + color = if (isRecruiting) colors.Red else colors.Grey01, + style = typography.menu_sb600_s12_h20, + maxLines = 1 + ) + } + } + } + } +} + + +@Preview() +@Composable +fun CardItemRoomPreview() { + ThipTheme { + Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { + CardItemRoom( + title = "모임방 이름입니다. 모임방 이름입니다.", + participants = 22, + maxParticipants = 30, + isRecruiting = true, + endDate = 3, + imageRes = R.drawable.bookcover_sample + ) + CardItemRoom( + title = "모임방 이름입니다. 모임방 이름입니다.", + participants = 22, + maxParticipants = 30, + isRecruiting = false, + endDate = 3, + imageRes = R.drawable.bookcover_sample + ) + CardItemRoom( + title = "모임방 이름입니다. 모임방 이름입니다.", + participants = 22, + maxParticipants = 30, + isRecruiting = true, + endDate = 3, + imageRes = R.drawable.bookcover_sample, + hasBorder = true + ) + CardItemRoom( + title = "모임방 이름입니다. 모임방 이름입니다.", + participants = 22, + maxParticipants = 30, + isRecruiting = false, + endDate = 3, + imageRes = R.drawable.bookcover_sample, + hasBorder = true + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/ui/common/cards/CardItemRoomSmall.kt b/app/src/main/java/com/texthip/thip/ui/common/cards/CardItemRoomSmall.kt new file mode 100644 index 00000000..8cac5e5b --- /dev/null +++ b/app/src/main/java/com/texthip/thip/ui/common/cards/CardItemRoomSmall.kt @@ -0,0 +1,151 @@ +package com.texthip.thip.ui.common.cards + +import androidx.compose.foundation.Image +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.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +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.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.texthip.thip.R +import com.texthip.thip.ui.theme.ThipTheme.colors +import com.texthip.thip.ui.theme.ThipTheme.typography + +@Composable +fun CardItemRoomSmall( + modifier: Modifier = Modifier, + title: String, + participants: Int, + maxParticipants: Int, + endDate: Int, + imageRes: Int? = R.drawable.bookcover_sample_small, + onClick: () -> Unit = {} +) { + Card( + modifier = modifier + .size(width = 232.dp, height = 104.dp) + .clickable { onClick() }, + colors = CardDefaults.cardColors( + containerColor = colors.DarkGrey50 + ), + elevation = CardDefaults.cardElevation(defaultElevation = 4.dp), + shape = RoundedCornerShape(12.dp) + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(12.dp) + ) { + Row( + modifier = Modifier.fillMaxWidth() + ) { + // 이미지 + Box( + modifier = Modifier + .size(width = 60.dp, height = 80.dp) + ) { + imageRes?.let { + Image( + painter = painterResource(id = it), + contentDescription = null, + modifier = Modifier.fillMaxSize(), + contentScale = ContentScale.Crop + ) + } + } + + Spacer(modifier = Modifier.width(8.dp)) + + Column( + modifier = Modifier.fillMaxWidth() + ) { + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = title, + color = colors.White, + style = typography.menu_sb600_s14_h24, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + Spacer(modifier = Modifier.height(4.dp)) + + Row(verticalAlignment = Alignment.CenterVertically) { + Icon( + modifier = Modifier.size(20.dp), + painter = painterResource(id = R.drawable.ic_group), + contentDescription = "그룹 아이콘", + tint = Color.White + ) + Spacer(modifier = Modifier.width(4.dp)) + + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = stringResource( + R.string.card_item_participant_count, + participants, + ), + style = typography.menu_sb600_s12, + color = colors.White + ) + Spacer(modifier = Modifier.width(2.dp)) + + Text( + text = stringResource( + R.string.card_item_participant_count_max, + maxParticipants + ), + style = typography.info_m500_s12, + color = colors.Grey + ) + } + } + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = stringResource(R.string.card_item_end_date_recruit, endDate), + color = colors.Red, + style = typography.menu_sb600_s12_h20 + ) + } + } + } + } +} + + +@Preview(showBackground = true, backgroundColor = 0xFF000000, widthDp = 360) +@Composable +fun CardItemRoomSmallPreview() { + Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { + CardItemRoomSmall( + title = "방 제목입니다 방 제목입니다", + participants = 22, + maxParticipants = 30, + endDate = 3, + imageRes = R.drawable.bookcover_sample + ) + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/ui/common/cards/CardRoomBook.kt b/app/src/main/java/com/texthip/thip/ui/common/cards/CardRoomBook.kt index 068a4ce5..80c68feb 100644 --- a/app/src/main/java/com/texthip/thip/ui/common/cards/CardRoomBook.kt +++ b/app/src/main/java/com/texthip/thip/ui/common/cards/CardRoomBook.kt @@ -25,6 +25,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview @@ -34,7 +35,7 @@ import com.texthip.thip.ui.theme.ThipTheme.colors import com.texthip.thip.ui.theme.ThipTheme.typography @Composable -fun DetailedDarkCard( +fun CardRoomBook( modifier: Modifier = Modifier, title: String, author: String, @@ -109,16 +110,34 @@ fun DetailedDarkCard( ) { Spacer(modifier = Modifier.height(7.dp)) - Text( - text = "$author 저 · $publisher", - color = colors.White, - style = typography.info_m500_s12, - ) + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = stringResource(R.string.card_author, author), + color = colors.White, + style = typography.info_m500_s12, + ) + Spacer(modifier = Modifier.width(4.dp)) + Text( + text = "·", + color = colors.Grey02, + style = typography.info_m500_s12, + ) + Spacer(modifier = Modifier.width(4.dp)) + + Text( + text = publisher, + color = colors.White, + style = typography.info_m500_s12, + ) + + } Spacer(modifier = Modifier.height(21.dp)) Text( - text = "도서 소개", + text = stringResource(R.string.card_book_explain), color = colors.White, style = typography.info_m500_s12, ) @@ -141,13 +160,13 @@ fun DetailedDarkCard( @Preview @Composable -fun PreviewDetailedDarkCard() { +fun PreviewCardRoomBook() { Column( modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp) ) { - DetailedDarkCard( + CardRoomBook( title = "도서명을 입력, 예시까지 최대 입력 후...", author = "저자명", publisher = "출판사", diff --git a/app/src/main/java/com/texthip/thip/ui/common/forms/FormTextFieldDefault.kt b/app/src/main/java/com/texthip/thip/ui/common/forms/FormTextFieldDefault.kt index cc8f2285..a04f60ba 100644 --- a/app/src/main/java/com/texthip/thip/ui/common/forms/FormTextFieldDefault.kt +++ b/app/src/main/java/com/texthip/thip/ui/common/forms/FormTextFieldDefault.kt @@ -2,6 +2,9 @@ package com.texthip.thip.ui.common.forms import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Icon @@ -25,62 +28,105 @@ import com.texthip.thip.ui.theme.ThipTheme.colors import com.texthip.thip.ui.theme.ThipTheme.typography @Composable -fun BaseInputTextField( +fun FormTextFieldDefault( modifier: Modifier = Modifier, - hint: String + hint: String, + showLimit: Boolean = false, + limit: Int = 10, + showIcon: Boolean = true ) { var text by rememberSaveable { mutableStateOf("") } val myStyle = typography.menu_r400_s14_h24.copy(lineHeight = 14.sp) - OutlinedTextField( - value = text, - onValueChange = { text = it }, - placeholder = { - Text( - text = hint, - color = colors.Grey02, - style = myStyle - ) - }, - textStyle = myStyle, - modifier = modifier.size(width = 320.dp, height = 48.dp), - shape = RoundedCornerShape(12.dp), - colors = TextFieldDefaults.colors( - focusedTextColor = colors.White, - focusedIndicatorColor = Color.Transparent, - unfocusedIndicatorColor = Color.Transparent, - focusedContainerColor = colors.Black, - unfocusedContainerColor = colors.Black, - cursorColor = colors.NeonGreen - ), - trailingIcon = { - if (text.isNotEmpty()) { - Icon( - painter = painterResource(id = R.drawable.ic_x_circle_white), - contentDescription = "Clear text", - modifier = Modifier.clickable { text = "" }, - tint = Color.Unspecified + // 글자수 제한 적용 + val displayText = if (showLimit && text.length > limit) text.substring(0, limit) else text + + Box(modifier = modifier) { + OutlinedTextField( + value = displayText, + onValueChange = { + // 글자수 제한 적용 + text = if (showLimit && it.length > limit) it.substring(0, limit) else it + }, + placeholder = { + Text( + text = hint, + color = colors.Grey02, + style = myStyle ) - } else { - Icon( - painter = painterResource(id = R.drawable.ic_x_circle), - contentDescription = "Clear text" + }, + textStyle = myStyle, + modifier = Modifier + .size(width = 320.dp, height = 48.dp), + shape = RoundedCornerShape(12.dp), + colors = TextFieldDefaults.colors( + focusedTextColor = colors.White, + focusedIndicatorColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent, + focusedContainerColor = colors.Black, + unfocusedContainerColor = colors.Black, + cursorColor = colors.NeonGreen + ), + trailingIcon = { + if (showIcon) { + if (text.isNotEmpty()) { + Icon( + painter = painterResource(id = R.drawable.ic_x_circle_white), + contentDescription = "Clear text", + modifier = Modifier.clickable { text = "" }, + tint = Color.Unspecified + ) + } else { + Icon( + painter = painterResource(id = R.drawable.ic_x_circle), + contentDescription = "Clear text" + ) + } + } + }, + singleLine = true + ) + + // 글자수 제한 표시 (오른쪽 상단) + if (showLimit) { + Box( + modifier = Modifier + .align(Alignment.CenterEnd) + .padding(end = 14.dp) + ) { + Text( + text = "${text.length}/$limit", + color = colors.White, + style = typography.info_r400_s12_h24 ) } - }, - singleLine = true - ) + } + } } + @Composable @Preview(showBackground = true, backgroundColor = 0xFF000000, widthDp = 360, heightDp = 200) -fun InputTextFieldPreviewEmpty() { +fun FormTextFieldDefaultPreview() { Box( modifier = Modifier.size(width = 360.dp, height = 200.dp), contentAlignment = Alignment.Center ) { - BaseInputTextField( - hint = "이곳에 텍스트를 입력하세요" - ) + Column { + FormTextFieldDefault( + hint = "이곳에 텍스트를 입력하세요", + showLimit = true, + limit = 20, + showIcon = false + ) + Spacer(modifier = Modifier.padding(vertical = 8.dp)) + + FormTextFieldDefault( + hint = "이곳에 텍스트를 입력하세요", + showLimit = false, + limit = 10, + showIcon = true + ) + } } } \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/ui/group/myroom/component/GenreChipRow.kt b/app/src/main/java/com/texthip/thip/ui/group/myroom/component/GenreChipRow.kt new file mode 100644 index 00000000..ae3fa69a --- /dev/null +++ b/app/src/main/java/com/texthip/thip/ui/group/myroom/component/GenreChipRow.kt @@ -0,0 +1,40 @@ +package com.texthip.thip.ui.group.myroom.component + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import com.texthip.thip.ui.common.buttons.OptionChipButton + +@Composable +fun GenreChipRow( + genres: List, + selectedIndex: Int, + onSelect: (Int) -> Unit +) { + Row( + Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly + ) { + genres.forEachIndexed { idx, genre -> + OptionChipButton( + text = genre, + isFilled = true, + isSelected = selectedIndex == idx, + onClick = { onSelect(idx) } + ) + } + } +} + +@Preview(showBackground = true, backgroundColor = 0xFF000000, widthDp = 360) +@Composable +fun PreviewGenreChipRow() { + GenreChipRow( + genres = listOf("문학", "과학·IT", "사회과학", "인문학", "예술"), + selectedIndex = 0, + onSelect = {} + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/ui/group/myroom/component/GroupDeadlineRoomSection.kt b/app/src/main/java/com/texthip/thip/ui/group/myroom/component/GroupDeadlineRoomSection.kt new file mode 100644 index 00000000..c7e3f7a8 --- /dev/null +++ b/app/src/main/java/com/texthip/thip/ui/group/myroom/component/GroupDeadlineRoomSection.kt @@ -0,0 +1,262 @@ +package com.texthip.thip.ui.group.myroom.component + +import android.annotation.SuppressLint +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.texthip.thip.R +import com.texthip.thip.ui.common.cards.CardItemRoom +import com.texthip.thip.ui.group.myroom.mock.GroupRoomSectionData +import com.texthip.thip.ui.group.myroom.mock.GroupCardItemRoomData +import com.texthip.thip.ui.theme.ThipTheme +import com.texthip.thip.ui.theme.ThipTheme.colors +import com.texthip.thip.ui.theme.ThipTheme.typography + +@SuppressLint("UnusedBoxWithConstraintsScope") +@Composable +fun GroupRoomDeadlineSection( + roomSections: List, + onRoomClick: (GroupCardItemRoomData) -> Unit +) { + val cardWidth = 320.dp + val pageSpacing = 12.dp + + val pagerState = rememberPagerState( + initialPage = 0, + pageCount = { roomSections.size } + ) + + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .fillMaxWidth() + ) { + BoxWithConstraints( + modifier = Modifier + .fillMaxWidth() + .height(588.dp), + contentAlignment = Alignment.Center + ) { + val horizontalPadding = ((maxWidth - cardWidth) / 2).coerceAtLeast(0.dp) + + HorizontalPager( + state = pagerState, + contentPadding = PaddingValues(horizontal = horizontalPadding), + pageSpacing = pageSpacing, + modifier = Modifier.fillMaxWidth() + ) { page -> + val section = roomSections[page] + var selectedGenre by remember { mutableStateOf(0) } + + val isCurrent = pagerState.currentPage == page + val scale = if (isCurrent) 1f else 0.9f + + Box( + modifier = Modifier + .width(cardWidth) + .graphicsLayer { + scaleX = scale + scaleY = scale + } + .fillMaxHeight() + .background( + brush = Brush.verticalGradient( + colors = listOf( + colors.White.copy(0.25f), + colors.Black.copy(0.2f) + ) + ), + shape = RoundedCornerShape(14.dp) + ) + .padding(vertical = 20.dp, horizontal = 12.dp) + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = section.title, + style = typography.title_b700_s20_h24, + color = colors.White + ) + Spacer(Modifier.height(40.dp)) + + GenreChipRow( + genres = section.genres, + selectedIndex = selectedGenre, + onSelect = { idx -> selectedGenre = idx } + ) + Spacer(Modifier.height(20.dp)) + + val cards = section.rooms.filter { it.genreIndex == selectedGenre }.take(3) + Column( + verticalArrangement = Arrangement.spacedBy(20.dp), + modifier = Modifier.fillMaxWidth() + ) { + cards.forEach { room -> + CardItemRoom( + title = room.title, + participants = room.participants, + maxParticipants = room.maxParticipants, + isRecruiting = room.isRecruiting, + endDate = room.endDate, + imageRes = room.imageRes, + onClick = { onRoomClick(room) }, + hasBorder = true, + ) + } + if (cards.size < 3) { + Spacer( + modifier = Modifier + .weight(1f, fill = true) + .fillMaxWidth() + ) + } + } + } + } + } + } + + SimplePagerIndicator( + pageCount = roomSections.size, + currentPage = pagerState.currentPage, + modifier = Modifier + .align(Alignment.CenterHorizontally) + .padding(top = 8.dp) + ) + } +} + +@Preview() +@Composable +fun PreviewGroupRoomPagerSection() { + ThipTheme { + val genres = listOf("문학", "과학·IT", "사회과학", "인문학", "예술") + + // 마감 임박한 독서 모임방 + val deadlineRooms = listOf( + GroupCardItemRoomData( + title = "시집만 읽는 사람들 3월", + participants = 22, + maxParticipants = 30, + isRecruiting = true, + endDate = 3, + genreIndex = 0 + ), + GroupCardItemRoomData( + title = "일본 소설 좋아하는 사람들", + participants = 15, + maxParticipants = 20, + isRecruiting = true, + endDate = 2, + genreIndex = 0 + ), + GroupCardItemRoomData( + title = "명작 같이 읽기방", + participants = 22, + maxParticipants = 30, + isRecruiting = true, + endDate = 3, + genreIndex = 0 + ), + GroupCardItemRoomData( + title = "물리책 읽는 방", + participants = 13, + maxParticipants = 20, + isRecruiting = true, + endDate = 1, + genreIndex = 1 + ) + ) + + // 인기 있는 독서 모임방 + val popularRooms = listOf( + GroupCardItemRoomData( + title = "베스트셀러 토론방", + participants = 28, + maxParticipants = 30, + isRecruiting = true, + endDate = 7, + genreIndex = 0 + ), + GroupCardItemRoomData( + title = "인기 소설 완독방", + participants = 25, + maxParticipants = 25, + isRecruiting = false, + endDate = 5, + genreIndex = 0 + ), + GroupCardItemRoomData( + title = "트렌드 과학서 읽기", + participants = 20, + maxParticipants = 25, + isRecruiting = true, + endDate = 10, + genreIndex = 1 + ) + ) + + // 인플루언서, 작가 독서 모임방 + val influencerRooms = listOf( + GroupCardItemRoomData( + title = "작가와 함께하는 독서방", + participants = 30, + maxParticipants = 30, + isRecruiting = false, + endDate = 14, + genreIndex = 0 + ), + GroupCardItemRoomData( + title = "유명 북튜버와 읽기", + participants = 18, + maxParticipants = 20, + isRecruiting = true, + endDate = 8, + genreIndex = 2 + ), + GroupCardItemRoomData( + title = "작가 초청 인문학방", + participants = 15, + maxParticipants = 20, + isRecruiting = true, + endDate = 12, + genreIndex = 3 + ) + ) + + val roomSections = listOf( + GroupRoomSectionData( + title = stringResource(R.string.deadline_string), + rooms = deadlineRooms, + genres = genres + ), + GroupRoomSectionData( + title = "인기 있는 독서 모임방", + rooms = popularRooms, + genres = genres + ), + GroupRoomSectionData( + title = "인플루언서·작가 독서 모임방", + rooms = influencerRooms, + genres = genres + ) + ) + + GroupRoomDeadlineSection( + roomSections = roomSections, + onRoomClick = {} + ) + } +} diff --git a/app/src/main/java/com/texthip/thip/ui/group/myroom/component/GroupMainCard.kt b/app/src/main/java/com/texthip/thip/ui/group/myroom/component/GroupMainCard.kt new file mode 100644 index 00000000..98ac640b --- /dev/null +++ b/app/src/main/java/com/texthip/thip/ui/group/myroom/component/GroupMainCard.kt @@ -0,0 +1,178 @@ +package com.texthip.thip.ui.group.myroom.component + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +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.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.LinearProgressIndicator +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.texthip.thip.R +import com.texthip.thip.ui.group.myroom.mock.GroupCardData +import com.texthip.thip.ui.theme.ThipTheme +import com.texthip.thip.ui.theme.ThipTheme.colors +import com.texthip.thip.ui.theme.ThipTheme.typography + +@Composable +fun GroupMainCard( + data: GroupCardData, + backgroundColor: Color = Color.White, + onClick: () -> Unit = {} +) { + // 그라데이션 + val gradient = Brush.linearGradient( + colors = listOf( + colors.White, + colors.Grey01 + ), + start = Offset(0f, 0f), + end = Offset(1000f, 1000f) + ) + + Card( + modifier = Modifier + .width(320.dp) + .height(176.dp) + .clickable { onClick() }, + shape = RoundedCornerShape(18.dp), + colors = CardDefaults.cardColors(containerColor = backgroundColor), + elevation = CardDefaults.cardElevation(defaultElevation = 0.dp) + ) { + Box( + Modifier + .fillMaxSize() + .background(brush = gradient) + ) { + Row( + modifier = Modifier + .fillMaxSize() + .padding(start = 12.dp, end = 12.dp, top = 34.dp, bottom = 34.dp), + verticalAlignment = Alignment.CenterVertically + ) { + // 책 이미지 + Image( + painter = painterResource(id = data.imageRes), + contentDescription = "책 이미지", + modifier = Modifier + .size(width = 80.dp, height = 107.dp) + ) + Spacer(Modifier.width(12.dp)) + + Column( + verticalArrangement = Arrangement.Center + ) { + Spacer(Modifier.height(2.dp)) + // 제목 + Text( + text = data.title, + style = typography.smalltitle_sb600_s18_h24, + color = colors.Black, + maxLines = 1 + ) + Spacer(Modifier.height(4.dp)) + // 인원 + Row(verticalAlignment = Alignment.CenterVertically) { + Image( + painter = painterResource(id = R.drawable.ic_group), + contentDescription = null, + modifier = Modifier.size(20.dp) + ) + Spacer(Modifier.width(2.dp)) + + Row ( + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = stringResource(R.string.group_participant, data.members), + color = colors.Grey02, + style = typography.menu_sb600_s12, + ) + Spacer(Modifier.width(2.dp)) + Text( + text = stringResource(R.string.group_participant_string), + color = colors.Grey02, + style = typography.info_m500_s12, + ) + } + } + Spacer(Modifier.height(16.dp)) + // 닉네임 + 진행도 + Row(verticalAlignment = Alignment.Bottom) { + Text( + text = stringResource(R.string.group_progress, data.nickname), + color = colors.Grey02, + style = typography.view_m500_s14 + ) + Spacer(Modifier.width(4.dp)) + Text( + text = "${data.progress}%", + color = colors.Purple, + style = typography.view_m500_s14, + ) + } + Spacer(Modifier.height(10.dp)) + + val percentage = data.progress.toFloat() + Box( + modifier = Modifier + .fillMaxWidth() + .height(8.dp) + .background(color = colors.Grey02, shape = RoundedCornerShape(12.dp)) + ) { + Box( + modifier = Modifier + .fillMaxWidth(fraction = percentage / 100f) + .fillMaxHeight() + .background(color = colors.Purple, shape = RoundedCornerShape(12.dp)) + ) + } + Spacer(Modifier.height(2.dp)) + } + } + } + } +} + + +@Preview() +@Composable +fun PreviewMyGroupMainCard() { + ThipTheme { + GroupMainCard( + data = GroupCardData( + title = "호르몬 체인지 완독하는 방", + members = 22, + imageRes = R.drawable.bookcover_sample, + progress = 42, + nickname = "uibowl" + ), + onClick = {} + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/ui/group/myroom/component/GroupMyRoomFilterRow.kt b/app/src/main/java/com/texthip/thip/ui/group/myroom/component/GroupMyRoomFilterRow.kt new file mode 100644 index 00000000..dfa8e3d6 --- /dev/null +++ b/app/src/main/java/com/texthip/thip/ui/group/myroom/component/GroupMyRoomFilterRow.kt @@ -0,0 +1,31 @@ +package com.texthip.thip.ui.group.myroom.component + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.texthip.thip.R +import com.texthip.thip.ui.common.buttons.OptionChipButton + +@Composable +fun GroupMyRoomFilterRow( + selectedStates: BooleanArray, + onToggle: (Int) -> Unit +) { + Row( + horizontalArrangement = Arrangement.spacedBy(12.dp) + ) { + OptionChipButton( + text = stringResource(R.string.on_going), + isFilled = true, + onClick = { onToggle(0) } + ) + OptionChipButton( + text = stringResource(R.string.recruiting), + isFilled = true, + onClick = { onToggle(1) } + ) + } +} + diff --git a/app/src/main/java/com/texthip/thip/ui/group/myroom/component/GroupMySectionHeader.kt b/app/src/main/java/com/texthip/thip/ui/group/myroom/component/GroupMySectionHeader.kt new file mode 100644 index 00000000..2148e1f1 --- /dev/null +++ b/app/src/main/java/com/texthip/thip/ui/group/myroom/component/GroupMySectionHeader.kt @@ -0,0 +1,50 @@ +package com.texthip.thip.ui.group.myroom.component + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.texthip.thip.R +import com.texthip.thip.ui.theme.ThipTheme.colors +import com.texthip.thip.ui.theme.ThipTheme.typography + +@Composable +fun GroupMySectionHeader(onClick: (() -> Unit)? = null) { + Row( + Modifier + .fillMaxWidth() + .clickable(enabled = onClick != null) { onClick?.invoke() } + .padding(horizontal = 20.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = stringResource(R.string.my_group), + style = typography.title_b700_s20_h24, + color = colors.White + ) + Spacer(Modifier.weight(1f)) + if (onClick != null) { + Icon( + painter = painterResource(id = R.drawable.ic_chevron_right), + contentDescription = null, + tint = colors.White + ) + } + } +} + +@Preview(showBackground = true, backgroundColor = 0xFF000000, widthDp = 360) +@Composable +fun PreviewMainSectionHeader() { + GroupMySectionHeader(){} +} diff --git a/app/src/main/java/com/texthip/thip/ui/group/myroom/component/GroupPager.kt b/app/src/main/java/com/texthip/thip/ui/group/myroom/component/GroupPager.kt new file mode 100644 index 00000000..69147a66 --- /dev/null +++ b/app/src/main/java/com/texthip/thip/ui/group/myroom/component/GroupPager.kt @@ -0,0 +1,106 @@ +package com.texthip.thip.ui.group.myroom.component + +import android.annotation.SuppressLint +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.texthip.thip.R +import com.texthip.thip.ui.group.myroom.mock.GroupCardData +import com.texthip.thip.ui.theme.ThipTheme +import com.texthip.thip.ui.theme.ThipTheme.colors + +@SuppressLint("UnusedBoxWithConstraintsScope") +@Composable +fun GroupPager( + groupCards: List, + onCardClick: (GroupCardData) -> Unit +) { + val cardWidth = 320.dp + val pageSpacing = 6.dp + + BoxWithConstraints( + modifier = Modifier + .fillMaxWidth() + .height(192.dp) + ) { + val screenWidth = maxWidth + val horizontalPadding = ((screenWidth - cardWidth) / 2).coerceAtLeast(0.dp) + + val pagerState = rememberPagerState( + initialPage = 0, + pageCount = { maxOf(1, groupCards.size) } + ) + + HorizontalPager( + state = pagerState, + contentPadding = PaddingValues(start = horizontalPadding, end = horizontalPadding), + pageSpacing = pageSpacing, + modifier = Modifier.fillMaxWidth() + ) { page -> + val isCurrent = pagerState.currentPage == page + val scale = if (isCurrent) 1f else 0.86f + val bgColor = if (isCurrent) colors.White else colors.DarkGrey + + Box( + modifier = Modifier + .width(cardWidth) + .graphicsLayer { + scaleX = scale + scaleY = scale + alpha = if (isCurrent) 1f else 0.7f + } + ) { + GroupMainCard( + data = groupCards[page], + onClick = { onCardClick(groupCards[page]) }, + backgroundColor = bgColor + ) + } + } + + SimplePagerIndicator( + pageCount = groupCards.size, + currentPage = pagerState.currentPage, + modifier = Modifier + .align(Alignment.BottomCenter) + .padding(top = 12.dp) + ) + } +} + +@Preview() +@Composable +fun PreviewMyGroupPager() { + ThipTheme { + val list = listOf( + GroupCardData( + title = "호르몬 체인지 완독하는 방", + members = 22, + imageRes = R.drawable.bookcover_sample, + progress = 40, + nickname = "uibowl1님" + ), + GroupCardData( + title = "명작 읽기방", + members = 10, + imageRes = R.drawable.bookcover_sample, + progress = 70, + nickname = "joyce님" + ), + GroupCardData( + title = "또 다른 방", + members = 13, + imageRes = R.drawable.bookcover_sample, + progress = 10, + nickname = "other님" + ) + ) + GroupPager(groupCards = list, onCardClick = {}) + } +} diff --git a/app/src/main/java/com/texthip/thip/ui/group/myroom/component/GroupSearchTextField.kt b/app/src/main/java/com/texthip/thip/ui/group/myroom/component/GroupSearchTextField.kt new file mode 100644 index 00000000..073a08c8 --- /dev/null +++ b/app/src/main/java/com/texthip/thip/ui/group/myroom/component/GroupSearchTextField.kt @@ -0,0 +1,85 @@ +package com.texthip.thip.ui.group.myroom.component + +import androidx.compose.foundation.layout.Box +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.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.material3.TextFieldDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.texthip.thip.R +import com.texthip.thip.ui.theme.ThipTheme.colors +import com.texthip.thip.ui.theme.ThipTheme.typography + +@Composable +fun GroupSearchTextField( + onValueChange: (String) -> Unit +) { + var value by rememberSaveable { mutableStateOf("") } + val textStyle = typography.menu_r400_s14_h24.copy(lineHeight = 20.sp) + + Box( + Modifier + .padding(horizontal = 20.dp) + .fillMaxWidth() + .height(48.dp) + ) { + OutlinedTextField( + value = value, + onValueChange = { newValue -> + value = newValue + onValueChange(newValue) + }, + textStyle = textStyle, + modifier = Modifier + .fillMaxWidth(), + placeholder = { + Text( + text = stringResource(R.string.group_search_placeholder), + color = colors.Grey02, + style = typography.menu_r400_s14_h24.copy(lineHeight = 2.sp) + ) + }, + singleLine = true, + shape = RoundedCornerShape(12.dp), + colors = TextFieldDefaults.colors( + focusedTextColor = colors.White, + focusedIndicatorColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent, + focusedContainerColor = colors.DarkGrey, + unfocusedContainerColor = colors.DarkGrey, + cursorColor = colors.NeonGreen + ), + trailingIcon = { + Icon( + painter = painterResource(id = R.drawable.ic_search), + contentDescription = "검색", + tint = colors.White, + modifier = Modifier.size(24.dp) + ) + } + ) + } +} + +@Preview(showBackground = true, backgroundColor = 0xFF000000, widthDp = 360) +@Composable +fun PreviewSearchTextField() { + GroupSearchTextField(onValueChange = {}) +} + diff --git a/app/src/main/java/com/texthip/thip/ui/group/myroom/component/SimplePagerIndicator.kt b/app/src/main/java/com/texthip/thip/ui/group/myroom/component/SimplePagerIndicator.kt new file mode 100644 index 00000000..983c8fe1 --- /dev/null +++ b/app/src/main/java/com/texthip/thip/ui/group/myroom/component/SimplePagerIndicator.kt @@ -0,0 +1,39 @@ +package com.texthip.thip.ui.group.myroom.component + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.texthip.thip.ui.theme.ThipTheme.colors + +@Composable +fun SimplePagerIndicator( + modifier: Modifier = Modifier, + pageCount: Int, + currentPage: Int +) { + Row( + modifier = modifier, + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + repeat(pageCount) { index -> + Box( + modifier = Modifier + .padding(horizontal = 6.dp) + .size(4.dp) + .background( + color = if (currentPage == index) colors.White else colors.Grey02, + shape = RoundedCornerShape(50) + ) + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/ui/group/myroom/mock/GroupBookData.kt b/app/src/main/java/com/texthip/thip/ui/group/myroom/mock/GroupBookData.kt new file mode 100644 index 00000000..ee24be4c --- /dev/null +++ b/app/src/main/java/com/texthip/thip/ui/group/myroom/mock/GroupBookData.kt @@ -0,0 +1,11 @@ +package com.texthip.thip.ui.group.myroom.mock + +import com.texthip.thip.R + +data class GroupBookData( + val title: String, + val author: String, + val publisher: String, + val description: String, + val imageRes: Int = R.drawable.bookcover_sample +) diff --git a/app/src/main/java/com/texthip/thip/ui/group/myroom/mock/GroupBottomButtonType.kt b/app/src/main/java/com/texthip/thip/ui/group/myroom/mock/GroupBottomButtonType.kt new file mode 100644 index 00000000..90ca8e0f --- /dev/null +++ b/app/src/main/java/com/texthip/thip/ui/group/myroom/mock/GroupBottomButtonType.kt @@ -0,0 +1,3 @@ +package com.texthip.thip.ui.group.myroom.mock + +enum class GroupBottomButtonType { JOIN, CANCEL, CLOSE } \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/ui/group/myroom/mock/GroupCardData.kt b/app/src/main/java/com/texthip/thip/ui/group/myroom/mock/GroupCardData.kt new file mode 100644 index 00000000..dab51e6b --- /dev/null +++ b/app/src/main/java/com/texthip/thip/ui/group/myroom/mock/GroupCardData.kt @@ -0,0 +1,11 @@ +package com.texthip.thip.ui.group.myroom.mock + +import com.texthip.thip.R + +data class GroupCardData( + val title: String, + val members: Int, + val imageRes: Int = R.drawable.bookcover_sample, + val progress: Int, // 진행률 (0~100) + val nickname: String +) \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/ui/group/myroom/mock/GroupCardItemRoomData.kt b/app/src/main/java/com/texthip/thip/ui/group/myroom/mock/GroupCardItemRoomData.kt new file mode 100644 index 00000000..b3fcedc2 --- /dev/null +++ b/app/src/main/java/com/texthip/thip/ui/group/myroom/mock/GroupCardItemRoomData.kt @@ -0,0 +1,15 @@ +package com.texthip.thip.ui.group.myroom.mock + +import com.texthip.thip.R + +data class GroupCardItemRoomData( + val title: String, + val participants: Int, + val maxParticipants: Int, + val isRecruiting: Boolean, + val endDate: Int, // 남은 일 수 + val imageRes: Int? = R.drawable.bookcover_sample, + val genreIndex: Int // 장르 인덱스 +) + + diff --git a/app/src/main/java/com/texthip/thip/ui/group/myroom/mock/GroupRoomData.kt b/app/src/main/java/com/texthip/thip/ui/group/myroom/mock/GroupRoomData.kt new file mode 100644 index 00000000..55468f20 --- /dev/null +++ b/app/src/main/java/com/texthip/thip/ui/group/myroom/mock/GroupRoomData.kt @@ -0,0 +1,15 @@ +package com.texthip.thip.ui.group.myroom.mock + +data class GroupRoomData( + val title: String, + val isSecret: Boolean, + val description: String, + val startDate: String, + val endDate: String, + val members: Int, + val maxMembers: Int, + val daysLeft: Int, + val genre: String, + val bookData: GroupBookData, + val recommendations: List +) diff --git a/app/src/main/java/com/texthip/thip/ui/group/myroom/mock/GroupRoomSectionData.kt b/app/src/main/java/com/texthip/thip/ui/group/myroom/mock/GroupRoomSectionData.kt new file mode 100644 index 00000000..756f33b7 --- /dev/null +++ b/app/src/main/java/com/texthip/thip/ui/group/myroom/mock/GroupRoomSectionData.kt @@ -0,0 +1,7 @@ +package com.texthip.thip.ui.group.myroom.mock + +data class GroupRoomSectionData( + val title: String, + val rooms: List, + val genres: List +) diff --git a/app/src/main/java/com/texthip/thip/ui/group/myroom/mock/GroupViewModel.kt b/app/src/main/java/com/texthip/thip/ui/group/myroom/mock/GroupViewModel.kt new file mode 100644 index 00000000..79139a7b --- /dev/null +++ b/app/src/main/java/com/texthip/thip/ui/group/myroom/mock/GroupViewModel.kt @@ -0,0 +1,90 @@ +package com.texthip.thip.ui.group.myroom.mock + +import androidx.lifecycle.ViewModel +import com.texthip.thip.R +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +class GroupViewModel : ViewModel() { + + private val _myGroups = MutableStateFlow>(emptyList()) + val myGroups: StateFlow> = _myGroups + + private val _roomSections = MutableStateFlow>(emptyList()) + val roomSections: StateFlow> = _roomSections + + private val _genres = listOf("문학", "과학·IT", "사회과학", "인문학", "예술") + val genres: List get() = _genres + + // 초기 데이터 세팅 (실제에선 repository/remote에서 받아옴) + init { + _myGroups.value = listOf( + GroupCardData("호르몬 체인지 완독하는 방", 22, R.drawable.bookcover_sample, 40, "uibowl1"), + GroupCardData("명작 읽기방", 10, R.drawable.bookcover_sample, 70, "joyce"), + GroupCardData("또 다른 방", 13, R.drawable.bookcover_sample, 10, "other") + ) + + // 마감 임박한 독서 모임방 + val deadlineRooms = listOf( + GroupCardItemRoomData("시집만 읽는 사람들 3월", 22, 30, true, 3, R.drawable.bookcover_sample, 0), + GroupCardItemRoomData("일본 소설 좋아하는 사람들", 15, 20, true, 2, R.drawable.bookcover_sample, 0), + GroupCardItemRoomData("명작 같이 읽기방", 22, 30, true, 3, R.drawable.bookcover_sample, 0), + GroupCardItemRoomData("물리책 읽는 방", 13, 20, true, 1, R.drawable.bookcover_sample, 1), + GroupCardItemRoomData("코딩 과학 동아리", 12, 15, true, 5, R.drawable.bookcover_sample, 1), + GroupCardItemRoomData("사회과학 인문 탐구", 8, 12, true, 4, R.drawable.bookcover_sample, 2) + ) + + // 인기 있는 독서 모임방 + val popularRooms = listOf( + GroupCardItemRoomData("베스트셀러 토론방", 28, 30, true, 7, R.drawable.bookcover_sample, 0), + GroupCardItemRoomData("인기 소설 완독방", 25, 25, false, 5, R.drawable.bookcover_sample, 0), + GroupCardItemRoomData("트렌드 과학서 읽기", 20, 25, true, 10, R.drawable.bookcover_sample, 1), + GroupCardItemRoomData("화제의 경영서", 18, 20, true, 8, R.drawable.bookcover_sample, 2), + GroupCardItemRoomData("인기 철학서 모임", 15, 18, true, 12, R.drawable.bookcover_sample, 3), + GroupCardItemRoomData("예술서 베스트", 12, 15, true, 6, R.drawable.bookcover_sample, 4) + ) + + // 인플루언서, 작가 독서 모임방 + val influencerRooms = listOf( + GroupCardItemRoomData("작가와 함께하는 독서방", 30, 30, false, 14, R.drawable.bookcover_sample, 0), + GroupCardItemRoomData("유명 북튜버와 읽기", 18, 20, true, 8, R.drawable.bookcover_sample, 2), + GroupCardItemRoomData("작가 초청 인문학방", 15, 20, true, 12, R.drawable.bookcover_sample, 3), + GroupCardItemRoomData("인플루언서 과학책", 22, 25, true, 9, R.drawable.bookcover_sample, 1), + GroupCardItemRoomData("유명작가 예술론", 16, 18, true, 11, R.drawable.bookcover_sample, 4) + ) + + _roomSections.value = listOf( + GroupRoomSectionData( + title = "마감 임박한 독서 모임방", + rooms = deadlineRooms, + genres = _genres + ), + GroupRoomSectionData( + title = "인기 있는 독서 모임방", + rooms = popularRooms, + genres = _genres + ), + GroupRoomSectionData( + title = "인플루언서·작가 독서 모임방", + rooms = influencerRooms, + genres = _genres + ) + ) + } + + fun onMyGroupHeaderClick() { + // 내 모임방 리스트로 이동 (Nav 이벤트 트리거 등) + } + + fun onMyGroupCardClick(data: GroupCardData) { + // 내 모임방 카드 클릭 (상세 진입) + } + + fun onRoomCardClick(data: GroupCardItemRoomData) { + // 방 카드 클릭 (상세 진입) + } + + fun onFabClick() { + // FAB 클릭(모임방 생성 등) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/ui/group/myroom/screen/GroupMyScreen.kt b/app/src/main/java/com/texthip/thip/ui/group/myroom/screen/GroupMyScreen.kt new file mode 100644 index 00000000..1938fbe2 --- /dev/null +++ b/app/src/main/java/com/texthip/thip/ui/group/myroom/screen/GroupMyScreen.kt @@ -0,0 +1,202 @@ +package com.texthip.thip.ui.group.myroom.screen + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.lazy.LazyColumn +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.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.texthip.thip.ui.common.cards.CardItemRoom +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.ui.Modifier +import com.texthip.thip.R +import com.texthip.thip.ui.theme.ThipTheme.colors +import androidx.compose.foundation.lazy.items +import com.texthip.thip.ui.common.topappbar.DefaultTopAppBar +import com.texthip.thip.ui.group.myroom.mock.GroupCardItemRoomData +import com.texthip.thip.ui.group.myroom.component.GroupMyRoomFilterRow +import com.texthip.thip.ui.theme.ThipTheme + +@Composable +fun GroupMyScreen( + allDataList: List, + onCardClick: (GroupCardItemRoomData) -> Unit = {} +) { + var selectedStates by remember { mutableStateOf(booleanArrayOf(false, false)) } + + val filteredList = remember(selectedStates, allDataList) { + if (selectedStates.all { !it } || selectedStates.all { it }) { + allDataList + } else if (selectedStates[0]) { + allDataList.filter { !it.isRecruiting } + } else if (selectedStates[1]) { + allDataList.filter { it.isRecruiting } + } else { + allDataList + } + } + + Column( + Modifier + .background(colors.Black) + .fillMaxSize() + ) { + DefaultTopAppBar( + title = stringResource(R.string.my_group_room), + onLeftClick = {}, + ) + Column( + Modifier + .background(colors.Black) + .fillMaxSize() + .padding(horizontal = 20.dp) + ) { + Spacer(modifier = Modifier.height(20.dp)) + + GroupMyRoomFilterRow( + selectedStates = selectedStates, + onToggle = { idx -> + selectedStates = selectedStates.copyOf().also { it[idx] = !it[idx] } + } + ) + + Spacer(modifier = Modifier.height(20.dp)) + + LazyColumn( + verticalArrangement = Arrangement.spacedBy(20.dp), + contentPadding = PaddingValues(bottom = 20.dp), + modifier = Modifier + .fillMaxSize() + ) { + items(filteredList) { item -> + CardItemRoom( + title = item.title, + participants = item.participants, + maxParticipants = item.maxParticipants, + isRecruiting = item.isRecruiting, + endDate = item.endDate, + imageRes = item.imageRes, + onClick = { onCardClick(item) } + ) + } + } + } + } +} + + +@Preview() +@Composable +fun MyGroupListFilterScreenPreview() { + ThipTheme { + val dataList = listOf( + GroupCardItemRoomData( + title = "모임방 이름입니다. 모임방...", + participants = 22, + maxParticipants = 30, + isRecruiting = true, + endDate = 3, + genreIndex = 0 + ), + GroupCardItemRoomData( + title = "모임방 이름입니다. 모임방...", + participants = 22, + maxParticipants = 30, + isRecruiting = false, + endDate = 30, + genreIndex = 0 + ), + GroupCardItemRoomData( + title = "모임방 이름입니다. 모임방...", + participants = 22, + maxParticipants = 30, + isRecruiting = true, + endDate = 1, + genreIndex = 0 + ), + GroupCardItemRoomData( + title = "모임방 이름입니다. 모임방...", + participants = 22, + maxParticipants = 30, + isRecruiting = false, + endDate = 3, + genreIndex = 0 + ), + GroupCardItemRoomData( + title = "모임방 이름입니다. 모임방...", + participants = 22, + maxParticipants = 30, + isRecruiting = true, + endDate = 3, + genreIndex = 0 + ), + GroupCardItemRoomData( + title = "모임방 이름입니다. 모임방...", + participants = 22, + maxParticipants = 30, + isRecruiting = false, + endDate = 30, + genreIndex = 0 + ), + GroupCardItemRoomData( + title = "모임방 이름입니다. 모임방...", + participants = 22, + maxParticipants = 30, + isRecruiting = true, + endDate = 1, + genreIndex = 0 + ), + GroupCardItemRoomData( + title = "모임방 이름입니다. 모임방...", + participants = 22, + maxParticipants = 30, + isRecruiting = false, + endDate = 3, + genreIndex = 0 + ), + GroupCardItemRoomData( + title = "모임방 이름입니다. 모임방...", + participants = 22, + maxParticipants = 30, + isRecruiting = true, + endDate = 3, + genreIndex = 0 + ), + GroupCardItemRoomData( + title = "모임방 이름입니다. 모임방...", + participants = 22, + maxParticipants = 30, + isRecruiting = false, + endDate = 30, + genreIndex = 0 + ), + GroupCardItemRoomData( + title = "모임방 이름입니다. 모임방...", + participants = 22, + maxParticipants = 30, + isRecruiting = true, + endDate = 1, + genreIndex = 0 + ), + GroupCardItemRoomData( + title = "모임방 이름입니다. 모임방...", + participants = 22, + maxParticipants = 30, + isRecruiting = false, + endDate = 3, + genreIndex = 0 + ) + ) + GroupMyScreen(allDataList = dataList) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/ui/group/myroom/screen/GroupRoomScreen.kt b/app/src/main/java/com/texthip/thip/ui/group/myroom/screen/GroupRoomScreen.kt new file mode 100644 index 00000000..8fa6adf6 --- /dev/null +++ b/app/src/main/java/com/texthip/thip/ui/group/myroom/screen/GroupRoomScreen.kt @@ -0,0 +1,408 @@ +package com.texthip.thip.ui.group.myroom.screen + +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.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.texthip.thip.R +import com.texthip.thip.ui.common.cards.CardItemRoomSmall +import com.texthip.thip.ui.common.cards.CardRoomBook +import com.texthip.thip.ui.common.topappbar.DefaultTopAppBar +import com.texthip.thip.ui.group.myroom.mock.GroupBookData +import com.texthip.thip.ui.group.myroom.mock.GroupBottomButtonType +import com.texthip.thip.ui.group.myroom.mock.GroupRoomData +import com.texthip.thip.ui.group.myroom.mock.GroupCardItemRoomData +import com.texthip.thip.ui.theme.ThipTheme +import com.texthip.thip.ui.theme.ThipTheme.colors +import com.texthip.thip.ui.theme.ThipTheme.typography + +@Composable +fun GroupRoomScreen( + detail: GroupRoomData, + buttonType: GroupBottomButtonType, + onBottomButtonClick: () -> Unit = {}, + onRecommendationClick: (GroupCardItemRoomData) -> Unit = {} +) { + Box(Modifier.fillMaxSize()) { + Column { + DefaultTopAppBar( + isRightIconVisible = false, + isTitleVisible = false, + onLeftClick = {}, + ) + + Column( + Modifier + .background(colors.Black) + .fillMaxSize() + .padding(start = 20.dp, end = 20.dp) + ) { + // TODO: 배경 이미지 추가 + Spacer(modifier = Modifier.height(20.dp)) + + //타이틀 + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = detail.title, + style = typography.bigtitle_b700_s22_h24, + color = colors.White + ) + if (detail.isSecret) { + Spacer(Modifier.width(2.dp)) + Icon( + painter = painterResource(id = R.drawable.ic_lock), + contentDescription = "비밀방", + tint = colors.White + ) + } else { + Spacer(Modifier.width(2.dp)) + Icon( + painter = painterResource(id = R.drawable.ic_unlock), + contentDescription = "오픈방", + tint = colors.White + ) + } + } + Spacer(modifier = Modifier.height(32.dp)) + + //소개글 + Text( + text = stringResource(R.string.group_room_desc), + style = typography.menu_sb600_s14_h24, + color = colors.White, + ) + + Text( + text = detail.description, + style = typography.copy_r400_s12_h20, + color = colors.Grey, + modifier = Modifier + .padding(top = 5.dp, bottom = 18.dp) + ) + + + Row( + Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + //모집 기간 + Column { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + painter = painterResource(id = R.drawable.ic_calendar), + contentDescription = "모임 활동기간", + tint = colors.White + ) + Spacer(Modifier.width(2.dp)) + Text( + text = stringResource(R.string.group_period), + style = typography.view_m500_s12_h20, + color = colors.White + ) + } + Spacer(Modifier.height(12.dp)) + Text( + text = stringResource( + R.string.group_room_period, + detail.startDate, + detail.endDate + ), + style = typography.info_r400_s12, + color = colors.Grey + ) + } + + //참여 인원 + Column { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + painter = painterResource(id = R.drawable.ic_group), + contentDescription = "참여 중인 독서 메이트", + tint = colors.White + ) + Spacer(Modifier.width(2.dp)) + Text( + text = stringResource(R.string.group_mate), + style = typography.view_m500_s12_h20, + color = colors.White + ) + } + Spacer(Modifier.height(12.dp)) + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = stringResource( + R.string.group_room_screen_participant_count, + detail.members + ), + style = typography.info_r400_s12, + color = colors.White + ) + Spacer(Modifier.width(2.dp)) + Text( + text = stringResource( + R.string.group_room_screen_participant_count_max, + detail.maxMembers + ), + style = typography.info_m500_s12, + color = colors.Grey + ) + } + } + } + + Spacer(Modifier.height(16.dp)) + Row( + verticalAlignment = Alignment.CenterVertically + ) { + // 모집 마감/장르 + Box( + Modifier + .background(colors.Grey03, shape = RoundedCornerShape(14.dp)) + .padding(horizontal = 12.dp, vertical = 8.dp) + ) { + Row { + Text( + text = stringResource(R.string.group_recruiting), + style = typography.info_m500_s12, + color = colors.White + ) + Spacer(Modifier.width(4.dp)) + Text( + text = stringResource( + R.string.group_room_screen_end_date, + detail.daysLeft + ), + style = typography.info_m500_s12, + color = colors.NeonGreen + ) + } + + } + Spacer(Modifier.width(12.dp)) + Box( + Modifier + .background(colors.Grey03, shape = RoundedCornerShape(14.dp)) + .padding(horizontal = 12.dp, vertical = 8.dp) + ) { + Row { + Text( + text = stringResource(R.string.group_genre), + style = typography.info_m500_s12, + color = colors.White + ) + Spacer(Modifier.width(4.dp)) + Text( + text = detail.genre, + style = typography.info_m500_s12, + color = colors.genreColor + ) + } + + } + } + + Spacer(Modifier.height(30.dp)) + + //읽을 책 정보 + CardRoomBook( + title = detail.bookData.title, + author = detail.bookData.author, + publisher = detail.bookData.publisher, + description = detail.bookData.description, + imageRes = detail.bookData.imageRes + ) + + Spacer(Modifier.height(40.dp)) + Text( + text = stringResource(R.string.group_recommend), + style = typography.smalltitle_sb600_s18_h24, + color = colors.White + ) + Spacer(Modifier.height(24.dp)) + + //추천 모임방 + LazyRow( + horizontalArrangement = Arrangement.spacedBy(20.dp) + ) { + items(detail.recommendations) { rec -> + CardItemRoomSmall( + title = rec.title, + participants = rec.participants, + maxParticipants = rec.maxParticipants, + endDate = rec.endDate, + imageRes = rec.imageRes, + onClick = { onRecommendationClick(rec) } + ) + } + } + + Spacer(Modifier.weight(1f)) + } + } + + // 하단 버튼 + val buttonText = when (buttonType) { + GroupBottomButtonType.JOIN -> stringResource(R.string.group_room_screen_participant) + GroupBottomButtonType.CANCEL -> stringResource(R.string.group_room_screen_cancel) + GroupBottomButtonType.CLOSE -> stringResource(R.string.group_room_screen_end) + } + val buttonColor = when (buttonType) { + GroupBottomButtonType.JOIN -> colors.Purple + GroupBottomButtonType.CANCEL -> colors.Red + GroupBottomButtonType.CLOSE -> colors.Grey02 + } + + Button( + onClick = onBottomButtonClick, + colors = ButtonDefaults.buttonColors( + containerColor = buttonColor + ), + modifier = Modifier + .align(Alignment.BottomCenter) + .fillMaxWidth() + .height(50.dp), + shape = RoundedCornerShape(0.dp) + ) { + Text( + text = buttonText, + style = typography.smalltitle_sb600_s18_h24, + color = colors.White + ) + } + } +} + +@Preview() +@Composable +fun GroupRoomDetailScreenPreview_AllCases() { + ThipTheme { + val recommendations = listOf( + GroupCardItemRoomData( + title = "일본 소설 좋아하는 사람들 일본 소설 좋아하는 사람들", + participants = 19, + maxParticipants = 25, + isRecruiting = true, + endDate = 2, + genreIndex = 0 + ), + GroupCardItemRoomData( + title = "일본 소설 좋아하는 사람들 일본 소설 좋아하는 사람들", + participants = 12, + maxParticipants = 16, + isRecruiting = true, + endDate = 6, + genreIndex = 0 + ), + GroupCardItemRoomData( + title = "일본 소설 좋아하는 사람들 일본 소설 좋아하는 사람들", + participants = 30, + maxParticipants = 30, + isRecruiting = false, + endDate = 0, + genreIndex = 0 + ), + GroupCardItemRoomData( + title = "일본 소설 좋아하는 사람들 일본 소설 좋아하는 사람들", + participants = 10, + maxParticipants = 12, + isRecruiting = true, + endDate = 8, + genreIndex = 0 + ), + GroupCardItemRoomData( + title = "에세이 나눔방", + participants = 14, + maxParticipants = 20, + isRecruiting = true, + endDate = 4, + genreIndex = 0 + ) + ) + + val bookData = GroupBookData( + title = "심장보다 단단한 토마토 한 알", + author = "고선지", + publisher = "푸른출판사", + description = "‘시집만 읽는 사람들’ 3월 모임에서 읽는 시집. 상처받고 단단해진 마음을 담은 감동적인 시와 해설이 어우러진 책으로, 읽는 이로 하여금 자신의 이야기를 투영하게 하는 힘이 있다.", + imageRes = R.drawable.bookcover_sample + ) + + val detailJoin = GroupRoomData( + title = "시집만 읽는 사람들 3월", + isSecret = true, + description = "‘시집만 읽는 사람들’ 3월 모임입니다. 이번 달 모임에서는 심장보다 단단한 토마토 한 알을 함께 읽어요.", + startDate = "2025.01.12", + endDate = "2025.02.12", + members = 22, + maxMembers = 30, + daysLeft = 4, + genre = "고전 문학", + bookData = bookData, + recommendations = recommendations + ) + val detailCancel = detailJoin.copy( + title = "참여 중인 독서모임", + isSecret = false, + members = 17 + ) + val detailHost = detailJoin.copy( + title = "내가 호스트인 독서모임", + isSecret = false, + members = 30, + maxMembers = 30 + ) + + Column(verticalArrangement = Arrangement.spacedBy(32.dp)) { + // 1. 참여 가능한 경우(참여하기) + GroupRoomScreen( + detail = detailJoin, + buttonType = GroupBottomButtonType.JOIN, + onBottomButtonClick = {} + ) + // 2. 참여 중인 경우(참여 취소하기) + GroupRoomScreen( + detail = detailCancel, + buttonType = GroupBottomButtonType.CANCEL, + onBottomButtonClick = {} + ) + // 3. 내가 호스트인 경우(모집 마감하기) + GroupRoomScreen( + detail = detailHost, + buttonType = GroupBottomButtonType.CLOSE, + onBottomButtonClick = {} + ) + } + } +} + + diff --git a/app/src/main/java/com/texthip/thip/ui/group/screen/GroupScreen.kt b/app/src/main/java/com/texthip/thip/ui/group/screen/GroupScreen.kt index e33da902..cfd918e8 100644 --- a/app/src/main/java/com/texthip/thip/ui/group/screen/GroupScreen.kt +++ b/app/src/main/java/com/texthip/thip/ui/group/screen/GroupScreen.kt @@ -1,19 +1,111 @@ package com.texthip.thip.ui.group.screen +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.Spacer import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material3.Text +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.navigation.NavController +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavHostController +import com.texthip.thip.R +import com.texthip.thip.ui.common.buttons.FloatingButton +import com.texthip.thip.ui.common.topappbar.LogoTopAppBar +import com.texthip.thip.ui.group.myroom.component.GroupMySectionHeader +import com.texthip.thip.ui.group.myroom.component.GroupPager +import com.texthip.thip.ui.group.myroom.component.GroupRoomDeadlineSection +import com.texthip.thip.ui.group.myroom.component.GroupSearchTextField +import com.texthip.thip.ui.group.myroom.mock.GroupViewModel +import com.texthip.thip.ui.theme.ThipTheme +import com.texthip.thip.ui.theme.ThipTheme.colors + @Composable -fun GroupScreen(navController: NavController) { +fun GroupScreen( + navController: NavHostController? = null, + viewModel: GroupViewModel = viewModel() +) { + val myGroups by viewModel.myGroups.collectAsState() + val roomSections by viewModel.roomSections.collectAsState() + val scrollState = rememberScrollState() + Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.Center + Modifier + .background(colors.Black) + .fillMaxSize() ) { - Text(text = "Group Screen") + Column( + Modifier + .fillMaxSize() + .verticalScroll(scrollState) + ) { + // 상단바 + LogoTopAppBar( + leftIcon = painterResource(R.drawable.ic_done), + hasNotification = false, + onLeftClick = { }, + onRightClick = { } + ) + Spacer(Modifier.height(16.dp)) + + // 검색창 + GroupSearchTextField(onValueChange = { }) + Spacer(Modifier.height(32.dp)) + + // 내 모임방 헤더 + 카드 + GroupMySectionHeader( + onClick = { viewModel.onMyGroupHeaderClick() } + ) + Spacer(Modifier.height(20.dp)) + + GroupPager( + groupCards = myGroups, + onCardClick = { viewModel.onMyGroupCardClick(it) } + ) + Spacer(Modifier.height(40.dp)) + + Spacer( + Modifier + .height(10.dp) + .fillMaxWidth() + .background(color = colors.DarkGrey02) + ) + Spacer(Modifier.height(32.dp)) + + // 마감 임박한 독서 모임방 + GroupRoomDeadlineSection( + roomSections = roomSections, + onRoomClick = { viewModel.onRoomCardClick(it) } + ) + Spacer(Modifier.height(32.dp)) + } + // 오른쪽 하단 FAB + FloatingButton( + icon = painterResource(id = R.drawable.ic_makegroup), + onClick = { viewModel.onFabClick() } + ) + } +} + + +@Preview() +@Composable +fun PreviewGroupScreen() { + ThipTheme { + val previewViewModel = remember { GroupViewModel() } + GroupScreen(viewModel = previewViewModel) } } \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/ui/myPage/screen/MyPageScreen.kt b/app/src/main/java/com/texthip/thip/ui/myPage/MyPageScreen.kt similarity index 74% rename from app/src/main/java/com/texthip/thip/ui/myPage/screen/MyPageScreen.kt rename to app/src/main/java/com/texthip/thip/ui/myPage/MyPageScreen.kt index 9195747e..0f35fe9a 100644 --- a/app/src/main/java/com/texthip/thip/ui/myPage/screen/MyPageScreen.kt +++ b/app/src/main/java/com/texthip/thip/ui/myPage/MyPageScreen.kt @@ -1,4 +1,4 @@ -package com.texthip.thip.ui.myPage.screen +package com.texthip.thip.ui.myPage import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize @@ -11,8 +11,8 @@ import androidx.navigation.NavController @Composable fun MyPageScreen(navController: NavController) { Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.Center + modifier = Modifier.Companion.fillMaxSize(), + contentAlignment = Alignment.Companion.Center ) { Text(text = "MyPage Screen") } diff --git a/app/src/main/java/com/texthip/thip/ui/navigator/MainNavHost.kt b/app/src/main/java/com/texthip/thip/ui/navigator/MainNavHost.kt index 16787c3f..19102a72 100644 --- a/app/src/main/java/com/texthip/thip/ui/navigator/MainNavHost.kt +++ b/app/src/main/java/com/texthip/thip/ui/navigator/MainNavHost.kt @@ -7,7 +7,7 @@ import androidx.navigation.NavHostController import com.texthip.thip.ui.bookSearch.screen.BookSearchScreen import com.texthip.thip.ui.feed.screen.FeedScreen import com.texthip.thip.ui.group.screen.GroupScreen -import com.texthip.thip.ui.myPage.screen.MyPageScreen +import com.texthip.thip.ui.myPage.MyPageScreen @Composable fun MainNavHost(navController: NavHostController) { diff --git a/app/src/main/java/com/texthip/thip/ui/theme/Color.kt b/app/src/main/java/com/texthip/thip/ui/theme/Color.kt index c5d2f5eb..945fd384 100644 --- a/app/src/main/java/com/texthip/thip/ui/theme/Color.kt +++ b/app/src/main/java/com/texthip/thip/ui/theme/Color.kt @@ -17,6 +17,7 @@ val Mint = Color(0xFFA0F8E8) val MintSub = Color(0xFF4FD9C0) val Orange = Color(0xFFFDB770) val OrangeSub = Color(0xFFFF8B17) +val genreColor = Color(0xFFB5B35D) val Skyblue = Color(0xFFA1D5FF) val SkyblueSub = Color(0xFF6DB5EE) val Lavendar = Color(0xFFC8A5FF) @@ -53,6 +54,7 @@ data class ThipColors( val MintSub: Color, val Orange: Color, val OrangeSub: Color, + val genreColor: Color, val Skyblue: Color, val SkyblueSub: Color, val Lavendar: Color, @@ -86,6 +88,7 @@ val defaultThipColors = ThipColors( MintSub = MintSub, Orange = Orange, OrangeSub = OrangeSub, + genreColor = genreColor, Skyblue = Skyblue, SkyblueSub = SkyblueSub, Lavendar = Lavendar, diff --git a/app/src/main/res/drawable/bookcover_sample_small.png b/app/src/main/res/drawable/bookcover_sample_small.png new file mode 100644 index 00000000..a59438f8 Binary files /dev/null and b/app/src/main/res/drawable/bookcover_sample_small.png differ diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 09dd92ee..18e266ad 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -38,8 +38,17 @@ 시간 전 - 모임 변경 + "%1$s일 뒤 " + 모집 마감 + 종료 + %1$s명 + 참여 + %1$s + / %1$s명 + %1$s일 뒤 모집 마감 + 도서 소개 + %1$s 저 기록장 🔥모임방의 뜨거운 감자 @@ -50,6 +59,32 @@ 님의 구독자 다음 페이지명 + + + + 진행중 + 모집중 + 내 모임방 + 소개글 + 모임 활동 기간 + 참여 중인 독서 메이트 + 이런 모임방은 어때요? + "모집" + "장르" + 마감 임박한 독서 모임방 + 내 모임방 + %1$s명 + 참여 + "%1$s님의 진행도 " + 모임방 참여할 사람! + %1$s + / %1$s명 + %1$s일 남음 + 참여하기 + 참여 취소하기 + 모집 마감하기 + %1$s ~ %2$s + %1$s 저 현재 페이지 %d % @@ -64,4 +99,5 @@ 독서메이트 님에게 답글 작성 메이트들과 간단한 인사를 나눠보세요! + \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 261b2b00..e91013f9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,6 +9,9 @@ lifecycleRuntimeKtx = "2.8.7" activityCompose = "1.10.1" composeBom = "2024.09.00" navigationCompose = "2.9.0" +navigationRuntimeAndroid = "2.9.0" +accompanistPager = "0.36.0" +accompanistPagerIndicators = "0.36.0" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -26,6 +29,9 @@ androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-man androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } androidx-material3 = { group = "androidx.compose.material3", name = "material3" } androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" } +androidx-navigation-runtime-android = { group = "androidx.navigation", name = "navigation-runtime-android", version.ref = "navigationRuntimeAndroid" } +accompanist-pager = { group = "com.google.accompanist", name = "accompanist-pager", version.ref = "accompanistPager" } +accompanist-pager-indicators = { group = "com.google.accompanist", name = "accompanist-pager-indicators", version.ref = "accompanistPagerIndicators" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" }