From 1f59b10451f0f14a81bc5e128a1802ee04cb42de Mon Sep 17 00:00:00 2001
From: JoGyuBin <128724038+rbqks529@users.noreply.github.com>
Date: Thu, 3 Jul 2025 16:09:47 +0900
Subject: [PATCH 01/21] =?UTF-8?q?[ui]:=20=EB=82=B4=20=EB=AA=A8=EC=9E=84?=
=?UTF-8?q?=EB=B0=A9=20Pager=20=EB=94=94=EC=9E=90=EC=9D=B8=20=EC=88=98?=
=?UTF-8?q?=EC=A0=95=20=EC=99=84=EB=A3=8C=20(#35)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.idea/deploymentTargetSelector.xml | 21 -------------------
.idea/misc.xml | 2 ++
.../ui/group/myroom/component/GenreChipRow.kt | 14 +++++++------
.../component/GroupDeadlineRoomSection.kt | 20 +++++++++++-------
.../group/myroom/component/GroupMainCard.kt | 2 +-
.../ui/group/myroom/component/GroupPager.kt | 12 +++++++----
6 files changed, 32 insertions(+), 39 deletions(-)
delete mode 100644 .idea/deploymentTargetSelector.xml
diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
deleted file mode 100644
index 00e85ee2..00000000
--- a/.idea/deploymentTargetSelector.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 1e536c6c..38860e7c 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,6 +1,8 @@
+
+
\ 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
index ae3fa69a..37d41cee 100644
--- 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
@@ -1,11 +1,10 @@
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.foundation.layout.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
import com.texthip.thip.ui.common.buttons.OptionChipButton
@Composable
@@ -15,8 +14,8 @@ fun GenreChipRow(
onSelect: (Int) -> Unit
) {
Row(
- Modifier.fillMaxWidth(),
- horizontalArrangement = Arrangement.SpaceEvenly
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.Center
) {
genres.forEachIndexed { idx, genre ->
OptionChipButton(
@@ -25,6 +24,9 @@ fun GenreChipRow(
isSelected = selectedIndex == idx,
onClick = { onSelect(idx) }
)
+ if (idx < genres.size - 1) {
+ Spacer(modifier = Modifier.width(4.dp))
+ }
}
}
}
@@ -37,4 +39,4 @@ fun PreviewGenreChipRow() {
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
index c7e3f7a8..565bd1ef 100644
--- 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
@@ -6,12 +6,14 @@ 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.OutlinedTextFieldDefaults.contentPadding
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.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@@ -29,8 +31,7 @@ fun GroupRoomDeadlineSection(
roomSections: List,
onRoomClick: (GroupCardItemRoomData) -> Unit
) {
- val cardWidth = 320.dp
- val pageSpacing = 12.dp
+ val sideMargin = 30.dp
val pagerState = rememberPagerState(
initialPage = 0,
@@ -48,16 +49,21 @@ fun GroupRoomDeadlineSection(
.height(588.dp),
contentAlignment = Alignment.Center
) {
- val horizontalPadding = ((maxWidth - cardWidth) / 2).coerceAtLeast(0.dp)
+ val horizontalPadding = sideMargin
+ val cardWidth = maxWidth - (horizontalPadding * 2)
+ val scale = 0.9f
+ val desiredGap = 12.dp // TODO: 이 부분을 10dp로 하면 양 옆의 카드에 살짝 다음 내용이 보여서 12정도가 어떤지
+
+ val pageSpacing = (-(cardWidth - (cardWidth * scale)) / 2) + desiredGap
HorizontalPager(
state = pagerState,
- contentPadding = PaddingValues(horizontal = horizontalPadding),
+ contentPadding = PaddingValues(horizontal = 30.dp),
pageSpacing = pageSpacing,
modifier = Modifier.fillMaxWidth()
) { page ->
val section = roomSections[page]
- var selectedGenre by remember { mutableStateOf(0) }
+ var selectedGenre by remember { mutableIntStateOf(0) }
val isCurrent = pagerState.currentPage == page
val scale = if (isCurrent) 1f else 0.9f
@@ -79,7 +85,7 @@ fun GroupRoomDeadlineSection(
),
shape = RoundedCornerShape(14.dp)
)
- .padding(vertical = 20.dp, horizontal = 12.dp)
+ .padding(vertical = 20.dp, horizontal = 20.dp)
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally
@@ -259,4 +265,4 @@ fun PreviewGroupRoomPagerSection() {
onRoomClick = {}
)
}
-}
+}
\ No newline at end of file
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
index 98ac640b..9b5a2d6b 100644
--- 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
@@ -57,7 +57,7 @@ fun GroupMainCard(
Card(
modifier = Modifier
- .width(320.dp)
+ .fillMaxWidth()
.height(176.dp)
.clickable { onClick() },
shape = RoundedCornerShape(18.dp),
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
index 69147a66..3b2ab2a2 100644
--- 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
@@ -21,16 +21,20 @@ fun GroupPager(
groupCards: List,
onCardClick: (GroupCardData) -> Unit
) {
- val cardWidth = 320.dp
- val pageSpacing = 6.dp
+ val scale = 0.86f
+ val desiredGap = 10.dp
BoxWithConstraints(
modifier = Modifier
.fillMaxWidth()
.height(192.dp)
) {
- val screenWidth = maxWidth
- val horizontalPadding = ((screenWidth - cardWidth) / 2).coerceAtLeast(0.dp)
+ val horizontalPadding = 30.dp
+ val cardWidth = maxWidth - (horizontalPadding * 2)
+
+ val pageSpacing = with(this) {
+ (-(cardWidth - (cardWidth * scale)) / 2f) + desiredGap
+ }
val pagerState = rememberPagerState(
initialPage = 0,
From 8f9846479cd0c1e1de6f88a1a0f597c1526cfe5f Mon Sep 17 00:00:00 2001
From: JoGyuBin <128724038+rbqks529@users.noreply.github.com>
Date: Sat, 5 Jul 2025 00:21:46 +0900
Subject: [PATCH 02/21] =?UTF-8?q?[ui]:=20GroupMakeRoomScreen=20=EC=B1=85?=
=?UTF-8?q?=20=EC=84=A0=ED=83=9D=20=EA=B5=AC=ED=98=84(=20(#35)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.idea/misc.xml | 1 -
.../thip/ui/common/cards/CardBookSearch.kt | 10 --
.../ui/common/forms/SearchBookTextField.kt | 105 ++++++++++++++++++
.../component/BookListWithScrollbar.kt | 72 ++++++++++++
.../component/BookSearchBottomSheet.kt | 98 ++++++++++++++++
.../makeroom/component/GroupSelectBook.kt | 83 ++++++++++++++
.../ui/group/makeroom/mock/GroupBookData.kt | 17 +++
.../makeroom/screen/GroupMakeRoomScreen.kt | 79 +++++++++++++
.../main/res/drawable/ic_x_circle_grey.xml | 16 +++
app/src/main/res/values/strings.xml | 3 +
10 files changed, 473 insertions(+), 11 deletions(-)
create mode 100644 app/src/main/java/com/texthip/thip/ui/common/forms/SearchBookTextField.kt
create mode 100644 app/src/main/java/com/texthip/thip/ui/group/makeroom/component/BookListWithScrollbar.kt
create mode 100644 app/src/main/java/com/texthip/thip/ui/group/makeroom/component/BookSearchBottomSheet.kt
create mode 100644 app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupSelectBook.kt
create mode 100644 app/src/main/java/com/texthip/thip/ui/group/makeroom/mock/GroupBookData.kt
create mode 100644 app/src/main/java/com/texthip/thip/ui/group/makeroom/screen/GroupMakeRoomScreen.kt
create mode 100644 app/src/main/res/drawable/ic_x_circle_grey.xml
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 38860e7c..62464c5c 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,4 +1,3 @@
-
diff --git a/app/src/main/java/com/texthip/thip/ui/common/cards/CardBookSearch.kt b/app/src/main/java/com/texthip/thip/ui/common/cards/CardBookSearch.kt
index 1bc7fa03..1528d708 100644
--- a/app/src/main/java/com/texthip/thip/ui/common/cards/CardBookSearch.kt
+++ b/app/src/main/java/com/texthip/thip/ui/common/cards/CardBookSearch.kt
@@ -28,7 +28,6 @@ import com.texthip.thip.ui.theme.ThipTheme.typography
@Composable
fun CardBookSearch(
modifier: Modifier = Modifier,
- number: Int,
title: String,
imageRes: Int? = R.drawable.bookcover_sample, // 기본 이미지 리소스
onClick: () -> Unit = {}
@@ -40,14 +39,6 @@ fun CardBookSearch(
.padding(vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically
) {
- // 넘버
- Text(
- text = "$number.",
- style = typography.menu_m500_s16_h24,
- color = colors.White,
- modifier = Modifier.padding(end = 12.dp)
- )
-
// 이미지
Box(
modifier = Modifier
@@ -84,7 +75,6 @@ fun CardBookSearchPreview() {
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
CardBookSearch(
- number = 1,
title = "단 한번의 삶"
)
}
diff --git a/app/src/main/java/com/texthip/thip/ui/common/forms/SearchBookTextField.kt b/app/src/main/java/com/texthip/thip/ui/common/forms/SearchBookTextField.kt
new file mode 100644
index 00000000..f841086e
--- /dev/null
+++ b/app/src/main/java/com/texthip/thip/ui/common/forms/SearchBookTextField.kt
@@ -0,0 +1,105 @@
+package com.texthip.thip.ui.common.forms
+
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+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.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.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+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 SearchBookTextField(
+ modifier: Modifier = Modifier,
+ hint: String,
+ onSearch: (String) -> Unit = {}
+) {
+ var text by rememberSaveable { mutableStateOf("") }
+ val myStyle = typography.menu_r400_s14_h24.copy(lineHeight = 14.sp)
+
+ Box(
+ modifier = modifier.height(48.dp)
+ ) {
+ OutlinedTextField(
+ value = text,
+ onValueChange = {
+ text = it
+ },
+ placeholder = {
+ Text(
+ text = hint,
+ color = colors.Grey02,
+ style = myStyle
+ )
+ },
+ textStyle = myStyle,
+ modifier = Modifier.fillMaxSize(),
+ shape = RoundedCornerShape(12.dp),
+ colors = TextFieldDefaults.colors(
+ focusedTextColor = colors.White,
+ focusedIndicatorColor = Color.Transparent,
+ unfocusedIndicatorColor = Color.Transparent,
+ focusedContainerColor = colors.DarkGrey02,
+ unfocusedContainerColor = colors.DarkGrey02,
+ cursorColor = colors.NeonGreen
+ ),
+ trailingIcon = {
+ Row(
+ horizontalArrangement = Arrangement.Center
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_x_circle_grey),
+ contentDescription = "Clear text",
+ modifier = Modifier
+ .clickable { text = "" },
+ tint = Color.Unspecified
+ )
+
+ Spacer(Modifier.width(20.dp))
+ Icon(
+ painter = painterResource(id = R.drawable.ic_search),
+ contentDescription = "Search",
+ modifier = Modifier
+ .clickable { onSearch(text) },
+ tint = colors.White
+ )
+ Spacer(Modifier.width(8.dp))
+ }
+ },
+ singleLine = true
+ )
+ }
+}
+
+@Preview()
+@Composable
+private fun SearchBookTextFieldPreview() {
+ SearchBookTextField(
+ hint = "책 제목, 저자검색",
+ onSearch = { /* 검색 실행 */ }
+ )
+}
diff --git a/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/BookListWithScrollbar.kt b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/BookListWithScrollbar.kt
new file mode 100644
index 00000000..b639b130
--- /dev/null
+++ b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/BookListWithScrollbar.kt
@@ -0,0 +1,72 @@
+package com.texthip.thip.ui.group.makeroom.component
+
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.texthip.thip.R
+import com.texthip.thip.ui.common.cards.CardBookSearch
+import com.texthip.thip.ui.group.makeroom.mock.BookData
+import com.texthip.thip.ui.common.modal.drawVerticalScrollbar
+import com.texthip.thip.ui.theme.ThipTheme
+
+@Composable
+fun BookListWithScrollbar(
+ books: List,
+ onBookClick: (BookData) -> Unit
+) {
+ val scrollState = rememberScrollState()
+
+ Box(
+ Modifier
+ .fillMaxWidth()
+ .height(320.dp)
+ ) {
+ Column(
+ Modifier
+ .verticalScroll(scrollState)
+ .fillMaxWidth()
+ ) {
+ books.forEach { book ->
+ CardBookSearch(
+ title = book.title,
+ imageRes = book.imageRes,
+ onClick = { onBookClick(book) }
+ )
+ }
+ }
+ // 커스텀 스크롤바
+ Box(
+ Modifier
+ .align(Alignment.CenterEnd)
+ .drawVerticalScrollbar(scrollState)
+ )
+ }
+}
+
+@Preview()
+@Composable
+fun PreviewBookListWithScrollbar() {
+ ThipTheme {
+ Column {
+ BookListWithScrollbar(
+ books = listOf(
+ BookData("단 한번의 삶", R.drawable.bookcover_sample),
+ BookData("토마토 컬러면", R.drawable.bookcover_sample),
+ BookData("사슴", R.drawable.bookcover_sample),
+ BookData("명작 읽기방", R.drawable.bookcover_sample),
+ BookData("또 다른 방", R.drawable.bookcover_sample)
+ ),
+ onBookClick = {}
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/BookSearchBottomSheet.kt b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/BookSearchBottomSheet.kt
new file mode 100644
index 00000000..5160c665
--- /dev/null
+++ b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/BookSearchBottomSheet.kt
@@ -0,0 +1,98 @@
+package com.texthip.thip.ui.group.makeroom.component
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.*
+import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.texthip.thip.ui.common.forms.SearchBookTextField
+import com.texthip.thip.ui.group.makeroom.mock.BookData
+import com.texthip.thip.ui.group.makeroom.mock.dummyGroupBooks
+import com.texthip.thip.ui.group.makeroom.mock.dummySavedBooks
+import com.texthip.thip.ui.theme.ThipTheme
+import com.texthip.thip.ui.theme.ThipTheme.colors
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun BookSearchBottomSheet(
+ onDismiss: () -> Unit,
+ onBookSelect: (BookData) -> Unit
+) {
+ var selectedTab by rememberSaveable { mutableIntStateOf(0) }
+ val tabs = listOf("저장한 책", "모임 책")
+
+ ModalBottomSheet(
+ onDismissRequest = onDismiss,
+ shape = RoundedCornerShape(topStart = 24.dp, topEnd = 24.dp),
+ containerColor = colors.DarkGrey
+ ) {
+ Column(
+ Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 16.dp, vertical = 12.dp)
+ ) {
+ // 검색창
+ SearchBookTextField(
+ hint = "책 제목, 저자검색",
+ onSearch = { /* 실제 검색 구현 시 여기에 */ }
+ )
+ Spacer(Modifier.height(20.dp))
+ // 탭
+ TabRow(
+ selectedTabIndex = selectedTab,
+ containerColor = colors.DarkGrey02,
+ contentColor = colors.White,
+ indicator = { tabPositions ->
+ TabRowDefaults.Indicator(
+ Modifier.tabIndicatorOffset(tabPositions[selectedTab]),
+ color = colors.White,
+ height = 2.dp
+ )
+ }
+ ) {
+ tabs.forEachIndexed { idx, tab ->
+ Tab(
+ selected = selectedTab == idx,
+ onClick = { selectedTab = idx },
+ text = { Text(tab, color = if (selectedTab == idx) colors.White else colors.Grey03) }
+ )
+ }
+ }
+ Spacer(Modifier.height(10.dp))
+ // 리스트 + 스크롤바
+ BookListWithScrollbar(
+ books = if (selectedTab == 0) dummySavedBooks else dummyGroupBooks,
+ onBookClick = onBookSelect
+ )
+ }
+ }
+}
+
+@Preview()
+@Composable
+fun PreviewBookSearchBottomSheet() {
+ ThipTheme {
+ var showSheet by remember { mutableStateOf(true) }
+ val onBookSelect: (BookData) -> Unit = {}
+
+ if (showSheet) {
+ BookSearchBottomSheet(
+ onDismiss = { showSheet = false },
+ onBookSelect = onBookSelect
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupSelectBook.kt b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupSelectBook.kt
new file mode 100644
index 00000000..5c08aecb
--- /dev/null
+++ b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupSelectBook.kt
@@ -0,0 +1,83 @@
+package com.texthip.thip.ui.group.makeroom.component
+
+import android.R.attr.contentDescription
+import android.R.attr.onClick
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.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.material.icons.Icons
+import androidx.compose.material.icons.filled.Search
+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.painter.Painter
+import androidx.compose.ui.res.painterResource
+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 GroupSelectBook(modifier: Modifier = Modifier, onButtonClick: () -> Unit = {}) {
+ Column(
+ modifier = modifier.fillMaxWidth(),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Text(
+ text = "책 선택 화면",
+ style = typography.smalltitle_sb600_s18_h24,
+ color = colors.White,
+ modifier = Modifier.align(Alignment.Start)
+ )
+ Spacer(modifier = Modifier.padding(top = 20.dp))
+
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.Center,
+ modifier = modifier
+ .clickable { onButtonClick() }
+ .fillMaxWidth()
+ .height(44.dp)
+ .border(
+ BorderStroke(1.dp, colors.Grey02),
+ shape = RoundedCornerShape(12.dp)
+ )
+ .padding(vertical = 10.dp)
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_search),
+ contentDescription = "검색 아이콘",
+ tint = colors.Grey01
+ )
+ Spacer(modifier = Modifier.width(8.dp))
+ Text(
+ text = "검색해서 찾기",
+ style = typography.menu_m500_s16_h24,
+ color = colors.Grey
+ )
+ }
+ }
+}
+
+@Preview
+@Composable
+private fun GroupSelectBookPreview() {
+ ThipTheme {
+ GroupSelectBook()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/texthip/thip/ui/group/makeroom/mock/GroupBookData.kt b/app/src/main/java/com/texthip/thip/ui/group/makeroom/mock/GroupBookData.kt
new file mode 100644
index 00000000..423f24b9
--- /dev/null
+++ b/app/src/main/java/com/texthip/thip/ui/group/makeroom/mock/GroupBookData.kt
@@ -0,0 +1,17 @@
+package com.texthip.thip.ui.group.makeroom.mock
+
+import com.texthip.thip.R
+
+data class BookData(
+ val title: String,
+ val imageRes: Int // drawable 리소스 or 이미지 URL
+)
+
+val dummySavedBooks = listOf(
+ BookData("토마토 컬러면", R.drawable.bookcover_sample),
+ BookData("사슴", R.drawable.bookcover_sample)
+)
+val dummyGroupBooks = listOf(
+ BookData("명작 읽기방", R.drawable.bookcover_sample),
+ BookData("또 다른 방", R.drawable.bookcover_sample)
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/texthip/thip/ui/group/makeroom/screen/GroupMakeRoomScreen.kt b/app/src/main/java/com/texthip/thip/ui/group/makeroom/screen/GroupMakeRoomScreen.kt
new file mode 100644
index 00000000..7d209735
--- /dev/null
+++ b/app/src/main/java/com/texthip/thip/ui/group/makeroom/screen/GroupMakeRoomScreen.kt
@@ -0,0 +1,79 @@
+package com.texthip.thip.ui.group.makeroom.screen
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.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.topappbar.InputTopAppBar
+import com.texthip.thip.ui.group.makeroom.component.GroupSelectBook
+import com.texthip.thip.ui.theme.ThipTheme
+import com.texthip.thip.ui.theme.ThipTheme.colors
+
+@Composable
+fun GroupMakeRoomScreen(modifier: Modifier = Modifier) {
+ var isButtonEnable by remember { mutableStateOf(false) }
+ val scrollState = rememberScrollState()
+
+ Column(
+ modifier = modifier
+ .verticalScroll(scrollState)
+ .fillMaxSize(),
+ verticalArrangement = Arrangement.Top,
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ InputTopAppBar(
+ title = stringResource(R.string.group_making_group),
+ isRightButtonEnabled = isButtonEnable,
+ onLeftClick = {},
+ onRightClick = {}
+ )
+ Spacer(modifier = Modifier.padding(top = 20.dp))
+
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(horizontal = 20.dp),
+ verticalArrangement = Arrangement.Top,
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ GroupSelectBook(
+ onButtonClick = { }
+ )
+ Spacer(modifier = Modifier.padding(top = 32.dp))
+ Spacer(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(1.dp)
+ .background(colors.DarkGrey02)
+ )
+ Spacer(modifier = Modifier.padding(top = 32.dp))
+
+
+ }
+ }
+}
+
+@Preview
+@Composable
+private fun GroupMakeRoomScreenPreview() {
+ ThipTheme {
+ GroupMakeRoomScreen()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_x_circle_grey.xml b/app/src/main/res/drawable/ic_x_circle_grey.xml
new file mode 100644
index 00000000..300f0c30
--- /dev/null
+++ b/app/src/main/res/drawable/ic_x_circle_grey.xml
@@ -0,0 +1,16 @@
+
+
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 1ddabbcc..6c32386d 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -158,4 +158,7 @@
님에게 답글 작성
메이트들과 간단한 인사를 나눠보세요!
+
+ 모임 만들기
+
\ No newline at end of file
From 1163694b26a05005f8d6d450d746df0f19f3ae7f Mon Sep 17 00:00:00 2001
From: JoGyuBin <128724038+rbqks529@users.noreply.github.com>
Date: Sat, 5 Jul 2025 03:10:44 +0900
Subject: [PATCH 03/21] =?UTF-8?q?[ui]:=20=EC=BB=A4=EC=8A=A4=ED=85=80=20Bot?=
=?UTF-8?q?tomSheet=20=EC=B6=94=EA=B0=80,=20=EC=B1=85=20=EC=B6=94=EA=B0=80?=
=?UTF-8?q?=20=EC=9A=94=EC=B2=AD=20=ED=99=94=EB=A9=B4=20=EC=B6=94=EA=B0=80?=
=?UTF-8?q?=20(#35)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.idea/deviceManager.xml | 13 ++
.../common/bottomsheet/CustomBottomSheet.kt | 144 ++++++++++++++++++
.../component/EmptyBookSheetContent.kt | 61 ++++++++
3 files changed, 218 insertions(+)
create mode 100644 .idea/deviceManager.xml
create mode 100644 app/src/main/java/com/texthip/thip/ui/common/bottomsheet/CustomBottomSheet.kt
create mode 100644 app/src/main/java/com/texthip/thip/ui/group/makeroom/component/EmptyBookSheetContent.kt
diff --git a/.idea/deviceManager.xml b/.idea/deviceManager.xml
new file mode 100644
index 00000000..91f95584
--- /dev/null
+++ b/.idea/deviceManager.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/texthip/thip/ui/common/bottomsheet/CustomBottomSheet.kt b/app/src/main/java/com/texthip/thip/ui/common/bottomsheet/CustomBottomSheet.kt
new file mode 100644
index 00000000..3d0f5819
--- /dev/null
+++ b/app/src/main/java/com/texthip/thip/ui/common/bottomsheet/CustomBottomSheet.kt
@@ -0,0 +1,144 @@
+package com.texthip.thip.ui.common.bottomsheet
+
+// com.texthip.thip.ui.common.bottomsheet.CustomBottomSheet.kt
+
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.gestures.detectVerticalDragGestures
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Button
+import androidx.compose.material3.Text
+import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.zIndex
+import com.texthip.thip.ui.theme.ThipTheme
+import com.texthip.thip.ui.theme.ThipTheme.colors
+import kotlinx.coroutines.launch
+
+@Composable
+fun CustomBottomSheet(
+ onDismiss: () -> Unit,
+ // 핵심: ColumnScope로 slot 전달!
+ content: @Composable ColumnScope.() -> Unit
+) {
+ val scope = rememberCoroutineScope()
+ val animatableOffset = remember { Animatable(300f) }
+ var offsetY by remember { mutableFloatStateOf(0f) }
+ var isDismissing by remember { mutableStateOf(false) }
+
+ // 등장 애니메이션
+ LaunchedEffect(Unit) {
+ animatableOffset.animateTo(
+ targetValue = 0f,
+ animationSpec = tween(durationMillis = 300)
+ )
+ }
+
+ // 바깥 클릭 감지
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .clickable(
+ indication = null,
+ interactionSource = remember { MutableInteractionSource() }
+ ) {
+ if (!isDismissing) {
+ isDismissing = true
+ scope.launch {
+ animatableOffset.animateTo(300f, tween(300))
+ onDismiss()
+ }
+ }
+ }
+ .zIndex(1f)
+ )
+
+ // BottomSheet 본체
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .zIndex(2f),
+ contentAlignment = Alignment.BottomCenter
+ ) {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .offset(y = (offsetY + animatableOffset.value).dp)
+ .background(
+ color = colors.DarkGrey,
+ shape = RoundedCornerShape(topEnd = 12.dp, topStart = 12.dp)
+ )
+ .pointerInput(Unit) {
+ detectVerticalDragGestures(
+ onVerticalDrag = { _, dragAmount ->
+ if (dragAmount > 0) {
+ offsetY += dragAmount / 2
+ }
+ },
+ onDragEnd = {
+ if (offsetY > 100f && !isDismissing) {
+ isDismissing = true
+ scope.launch {
+ animatableOffset.animateTo(300f, tween(300))
+ onDismiss()
+ }
+ } else {
+ offsetY = 0f
+ }
+ }
+ )
+ }
+ .clickable(enabled = true) {} // 내부 클릭 먹히게
+ ) {
+ Column(modifier = Modifier.fillMaxWidth()) {
+ content() // <--- 이 부분이 핵심! slot에 원하는 Compose UI를 전달
+ }
+ }
+ }
+}
+
+
+@Preview()
+@Composable
+fun PreviewCustomBottomSheet() {
+ var showSheet by remember { mutableStateOf(true) }
+
+ ThipTheme {
+ Box(Modifier.fillMaxSize()) {
+ // 배경 컨텐츠 예시
+ Text(
+ text = "Main Content Area",
+ color = Color.White,
+ modifier = Modifier
+ .align(Alignment.Center)
+ )
+
+ if (showSheet) {
+ CustomBottomSheet(
+ onDismiss = { showSheet = false }
+ ) {
+ Text(
+ "바텀 시트 예시",
+ color = Color.White,
+ modifier = Modifier.padding(bottom = 16.dp)
+ )
+ Button(
+ onClick = { showSheet = false },
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Text("닫기", color = Color.Black)
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/EmptyBookSheetContent.kt b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/EmptyBookSheetContent.kt
new file mode 100644
index 00000000..d0e15fe7
--- /dev/null
+++ b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/EmptyBookSheetContent.kt
@@ -0,0 +1,61 @@
+package com.texthip.thip.ui.group.makeroom.component
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.*
+import androidx.compose.material3.Button
+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.buttons.ActionMediumButton
+import com.texthip.thip.ui.theme.ThipTheme
+import com.texthip.thip.ui.theme.ThipTheme.colors
+
+@Composable
+fun EmptyBookSheetContent(
+ onRequestBook: () -> Unit
+) {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(bottom = 30.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Image(
+ painter = painterResource(id = R.drawable.ic_notice),
+ contentDescription = null
+ )
+ Spacer(Modifier.height(12.dp))
+ Text(
+ text = stringResource(R.string.group_register_book_comment),
+ color = ThipTheme.colors.Grey02,
+ style = ThipTheme.typography.copy_m500_s14_h20
+ )
+ Spacer(Modifier.height(24.dp))
+
+ ActionMediumButton(
+ text = stringResource(R.string.group_register_book),
+ contentColor = colors.White,
+ backgroundColor = colors.Purple,
+ modifier = Modifier
+ .width(97.dp)
+ .height(44.dp),
+ onClick = { onRequestBook() },
+ )
+ }
+}
+
+@Preview
+@Composable
+private fun EmptyBookSheetContentPreview() {
+ ThipTheme {
+ EmptyBookSheetContent(
+ onRequestBook = {}
+ )
+ }
+}
From 64753aa0b69d1360f8ce85d0e5fb373924cc1e8d Mon Sep 17 00:00:00 2001
From: JoGyuBin <128724038+rbqks529@users.noreply.github.com>
Date: Sat, 5 Jul 2025 03:11:26 +0900
Subject: [PATCH 04/21] =?UTF-8?q?[ui]:=20=EC=B1=85=20=EC=84=A0=ED=83=9D=20?=
=?UTF-8?q?=ED=99=94=EB=A9=B4=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20=EA=B5=AC?=
=?UTF-8?q?=ED=98=84=20=EC=99=84=EB=A3=8C=20(#35)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../thip/ui/common/cards/CardBookSearch.kt | 3 +-
.../component/BookListWithScrollbar.kt | 34 ++---
.../component/BookSearchBottomSheet.kt | 118 ++++++++++++------
.../ui/group/makeroom/mock/GroupBookData.kt | 12 ++
.../makeroom/screen/GroupMakeRoomScreen.kt | 96 ++++++++------
.../screen/GroupRegisterBookScreen.kt | 62 +++++++++
app/src/main/res/drawable/ic_notice.xml | 22 +---
app/src/main/res/values/strings.xml | 8 ++
8 files changed, 240 insertions(+), 115 deletions(-)
create mode 100644 app/src/main/java/com/texthip/thip/ui/group/makeroom/screen/GroupRegisterBookScreen.kt
diff --git a/app/src/main/java/com/texthip/thip/ui/common/cards/CardBookSearch.kt b/app/src/main/java/com/texthip/thip/ui/common/cards/CardBookSearch.kt
index 1528d708..e9cdb408 100644
--- a/app/src/main/java/com/texthip/thip/ui/common/cards/CardBookSearch.kt
+++ b/app/src/main/java/com/texthip/thip/ui/common/cards/CardBookSearch.kt
@@ -35,8 +35,7 @@ fun CardBookSearch(
Row(
modifier = modifier
.fillMaxWidth()
- .clickable { onClick() }
- .padding(vertical = 8.dp),
+ .clickable { onClick() },
verticalAlignment = Alignment.CenterVertically
) {
// 이미지
diff --git a/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/BookListWithScrollbar.kt b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/BookListWithScrollbar.kt
index b639b130..a889bcef 100644
--- a/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/BookListWithScrollbar.kt
+++ b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/BookListWithScrollbar.kt
@@ -1,22 +1,24 @@
package com.texthip.thip.ui.group.makeroom.component
-
+import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.texthip.thip.R
import com.texthip.thip.ui.common.cards.CardBookSearch
-import com.texthip.thip.ui.group.makeroom.mock.BookData
import com.texthip.thip.ui.common.modal.drawVerticalScrollbar
+import com.texthip.thip.ui.group.makeroom.mock.BookData
import com.texthip.thip.ui.theme.ThipTheme
+import com.texthip.thip.ui.theme.ThipTheme.colors
+
@Composable
fun BookListWithScrollbar(
@@ -33,6 +35,7 @@ fun BookListWithScrollbar(
Column(
Modifier
.verticalScroll(scrollState)
+ .drawVerticalScrollbar(scrollState)
.fillMaxWidth()
) {
books.forEach { book ->
@@ -41,14 +44,16 @@ fun BookListWithScrollbar(
imageRes = book.imageRes,
onClick = { onBookClick(book) }
)
+
+ Spacer(modifier = Modifier.height(12.dp))
+ Spacer(modifier = Modifier
+ .fillMaxWidth()
+ .height(1.dp)
+ .background(color = colors.Grey02)
+ )
+ Spacer(modifier = Modifier.height(12.dp))
}
}
- // 커스텀 스크롤바
- Box(
- Modifier
- .align(Alignment.CenterEnd)
- .drawVerticalScrollbar(scrollState)
- )
}
}
@@ -58,15 +63,10 @@ fun PreviewBookListWithScrollbar() {
ThipTheme {
Column {
BookListWithScrollbar(
- books = listOf(
- BookData("단 한번의 삶", R.drawable.bookcover_sample),
- BookData("토마토 컬러면", R.drawable.bookcover_sample),
- BookData("사슴", R.drawable.bookcover_sample),
- BookData("명작 읽기방", R.drawable.bookcover_sample),
- BookData("또 다른 방", R.drawable.bookcover_sample)
- ),
+ books = List(20) { BookData("Book $it", R.drawable.bookcover_sample) },
onBookClick = {}
)
}
}
-}
\ No newline at end of file
+}
+
diff --git a/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/BookSearchBottomSheet.kt b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/BookSearchBottomSheet.kt
index 5160c665..d637b288 100644
--- a/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/BookSearchBottomSheet.kt
+++ b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/BookSearchBottomSheet.kt
@@ -5,9 +5,6 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material3.*
-import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
@@ -16,82 +13,123 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
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.texthip.thip.R
import com.texthip.thip.ui.common.forms.SearchBookTextField
import com.texthip.thip.ui.group.makeroom.mock.BookData
import com.texthip.thip.ui.group.makeroom.mock.dummyGroupBooks
import com.texthip.thip.ui.group.makeroom.mock.dummySavedBooks
import com.texthip.thip.ui.theme.ThipTheme
import com.texthip.thip.ui.theme.ThipTheme.colors
+import com.texthip.thip.ui.common.bottomsheet.CustomBottomSheet
+import com.texthip.thip.ui.common.header.HeaderMenuBarTab
-@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun BookSearchBottomSheet(
onDismiss: () -> Unit,
- onBookSelect: (BookData) -> Unit
+ onBookSelect: (BookData) -> Unit,
+ onRequestBook: () -> Unit,
+ savedBooks: List = emptyList(),
+ groupBooks: List = emptyList(),
+ defaultTab: Int = 0
) {
- var selectedTab by rememberSaveable { mutableIntStateOf(0) }
- val tabs = listOf("저장한 책", "모임 책")
+ // 책이 있는지 여부 체크
+ val hasBooks = savedBooks.isNotEmpty() || groupBooks.isNotEmpty()
+ var selectedTab by rememberSaveable { mutableIntStateOf(defaultTab) }
+ val tabs = listOf(
+ stringResource(R.string.group_saved_book), stringResource(R.string.group_book)
+ )
+ val books = if (selectedTab == 0) savedBooks else groupBooks
- ModalBottomSheet(
- onDismissRequest = onDismiss,
- shape = RoundedCornerShape(topStart = 24.dp, topEnd = 24.dp),
- containerColor = colors.DarkGrey
+ CustomBottomSheet(
+ onDismiss = onDismiss
) {
Column(
Modifier
.fillMaxWidth()
- .padding(horizontal = 16.dp, vertical = 12.dp)
+ .padding(start = 20.dp, end = 20.dp, top = 20.dp)
) {
// 검색창
SearchBookTextField(
- hint = "책 제목, 저자검색",
- onSearch = { /* 실제 검색 구현 시 여기에 */ }
+ hint = stringResource(R.string.group_book_search_hint),
+ onSearch = { /* 검색 구현 */ }
)
Spacer(Modifier.height(20.dp))
- // 탭
- TabRow(
+ }
+
+ if (hasBooks) {
+ HeaderMenuBarTab(
+ titles = tabs,
selectedTabIndex = selectedTab,
- containerColor = colors.DarkGrey02,
- contentColor = colors.White,
- indicator = { tabPositions ->
- TabRowDefaults.Indicator(
- Modifier.tabIndicatorOffset(tabPositions[selectedTab]),
- color = colors.White,
- height = 2.dp
- )
- }
+ onTabSelected = { selectedTab = it },
+ indicatorColor = ThipTheme.colors.White,
+ modifier = Modifier.fillMaxWidth()
+ )
+
+ Column(
+ Modifier
+ .fillMaxWidth()
+ .padding(start = 20.dp, end = 20.dp, bottom = 20.dp)
) {
- tabs.forEachIndexed { idx, tab ->
- Tab(
- selected = selectedTab == idx,
- onClick = { selectedTab = idx },
- text = { Text(tab, color = if (selectedTab == idx) colors.White else colors.Grey03) }
+ Spacer(Modifier.height(20.dp))
+ if (books.isEmpty()) {
+ EmptyBookSheetContent(onRequestBook = onRequestBook)
+ } else {
+ BookListWithScrollbar(
+ books = books,
+ onBookClick = onBookSelect
)
}
}
- Spacer(Modifier.height(10.dp))
- // 리스트 + 스크롤바
- BookListWithScrollbar(
- books = if (selectedTab == 0) dummySavedBooks else dummyGroupBooks,
- onBookClick = onBookSelect
- )
+ } else {
+ // 탭 없이 바로 안내화면
+ Column(
+ Modifier
+ .fillMaxWidth()
+ .padding(start = 20.dp, end = 20.dp, bottom = 20.dp)
+ ) {
+ Spacer(Modifier.height(20.dp))
+ EmptyBookSheetContent(onRequestBook = onRequestBook)
+ }
}
}
}
-@Preview()
+
+
+@Preview(showBackground = true)
@Composable
-fun PreviewBookSearchBottomSheet() {
+fun PreviewBookSearchBottomSheet_HasBooks() {
ThipTheme {
var showSheet by remember { mutableStateOf(true) }
- val onBookSelect: (BookData) -> Unit = {}
+ if (showSheet) {
+ BookSearchBottomSheet(
+ onDismiss = { showSheet = false },
+ onBookSelect = {},
+ onRequestBook = {},
+ savedBooks = dummySavedBooks, // 데이터 있음
+ groupBooks = dummyGroupBooks,
+ defaultTab = 0
+ )
+ }
+ }
+}
+@Preview(showBackground = true)
+@Composable
+fun PreviewBookSearchBottomSheet_Empty() {
+ ThipTheme {
+ var showSheet by remember { mutableStateOf(true) }
if (showSheet) {
BookSearchBottomSheet(
onDismiss = { showSheet = false },
- onBookSelect = onBookSelect
+ onBookSelect = {},
+ onRequestBook = {},
+ savedBooks = emptyList(), // 데이터 없음
+ groupBooks = emptyList(),
+ defaultTab = 0
)
}
}
diff --git a/app/src/main/java/com/texthip/thip/ui/group/makeroom/mock/GroupBookData.kt b/app/src/main/java/com/texthip/thip/ui/group/makeroom/mock/GroupBookData.kt
index 423f24b9..c8daa613 100644
--- a/app/src/main/java/com/texthip/thip/ui/group/makeroom/mock/GroupBookData.kt
+++ b/app/src/main/java/com/texthip/thip/ui/group/makeroom/mock/GroupBookData.kt
@@ -8,10 +8,22 @@ data class BookData(
)
val dummySavedBooks = listOf(
+ BookData("토마토 컬러면", R.drawable.bookcover_sample),
+ BookData("사슴", R.drawable.bookcover_sample),
+ BookData("토마토 컬러면", R.drawable.bookcover_sample),
+ BookData("사슴", R.drawable.bookcover_sample),
+ BookData("토마토 컬러면", R.drawable.bookcover_sample),
+ BookData("사슴", R.drawable.bookcover_sample),
BookData("토마토 컬러면", R.drawable.bookcover_sample),
BookData("사슴", R.drawable.bookcover_sample)
)
val dummyGroupBooks = listOf(
+ BookData("명작 읽기방", R.drawable.bookcover_sample),
+ BookData("또 다른 방", R.drawable.bookcover_sample),
+ BookData("명작 읽기방", R.drawable.bookcover_sample),
+ BookData("또 다른 방", R.drawable.bookcover_sample),
+ BookData("명작 읽기방", R.drawable.bookcover_sample),
+ BookData("또 다른 방", R.drawable.bookcover_sample),
BookData("명작 읽기방", R.drawable.bookcover_sample),
BookData("또 다른 방", R.drawable.bookcover_sample)
)
\ No newline at end of file
diff --git a/app/src/main/java/com/texthip/thip/ui/group/makeroom/screen/GroupMakeRoomScreen.kt b/app/src/main/java/com/texthip/thip/ui/group/makeroom/screen/GroupMakeRoomScreen.kt
index 7d209735..b007d4b8 100644
--- a/app/src/main/java/com/texthip/thip/ui/group/makeroom/screen/GroupMakeRoomScreen.kt
+++ b/app/src/main/java/com/texthip/thip/ui/group/makeroom/screen/GroupMakeRoomScreen.kt
@@ -1,28 +1,23 @@
package com.texthip.thip.ui.group.makeroom.screen
import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
-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.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.blur
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.topappbar.InputTopAppBar
import com.texthip.thip.ui.group.makeroom.component.GroupSelectBook
+import com.texthip.thip.ui.group.makeroom.component.BookSearchBottomSheet
+import com.texthip.thip.ui.group.makeroom.mock.BookData
+import com.texthip.thip.ui.group.makeroom.mock.dummySavedBooks
+import com.texthip.thip.ui.group.makeroom.mock.dummyGroupBooks
import com.texthip.thip.ui.theme.ThipTheme
import com.texthip.thip.ui.theme.ThipTheme.colors
@@ -30,42 +25,63 @@ import com.texthip.thip.ui.theme.ThipTheme.colors
fun GroupMakeRoomScreen(modifier: Modifier = Modifier) {
var isButtonEnable by remember { mutableStateOf(false) }
val scrollState = rememberScrollState()
+ var showBookSearchSheet by remember { mutableStateOf(false) }
- Column(
- modifier = modifier
- .verticalScroll(scrollState)
- .fillMaxSize(),
- verticalArrangement = Arrangement.Top,
- horizontalAlignment = Alignment.CenterHorizontally
- ) {
- InputTopAppBar(
- title = stringResource(R.string.group_making_group),
- isRightButtonEnabled = isButtonEnable,
- onLeftClick = {},
- onRightClick = {}
- )
- Spacer(modifier = Modifier.padding(top = 20.dp))
-
+ Box {
Column(
- modifier = Modifier
+ modifier = modifier
+ .verticalScroll(scrollState)
.fillMaxSize()
- .padding(horizontal = 20.dp),
+ .then(
+ if (showBookSearchSheet) Modifier.blur(5.dp) else Modifier
+ ),
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.CenterHorizontally
) {
- GroupSelectBook(
- onButtonClick = { }
- )
- Spacer(modifier = Modifier.padding(top = 32.dp))
- Spacer(
- modifier = Modifier
- .fillMaxWidth()
- .height(1.dp)
- .background(colors.DarkGrey02)
+ InputTopAppBar(
+ title = stringResource(R.string.group_making_group),
+ isRightButtonEnabled = isButtonEnable,
+ onLeftClick = {},
+ onRightClick = {}
)
- Spacer(modifier = Modifier.padding(top = 32.dp))
+ Spacer(modifier = Modifier.padding(top = 20.dp))
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(horizontal = 20.dp),
+ verticalArrangement = Arrangement.Top,
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ GroupSelectBook(
+ onButtonClick = { showBookSearchSheet = true } // 검색해서 찾기 버튼에서 호출
+ )
+ Spacer(modifier = Modifier.padding(top = 32.dp))
+ Spacer(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(1.dp)
+ .background(colors.DarkGrey02)
+ )
+ Spacer(modifier = Modifier.padding(top = 32.dp))
+ }
+ }
+ if (showBookSearchSheet) {
+ BookSearchBottomSheet(
+ onDismiss = { showBookSearchSheet = false },
+ onBookSelect = { _: BookData ->
+ // 책 선택 처리
+ showBookSearchSheet = false
+ },
+ onRequestBook = {
+ // 책 신청하기 버튼
+ showBookSearchSheet = false
+ },
+ savedBooks = dummySavedBooks,
+ groupBooks = dummyGroupBooks,
+ defaultTab = 0
+ )
}
}
}
@@ -76,4 +92,4 @@ private fun GroupMakeRoomScreenPreview() {
ThipTheme {
GroupMakeRoomScreen()
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/com/texthip/thip/ui/group/makeroom/screen/GroupRegisterBookScreen.kt b/app/src/main/java/com/texthip/thip/ui/group/makeroom/screen/GroupRegisterBookScreen.kt
new file mode 100644
index 00000000..68f86129
--- /dev/null
+++ b/app/src/main/java/com/texthip/thip/ui/group/makeroom/screen/GroupRegisterBookScreen.kt
@@ -0,0 +1,62 @@
+package com.texthip.thip.ui.group.makeroom.screen
+
+import androidx.compose.foundation.layout.Arrangement
+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.material.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.texthip.thip.R
+import com.texthip.thip.ui.common.topappbar.DefaultTopAppBar
+import com.texthip.thip.ui.common.topappbar.InputTopAppBar
+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 GroupRegisterBookScreen(modifier: Modifier = Modifier) {
+ Column(
+ modifier = modifier
+ .fillMaxSize(),
+ verticalArrangement = Arrangement.Top,
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ DefaultTopAppBar(
+ title = stringResource(R.string.group_request_book),
+ onLeftClick = {},
+ )
+ Column (
+ modifier = Modifier
+ .fillMaxSize(),
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Text(
+ text = stringResource(R.string.group_thip_email),
+ style = typography.smalltitle_sb600_s18_h24,
+ color = colors.White
+ )
+ Spacer(modifier = Modifier.padding(top = 8.dp))
+
+ Text(
+ text = stringResource(R.string.group_request_book_comment),
+ style = typography.copy_r400_s14,
+ color = colors.White
+ )
+ }
+ }
+}
+
+@Preview
+@Composable
+private fun GroupRegisterBookPreview() {
+ ThipTheme {
+ GroupRegisterBookScreen()
+ }
+}
diff --git a/app/src/main/res/drawable/ic_notice.xml b/app/src/main/res/drawable/ic_notice.xml
index 4cec80dd..42aaeff8 100644
--- a/app/src/main/res/drawable/ic_notice.xml
+++ b/app/src/main/res/drawable/ic_notice.xml
@@ -1,19 +1,9 @@
+ android:width="51dp"
+ android:height="51dp"
+ android:viewportWidth="51"
+ android:viewportHeight="51">
-
+ android:pathData="M25.5,2.5C38.202,2.5 48.5,12.797 48.5,25.5C48.5,38.202 38.202,48.5 25.5,48.5C12.797,48.5 2.5,38.202 2.5,25.5C2.5,12.797 12.797,2.5 25.5,2.5ZM25.5,32.5C24.395,32.5 23.5,33.395 23.5,34.5C23.5,35.605 24.395,36.5 25.5,36.5C26.605,36.5 27.5,35.605 27.5,34.5C27.5,33.395 26.605,32.5 25.5,32.5ZM25.5,15C24.672,15 24,15.672 24,16.5V28.5C24,29.328 24.672,30 25.5,30C26.328,30 27,29.328 27,28.5V16.5C27,15.672 26.328,15 25.5,15Z"
+ android:fillColor="#888888"/>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 49c0cd3e..a539a157 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -169,5 +169,13 @@
모임 만들기
+ 저장한 책
+ 모임 책
+ 책 제목, 저자 검색
+ 책 등록하기
+ 현재 등록된 책이 아닙니다.\n원하시는 책을 신청해주세요.
+ 책 신청
+ texthip2025@gmail.com
+ 이메일로 책 제목, 출판사를 보내주시면\n빠른 시일내로 책을 추가해드릴게요!
\ No newline at end of file
From ff051217499c7e0d4d5f8801495817a8cb71fc5e Mon Sep 17 00:00:00 2001
From: Gyubin
Date: Sun, 6 Jul 2025 22:51:22 +0900
Subject: [PATCH 05/21] =?UTF-8?q?[ui]:=20=ED=8C=8C=EC=9D=BC=20=EC=9D=B4?=
=?UTF-8?q?=EB=A6=84=20=EB=B0=8F=20=EC=9C=84=EC=B9=98=20=EB=B3=80=EA=B2=BD?=
=?UTF-8?q?=20(#35)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../buttons}/GenreChipRow.kt | 6 +-
.../thip/ui/common/forms/WarningTextField.kt | 138 +++++++++++-------
...llbar.kt => GroupBookListWithScrollbar.kt} | 4 +-
...Sheet.kt => GroupBookSearchBottomSheet.kt} | 9 +-
...ntent.kt => GroupEmptyBookSheetContent.kt} | 1 -
.../screen/GroupRegisterBookScreen.kt | 3 +-
.../component/GroupDeadlineRoomSection.kt | 3 +-
.../ui/group/myroom/screen/GroupMyScreen.kt | 4 +-
8 files changed, 95 insertions(+), 73 deletions(-)
rename app/src/main/java/com/texthip/thip/ui/{group/myroom/component => common/buttons}/GenreChipRow.kt (86%)
rename app/src/main/java/com/texthip/thip/ui/group/makeroom/component/{BookListWithScrollbar.kt => GroupBookListWithScrollbar.kt} (96%)
rename app/src/main/java/com/texthip/thip/ui/group/makeroom/component/{BookSearchBottomSheet.kt => GroupBookSearchBottomSheet.kt} (95%)
rename app/src/main/java/com/texthip/thip/ui/group/makeroom/component/{EmptyBookSheetContent.kt => GroupEmptyBookSheetContent.kt} (97%)
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/common/buttons/GenreChipRow.kt
similarity index 86%
rename from app/src/main/java/com/texthip/thip/ui/group/myroom/component/GenreChipRow.kt
rename to app/src/main/java/com/texthip/thip/ui/common/buttons/GenreChipRow.kt
index 37d41cee..1a69c9f5 100644
--- a/app/src/main/java/com/texthip/thip/ui/group/myroom/component/GenreChipRow.kt
+++ b/app/src/main/java/com/texthip/thip/ui/common/buttons/GenreChipRow.kt
@@ -1,14 +1,14 @@
-package com.texthip.thip.ui.group.myroom.component
+package com.texthip.thip.ui.common.buttons
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import com.texthip.thip.ui.common.buttons.OptionChipButton
@Composable
fun GenreChipRow(
+ modifier: Modifier = Modifier.width(4.dp),
genres: List,
selectedIndex: Int,
onSelect: (Int) -> Unit
@@ -25,7 +25,7 @@ fun GenreChipRow(
onClick = { onSelect(idx) }
)
if (idx < genres.size - 1) {
- Spacer(modifier = Modifier.width(4.dp))
+ Spacer(modifier = modifier)
}
}
}
diff --git a/app/src/main/java/com/texthip/thip/ui/common/forms/WarningTextField.kt b/app/src/main/java/com/texthip/thip/ui/common/forms/WarningTextField.kt
index 533cfcea..2074ddac 100644
--- a/app/src/main/java/com/texthip/thip/ui/common/forms/WarningTextField.kt
+++ b/app/src/main/java/com/texthip/thip/ui/common/forms/WarningTextField.kt
@@ -2,11 +2,12 @@ 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.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Icon
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
@@ -14,12 +15,13 @@ 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.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@@ -29,76 +31,92 @@ import com.texthip.thip.ui.theme.ThipTheme.typography
@Composable
fun WarningTextField(
+ value: String,
+ onValueChange: (String) -> Unit,
modifier: Modifier = Modifier,
hint: String,
warningMessage: String = "경고 메시지를 입력해주세요.",
- showWarning: Boolean
+ showWarning: Boolean = false,
+ maxLength: Int = Int.MAX_VALUE,
+ isNumberOnly: Boolean = false,
+ keyboardType: KeyboardType = KeyboardType.Text
) {
- var text by rememberSaveable { mutableStateOf("") }
val myStyle = typography.menu_r400_s14_h24.copy(lineHeight = 14.sp)
-
- Column {
- 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 = if (showWarning) colors.Red else Color.Transparent,
- unfocusedIndicatorColor = if (showWarning) colors.Red else 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
- )
- } else {
- Icon(
- painter = painterResource(id = R.drawable.ic_x_circle),
- contentDescription = "Clear text"
- )
- }
- },
- singleLine = true
- )
-
- if (showWarning) {
- Spacer(modifier = Modifier.height(4.dp))
+ OutlinedTextField(
+ value = value,
+ onValueChange = { input ->
+ var filtered = input
+ if (isNumberOnly) filtered = filtered.filter { it.isDigit() }
+ if (filtered.length > maxLength) filtered = filtered.take(maxLength)
+ onValueChange(filtered)
+ },
+ placeholder = {
Text(
- text = warningMessage,
- color = colors.Red,
- style = typography.info_r400_s12.copy(lineHeight = 12.sp)
+ text = hint,
+ color = colors.Grey02,
+ style = myStyle
)
- }
+ },
+ textStyle = myStyle,
+ modifier = modifier
+ .fillMaxWidth()
+ .height(48.dp),
+ shape = RoundedCornerShape(12.dp),
+ colors = TextFieldDefaults.colors(
+ focusedTextColor = colors.White,
+ focusedIndicatorColor = if (showWarning) colors.Red else Color.Transparent,
+ unfocusedIndicatorColor = if (showWarning) colors.Red else Color.Transparent,
+ focusedContainerColor = colors.DarkGrey50,
+ unfocusedContainerColor = colors.DarkGrey50,
+ cursorColor = colors.NeonGreen
+ ),
+ trailingIcon = {
+ if (value.isNotEmpty()) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_x_circle_white),
+ contentDescription = "Clear text",
+ modifier = Modifier.clickable { onValueChange("") },
+ tint = Color.Unspecified
+ )
+ } else {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_x_circle),
+ contentDescription = "Clear text"
+ )
+ }
+ },
+ singleLine = true,
+ keyboardOptions = KeyboardOptions(keyboardType = keyboardType)
+ )
+ if (showWarning) {
+ Spacer(modifier = Modifier.height(4.dp))
+ Text(
+ text = warningMessage,
+ color = colors.Red,
+ style = typography.info_r400_s12.copy(lineHeight = 12.sp)
+ )
}
}
+
@Composable
@Preview(showBackground = true, backgroundColor = 0xFF000000, widthDp = 360, heightDp = 200)
fun WarningTextFieldPreviewEmpty() {
+ var password by remember { mutableStateOf("") }
+
Box(
modifier = Modifier.size(width = 360.dp, height = 200.dp),
contentAlignment = Alignment.Center
) {
WarningTextField(
- hint = "인풋 텍스트",
- showWarning = true,
- warningMessage = "경고 메시지를 입력해주세요."
+ value = password,
+ onValueChange = { password = it },
+ hint = "4자리 숫자로 입장 비밀번호를 설정",
+ showWarning = password.isNotEmpty() && password.length < 4,
+ warningMessage = "4자리 숫자를 입력해주세요.",
+ maxLength = 4,
+ isNumberOnly = true,
+ keyboardType = KeyboardType.NumberPassword
)
}
}
@@ -106,13 +124,21 @@ fun WarningTextFieldPreviewEmpty() {
@Composable
@Preview(showBackground = true, backgroundColor = 0xFF000000, widthDp = 360, heightDp = 200)
fun WarningTextFieldPreviewNormal() {
+ var password by remember { mutableStateOf("") }
+
Box(
modifier = Modifier.size(width = 360.dp, height = 200.dp),
contentAlignment = Alignment.Center
) {
WarningTextField(
- hint = "인풋 텍스트",
- showWarning = false
+ value = password,
+ onValueChange = { password = it },
+ hint = "4자리 숫자로 입장 비밀번호를 설정",
+ showWarning = false,
+ warningMessage = "4자리 숫자를 입력해주세요.",
+ maxLength = 4,
+ isNumberOnly = true,
+ keyboardType = KeyboardType.NumberPassword
)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/BookListWithScrollbar.kt b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupBookListWithScrollbar.kt
similarity index 96%
rename from app/src/main/java/com/texthip/thip/ui/group/makeroom/component/BookListWithScrollbar.kt
rename to app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupBookListWithScrollbar.kt
index a889bcef..593171f9 100644
--- a/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/BookListWithScrollbar.kt
+++ b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupBookListWithScrollbar.kt
@@ -21,7 +21,7 @@ import com.texthip.thip.ui.theme.ThipTheme.colors
@Composable
-fun BookListWithScrollbar(
+fun GroupBookListWithScrollbar(
books: List,
onBookClick: (BookData) -> Unit
) {
@@ -62,7 +62,7 @@ fun BookListWithScrollbar(
fun PreviewBookListWithScrollbar() {
ThipTheme {
Column {
- BookListWithScrollbar(
+ GroupBookListWithScrollbar(
books = List(20) { BookData("Book $it", R.drawable.bookcover_sample) },
onBookClick = {}
)
diff --git a/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/BookSearchBottomSheet.kt b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupBookSearchBottomSheet.kt
similarity index 95%
rename from app/src/main/java/com/texthip/thip/ui/group/makeroom/component/BookSearchBottomSheet.kt
rename to app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupBookSearchBottomSheet.kt
index d637b288..8cd62f67 100644
--- a/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/BookSearchBottomSheet.kt
+++ b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupBookSearchBottomSheet.kt
@@ -22,12 +22,11 @@ import com.texthip.thip.ui.group.makeroom.mock.BookData
import com.texthip.thip.ui.group.makeroom.mock.dummyGroupBooks
import com.texthip.thip.ui.group.makeroom.mock.dummySavedBooks
import com.texthip.thip.ui.theme.ThipTheme
-import com.texthip.thip.ui.theme.ThipTheme.colors
import com.texthip.thip.ui.common.bottomsheet.CustomBottomSheet
import com.texthip.thip.ui.common.header.HeaderMenuBarTab
@Composable
-fun BookSearchBottomSheet(
+fun GroupBookSearchBottomSheet(
onDismiss: () -> Unit,
onBookSelect: (BookData) -> Unit,
onRequestBook: () -> Unit,
@@ -77,7 +76,7 @@ fun BookSearchBottomSheet(
if (books.isEmpty()) {
EmptyBookSheetContent(onRequestBook = onRequestBook)
} else {
- BookListWithScrollbar(
+ GroupBookListWithScrollbar(
books = books,
onBookClick = onBookSelect
)
@@ -105,7 +104,7 @@ fun PreviewBookSearchBottomSheet_HasBooks() {
ThipTheme {
var showSheet by remember { mutableStateOf(true) }
if (showSheet) {
- BookSearchBottomSheet(
+ GroupBookSearchBottomSheet(
onDismiss = { showSheet = false },
onBookSelect = {},
onRequestBook = {},
@@ -123,7 +122,7 @@ fun PreviewBookSearchBottomSheet_Empty() {
ThipTheme {
var showSheet by remember { mutableStateOf(true) }
if (showSheet) {
- BookSearchBottomSheet(
+ GroupBookSearchBottomSheet(
onDismiss = { showSheet = false },
onBookSelect = {},
onRequestBook = {},
diff --git a/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/EmptyBookSheetContent.kt b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupEmptyBookSheetContent.kt
similarity index 97%
rename from app/src/main/java/com/texthip/thip/ui/group/makeroom/component/EmptyBookSheetContent.kt
rename to app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupEmptyBookSheetContent.kt
index d0e15fe7..2e69daf5 100644
--- a/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/EmptyBookSheetContent.kt
+++ b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupEmptyBookSheetContent.kt
@@ -2,7 +2,6 @@ package com.texthip.thip.ui.group.makeroom.component
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
-import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
diff --git a/app/src/main/java/com/texthip/thip/ui/group/makeroom/screen/GroupRegisterBookScreen.kt b/app/src/main/java/com/texthip/thip/ui/group/makeroom/screen/GroupRegisterBookScreen.kt
index 68f86129..ffcde235 100644
--- a/app/src/main/java/com/texthip/thip/ui/group/makeroom/screen/GroupRegisterBookScreen.kt
+++ b/app/src/main/java/com/texthip/thip/ui/group/makeroom/screen/GroupRegisterBookScreen.kt
@@ -5,7 +5,7 @@ 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.material.Text
+import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -14,7 +14,6 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.texthip.thip.R
import com.texthip.thip.ui.common.topappbar.DefaultTopAppBar
-import com.texthip.thip.ui.common.topappbar.InputTopAppBar
import com.texthip.thip.ui.theme.ThipTheme
import com.texthip.thip.ui.theme.ThipTheme.colors
import com.texthip.thip.ui.theme.ThipTheme.typography
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
index 565bd1ef..4ff9b13d 100644
--- 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
@@ -6,18 +6,17 @@ 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.OutlinedTextFieldDefaults.contentPadding
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.platform.LocalDensity
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.buttons.GenreChipRow
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
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
index 1938fbe2..7f9b4889 100644
--- 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
@@ -70,11 +70,11 @@ fun GroupMyScreen(
}
)
- Spacer(modifier = Modifier.height(20.dp))
+ Spacer(modifier = Modifier.height(10.dp))
LazyColumn(
verticalArrangement = Arrangement.spacedBy(20.dp),
- contentPadding = PaddingValues(bottom = 20.dp),
+ contentPadding = PaddingValues(top = 10.dp, bottom = 20.dp),
modifier = Modifier
.fillMaxSize()
) {
From 2f5250e2450e058b2f20117e8e607bdd04c28c5b Mon Sep 17 00:00:00 2001
From: Gyubin
Date: Sun, 6 Jul 2025 22:54:16 +0900
Subject: [PATCH 06/21] =?UTF-8?q?[ui]:=20String=20=EC=B6=94=EA=B0=80=20?=
=?UTF-8?q?=EB=B0=8F=20=EC=B1=85=20=EC=84=A0=ED=83=9D=20=EC=BB=B4=ED=8F=AC?=
=?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EC=88=98=EC=A0=95(#35)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../makeroom/component/GroupSelectBook.kt | 166 +++++++++++++-----
.../ui/group/makeroom/mock/GroupBookData.kt | 3 +-
app/src/main/res/values/strings.xml | 19 ++
3 files changed, 143 insertions(+), 45 deletions(-)
diff --git a/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupSelectBook.kt b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupSelectBook.kt
index 5c08aecb..9856d9fa 100644
--- a/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupSelectBook.kt
+++ b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupSelectBook.kt
@@ -1,83 +1,161 @@
package com.texthip.thip.ui.group.makeroom.component
-import android.R.attr.contentDescription
-import android.R.attr.onClick
import androidx.compose.foundation.BorderStroke
-import androidx.compose.foundation.background
+import androidx.compose.foundation.Image
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.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.material.icons.Icons
-import androidx.compose.material.icons.filled.Search
+import androidx.compose.material3.Button
import androidx.compose.material3.Icon
+import androidx.compose.material3.OutlinedButton
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.painter.Painter
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.buttons.OptionChipButton
+import com.texthip.thip.ui.group.makeroom.mock.BookData
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 GroupSelectBook(modifier: Modifier = Modifier, onButtonClick: () -> Unit = {}) {
+fun GroupSelectBook(
+ selectedBook: BookData?,
+ onChangeBookClick: () -> Unit,
+ onSelectBookClick: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
Column(
modifier = modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
- text = "책 선택 화면",
+ text = stringResource(R.string.group_select_book_title),
style = typography.smalltitle_sb600_s18_h24,
color = colors.White,
modifier = Modifier.align(Alignment.Start)
)
- Spacer(modifier = Modifier.padding(top = 20.dp))
+ Spacer(modifier = Modifier.height(20.dp))
- Row(
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.Center,
- modifier = modifier
- .clickable { onButtonClick() }
- .fillMaxWidth()
- .height(44.dp)
- .border(
- BorderStroke(1.dp, colors.Grey02),
- shape = RoundedCornerShape(12.dp)
+ if (selectedBook == null) {
+ // 미선택 상태: 기존 검색 UI
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.Center,
+ modifier = Modifier
+ .clickable { onSelectBookClick() }
+ .fillMaxWidth()
+ .height(44.dp)
+ .border(
+ BorderStroke(1.dp, colors.Grey02),
+ shape = RoundedCornerShape(12.dp)
+ )
+ .padding(vertical = 10.dp)
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_search),
+ contentDescription = "검색 아이콘",
+ tint = colors.Grey01
)
- .padding(vertical = 10.dp)
- ) {
- Icon(
- painter = painterResource(id = R.drawable.ic_search),
- contentDescription = "검색 아이콘",
- tint = colors.Grey01
- )
- Spacer(modifier = Modifier.width(8.dp))
- Text(
- text = "검색해서 찾기",
- style = typography.menu_m500_s16_h24,
- color = colors.Grey
- )
+ Spacer(modifier = Modifier.width(8.dp))
+ Text(
+ text = "검색해서 찾기",
+ style = typography.menu_m500_s16_h24,
+ color = colors.Grey
+ )
+ }
+ } else {
+ // 선택된 상태: 커버, 제목, 저자, 변경 버튼
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(80.dp),
+ verticalAlignment = Alignment.Bottom
+ ) {
+ Image(
+ painter = painterResource(selectedBook.imageRes),
+ contentDescription = selectedBook.title,
+ modifier = Modifier
+ .height(80.dp)
+ .width(60.dp)
+ )
+ Spacer(modifier = Modifier.width(12.dp))
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .weight(1f),
+ verticalArrangement = Arrangement.Top
+ ) {
+ Text(
+ text = selectedBook.title,
+ color = colors.White,
+ style = typography.menu_sb600_s14_h24
+ )
+ Spacer(modifier = Modifier.height(8.dp))
+ selectedBook.author?.let {
+ Text(
+ text = stringResource(
+ R.string.group_selected_book_author,
+ selectedBook.author
+ ),
+ color = colors.Grey01,
+ style = typography.info_r400_s12,
+ maxLines = 1
+ )
+ }
+ }
+ OptionChipButton(
+ text = stringResource(R.string.change),
+ onClick = onChangeBookClick,
+ isSelected = true
+ )
+ }
}
}
}
-@Preview
+// -------------------------
+// 프리뷰용 더미 BookData
+// -------------------------
+private val dummyBook = BookData(
+ title = "호르몬 체인지",
+ imageRes = R.drawable.bookcover_sample, // drawable 샘플로 교체
+ author = "최정화"
+)
+
+// -------------------------
+// PREVIEW: 책 미선택 상태
+// -------------------------
+@Preview(showBackground = true)
+@Composable
+fun GroupSelectBookPreview_Unselected() {
+ ThipTheme {
+ GroupSelectBook(
+ selectedBook = null,
+ onChangeBookClick = {},
+ onSelectBookClick = {}
+ )
+ }
+}
+
+// -------------------------
+// PREVIEW: 책 선택된 상태
+// -------------------------
+@Preview(showBackground = true)
@Composable
-private fun GroupSelectBookPreview() {
+fun GroupSelectBookPreview_Selected() {
ThipTheme {
- GroupSelectBook()
+ GroupSelectBook(
+ selectedBook = dummyBook,
+ onChangeBookClick = {},
+ onSelectBookClick = {}
+ )
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/com/texthip/thip/ui/group/makeroom/mock/GroupBookData.kt b/app/src/main/java/com/texthip/thip/ui/group/makeroom/mock/GroupBookData.kt
index c8daa613..7d3366ff 100644
--- a/app/src/main/java/com/texthip/thip/ui/group/makeroom/mock/GroupBookData.kt
+++ b/app/src/main/java/com/texthip/thip/ui/group/makeroom/mock/GroupBookData.kt
@@ -4,7 +4,8 @@ import com.texthip.thip.R
data class BookData(
val title: String,
- val imageRes: Int // drawable 리소스 or 이미지 URL
+ val imageRes: Int, // drawable 리소스 or 이미지 URL
+ val author: String? = null
)
val dummySavedBooks = listOf(
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index a539a157..6aae7f72 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -177,5 +177,24 @@
책 신청
texthip2025@gmail.com
이메일로 책 제목, 출판사를 보내주시면\n빠른 시일내로 책을 추가해드릴게요!
+ 책 선택
+ 책 장르
+ 책을 가장 잘 설명하는 장르를 하나 골라주세요
+ 방 제목
+ 방 제목을 입력해주세요
+ 한 줄 소개
+ 방에 대한 짧은 소개글을 작성해주세요
+ 모임 활동기간
+ 모임 활동 기간은 최대 3개월까지 설정 가능합니다.
+ (선택된 기간: %1$d일)
+ 인원 제한
+ 모집 인원은 최대 30명까지 입력할 수 있습니다.
+ 명의 독서 메이트를 모집합니다
+ 1개만 선택 가능합니다.
+ %1$s 저
+ 4자리 숫자로 입장 비밀번호를 설정
+ 년
+ 월
+ 일
\ No newline at end of file
From dbb85d92e56dec091177d64315d91193622f87ae Mon Sep 17 00:00:00 2001
From: Gyubin
Date: Mon, 7 Jul 2025 02:25:53 +0900
Subject: [PATCH 07/21] =?UTF-8?q?[ui]:=20Picker=20Component=20=EA=B0=9C?=
=?UTF-8?q?=EB=B0=9C=20(#35)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../makeroom/component/GroupDatePicker.kt | 153 +++++++++++
.../component/GroupMemberLimitPicker.kt | 97 +++++++
.../component/GroupRoomDurationPicker.kt | 248 ++++++++++++++++++
3 files changed, 498 insertions(+)
create mode 100644 app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupDatePicker.kt
create mode 100644 app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupMemberLimitPicker.kt
create mode 100644 app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupRoomDurationPicker.kt
diff --git a/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupDatePicker.kt b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupDatePicker.kt
new file mode 100644
index 00000000..4ebf5f65
--- /dev/null
+++ b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupDatePicker.kt
@@ -0,0 +1,153 @@
+package com.texthip.thip.ui.group.makeroom.component
+
+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.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.setValue
+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.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
+import java.time.LocalDate
+
+@Composable
+fun GroupDatePicker(
+ modifier: Modifier = Modifier,
+ year: Int,
+ month: Int,
+ day: Int,
+ years: List,
+ months: List,
+ days: List,
+ onYearSelected: (Int) -> Unit,
+ onMonthSelected: (Int) -> Unit,
+ onDaySelected: (Int) -> Unit
+) {
+ Row(
+ modifier = modifier,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ // 년도 선택기
+ GroupWheelPicker(
+ modifier = Modifier.width(48.dp),
+ items = years,
+ selectedItem = year,
+ onItemSelected = onYearSelected,
+ displayText = { it.toString() }
+ )
+ Spacer(modifier = Modifier.width(2.dp))
+ Text(
+ text = stringResource(R.string.group_year),
+ style = typography.info_r400_s12,
+ color = colors.White
+ )
+ Spacer(modifier = Modifier.width(4.dp))
+
+ // 월 선택기
+ GroupWheelPicker(
+ modifier = Modifier.width(32.dp),
+ items = months,
+ selectedItem = month,
+ onItemSelected = onMonthSelected,
+ displayText = { it.toString() }
+ )
+ Spacer(modifier = Modifier.width(2.dp))
+ Text(
+ text = stringResource(R.string.group_month),
+ style = typography.info_r400_s12,
+ color = colors.White
+ )
+ Spacer(modifier = Modifier.width(4.dp))
+
+ // 일 선택기
+ GroupWheelPicker(
+ modifier = Modifier.width(32.dp),
+ items = days,
+ selectedItem = day,
+ onItemSelected = onDaySelected,
+ displayText = { it.toString() }
+ )
+ Spacer(modifier = Modifier.width(2.dp))
+ Text(
+ text = stringResource(R.string.group_day),
+ style = typography.info_r400_s12,
+ color = colors.White
+ )
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+fun DatePickerGroupPreview() {
+ ThipTheme {
+ val today = LocalDate.now()
+ val years = (2020..2030).toList()
+ val months = (1..12).toList()
+ val getDaysInMonth = { year: Int, month: Int ->
+ val date = LocalDate.of(year, month, 1)
+ (1..date.lengthOfMonth()).toList()
+ }
+
+ // 각각 독립적으로 관리!
+ var startYear by remember { mutableStateOf(today.year) }
+ var startMonth by remember { mutableStateOf(today.monthValue) }
+ var startDay by remember { mutableStateOf(today.dayOfMonth) }
+
+ val tomorrow = today.plusDays(1)
+ var endYear by remember { mutableStateOf(tomorrow.year) }
+ var endMonth by remember { mutableStateOf(tomorrow.monthValue) }
+ var endDay by remember { mutableStateOf(tomorrow.dayOfMonth) }
+
+ Box(
+ modifier = Modifier
+ .background(colors.Black)
+ .padding(16.dp)
+ ) {
+ Column(
+ verticalArrangement = Arrangement.spacedBy(24.dp)
+ ) {
+ // 시작 날짜
+ Text("시작 날짜", color = colors.White)
+ GroupDatePicker(
+ year = startYear,
+ month = startMonth,
+ day = startDay,
+ years = years,
+ months = months,
+ days = getDaysInMonth(startYear, startMonth),
+ onYearSelected = { startYear = it },
+ onMonthSelected = { startMonth = it },
+ onDaySelected = { startDay = it }
+ )
+ // 끝 날짜
+ Text("끝 날짜", color = colors.White)
+ GroupDatePicker(
+ year = endYear,
+ month = endMonth,
+ day = endDay,
+ years = years,
+ months = months,
+ days = getDaysInMonth(endYear, endMonth),
+ onYearSelected = { endYear = it },
+ onMonthSelected = { endMonth = it },
+ onDaySelected = { endDay = it }
+ )
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupMemberLimitPicker.kt b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupMemberLimitPicker.kt
new file mode 100644
index 00000000..ff47eaff
--- /dev/null
+++ b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupMemberLimitPicker.kt
@@ -0,0 +1,97 @@
+package com.texthip.thip.ui.group.makeroom.component
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+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 MemberLimitPicker(
+ modifier: Modifier = Modifier,
+ selectedCount: Int = 30,
+ onCountSelected: (Int) -> Unit = { }
+) {
+ val memberCounts = remember { (1..30).toList() }
+
+ Column(
+ modifier = modifier.fillMaxWidth()
+ ) {
+ // 제목
+ Text(
+ text = stringResource(R.string.group_room_member_limit_title),
+ style = typography.smalltitle_sb600_s18_h24,
+ color = colors.White
+ )
+
+ // 인원 선택기
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(top = 12.dp),
+ horizontalArrangement = Arrangement.Center,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ // 숫자 선택기
+ GroupWheelPicker(
+ modifier = Modifier.width(32.dp),
+ items = memberCounts,
+ selectedItem = selectedCount,
+ onItemSelected = onCountSelected,
+ displayText = { it.toString() }
+ )
+
+ // 단위 텍스트
+ Text(
+ text = stringResource(R.string.group_room_limit),
+ style = typography.info_r400_s12,
+ color = colors.White,
+ modifier = Modifier.padding(start = 8.dp)
+ )
+ }
+
+ // 안내 메시지
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.End
+ ) {
+ Text(
+ text = stringResource(R.string.group_room_member_limit_comment),
+ style = typography.info_r400_s12,
+ color = colors.NeonGreen,
+ textAlign = TextAlign.End,
+ modifier = Modifier.padding(top = 12.dp)
+ )
+ }
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+fun MemberLimitPickerPreview() {
+ ThipTheme {
+ var selectedCount by remember { mutableStateOf(30) }
+
+ MemberLimitPicker(
+ selectedCount = selectedCount,
+ onCountSelected = { selectedCount = it }
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupRoomDurationPicker.kt b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupRoomDurationPicker.kt
new file mode 100644
index 00000000..57cd81dd
--- /dev/null
+++ b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupRoomDurationPicker.kt
@@ -0,0 +1,248 @@
+package com.texthip.thip.ui.group.makeroom.component
+
+import androidx.compose.foundation.gestures.detectTapGestures
+import androidx.compose.foundation.layout.*
+import androidx.compose.material3.Text
+import androidx.compose.runtime.*
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+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
+import java.time.LocalDate
+import java.time.temporal.ChronoUnit
+
+@Composable
+fun GroupRoomDurationPicker(
+ modifier: Modifier = Modifier,
+ onDateRangeSelected: (LocalDate, LocalDate) -> Unit = { _, _ -> }
+) {
+ val today = LocalDate.now()
+ val maxDate = today.plusMonths(3)
+
+ // 날짜 상태
+ var startYear by rememberSaveable { mutableStateOf(today.year) }
+ var startMonth by rememberSaveable { mutableStateOf(today.monthValue) }
+ var startDay by rememberSaveable { mutableStateOf(today.dayOfMonth) }
+
+ // 유효한 날짜 범위 계산
+ val years = remember { (today.year..maxDate.year).toList() }
+
+ // 월 범위 계산 (년도에 따라 동적으로)
+ val months = remember(startYear) {
+ when (startYear) {
+ today.year -> (today.monthValue..12).toList()
+ maxDate.year -> (1..maxDate.monthValue).toList()
+ else -> (1..12).toList()
+ }
+ }
+
+ // 일 범위 계산 (년도, 월에 따라 동적으로)
+ val days = remember(startYear, startMonth) {
+ val selectedDate = LocalDate.of(startYear, startMonth, 1)
+ val startDayOfMonth = if (startYear == today.year && startMonth == today.monthValue) {
+ today.dayOfMonth
+ } else {
+ 1
+ }
+
+ val endDayOfMonth = if (startYear == maxDate.year && startMonth == maxDate.monthValue) {
+ minOf(selectedDate.lengthOfMonth(), maxDate.dayOfMonth)
+ } else {
+ selectedDate.lengthOfMonth()
+ }
+
+ (startDayOfMonth..endDayOfMonth).toList()
+ }
+
+ // 날짜 유효성 검사 및 자동 보정
+ LaunchedEffect(startYear, startMonth, days) {
+ if (startDay !in days) {
+ startDay = days.lastOrNull() ?: startDay
+ }
+ }
+
+ // 오늘 이전 날짜 선택 방지
+ LaunchedEffect(startYear, startMonth, startDay) {
+ val selectedDate = LocalDate.of(startYear, startMonth, startDay)
+ if (selectedDate.isBefore(today)) {
+ startYear = today.year
+ startMonth = today.monthValue
+ startDay = today.dayOfMonth
+ }
+ }
+
+ // 날짜 객체로 변환
+ val startDate = remember(startYear, startMonth, startDay) {
+ try {
+ LocalDate.of(startYear, startMonth, startDay)
+ } catch (e: Exception) {
+ // 유효하지 않은 날짜인 경우 오늘 날짜로 fallback
+ today
+ }
+ }
+ val endDate = remember(startDate) { startDate.plusDays(1) }
+
+ // 90일 초과 체크
+ val daysBetween = ChronoUnit.DAYS.between(startDate, endDate)
+ val isOverLimit = daysBetween > 90
+
+ var isPickerTouched by rememberSaveable { mutableStateOf(false) }
+
+ // 날짜 변경 시 콜백
+ LaunchedEffect(startDate, endDate) {
+ onDateRangeSelected(startDate, endDate)
+ }
+
+ Column(modifier = modifier.fillMaxWidth()) {
+ Text(
+ text = stringResource(R.string.group_room_duration_title),
+ style = typography.smalltitle_sb600_s18_h24,
+ color = colors.White
+ )
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(top = 12.dp, start = 12.dp, end = 12.dp),
+ horizontalArrangement = Arrangement.Center,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ // 시작 날짜
+ GroupDatePicker(
+ year = startYear,
+ month = startMonth,
+ day = startDay,
+ years = years,
+ months = months,
+ days = days,
+ onYearSelected = { newYear ->
+ startYear = newYear
+ // 년도 변경 시 월 유효성 검사
+ val validMonths = when (newYear) {
+ today.year -> (today.monthValue..12).toList()
+ maxDate.year -> (1..maxDate.monthValue).toList()
+ else -> (1..12).toList()
+ }
+ if (startMonth !in validMonths) {
+ startMonth = validMonths.first()
+ }
+ },
+ onMonthSelected = { newMonth ->
+ startMonth = newMonth
+ // 월 변경 시 일 유효성 검사
+ val tempDate = LocalDate.of(startYear, newMonth, 1)
+ val validStartDay = if (startYear == today.year && newMonth == today.monthValue) {
+ today.dayOfMonth
+ } else {
+ 1
+ }
+
+ val validEndDay = if (startYear == maxDate.year && newMonth == maxDate.monthValue) {
+ minOf(tempDate.lengthOfMonth(), maxDate.dayOfMonth)
+ } else {
+ tempDate.lengthOfMonth()
+ }
+
+ if (startDay < validStartDay) {
+ startDay = validStartDay
+ } else if (startDay > validEndDay) {
+ startDay = validEndDay
+ }
+ },
+ onDaySelected = { startDay = it },
+ modifier = Modifier.pointerInput(Unit) {
+ detectTapGestures(
+ onPress = { isPickerTouched = true }
+ )
+ }
+ )
+ // 구분자
+ Text(
+ text = "~",
+ style = typography.info_r400_s12,
+ color = colors.White,
+ modifier = Modifier.padding(horizontal = 4.dp)
+ )
+ // 종료 날짜(선택 불가, 읽기 전용)
+ GroupDatePicker(
+ year = endDate.year,
+ month = endDate.monthValue,
+ day = endDate.dayOfMonth,
+ years = years, // 전체 년도 범위 제공
+ months = (1..12).toList(), // 전체 월 범위 제공
+ days = (1..LocalDate.of(endDate.year, endDate.monthValue, 1).lengthOfMonth()).toList(), // 해당 월의 전체 일 범위 제공
+ onYearSelected = {},
+ onMonthSelected = {},
+ onDaySelected = {},
+ modifier = Modifier // 비활성화 UI 추가 가능
+ )
+ }
+ // 안내/에러 메시지
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.End
+ ) {
+ when {
+ isOverLimit -> {
+ Text(
+ text = stringResource(R.string.group_room_duration_comment),
+ style = typography.info_r400_s12,
+ color = colors.Red,
+ textAlign = TextAlign.End,
+ modifier = Modifier.padding(top = 12.dp)
+ )
+ }
+ !isPickerTouched -> {
+ Text(
+ text = stringResource(R.string.group_room_duration_comment),
+ style = typography.info_r400_s12,
+ color = colors.NeonGreen,
+ textAlign = TextAlign.End,
+ modifier = Modifier.padding(top = 12.dp)
+ )
+ }
+ else -> {
+ Text(
+ text = "${startDate.monthValue}월 ${startDate.dayOfMonth}일 자정에 자동으로 모집 마감되고 활동이 가능합니다.",
+ style = typography.info_r400_s12,
+ color = colors.NeonGreen,
+ textAlign = TextAlign.End,
+ modifier = Modifier.padding(top = 12.dp)
+ )
+ }
+ }
+ }
+ // 에러 메시지: 90일 초과
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.End
+ ) {
+ if (isOverLimit) {
+ Text(
+ text = stringResource(R.string.group_room_duration_error, daysBetween),
+ style = typography.info_r400_s12,
+ color = colors.Red,
+ textAlign = TextAlign.End,
+ modifier = Modifier.padding(top = 4.dp)
+ )
+ }
+ }
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+fun MeetingDurationPickerPreview() {
+ ThipTheme {
+ GroupRoomDurationPicker { startDate, endDate ->
+ println("Selected date range: $startDate to $endDate")
+ }
+ }
+}
\ No newline at end of file
From 96c950a35ed182627e6621599f24202f279f360d Mon Sep 17 00:00:00 2001
From: Gyubin
Date: Mon, 7 Jul 2025 02:25:59 +0900
Subject: [PATCH 08/21] =?UTF-8?q?[ui]:=20Picker=20Component=20=EA=B0=9C?=
=?UTF-8?q?=EB=B0=9C=20(#35)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../makeroom/component/GroupWheelPicker.kt | 283 ++++++++++++++++++
1 file changed, 283 insertions(+)
create mode 100644 app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupWheelPicker.kt
diff --git a/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupWheelPicker.kt b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupWheelPicker.kt
new file mode 100644
index 00000000..3a4bed44
--- /dev/null
+++ b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupWheelPicker.kt
@@ -0,0 +1,283 @@
+package com.texthip.thip.ui.group.makeroom.component
+
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.exponentialDecay
+import androidx.compose.animation.core.spring
+import androidx.compose.foundation.background
+import androidx.compose.foundation.gestures.detectDragGestures
+import androidx.compose.foundation.layout.*
+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.Color
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.dp
+import com.texthip.thip.ui.theme.ThipTheme.colors
+import com.texthip.thip.ui.theme.ThipTheme.typography
+import kotlinx.coroutines.launch
+import kotlin.math.abs
+import kotlin.math.roundToInt
+
+@Composable
+fun GroupWheelPicker(
+ modifier: Modifier = Modifier,
+ items: List,
+ selectedItem: T,
+ onItemSelected: (T) -> Unit,
+ displayText: (T) -> String = { it.toString() },
+ selectedBackgroundColor: Color = colors.DarkGrey50,
+ itemHeight: Int = 20,
+ isCircular: Boolean = true
+) {
+ if (items.isEmpty()) return
+
+ // 아이템이 하나인 경우 스크롤 비활성화
+ val isScrollEnabled = items.size > 1
+ val circular = isCircular && items.size > 2
+
+ val selectedIndex = items.indexOf(selectedItem)
+ val animatableOffset = remember { Animatable(0f) }
+ val coroutineScope = rememberCoroutineScope()
+ var isDragging by remember { mutableStateOf(false) }
+ var velocity by remember { mutableFloatStateOf(0f) }
+
+ val density = LocalDensity.current
+ val itemHeightPx = with(density) { itemHeight.dp.toPx() }
+ val spacingPx = with(density) { 9.dp.toPx() }
+ val itemSpacing = itemHeightPx + spacingPx
+
+ // index가 음수/양수 모두에서 올바르게 작동하도록
+ fun getCircularIndex(index: Int): Int {
+ val size = items.size
+ return ((index % size) + size) % size
+ }
+
+ // offset을 항상 0 ~ (items.size-1)*itemSpacing 범위로
+ fun normalizeOffset(offset: Float): Float {
+ if (!circular) return offset
+ val total = items.size * itemSpacing
+ return ((offset % total) + total) % total
+ }
+
+ // 오프셋을 아이템 인덱스로 변환 (0이 중앙)
+ fun offsetToIndex(offset: Float): Int {
+ val total = items.size * itemSpacing
+ val normalized = if (circular) normalizeOffset(offset) else offset
+ val centerIndex = (-normalized / itemSpacing).roundToInt()
+ return if (circular) getCircularIndex(centerIndex) else centerIndex.coerceIn(
+ 0,
+ items.size - 1
+ )
+ }
+
+ // 선택 아이템이 바뀌면 중앙에 오도록 offset 이동
+ LaunchedEffect(selectedItem) {
+ if (!isDragging && isScrollEnabled) {
+ val targetOffset = -selectedIndex * itemSpacing
+ animatableOffset.animateTo(
+ if (circular) normalizeOffset(targetOffset) else targetOffset,
+ animationSpec = spring()
+ )
+ }
+ }
+
+ // 오프셋이 바뀔 때 마다 선택 아이템을 갱신
+ LaunchedEffect(animatableOffset.value) {
+ if (!isDragging && isScrollEnabled) {
+ val newSelectedIndex = offsetToIndex(animatableOffset.value)
+ if (items[newSelectedIndex] != selectedItem) {
+ onItemSelected(items[newSelectedIndex])
+ }
+ }
+ }
+
+ Box(
+ modifier = modifier
+ .height((itemHeight * 3 + 36).dp)
+ ) {
+ // 중앙 고정 박스
+ Box(
+ modifier = Modifier
+ .align(Alignment.Center)
+ .background(
+ selectedBackgroundColor,
+ RoundedCornerShape(4.dp)
+ )
+ .padding(horizontal = 8.dp, vertical = 9.dp)
+ ) {
+ Text(
+ text = displayText(selectedItem),
+ style = typography.info_r400_s12,
+ color = Color.Transparent,
+ textAlign = TextAlign.Center
+ )
+ }
+
+ // 아이템들
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .then(
+ if (isScrollEnabled) {
+ Modifier.pointerInput(Unit) {
+ detectDragGestures(
+ onDragStart = {
+ isDragging = true
+ velocity = 0f
+ },
+ onDragEnd = {
+ isDragging = false
+ coroutineScope.launch {
+ // 관성 스크롤
+ if (abs(velocity) > 100f) {
+ animatableOffset.animateDecay(
+ initialVelocity = velocity,
+ animationSpec = exponentialDecay(
+ frictionMultiplier = 0.9f,
+ absVelocityThreshold = 0.1f
+ )
+ )
+ }
+
+ // offset 스냅
+ val currOffset = animatableOffset.value
+ val normalized =
+ if (circular) normalizeOffset(currOffset) else currOffset
+ val snapIndex = (-normalized / itemSpacing).roundToInt()
+ val snapOffset = -snapIndex * itemSpacing
+ animatableOffset.animateTo(
+ if (circular) normalizeOffset(snapOffset) else snapOffset,
+ animationSpec = spring(
+ dampingRatio = 0.8f,
+ stiffness = 400f
+ )
+ )
+ }
+ }
+ ) { _, dragAmount ->
+ velocity = dragAmount.y
+ coroutineScope.launch {
+ val newOffset = animatableOffset.value + dragAmount.y
+ if (circular) {
+ animatableOffset.snapTo(normalizeOffset(newOffset))
+ } else {
+ val maxOffset = itemSpacing + spacingPx
+ val minOffset =
+ -(items.size - 1) * itemSpacing - itemSpacing - spacingPx
+ animatableOffset.snapTo(
+ newOffset.coerceIn(
+ minOffset,
+ maxOffset
+ )
+ )
+ }
+ }
+ }
+ }
+ } else {
+ Modifier
+ }
+ )
+ ) {
+ val currOffset =
+ if (circular && isScrollEnabled) normalizeOffset(animatableOffset.value) else animatableOffset.value
+ val centerIndex = if (isScrollEnabled) (-currOffset / itemSpacing).roundToInt() else 0
+
+ // 중앙 + 위 아래 한 개만 보이도록!
+ val visibleRange = if (isScrollEnabled) -1..1 else 0..0
+
+ visibleRange.forEach { relIdx ->
+ val displayIndex = centerIndex + relIdx
+ val actualIndex =
+ if (circular && isScrollEnabled) getCircularIndex(displayIndex) else {
+ if (displayIndex in 0 until items.size) displayIndex else return@forEach
+ }
+ val item = items[actualIndex]
+ val itemOffset =
+ if (isScrollEnabled) currOffset + (displayIndex * itemSpacing) else 0f
+ val itemY = itemOffset + itemSpacing + spacingPx
+
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(itemHeight.dp)
+ .offset { IntOffset(0, itemY.roundToInt()) },
+ contentAlignment = Alignment.Center
+ ) {
+ Text(
+ text = displayText(item),
+ style = typography.info_r400_s12,
+ color = colors.White,
+ textAlign = TextAlign.Center
+ )
+ }
+ }
+ }
+
+ // 그라데이션 오버레이 (아이템이 여러 개일 때만 표시)
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(
+ brush = Brush.verticalGradient(
+ colors = listOf(
+ colors.Black.copy(alpha = 0.8f),
+ Color.Transparent,
+ Color.Transparent,
+ colors.Black.copy(alpha = 0.8f)
+ ),
+ startY = 0f,
+ endY = Float.POSITIVE_INFINITY
+ )
+ )
+ )
+ }
+}
+
+
+@Preview(showBackground = true)
+@Composable
+fun WheelPickerPreview() {
+ var selectedYear by remember { mutableStateOf(2025) }
+ val years = (2020..2030).toList() // 11개
+
+ var selectedSingleItem by remember { mutableStateOf("Only Item") }
+ val singleItemList = listOf("Only Item") // 1개
+
+ Box(
+ modifier = Modifier
+ .background(Color.Black),
+ contentAlignment = Alignment.Center
+ ) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ // 순환식 년 선택기 (4개 이상이므로 순환)
+ GroupWheelPicker(
+ modifier = Modifier.width(60.dp),
+ items = years,
+ selectedItem = selectedYear,
+ onItemSelected = { selectedYear = it },
+ displayText = { it.toString() },
+ isCircular = true
+ )
+
+ // 단일 아이템 선택기 (스크롤 비활성화)
+ GroupWheelPicker(
+ modifier = Modifier.width(60.dp),
+ items = singleItemList,
+ selectedItem = selectedSingleItem,
+ onItemSelected = { selectedSingleItem = it },
+ displayText = { it },
+ isCircular = false
+ )
+ }
+ }
+}
\ No newline at end of file
From 39c468c6272d67cbeb60cd70980d79886787945a Mon Sep 17 00:00:00 2001
From: Gyubin
Date: Mon, 7 Jul 2025 02:26:26 +0900
Subject: [PATCH 09/21] =?UTF-8?q?[ui]:=20=EC=A0=84=EC=B2=B4=20=EB=B0=A9=20?=
=?UTF-8?q?=EB=A7=8C=EB=93=A4=EA=B8=B0=20=ED=99=94=EB=A9=B4=20=EA=B0=9C?=
=?UTF-8?q?=EB=B0=9C=20=EC=99=84=EB=A3=8C=20(#35)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../makeroom/component/GroupInputField.kt | 102 +++++++++
.../makeroom/screen/GroupMakeRoomScreen.kt | 206 ++++++++++++++++--
2 files changed, 293 insertions(+), 15 deletions(-)
create mode 100644 app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupInputField.kt
diff --git a/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupInputField.kt b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupInputField.kt
new file mode 100644
index 00000000..0efd73b1
--- /dev/null
+++ b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupInputField.kt
@@ -0,0 +1,102 @@
+package com.texthip.thip.ui.group.makeroom.component
+
+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.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.text.BasicTextField
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+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 GroupInputField(
+ title: String,
+ hint: String,
+ value: String,
+ onValueChange: (String) -> Unit,
+ maxLength: Int = 75
+) {
+ val isOverflow = value.length >= maxLength
+
+ Column(
+ Modifier.fillMaxWidth()
+ ) {
+ Text(
+ text = title,
+ style = typography.smalltitle_sb600_s18_h24,
+ color = colors.White
+ )
+
+ Box(modifier = Modifier.fillMaxWidth()) {
+ BasicTextField(
+ value = value,
+ onValueChange = { new ->
+ if (new.length <= maxLength) onValueChange(new)
+ },
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(top = 12.dp, bottom = 12.dp),
+ textStyle = typography.menu_r400_s14_h24.copy(color = colors.White),
+ cursorBrush = SolidColor(colors.NeonGreen),
+ decorationBox = { innerTextField ->
+ Box(
+ Modifier.fillMaxWidth()
+ ) {
+ if (value.isEmpty()) {
+ Text(
+ hint,
+ style = typography.menu_r400_s14_h24,
+ color = colors.Grey02
+ )
+ }
+ innerTextField()
+ }
+ }
+ )
+ }
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.End
+ ) {
+ Text(
+ text = "${value.length} / $maxLength",
+ style = typography.info_r400_s12,
+ color = if (isOverflow) colors.Red else colors.NeonGreen
+ )
+ }
+ }
+}
+
+
+@Preview()
+@Composable
+fun PreviewRoomTitleInputField() {
+ var text by remember { mutableStateOf("") }
+
+ ThipTheme {
+ Column(
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ GroupInputField(
+ title = "방 제목",
+ hint = "방 제목을 입력해주세요",
+ value = text,
+ onValueChange = { text = it },
+ maxLength = 15
+ )
+ }
+ }
+}
diff --git a/app/src/main/java/com/texthip/thip/ui/group/makeroom/screen/GroupMakeRoomScreen.kt b/app/src/main/java/com/texthip/thip/ui/group/makeroom/screen/GroupMakeRoomScreen.kt
index b007d4b8..45972119 100644
--- a/app/src/main/java/com/texthip/thip/ui/group/makeroom/screen/GroupMakeRoomScreen.kt
+++ b/app/src/main/java/com/texthip/thip/ui/group/makeroom/screen/GroupMakeRoomScreen.kt
@@ -4,59 +4,194 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.blur
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.texthip.thip.R
+import com.texthip.thip.ui.common.buttons.GenreChipRow
+import com.texthip.thip.ui.common.buttons.ToggleSwitchButton
+import com.texthip.thip.ui.common.forms.WarningTextField
import com.texthip.thip.ui.common.topappbar.InputTopAppBar
import com.texthip.thip.ui.group.makeroom.component.GroupSelectBook
-import com.texthip.thip.ui.group.makeroom.component.BookSearchBottomSheet
+import com.texthip.thip.ui.group.makeroom.component.GroupBookSearchBottomSheet
+import com.texthip.thip.ui.group.makeroom.component.GroupInputField
+import com.texthip.thip.ui.group.makeroom.component.GroupRoomDurationPicker
+import com.texthip.thip.ui.group.makeroom.component.MemberLimitPicker
import com.texthip.thip.ui.group.makeroom.mock.BookData
import com.texthip.thip.ui.group.makeroom.mock.dummySavedBooks
import com.texthip.thip.ui.group.makeroom.mock.dummyGroupBooks
import com.texthip.thip.ui.theme.ThipTheme
import com.texthip.thip.ui.theme.ThipTheme.colors
+import com.texthip.thip.ui.theme.ThipTheme.typography
+import java.time.LocalDate
+import java.time.temporal.ChronoUnit
@Composable
fun GroupMakeRoomScreen(modifier: Modifier = Modifier) {
- var isButtonEnable by remember { mutableStateOf(false) }
val scrollState = rememberScrollState()
+
+ var selectedBook by remember { mutableStateOf(null) }
var showBookSearchSheet by remember { mutableStateOf(false) }
+ val genres = listOf("문학", "과학·IT", "사회과학", "인문학", "예술")
+ var selectedGenreIndex by remember { mutableIntStateOf(-1) }
+ var roomTitle by remember { mutableStateOf("") }
+ var roomDescription by remember { mutableStateOf("") }
+ var meetingStartDate by remember { mutableStateOf(LocalDate.now()) }
+ var meetingEndDate by remember { mutableStateOf(LocalDate.now().plusDays(1)) }
+ var selectedCount by remember { mutableStateOf(30) }
+ var isPrivate by remember { mutableStateOf(false) }
+ var password by remember { mutableStateOf("") }
+
+ val daysBetween = ChronoUnit.DAYS.between(meetingStartDate, meetingEndDate)
+ val isDurationValid = (daysBetween in 1..90)
+ val isCountValid = selectedCount in 2..30
+ val isPasswordValid = !isPrivate || (password.length == 4)
+
+ val isButtonEnabled = selectedBook != null &&
+ selectedGenreIndex >= 0 &&
+ roomTitle.isNotBlank() &&
+ roomDescription.isNotBlank() &&
+ isDurationValid &&
+ isCountValid &&
+ isPasswordValid
Box {
Column(
modifier = modifier
- .verticalScroll(scrollState)
.fillMaxSize()
- .then(
- if (showBookSearchSheet) Modifier.blur(5.dp) else Modifier
- ),
+ .then(if (showBookSearchSheet) Modifier.blur(5.dp) else Modifier),
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.CenterHorizontally
) {
InputTopAppBar(
title = stringResource(R.string.group_making_group),
- isRightButtonEnabled = isButtonEnable,
+ isRightButtonEnabled = isButtonEnabled,
onLeftClick = {},
- onRightClick = {}
+ onRightClick = {
+ // 완료 버튼 클릭 로직
+ }
)
- Spacer(modifier = Modifier.padding(top = 20.dp))
Column(
modifier = Modifier
+ .verticalScroll(scrollState)
.fillMaxSize()
.padding(horizontal = 20.dp),
verticalArrangement = Arrangement.Top,
- horizontalAlignment = Alignment.CenterHorizontally
) {
+ Spacer(modifier = Modifier.padding(top = 20.dp))
+
GroupSelectBook(
- onButtonClick = { showBookSearchSheet = true } // 검색해서 찾기 버튼에서 호출
+ selectedBook = selectedBook,
+ onChangeBookClick = { showBookSearchSheet = true },
+ onSelectBookClick = { showBookSearchSheet = true }
+ )
+
+ Spacer(modifier = Modifier.padding(top = 32.dp))
+ Spacer(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(1.dp)
+ .background(colors.DarkGrey02)
+ )
+ Spacer(modifier = Modifier.padding(top = 32.dp))
+
+ Text(
+ text = stringResource(R.string.group_book_genre),
+ style = typography.smalltitle_sb600_s18_h24,
+ color = colors.White,
+ )
+ Spacer(modifier = Modifier.padding(top = 12.dp))
+ GenreChipRow(
+ modifier = Modifier.width(18.dp),
+ genres = genres,
+ selectedIndex = selectedGenreIndex,
+ onSelect = { selectedGenreIndex = it }
+ )
+ Spacer(modifier = Modifier.height(12.dp))
+
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.End
+ ) {
+ Text(
+ text = if (selectedGenreIndex >= 0) stringResource(R.string.group_genre_selected_comment)
+ else stringResource(R.string.group_genre_select_comment),
+ style = typography.info_r400_s12,
+ color = colors.NeonGreen
+ )
+ }
+
+ Spacer(modifier = Modifier.padding(top = 32.dp))
+ Spacer(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(1.dp)
+ .background(colors.DarkGrey02)
+ )
+ Spacer(modifier = Modifier.padding(top = 32.dp))
+
+ GroupInputField(
+ title = stringResource(R.string.group_room_title),
+ hint = stringResource(R.string.group_room_title_hint),
+ value = roomTitle,
+ maxLength = 15,
+ onValueChange = { roomTitle = it }
+ )
+
+ Spacer(modifier = Modifier.padding(top = 32.dp))
+ Spacer(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(1.dp)
+ .background(colors.DarkGrey02)
+ )
+ Spacer(modifier = Modifier.padding(top = 32.dp))
+
+ GroupInputField(
+ title = stringResource(R.string.group_room_explain),
+ hint = stringResource(R.string.group_room_explain_hint),
+ value = roomDescription,
+ onValueChange = { roomDescription = it }
+ )
+
+ Spacer(modifier = Modifier.padding(top = 32.dp))
+ Spacer(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(1.dp)
+ .background(colors.DarkGrey02)
+ )
+ Spacer(modifier = Modifier.padding(top = 32.dp))
+
+ GroupRoomDurationPicker(
+ onDateRangeSelected = { startDate, endDate ->
+ meetingStartDate = startDate
+ meetingEndDate = endDate
+ }
+ )
+
+ Spacer(modifier = Modifier.padding(bottom = 32.dp))
+ Spacer(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(1.dp)
+ .background(colors.DarkGrey02)
)
Spacer(modifier = Modifier.padding(top = 32.dp))
+
+ MemberLimitPicker(
+ selectedCount = selectedCount,
+ onCountSelected = { selectedCount = it }
+ )
+
+ Spacer(modifier = Modifier.padding(bottom = 32.dp))
Spacer(
modifier = Modifier
.fillMaxWidth()
@@ -64,18 +199,59 @@ fun GroupMakeRoomScreen(modifier: Modifier = Modifier) {
.background(colors.DarkGrey02)
)
Spacer(modifier = Modifier.padding(top = 32.dp))
+
+ // --- 공개 설정 ---
+ Text(
+ text = "공개 설정",
+ style = typography.smalltitle_sb600_s18_h24,
+ color = colors.White
+ )
+ Spacer(modifier = Modifier.padding(top = 12.dp))
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ Text(
+ text = "비공개로 설정하기",
+ style = typography.menu_r400_s14_h24,
+ color = colors.White
+ )
+ ToggleSwitchButton(
+ isChecked = isPrivate,
+ onToggleChange = {
+ isPrivate = it
+ if (!it) password = ""
+ }
+ )
+ }
+
+ if (isPrivate) {
+ Spacer(modifier = Modifier.height(12.dp))
+ WarningTextField(
+ value = password,
+ onValueChange = { password = it },
+ hint = stringResource(R.string.group_password_hint),
+ showWarning = password.isNotEmpty() && password.length < 4,
+ warningMessage = "4자리 숫자를 입력해주세요.",
+ maxLength = 4,
+ isNumberOnly = true,
+ keyboardType = KeyboardType.NumberPassword
+ )
+ }
+
+ Spacer(modifier = Modifier.padding(top = 134.dp))
}
}
if (showBookSearchSheet) {
- BookSearchBottomSheet(
+ GroupBookSearchBottomSheet(
onDismiss = { showBookSearchSheet = false },
- onBookSelect = { _: BookData ->
- // 책 선택 처리
+ onBookSelect = { book: BookData ->
+ selectedBook = book
showBookSearchSheet = false
},
onRequestBook = {
- // 책 신청하기 버튼
showBookSearchSheet = false
},
savedBooks = dummySavedBooks,
From 0230de21b049e5716c038182718a30d9bcb4f358 Mon Sep 17 00:00:00 2001
From: Gyubin
Date: Mon, 7 Jul 2025 15:33:02 +0900
Subject: [PATCH 10/21] =?UTF-8?q?[ui]:=20=EB=8F=85=EC=84=9C=20=EB=AA=A8?=
=?UTF-8?q?=EC=9E=84=EB=B0=A9=20pager=20Indicator=20=EC=82=AD=EC=A0=9C=20(?=
=?UTF-8?q?#35)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../ui/group/myroom/component/GroupDeadlineRoomSection.kt | 8 --------
.../java/com/texthip/thip/ui/group/screen/GroupScreen.kt | 2 +-
2 files changed, 1 insertion(+), 9 deletions(-)
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
index 4ff9b13d..a13b58f6 100644
--- 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
@@ -132,14 +132,6 @@ fun GroupRoomDeadlineSection(
}
}
}
-
- SimplePagerIndicator(
- pageCount = roomSections.size,
- currentPage = pagerState.currentPage,
- modifier = Modifier
- .align(Alignment.CenterHorizontally)
- .padding(top = 8.dp)
- )
}
}
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 cfd918e8..9f5ce6dd 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
@@ -55,7 +55,7 @@ fun GroupScreen(
// 상단바
LogoTopAppBar(
leftIcon = painterResource(R.drawable.ic_done),
- hasNotification = false,
+ hasNotification = true,
onLeftClick = { },
onRightClick = { }
)
From 6608f247760f2add0c14cc5ff7f077957b3f6b3e Mon Sep 17 00:00:00 2001
From: Gyubin
Date: Mon, 7 Jul 2025 15:51:39 +0900
Subject: [PATCH 11/21] =?UTF-8?q?[ui]:=20String=20Resource=20=EC=B6=94?=
=?UTF-8?q?=EC=B6=9C=20(#35)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../makeroom/component/GroupMemberLimitPicker.kt | 3 ++-
.../makeroom/component/GroupRoomDurationPicker.kt | 12 ++++++++----
.../ui/group/makeroom/component/GroupSelectBook.kt | 14 +++-----------
.../group/makeroom/screen/GroupMakeRoomScreen.kt | 10 +++++-----
app/src/main/res/values/strings.xml | 5 +++++
5 files changed, 23 insertions(+), 21 deletions(-)
diff --git a/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupMemberLimitPicker.kt b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupMemberLimitPicker.kt
index ff47eaff..b7e7445e 100644
--- a/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupMemberLimitPicker.kt
+++ b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupMemberLimitPicker.kt
@@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.width
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
@@ -87,7 +88,7 @@ fun MemberLimitPicker(
@Composable
fun MemberLimitPickerPreview() {
ThipTheme {
- var selectedCount by remember { mutableStateOf(30) }
+ var selectedCount by remember { mutableIntStateOf(30) }
MemberLimitPicker(
selectedCount = selectedCount,
diff --git a/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupRoomDurationPicker.kt b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupRoomDurationPicker.kt
index 57cd81dd..0efdf035 100644
--- a/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupRoomDurationPicker.kt
+++ b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupRoomDurationPicker.kt
@@ -28,9 +28,9 @@ fun GroupRoomDurationPicker(
val maxDate = today.plusMonths(3)
// 날짜 상태
- var startYear by rememberSaveable { mutableStateOf(today.year) }
- var startMonth by rememberSaveable { mutableStateOf(today.monthValue) }
- var startDay by rememberSaveable { mutableStateOf(today.dayOfMonth) }
+ var startYear by rememberSaveable { mutableIntStateOf(today.year) }
+ var startMonth by rememberSaveable { mutableIntStateOf(today.monthValue) }
+ var startDay by rememberSaveable { mutableIntStateOf(today.dayOfMonth) }
// 유효한 날짜 범위 계산
val years = remember { (today.year..maxDate.year).toList() }
@@ -210,7 +210,11 @@ fun GroupRoomDurationPicker(
}
else -> {
Text(
- text = "${startDate.monthValue}월 ${startDate.dayOfMonth}일 자정에 자동으로 모집 마감되고 활동이 가능합니다.",
+ text = stringResource(
+ R.string.group_room_duration_active_comment,
+ startDate.monthValue,
+ startDate.dayOfMonth
+ ),
style = typography.info_r400_s12,
color = colors.NeonGreen,
textAlign = TextAlign.End,
diff --git a/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupSelectBook.kt b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupSelectBook.kt
index 9856d9fa..f15dd34c 100644
--- a/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupSelectBook.kt
+++ b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupSelectBook.kt
@@ -66,7 +66,7 @@ fun GroupSelectBook(
)
Spacer(modifier = Modifier.width(8.dp))
Text(
- text = "검색해서 찾기",
+ text = stringResource(R.string.group_book_search),
style = typography.menu_m500_s16_h24,
color = colors.Grey
)
@@ -121,18 +121,13 @@ fun GroupSelectBook(
}
}
-// -------------------------
-// 프리뷰용 더미 BookData
-// -------------------------
+
private val dummyBook = BookData(
title = "호르몬 체인지",
- imageRes = R.drawable.bookcover_sample, // drawable 샘플로 교체
+ imageRes = R.drawable.bookcover_sample,
author = "최정화"
)
-// -------------------------
-// PREVIEW: 책 미선택 상태
-// -------------------------
@Preview(showBackground = true)
@Composable
fun GroupSelectBookPreview_Unselected() {
@@ -145,9 +140,6 @@ fun GroupSelectBookPreview_Unselected() {
}
}
-// -------------------------
-// PREVIEW: 책 선택된 상태
-// -------------------------
@Preview(showBackground = true)
@Composable
fun GroupSelectBookPreview_Selected() {
diff --git a/app/src/main/java/com/texthip/thip/ui/group/makeroom/screen/GroupMakeRoomScreen.kt b/app/src/main/java/com/texthip/thip/ui/group/makeroom/screen/GroupMakeRoomScreen.kt
index 45972119..3b7900d3 100644
--- a/app/src/main/java/com/texthip/thip/ui/group/makeroom/screen/GroupMakeRoomScreen.kt
+++ b/app/src/main/java/com/texthip/thip/ui/group/makeroom/screen/GroupMakeRoomScreen.kt
@@ -44,7 +44,7 @@ fun GroupMakeRoomScreen(modifier: Modifier = Modifier) {
var roomDescription by remember { mutableStateOf("") }
var meetingStartDate by remember { mutableStateOf(LocalDate.now()) }
var meetingEndDate by remember { mutableStateOf(LocalDate.now().plusDays(1)) }
- var selectedCount by remember { mutableStateOf(30) }
+ var selectedCount by remember { mutableIntStateOf(30) }
var isPrivate by remember { mutableStateOf(false) }
var password by remember { mutableStateOf("") }
@@ -200,9 +200,9 @@ fun GroupMakeRoomScreen(modifier: Modifier = Modifier) {
)
Spacer(modifier = Modifier.padding(top = 32.dp))
- // --- 공개 설정 ---
+
Text(
- text = "공개 설정",
+ text = stringResource(R.string.group_private_option),
style = typography.smalltitle_sb600_s18_h24,
color = colors.White
)
@@ -213,7 +213,7 @@ fun GroupMakeRoomScreen(modifier: Modifier = Modifier) {
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
- text = "비공개로 설정하기",
+ text = stringResource(R.string.group_private_comment),
style = typography.menu_r400_s14_h24,
color = colors.White
)
@@ -233,7 +233,7 @@ fun GroupMakeRoomScreen(modifier: Modifier = Modifier) {
onValueChange = { password = it },
hint = stringResource(R.string.group_password_hint),
showWarning = password.isNotEmpty() && password.length < 4,
- warningMessage = "4자리 숫자를 입력해주세요.",
+ warningMessage = stringResource(R.string.group_private_warning_message),
maxLength = 4,
isNumberOnly = true,
keyboardType = KeyboardType.NumberPassword
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 6aae7f72..f36cb60f 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -196,5 +196,10 @@
년
월
일
+ 공개 설정
+ 비공개로 설정하기
+ 4자리 숫자를 입력해주세요.
+ %1$d월 %2$d일 자정에 자동으로 모집 마감되고 활동이 가능합니다.
+ 검색해서 찾기
\ No newline at end of file
From 81acf6360d64c7b7324c7d9da67eba47159c6f4d Mon Sep 17 00:00:00 2001
From: Gyubin
Date: Mon, 7 Jul 2025 15:54:25 +0900
Subject: [PATCH 12/21] =?UTF-8?q?[chore]:=20=EB=B6=88=20=ED=95=84=EC=9A=94?=
=?UTF-8?q?=ED=95=9C=20=EC=A3=BC=EC=84=9D=20=EC=A0=9C=EA=B1=B0=20(#35)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../thip/ui/common/bottomsheet/CustomBottomSheet.kt | 7 ++-----
.../group/makeroom/component/GroupBookSearchBottomSheet.kt | 1 -
2 files changed, 2 insertions(+), 6 deletions(-)
diff --git a/app/src/main/java/com/texthip/thip/ui/common/bottomsheet/CustomBottomSheet.kt b/app/src/main/java/com/texthip/thip/ui/common/bottomsheet/CustomBottomSheet.kt
index 3d0f5819..0c745866 100644
--- a/app/src/main/java/com/texthip/thip/ui/common/bottomsheet/CustomBottomSheet.kt
+++ b/app/src/main/java/com/texthip/thip/ui/common/bottomsheet/CustomBottomSheet.kt
@@ -1,6 +1,5 @@
package com.texthip.thip.ui.common.bottomsheet
-// com.texthip.thip.ui.common.bottomsheet.CustomBottomSheet.kt
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.tween
@@ -27,7 +26,6 @@ import kotlinx.coroutines.launch
@Composable
fun CustomBottomSheet(
onDismiss: () -> Unit,
- // 핵심: ColumnScope로 slot 전달!
content: @Composable ColumnScope.() -> Unit
) {
val scope = rememberCoroutineScope()
@@ -97,10 +95,10 @@ fun CustomBottomSheet(
}
)
}
- .clickable(enabled = true) {} // 내부 클릭 먹히게
+ .clickable(enabled = true) {}
) {
Column(modifier = Modifier.fillMaxWidth()) {
- content() // <--- 이 부분이 핵심! slot에 원하는 Compose UI를 전달
+ content()
}
}
}
@@ -114,7 +112,6 @@ fun PreviewCustomBottomSheet() {
ThipTheme {
Box(Modifier.fillMaxSize()) {
- // 배경 컨텐츠 예시
Text(
text = "Main Content Area",
color = Color.White,
diff --git a/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupBookSearchBottomSheet.kt b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupBookSearchBottomSheet.kt
index 8cd62f67..e98521d3 100644
--- a/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupBookSearchBottomSheet.kt
+++ b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupBookSearchBottomSheet.kt
@@ -83,7 +83,6 @@ fun GroupBookSearchBottomSheet(
}
}
} else {
- // 탭 없이 바로 안내화면
Column(
Modifier
.fillMaxWidth()
From 01aa920145c69af93944ffedcc65a9723e5adc7d Mon Sep 17 00:00:00 2001
From: Gyubin
Date: Wed, 9 Jul 2025 15:58:40 +0900
Subject: [PATCH 13/21] =?UTF-8?q?[ui]:=20=EB=B0=94=ED=85=80=EC=8B=9C?=
=?UTF-8?q?=ED=8A=B8=20=EC=8A=A4=ED=81=AC=EB=A1=A4=EB=B0=94=20=EC=88=98?=
=?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=20?=
=?UTF-8?q?=EC=88=98=EC=A0=95=20(#36)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../makeroom/component/GroupBookListWithScrollbar.kt | 4 +++-
.../makeroom/component/GroupBookSearchBottomSheet.kt | 11 ++++-------
2 files changed, 7 insertions(+), 8 deletions(-)
diff --git a/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupBookListWithScrollbar.kt b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupBookListWithScrollbar.kt
index 593171f9..9d8965e6 100644
--- a/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupBookListWithScrollbar.kt
+++ b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupBookListWithScrollbar.kt
@@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
@@ -34,9 +35,9 @@ fun GroupBookListWithScrollbar(
) {
Column(
Modifier
+ .fillMaxWidth()
.verticalScroll(scrollState)
.drawVerticalScrollbar(scrollState)
- .fillMaxWidth()
) {
books.forEach { book ->
CardBookSearch(
@@ -48,6 +49,7 @@ fun GroupBookListWithScrollbar(
Spacer(modifier = Modifier.height(12.dp))
Spacer(modifier = Modifier
.fillMaxWidth()
+ .padding(end = 6.dp)
.height(1.dp)
.background(color = colors.Grey02)
)
diff --git a/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupBookSearchBottomSheet.kt b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupBookSearchBottomSheet.kt
index e98521d3..3c31caf8 100644
--- a/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupBookSearchBottomSheet.kt
+++ b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupBookSearchBottomSheet.kt
@@ -31,12 +31,11 @@ fun GroupBookSearchBottomSheet(
onBookSelect: (BookData) -> Unit,
onRequestBook: () -> Unit,
savedBooks: List = emptyList(),
- groupBooks: List = emptyList(),
- defaultTab: Int = 0
+ groupBooks: List = emptyList()
) {
// 책이 있는지 여부 체크
val hasBooks = savedBooks.isNotEmpty() || groupBooks.isNotEmpty()
- var selectedTab by rememberSaveable { mutableIntStateOf(defaultTab) }
+ var selectedTab by rememberSaveable { mutableIntStateOf(0) }
val tabs = listOf(
stringResource(R.string.group_saved_book), stringResource(R.string.group_book)
)
@@ -108,8 +107,7 @@ fun PreviewBookSearchBottomSheet_HasBooks() {
onBookSelect = {},
onRequestBook = {},
savedBooks = dummySavedBooks, // 데이터 있음
- groupBooks = dummyGroupBooks,
- defaultTab = 0
+ groupBooks = dummyGroupBooks
)
}
}
@@ -126,8 +124,7 @@ fun PreviewBookSearchBottomSheet_Empty() {
onBookSelect = {},
onRequestBook = {},
savedBooks = emptyList(), // 데이터 없음
- groupBooks = emptyList(),
- defaultTab = 0
+ groupBooks = emptyList()
)
}
}
From 258ac9b20f3b1ca712ebce280849e02a24dac912 Mon Sep 17 00:00:00 2001
From: Gyubin
Date: Wed, 9 Jul 2025 16:18:02 +0900
Subject: [PATCH 14/21] =?UTF-8?q?[ui]:=20Picker=20=EB=B0=8F=20=EB=B0=A9=20?=
=?UTF-8?q?=EC=83=9D=EC=84=B1=20=ED=99=94=EB=A9=B4=20=EC=B6=94=EA=B0=80=20?=
=?UTF-8?q?=EC=82=AC=ED=95=AD=20=EC=88=98=EC=A0=95=20(#36)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../makeroom/component/GroupDatePicker.kt | 195 ++++++++--------
.../component/GroupMemberLimitPicker.kt | 14 --
.../component/GroupRoomDurationPicker.kt | 208 +++++++-----------
.../makeroom/component/GroupWheelPicker.kt | 2 +-
.../makeroom/screen/GroupMakeRoomScreen.kt | 6 +-
app/src/main/res/values/strings.xml | 1 +
6 files changed, 184 insertions(+), 242 deletions(-)
diff --git a/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupDatePicker.kt b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupDatePicker.kt
index 4ebf5f65..9b5655e0 100644
--- a/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupDatePicker.kt
+++ b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupDatePicker.kt
@@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Text
@@ -27,67 +28,97 @@ import java.time.LocalDate
@Composable
fun GroupDatePicker(
- modifier: Modifier = Modifier,
- year: Int,
- month: Int,
- day: Int,
- years: List,
- months: List,
- days: List,
- onYearSelected: (Int) -> Unit,
- onMonthSelected: (Int) -> Unit,
- onDaySelected: (Int) -> Unit
+ selectedDate: LocalDate,
+ minDate: LocalDate,
+ maxDate: LocalDate,
+ onDateSelected: (LocalDate) -> Unit,
+ modifier: Modifier = Modifier
) {
+ // 선택된 날짜에서 년/월/일 추출
+ val year = selectedDate.year
+ val month = selectedDate.monthValue
+ val day = selectedDate.dayOfMonth
+
+ // 유효한 범위 계산
+ val years = (minDate.year..maxDate.year).toList()
+ val months = (1..12).toList()
+ val days = (1..selectedDate.lengthOfMonth()).toList()
+
Row(
- modifier = modifier,
- verticalAlignment = Alignment.CenterVertically
+ modifier = modifier.fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween
) {
- // 년도 선택기
- GroupWheelPicker(
- modifier = Modifier.width(48.dp),
- items = years,
- selectedItem = year,
- onItemSelected = onYearSelected,
- displayText = { it.toString() }
- )
- Spacer(modifier = Modifier.width(2.dp))
- Text(
- text = stringResource(R.string.group_year),
- style = typography.info_r400_s12,
- color = colors.White
- )
- Spacer(modifier = Modifier.width(4.dp))
+ Row(
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ GroupWheelPicker(
+ modifier = Modifier.width(48.dp),
+ items = years,
+ selectedItem = year,
+ onItemSelected = { newYear ->
+ val newDate = try {
+ LocalDate.of(newYear, month, day)
+ } catch (e: Exception) {
+ LocalDate.of(newYear, month, 1)
+ }
+ onDateSelected(newDate)
+ },
+ displayText = { it.toString() }
+ )
+ Spacer(modifier = Modifier.width(2.dp))
+ Text(
+ text = stringResource(R.string.group_year),
+ style = typography.info_r400_s12,
+ color = colors.White
+ )
+ }
- // 월 선택기
- GroupWheelPicker(
- modifier = Modifier.width(32.dp),
- items = months,
- selectedItem = month,
- onItemSelected = onMonthSelected,
- displayText = { it.toString() }
- )
- Spacer(modifier = Modifier.width(2.dp))
- Text(
- text = stringResource(R.string.group_month),
- style = typography.info_r400_s12,
- color = colors.White
- )
- Spacer(modifier = Modifier.width(4.dp))
+ Row(
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ GroupWheelPicker(
+ modifier = Modifier.width(32.dp),
+ items = months,
+ selectedItem = month,
+ onItemSelected = { newMonth ->
+ val newDate = try {
+ LocalDate.of(year, newMonth, day)
+ } catch (e: Exception) {
+ LocalDate.of(year, newMonth, 1)
+ }
+ onDateSelected(newDate)
+ },
+ displayText = { it.toString() }
+ )
+ Spacer(modifier = Modifier.width(2.dp))
+ Text(
+ text = stringResource(R.string.group_month),
+ style = typography.info_r400_s12,
+ color = colors.White
+ )
+ }
- // 일 선택기
- GroupWheelPicker(
- modifier = Modifier.width(32.dp),
- items = days,
- selectedItem = day,
- onItemSelected = onDaySelected,
- displayText = { it.toString() }
- )
- Spacer(modifier = Modifier.width(2.dp))
- Text(
- text = stringResource(R.string.group_day),
- style = typography.info_r400_s12,
- color = colors.White
- )
+ Row(
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ GroupWheelPicker(
+ modifier = Modifier.width(32.dp),
+ items = days,
+ selectedItem = day,
+ onItemSelected = { newDay ->
+ val newDate = LocalDate.of(year, month, newDay)
+ onDateSelected(newDate)
+ },
+ displayText = { it.toString() }
+ )
+ Spacer(modifier = Modifier.width(2.dp))
+ Text(
+ text = stringResource(R.string.group_day),
+ style = typography.info_r400_s12,
+ color = colors.White
+ )
+ }
}
}
@@ -96,22 +127,11 @@ fun GroupDatePicker(
fun DatePickerGroupPreview() {
ThipTheme {
val today = LocalDate.now()
- val years = (2020..2030).toList()
- val months = (1..12).toList()
- val getDaysInMonth = { year: Int, month: Int ->
- val date = LocalDate.of(year, month, 1)
- (1..date.lengthOfMonth()).toList()
- }
-
- // 각각 독립적으로 관리!
- var startYear by remember { mutableStateOf(today.year) }
- var startMonth by remember { mutableStateOf(today.monthValue) }
- var startDay by remember { mutableStateOf(today.dayOfMonth) }
-
val tomorrow = today.plusDays(1)
- var endYear by remember { mutableStateOf(tomorrow.year) }
- var endMonth by remember { mutableStateOf(tomorrow.monthValue) }
- var endDay by remember { mutableStateOf(tomorrow.dayOfMonth) }
+ val maxDate = today.plusMonths(12)
+
+ var startDate by remember { mutableStateOf(today) }
+ var endDate by remember { mutableStateOf(tomorrow) }
Box(
modifier = Modifier
@@ -124,30 +144,25 @@ fun DatePickerGroupPreview() {
// 시작 날짜
Text("시작 날짜", color = colors.White)
GroupDatePicker(
- year = startYear,
- month = startMonth,
- day = startDay,
- years = years,
- months = months,
- days = getDaysInMonth(startYear, startMonth),
- onYearSelected = { startYear = it },
- onMonthSelected = { startMonth = it },
- onDaySelected = { startDay = it }
+ selectedDate = startDate,
+ minDate = today,
+ maxDate = maxDate,
+ onDateSelected = { newDate ->
+ startDate = newDate
+ }
)
+
// 끝 날짜
Text("끝 날짜", color = colors.White)
GroupDatePicker(
- year = endYear,
- month = endMonth,
- day = endDay,
- years = years,
- months = months,
- days = getDaysInMonth(endYear, endMonth),
- onYearSelected = { endYear = it },
- onMonthSelected = { endMonth = it },
- onDaySelected = { endDay = it }
+ selectedDate = endDate,
+ minDate = today,
+ maxDate = maxDate,
+ onDateSelected = { newDate ->
+ endDate = newDate
+ }
)
}
}
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupMemberLimitPicker.kt b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupMemberLimitPicker.kt
index b7e7445e..09d9d1cc 100644
--- a/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupMemberLimitPicker.kt
+++ b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupMemberLimitPicker.kt
@@ -67,20 +67,6 @@ fun MemberLimitPicker(
modifier = Modifier.padding(start = 8.dp)
)
}
-
- // 안내 메시지
- Row(
- modifier = Modifier.fillMaxWidth(),
- horizontalArrangement = Arrangement.End
- ) {
- Text(
- text = stringResource(R.string.group_room_member_limit_comment),
- style = typography.info_r400_s12,
- color = colors.NeonGreen,
- textAlign = TextAlign.End,
- modifier = Modifier.padding(top = 12.dp)
- )
- }
}
}
diff --git a/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupRoomDurationPicker.kt b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupRoomDurationPicker.kt
index 0efdf035..3b8acac4 100644
--- a/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupRoomDurationPicker.kt
+++ b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupRoomDurationPicker.kt
@@ -25,80 +25,61 @@ fun GroupRoomDurationPicker(
onDateRangeSelected: (LocalDate, LocalDate) -> Unit = { _, _ -> }
) {
val today = LocalDate.now()
- val maxDate = today.plusMonths(3)
-
- // 날짜 상태
- var startYear by rememberSaveable { mutableIntStateOf(today.year) }
- var startMonth by rememberSaveable { mutableIntStateOf(today.monthValue) }
- var startDay by rememberSaveable { mutableIntStateOf(today.dayOfMonth) }
-
- // 유효한 날짜 범위 계산
- val years = remember { (today.year..maxDate.year).toList() }
-
- // 월 범위 계산 (년도에 따라 동적으로)
- val months = remember(startYear) {
- when (startYear) {
- today.year -> (today.monthValue..12).toList()
- maxDate.year -> (1..maxDate.monthValue).toList()
- else -> (1..12).toList()
+ val maxDate = today.plusMonths(12)
+ var isInitialized by rememberSaveable { mutableStateOf(false) }
+
+ var startDate by rememberSaveable { mutableStateOf(today) }
+ var endDate by rememberSaveable { mutableStateOf(today.plusDays(1)) }
+ var isPickerTouched by rememberSaveable { mutableStateOf(false) }
+
+ // 첫 시작 시에만 모든 날짜를 오늘 기준으로 초기화
+ LaunchedEffect(Unit) {
+ if (!isInitialized) {
+ startDate = today
+ endDate = today.plusDays(1)
+ isInitialized = true
}
}
- // 일 범위 계산 (년도, 월에 따라 동적으로)
- val days = remember(startYear, startMonth) {
- val selectedDate = LocalDate.of(startYear, startMonth, 1)
- val startDayOfMonth = if (startYear == today.year && startMonth == today.monthValue) {
- today.dayOfMonth
- } else {
- 1
- }
+ // 날짜 범위 계산
+ val daysBetween = ChronoUnit.DAYS.between(startDate, endDate)
+ val isOverLimit = daysBetween > 91
- val endDayOfMonth = if (startYear == maxDate.year && startMonth == maxDate.monthValue) {
- minOf(selectedDate.lengthOfMonth(), maxDate.dayOfMonth)
- } else {
- selectedDate.lengthOfMonth()
+ // 날짜 선택 콜백
+ LaunchedEffect(startDate, endDate) {
+ if (endDate.isAfter(startDate)) {
+ onDateRangeSelected(startDate, endDate)
}
-
- (startDayOfMonth..endDayOfMonth).toList()
}
- // 날짜 유효성 검사 및 자동 보정
- LaunchedEffect(startYear, startMonth, days) {
- if (startDay !in days) {
- startDay = days.lastOrNull() ?: startDay
+ // 날짜 유효성 검사 및 자동 조정
+ LaunchedEffect(startDate) {
+ val adjustedStartDate = when {
+ startDate.isBefore(today) -> today
+ startDate.isAfter(maxDate) -> maxDate
+ else -> startDate
}
- }
- // 오늘 이전 날짜 선택 방지
- LaunchedEffect(startYear, startMonth, startDay) {
- val selectedDate = LocalDate.of(startYear, startMonth, startDay)
- if (selectedDate.isBefore(today)) {
- startYear = today.year
- startMonth = today.monthValue
- startDay = today.dayOfMonth
+ if (adjustedStartDate != startDate) {
+ startDate = adjustedStartDate
}
- }
- // 날짜 객체로 변환
- val startDate = remember(startYear, startMonth, startDay) {
- try {
- LocalDate.of(startYear, startMonth, startDay)
- } catch (e: Exception) {
- // 유효하지 않은 날짜인 경우 오늘 날짜로 fallback
- today
+ // 끝 날짜가 시작 날짜보다 빠르면 조정
+ if (endDate.isBefore(startDate.plusDays(1))) {
+ endDate = startDate.plusDays(1)
}
}
- val endDate = remember(startDate) { startDate.plusDays(1) }
- // 90일 초과 체크
- val daysBetween = ChronoUnit.DAYS.between(startDate, endDate)
- val isOverLimit = daysBetween > 90
-
- var isPickerTouched by rememberSaveable { mutableStateOf(false) }
+ LaunchedEffect(endDate) {
+ val adjustedEndDate = when {
+ endDate.isAfter(maxDate) -> maxDate
+ endDate.isBefore(startDate.plusDays(1)) -> startDate.plusDays(1)
+ else -> endDate
+ }
- // 날짜 변경 시 콜백
- LaunchedEffect(startDate, endDate) {
- onDateRangeSelected(startDate, endDate)
+ if (adjustedEndDate != endDate) {
+ endDate = adjustedEndDate
+ }
}
Column(modifier = modifier.fillMaxWidth()) {
@@ -107,83 +88,59 @@ fun GroupRoomDurationPicker(
style = typography.smalltitle_sb600_s18_h24,
color = colors.White
)
+
Row(
modifier = Modifier
.fillMaxWidth()
- .padding(top = 12.dp, start = 12.dp, end = 12.dp),
+ .padding(top = 12.dp),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
- // 시작 날짜
+ // 시작 날짜 Picker
GroupDatePicker(
- year = startYear,
- month = startMonth,
- day = startDay,
- years = years,
- months = months,
- days = days,
- onYearSelected = { newYear ->
- startYear = newYear
- // 년도 변경 시 월 유효성 검사
- val validMonths = when (newYear) {
- today.year -> (today.monthValue..12).toList()
- maxDate.year -> (1..maxDate.monthValue).toList()
- else -> (1..12).toList()
- }
- if (startMonth !in validMonths) {
- startMonth = validMonths.first()
- }
+ selectedDate = startDate,
+ minDate = today,
+ maxDate = maxDate,
+ onDateSelected = { newDate ->
+ startDate = newDate
},
- onMonthSelected = { newMonth ->
- startMonth = newMonth
- // 월 변경 시 일 유효성 검사
- val tempDate = LocalDate.of(startYear, newMonth, 1)
- val validStartDay = if (startYear == today.year && newMonth == today.monthValue) {
- today.dayOfMonth
- } else {
- 1
+ modifier = Modifier
+ .weight(1f)
+ .fillMaxWidth()
+ .pointerInput(Unit) {
+ detectTapGestures(
+ onPress = { isPickerTouched = true }
+ )
}
-
- val validEndDay = if (startYear == maxDate.year && newMonth == maxDate.monthValue) {
- minOf(tempDate.lengthOfMonth(), maxDate.dayOfMonth)
- } else {
- tempDate.lengthOfMonth()
- }
-
- if (startDay < validStartDay) {
- startDay = validStartDay
- } else if (startDay > validEndDay) {
- startDay = validEndDay
- }
- },
- onDaySelected = { startDay = it },
- modifier = Modifier.pointerInput(Unit) {
- detectTapGestures(
- onPress = { isPickerTouched = true }
- )
- }
)
+
// 구분자
Text(
text = "~",
style = typography.info_r400_s12,
color = colors.White,
- modifier = Modifier.padding(horizontal = 4.dp)
+ modifier = Modifier.padding(horizontal = 10.dp)
)
- // 종료 날짜(선택 불가, 읽기 전용)
+
+ // 끝 날짜 Picker
GroupDatePicker(
- year = endDate.year,
- month = endDate.monthValue,
- day = endDate.dayOfMonth,
- years = years, // 전체 년도 범위 제공
- months = (1..12).toList(), // 전체 월 범위 제공
- days = (1..LocalDate.of(endDate.year, endDate.monthValue, 1).lengthOfMonth()).toList(), // 해당 월의 전체 일 범위 제공
- onYearSelected = {},
- onMonthSelected = {},
- onDaySelected = {},
- modifier = Modifier // 비활성화 UI 추가 가능
+ selectedDate = endDate,
+ minDate = today,
+ maxDate = maxDate,
+ onDateSelected = { newDate ->
+ endDate = newDate
+ },
+ modifier = Modifier
+ .weight(1f)
+ .fillMaxWidth()
+ .pointerInput(Unit) {
+ detectTapGestures(
+ onPress = { isPickerTouched = true }
+ )
+ }
)
}
+
// 안내/에러 메시지
Row(
modifier = Modifier.fillMaxWidth(),
@@ -201,7 +158,7 @@ fun GroupRoomDurationPicker(
}
!isPickerTouched -> {
Text(
- text = stringResource(R.string.group_room_duration_comment),
+ text = stringResource(R.string.group_room_duration_initial_comment),
style = typography.info_r400_s12,
color = colors.NeonGreen,
textAlign = TextAlign.End,
@@ -223,21 +180,6 @@ fun GroupRoomDurationPicker(
}
}
}
- // 에러 메시지: 90일 초과
- Row(
- modifier = Modifier.fillMaxWidth(),
- horizontalArrangement = Arrangement.End
- ) {
- if (isOverLimit) {
- Text(
- text = stringResource(R.string.group_room_duration_error, daysBetween),
- style = typography.info_r400_s12,
- color = colors.Red,
- textAlign = TextAlign.End,
- modifier = Modifier.padding(top = 4.dp)
- )
- }
- }
}
}
diff --git a/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupWheelPicker.kt b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupWheelPicker.kt
index 3a4bed44..bfe22b40 100644
--- a/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupWheelPicker.kt
+++ b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupWheelPicker.kt
@@ -32,7 +32,7 @@ fun GroupWheelPicker(
selectedItem: T,
onItemSelected: (T) -> Unit,
displayText: (T) -> String = { it.toString() },
- selectedBackgroundColor: Color = colors.DarkGrey50,
+ selectedBackgroundColor: Color = colors.DarkGrey,
itemHeight: Int = 20,
isCircular: Boolean = true
) {
diff --git a/app/src/main/java/com/texthip/thip/ui/group/makeroom/screen/GroupMakeRoomScreen.kt b/app/src/main/java/com/texthip/thip/ui/group/makeroom/screen/GroupMakeRoomScreen.kt
index 3b7900d3..dad54249 100644
--- a/app/src/main/java/com/texthip/thip/ui/group/makeroom/screen/GroupMakeRoomScreen.kt
+++ b/app/src/main/java/com/texthip/thip/ui/group/makeroom/screen/GroupMakeRoomScreen.kt
@@ -121,8 +121,7 @@ fun GroupMakeRoomScreen(modifier: Modifier = Modifier) {
horizontalArrangement = Arrangement.End
) {
Text(
- text = if (selectedGenreIndex >= 0) stringResource(R.string.group_genre_selected_comment)
- else stringResource(R.string.group_genre_select_comment),
+ text = stringResource(R.string.group_genre_select_comment),
style = typography.info_r400_s12,
color = colors.NeonGreen
)
@@ -255,8 +254,7 @@ fun GroupMakeRoomScreen(modifier: Modifier = Modifier) {
showBookSearchSheet = false
},
savedBooks = dummySavedBooks,
- groupBooks = dummyGroupBooks,
- defaultTab = 0
+ groupBooks = dummyGroupBooks
)
}
}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index f36cb60f..a91eb98a 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -201,5 +201,6 @@
4자리 숫자를 입력해주세요.
%1$d월 %2$d일 자정에 자동으로 모집 마감되고 활동이 가능합니다.
검색해서 찾기
+ 모임방 활동이 시작되면, 독서메이트 모집이 자동으로 종료돼요.
\ No newline at end of file
From a4a87ba203d530272527add1c90e81b78a543444 Mon Sep 17 00:00:00 2001
From: Gyubin
Date: Wed, 9 Jul 2025 16:22:53 +0900
Subject: [PATCH 15/21] =?UTF-8?q?[ui]:=20Image=EB=A5=BC=20Icon=EC=9C=BC?=
=?UTF-8?q?=EB=A1=9C=20=EC=88=98=EC=A0=95=20(#36)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../thip/ui/common/topappbar/LogoTopAppBar.kt | 2 +-
.../component/GroupEmptyBookSheetContent.kt | 16 +++++++++-----
.../thip/ui/myPage/screen/MyPageScreen.kt | 2 +-
app/src/main/res/drawable/ic_notice.xml | 22 ++++++++++++++-----
4 files changed, 29 insertions(+), 13 deletions(-)
diff --git a/app/src/main/java/com/texthip/thip/ui/common/topappbar/LogoTopAppBar.kt b/app/src/main/java/com/texthip/thip/ui/common/topappbar/LogoTopAppBar.kt
index 1885b647..f1910b66 100644
--- a/app/src/main/java/com/texthip/thip/ui/common/topappbar/LogoTopAppBar.kt
+++ b/app/src/main/java/com/texthip/thip/ui/common/topappbar/LogoTopAppBar.kt
@@ -31,7 +31,7 @@ fun LogoTopAppBar(
val rightIcon = if (hasNotification) {
painterResource(R.drawable.ic_notice_yes)
} else {
- painterResource(R.drawable.ic_notice)
+ painterResource(R.drawable.ic_notification)
}
Box(
diff --git a/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupEmptyBookSheetContent.kt b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupEmptyBookSheetContent.kt
index 2e69daf5..d0d1028b 100644
--- a/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupEmptyBookSheetContent.kt
+++ b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupEmptyBookSheetContent.kt
@@ -1,7 +1,12 @@
package com.texthip.thip.ui.group.makeroom.component
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
@@ -25,9 +30,10 @@ fun EmptyBookSheetContent(
.padding(bottom = 30.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
- Image(
- painter = painterResource(id = R.drawable.ic_notice),
- contentDescription = null
+ Icon(
+ painter = painterResource(id = R.drawable.ic_notification),
+ contentDescription = null,
+ tint = colors.Grey02
)
Spacer(Modifier.height(12.dp))
Text(
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/screen/MyPageScreen.kt
index e746dfca..e5f3d666 100644
--- a/app/src/main/java/com/texthip/thip/ui/myPage/screen/MyPageScreen.kt
+++ b/app/src/main/java/com/texthip/thip/ui/myPage/screen/MyPageScreen.kt
@@ -109,7 +109,7 @@ fun MyPageScreen(
)
MenuItemButton(
text = stringResource(R.string.notification_settings),
- icon = painterResource(R.drawable.ic_notice),
+ icon = painterResource(R.drawable.ic_notification),
contentColor = colors.White,
backgroundColor = colors.DarkGrey02,
hasRightIcon = true,
diff --git a/app/src/main/res/drawable/ic_notice.xml b/app/src/main/res/drawable/ic_notice.xml
index 42aaeff8..5ccd9f64 100644
--- a/app/src/main/res/drawable/ic_notice.xml
+++ b/app/src/main/res/drawable/ic_notice.xml
@@ -1,9 +1,19 @@
+ android:width="25dp"
+ android:height="24dp"
+ android:viewportWidth="25"
+ android:viewportHeight="24">
+ android:pathData="M19.182,14.98C18.884,14.503 18.726,13.952 18.725,13.39V9.226C18.725,7.508 18.043,5.861 16.828,4.646C15.613,3.431 13.966,2.749 12.248,2.749C10.53,2.749 8.883,3.431 7.668,4.646C6.454,5.861 5.771,7.508 5.771,9.226L5.771,13.388C5.771,13.951 5.613,14.503 5.314,14.98L4.226,16.72C4.132,16.871 4.079,17.045 4.075,17.224C4.07,17.402 4.113,17.579 4.2,17.735C4.286,17.891 4.413,18.021 4.567,18.112C4.72,18.202 4.896,18.25 5.074,18.25H19.422C19.601,18.25 19.776,18.202 19.93,18.112C20.084,18.021 20.21,17.891 20.297,17.735C20.383,17.579 20.427,17.402 20.422,17.224C20.417,17.045 20.365,16.871 20.27,16.72L19.182,14.98Z"
+ android:strokeLineJoin="round"
+ android:strokeWidth="1.5"
+ android:fillColor="#00000000"
+ android:strokeColor="#FEFEFE"
+ android:strokeLineCap="round"/>
+
From 5fb817156ad68153fd8e3419a528ba11c3912ec9 Mon Sep 17 00:00:00 2001
From: Gyubin
Date: Wed, 9 Jul 2025 16:23:22 +0900
Subject: [PATCH 16/21] =?UTF-8?q?[ui]:=20Image=EB=A5=BC=20Icon=EC=9C=BC?=
=?UTF-8?q?=EB=A1=9C=20=EC=88=98=EC=A0=95=20(#36)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/src/main/res/drawable/ic_notification.xml | 9 +++++++++
1 file changed, 9 insertions(+)
create mode 100644 app/src/main/res/drawable/ic_notification.xml
diff --git a/app/src/main/res/drawable/ic_notification.xml b/app/src/main/res/drawable/ic_notification.xml
new file mode 100644
index 00000000..42aaeff8
--- /dev/null
+++ b/app/src/main/res/drawable/ic_notification.xml
@@ -0,0 +1,9 @@
+
+
+
From ea54253ac428289de18e93f08324ea49673e7248 Mon Sep 17 00:00:00 2001
From: Gyubin
Date: Wed, 9 Jul 2025 16:40:38 +0900
Subject: [PATCH 17/21] =?UTF-8?q?[ui]:=20String,=20util=20=ED=95=A8?=
=?UTF-8?q?=EC=88=98=20=EC=B6=94=EC=B6=9C=20(#36)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../makeroom/component/GroupInputField.kt | 4 ++-
.../makeroom/component/GroupSelectBook.kt | 1 -
.../makeroom/component/GroupWheelPicker.kt | 20 +++----------
.../makeroom/util/WheelPickerDisplayUtils.kt | 30 +++++++++++++++++++
app/src/main/res/values/strings.xml | 1 +
5 files changed, 38 insertions(+), 18 deletions(-)
create mode 100644 app/src/main/java/com/texthip/thip/ui/group/makeroom/util/WheelPickerDisplayUtils.kt
diff --git a/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupInputField.kt b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupInputField.kt
index 0efd73b1..2e4a589e 100644
--- a/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupInputField.kt
+++ b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupInputField.kt
@@ -15,8 +15,10 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.SolidColor
+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
import com.texthip.thip.ui.theme.ThipTheme.colors
import com.texthip.thip.ui.theme.ThipTheme.typography
@@ -72,7 +74,7 @@ fun GroupInputField(
horizontalArrangement = Arrangement.End
) {
Text(
- text = "${value.length} / $maxLength",
+ text = stringResource(R.string.group_input_count, value.length, maxLength),
style = typography.info_r400_s12,
color = if (isOverflow) colors.Red else colors.NeonGreen
)
diff --git a/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupSelectBook.kt b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupSelectBook.kt
index f15dd34c..0b31c50b 100644
--- a/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupSelectBook.kt
+++ b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupSelectBook.kt
@@ -45,7 +45,6 @@ fun GroupSelectBook(
Spacer(modifier = Modifier.height(20.dp))
if (selectedBook == null) {
- // 미선택 상태: 기존 검색 UI
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
diff --git a/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupWheelPicker.kt b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupWheelPicker.kt
index bfe22b40..b7d353c0 100644
--- a/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupWheelPicker.kt
+++ b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupWheelPicker.kt
@@ -19,6 +19,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
+import com.texthip.thip.ui.group.makeroom.util.WheelPickerUtils
import com.texthip.thip.ui.theme.ThipTheme.colors
import com.texthip.thip.ui.theme.ThipTheme.typography
import kotlinx.coroutines.launch
@@ -38,7 +39,6 @@ fun GroupWheelPicker(
) {
if (items.isEmpty()) return
- // 아이템이 하나인 경우 스크롤 비활성화
val isScrollEnabled = items.size > 1
val circular = isCircular && items.size > 2
@@ -53,28 +53,16 @@ fun GroupWheelPicker(
val spacingPx = with(density) { 9.dp.toPx() }
val itemSpacing = itemHeightPx + spacingPx
- // index가 음수/양수 모두에서 올바르게 작동하도록
fun getCircularIndex(index: Int): Int {
- val size = items.size
- return ((index % size) + size) % size
+ return WheelPickerUtils.getCircularIndex(index, items.size)
}
- // offset을 항상 0 ~ (items.size-1)*itemSpacing 범위로
fun normalizeOffset(offset: Float): Float {
- if (!circular) return offset
- val total = items.size * itemSpacing
- return ((offset % total) + total) % total
+ return WheelPickerUtils.normalizeOffset(offset, itemSpacing, items.size, circular)
}
- // 오프셋을 아이템 인덱스로 변환 (0이 중앙)
fun offsetToIndex(offset: Float): Int {
- val total = items.size * itemSpacing
- val normalized = if (circular) normalizeOffset(offset) else offset
- val centerIndex = (-normalized / itemSpacing).roundToInt()
- return if (circular) getCircularIndex(centerIndex) else centerIndex.coerceIn(
- 0,
- items.size - 1
- )
+ return WheelPickerUtils.offsetToIndex(offset, itemSpacing, items.size, circular)
}
// 선택 아이템이 바뀌면 중앙에 오도록 offset 이동
diff --git a/app/src/main/java/com/texthip/thip/ui/group/makeroom/util/WheelPickerDisplayUtils.kt b/app/src/main/java/com/texthip/thip/ui/group/makeroom/util/WheelPickerDisplayUtils.kt
new file mode 100644
index 00000000..9a355d52
--- /dev/null
+++ b/app/src/main/java/com/texthip/thip/ui/group/makeroom/util/WheelPickerDisplayUtils.kt
@@ -0,0 +1,30 @@
+package com.texthip.thip.ui.group.makeroom.util
+
+import kotlin.math.roundToInt
+
+object WheelPickerUtils {
+ @JvmStatic
+ fun getCircularIndex(index: Int, size: Int): Int {
+ return ((index % size) + size) % size
+ }
+
+ @JvmStatic
+ fun normalizeOffset(offset: Float, itemSpacing: Float, size: Int, circular: Boolean): Float {
+ if (!circular) return offset
+ val total = size * itemSpacing
+ return ((offset % total) + total) % total
+ }
+
+ @JvmStatic
+ fun offsetToIndex(
+ offset: Float,
+ itemSpacing: Float,
+ size: Int,
+ circular: Boolean
+ ): Int {
+ val normalized = if (circular) normalizeOffset(offset, itemSpacing, size, circular) else offset
+ val centerIndex = (-normalized / itemSpacing).roundToInt()
+ return if (circular) getCircularIndex(centerIndex, size)
+ else centerIndex.coerceIn(0, size - 1)
+ }
+}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index a91eb98a..70ded922 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -202,5 +202,6 @@
%1$d월 %2$d일 자정에 자동으로 모집 마감되고 활동이 가능합니다.
검색해서 찾기
모임방 활동이 시작되면, 독서메이트 모집이 자동으로 종료돼요.
+ %1$d / %2$d
\ No newline at end of file
From ffc818ba4bc7d828ebae015674f9dd8cc722aaf9 Mon Sep 17 00:00:00 2001
From: Gyubin
Date: Wed, 9 Jul 2025 16:49:46 +0900
Subject: [PATCH 18/21] =?UTF-8?q?[ui]:=20section=20dp=20=EC=88=98=EC=A0=95?=
=?UTF-8?q?=20=EB=B0=8F=20=EC=84=B8=EB=A1=9C=20=EC=A0=95=EB=A0=AC=20?=
=?UTF-8?q?=EC=88=98=EC=A0=95=20(#36)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../ui/group/makeroom/screen/GroupRegisterBookScreen.kt | 7 ++++++-
.../ui/group/myroom/component/GroupDeadlineRoomSection.kt | 3 +--
.../java/com/texthip/thip/ui/group/screen/GroupScreen.kt | 2 +-
app/src/main/res/values/strings.xml | 3 ++-
4 files changed, 10 insertions(+), 5 deletions(-)
diff --git a/app/src/main/java/com/texthip/thip/ui/group/makeroom/screen/GroupRegisterBookScreen.kt b/app/src/main/java/com/texthip/thip/ui/group/makeroom/screen/GroupRegisterBookScreen.kt
index ffcde235..6f497c9b 100644
--- a/app/src/main/java/com/texthip/thip/ui/group/makeroom/screen/GroupRegisterBookScreen.kt
+++ b/app/src/main/java/com/texthip/thip/ui/group/makeroom/screen/GroupRegisterBookScreen.kt
@@ -44,7 +44,12 @@ fun GroupRegisterBookScreen(modifier: Modifier = Modifier) {
Spacer(modifier = Modifier.padding(top = 8.dp))
Text(
- text = stringResource(R.string.group_request_book_comment),
+ text = stringResource(R.string.group_request_book_comment_1),
+ style = typography.copy_r400_s14,
+ color = colors.White
+ )
+ Text(
+ text = stringResource(R.string.group_request_book_comment_2),
style = typography.copy_r400_s14,
color = colors.White
)
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
index a13b58f6..a6fb52fd 100644
--- 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
@@ -44,8 +44,7 @@ fun GroupRoomDeadlineSection(
) {
BoxWithConstraints(
modifier = Modifier
- .fillMaxWidth()
- .height(588.dp),
+ .fillMaxWidth(),
contentAlignment = Alignment.Center
) {
val horizontalPadding = sideMargin
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 9f5ce6dd..a9ddb7d4 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
@@ -90,7 +90,7 @@ fun GroupScreen(
roomSections = roomSections,
onRoomClick = { viewModel.onRoomCardClick(it) }
)
- Spacer(Modifier.height(32.dp))
+ Spacer(Modifier.height(102.dp))
}
// 오른쪽 하단 FAB
FloatingButton(
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 70ded922..43c387bd 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -176,7 +176,8 @@
현재 등록된 책이 아닙니다.\n원하시는 책을 신청해주세요.
책 신청
texthip2025@gmail.com
- 이메일로 책 제목, 출판사를 보내주시면\n빠른 시일내로 책을 추가해드릴게요!
+ 이메일로 책 제목, 출판사를 보내주시면
+ 빠른 시일내로 책을 추가해드릴게요!
책 선택
책 장르
책을 가장 잘 설명하는 장르를 하나 골라주세요
From 99e2b08ac4b5e449122d9757b6871963929000d6 Mon Sep 17 00:00:00 2001
From: Gyubin
Date: Wed, 9 Jul 2025 20:27:00 +0900
Subject: [PATCH 19/21] =?UTF-8?q?[refactor]:=20string=20=EC=88=98=EC=A0=95?=
=?UTF-8?q?=20=EB=B0=8F=20=EA=B8=B0=ED=83=80=20=EC=88=98=EC=A0=95=20(#36)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../java/com/texthip/thip/ui/common/topappbar/LogoTopAppBar.kt | 2 +-
.../thip/ui/group/makeroom/screen/GroupRegisterBookScreen.kt | 2 +-
.../main/java/com/texthip/thip/ui/group/screen/GroupScreen.kt | 2 +-
app/src/main/res/values/strings.xml | 1 -
4 files changed, 3 insertions(+), 4 deletions(-)
diff --git a/app/src/main/java/com/texthip/thip/ui/common/topappbar/LogoTopAppBar.kt b/app/src/main/java/com/texthip/thip/ui/common/topappbar/LogoTopAppBar.kt
index f1910b66..1885b647 100644
--- a/app/src/main/java/com/texthip/thip/ui/common/topappbar/LogoTopAppBar.kt
+++ b/app/src/main/java/com/texthip/thip/ui/common/topappbar/LogoTopAppBar.kt
@@ -31,7 +31,7 @@ fun LogoTopAppBar(
val rightIcon = if (hasNotification) {
painterResource(R.drawable.ic_notice_yes)
} else {
- painterResource(R.drawable.ic_notification)
+ painterResource(R.drawable.ic_notice)
}
Box(
diff --git a/app/src/main/java/com/texthip/thip/ui/group/makeroom/screen/GroupRegisterBookScreen.kt b/app/src/main/java/com/texthip/thip/ui/group/makeroom/screen/GroupRegisterBookScreen.kt
index 6f497c9b..f9ec8805 100644
--- a/app/src/main/java/com/texthip/thip/ui/group/makeroom/screen/GroupRegisterBookScreen.kt
+++ b/app/src/main/java/com/texthip/thip/ui/group/makeroom/screen/GroupRegisterBookScreen.kt
@@ -37,7 +37,7 @@ fun GroupRegisterBookScreen(modifier: Modifier = Modifier) {
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
- text = stringResource(R.string.group_thip_email),
+ text = stringResource(R.string.customer_center_email),
style = typography.smalltitle_sb600_s18_h24,
color = colors.White
)
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 a9ddb7d4..e9746096 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
@@ -55,7 +55,7 @@ fun GroupScreen(
// 상단바
LogoTopAppBar(
leftIcon = painterResource(R.drawable.ic_done),
- hasNotification = true,
+ hasNotification = false,
onLeftClick = { },
onRightClick = { }
)
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 43c387bd..1880926f 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -175,7 +175,6 @@
책 등록하기
현재 등록된 책이 아닙니다.\n원하시는 책을 신청해주세요.
책 신청
- texthip2025@gmail.com
이메일로 책 제목, 출판사를 보내주시면
빠른 시일내로 책을 추가해드릴게요!
책 선택
From a7513d1e7b41c75f9347b911d28f2e390b1c7378 Mon Sep 17 00:00:00 2001
From: Gyubin
Date: Wed, 9 Jul 2025 20:27:32 +0900
Subject: [PATCH 20/21] =?UTF-8?q?[refactor]:=20GroupMakeRoomScreen?=
=?UTF-8?q?=EC=9D=98=20=EC=83=81=ED=83=9C=20=EB=B0=8F=20=EB=8D=B0=EC=9D=B4?=
=?UTF-8?q?=ED=84=B0=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EB=B6=84=EB=A6=AC=20?=
=?UTF-8?q?=EB=B0=8F=20=EB=B7=B0=EB=AA=A8=EB=8D=B8=20=EC=98=88=EC=8B=9C=20?=
=?UTF-8?q?=EC=9E=91=EC=84=B1=20(#36)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../makeroom/component/SectionDivider.kt | 23 ++
.../makeroom/mock/GroupMakeRoomRequest.kt | 15 ++
.../makeroom/mock/GroupMakeRoomUiState.kt | 56 +++++
.../makeroom/screen/GroupMakeRoomScreen.kt | 211 ++++++++----------
.../viewmodel/GroupMakeRoomViewModel.kt | 125 +++++++++++
5 files changed, 318 insertions(+), 112 deletions(-)
create mode 100644 app/src/main/java/com/texthip/thip/ui/group/makeroom/component/SectionDivider.kt
create mode 100644 app/src/main/java/com/texthip/thip/ui/group/makeroom/mock/GroupMakeRoomRequest.kt
create mode 100644 app/src/main/java/com/texthip/thip/ui/group/makeroom/mock/GroupMakeRoomUiState.kt
create mode 100644 app/src/main/java/com/texthip/thip/ui/group/makeroom/viewmodel/GroupMakeRoomViewModel.kt
diff --git a/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/SectionDivider.kt b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/SectionDivider.kt
new file mode 100644
index 00000000..8643e413
--- /dev/null
+++ b/app/src/main/java/com/texthip/thip/ui/group/makeroom/component/SectionDivider.kt
@@ -0,0 +1,23 @@
+package com.texthip.thip.ui.group.makeroom.component
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import com.texthip.thip.ui.theme.ThipTheme.colors
+
+@Composable
+fun SectionDivider() {
+ Spacer(modifier = Modifier.padding(top = 32.dp))
+ Spacer(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(1.dp)
+ .background(colors.DarkGrey02)
+ )
+ Spacer(modifier = Modifier.padding(top = 32.dp))
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/texthip/thip/ui/group/makeroom/mock/GroupMakeRoomRequest.kt b/app/src/main/java/com/texthip/thip/ui/group/makeroom/mock/GroupMakeRoomRequest.kt
new file mode 100644
index 00000000..b41d0b8b
--- /dev/null
+++ b/app/src/main/java/com/texthip/thip/ui/group/makeroom/mock/GroupMakeRoomRequest.kt
@@ -0,0 +1,15 @@
+package com.texthip.thip.ui.group.makeroom.mock
+
+import java.time.LocalDate
+
+data class GroupMakeRoomRequest(
+ val selectedBook: BookData?,
+ val genreIndex: Int,
+ val roomTitle: String,
+ val roomDescription: String,
+ val meetingStartDate: LocalDate,
+ val meetingEndDate: LocalDate,
+ val memberLimit: Int,
+ val isPrivate: Boolean,
+ val password: String = ""
+)
diff --git a/app/src/main/java/com/texthip/thip/ui/group/makeroom/mock/GroupMakeRoomUiState.kt b/app/src/main/java/com/texthip/thip/ui/group/makeroom/mock/GroupMakeRoomUiState.kt
new file mode 100644
index 00000000..7143a1d2
--- /dev/null
+++ b/app/src/main/java/com/texthip/thip/ui/group/makeroom/mock/GroupMakeRoomUiState.kt
@@ -0,0 +1,56 @@
+package com.texthip.thip.ui.group.makeroom.mock
+
+import java.time.LocalDate
+import java.time.temporal.ChronoUnit
+
+data class GroupMakeRoomUiState(
+ val selectedBook: BookData? = null,
+ val showBookSearchSheet: Boolean = false,
+ val selectedGenreIndex: Int = -1,
+ val roomTitle: String = "",
+ val roomDescription: String = "",
+ val meetingStartDate: LocalDate = LocalDate.now(),
+ val meetingEndDate: LocalDate = LocalDate.now().plusDays(1),
+ val memberLimit: Int = 30,
+ val isPrivate: Boolean = false,
+ val password: String = "",
+ val isLoading: Boolean = false,
+ val errorMessage: String? = null
+) {
+ // 유효성 검사 로직
+ val isDurationValid: Boolean
+ get() {
+ val daysBetween = ChronoUnit.DAYS.between(meetingStartDate, meetingEndDate)
+ return daysBetween in 1..90
+ }
+
+ val isCountValid: Boolean
+ get() = memberLimit in 2..30
+
+ val isPasswordValid: Boolean
+ get() = !isPrivate || password.length == 4
+
+ val isFormValid: Boolean
+ get() = selectedBook != null &&
+ selectedGenreIndex >= 0 &&
+ roomTitle.isNotBlank() &&
+ roomDescription.isNotBlank() &&
+ isDurationValid &&
+ isCountValid &&
+ isPasswordValid
+
+ // 서버 전송용 데이터로 변환
+ fun toRequest(): GroupMakeRoomRequest {
+ return GroupMakeRoomRequest(
+ selectedBook = selectedBook,
+ genreIndex = selectedGenreIndex,
+ roomTitle = roomTitle.trim(),
+ roomDescription = roomDescription.trim(),
+ meetingStartDate = meetingStartDate,
+ meetingEndDate = meetingEndDate,
+ memberLimit = memberLimit,
+ isPrivate = isPrivate,
+ password = if (isPrivate) password else ""
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/texthip/thip/ui/group/makeroom/screen/GroupMakeRoomScreen.kt b/app/src/main/java/com/texthip/thip/ui/group/makeroom/screen/GroupMakeRoomScreen.kt
index dad54249..7acab2cc 100644
--- a/app/src/main/java/com/texthip/thip/ui/group/makeroom/screen/GroupMakeRoomScreen.kt
+++ b/app/src/main/java/com/texthip/thip/ui/group/makeroom/screen/GroupMakeRoomScreen.kt
@@ -1,6 +1,5 @@
package com.texthip.thip.ui.group.makeroom.screen
-import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
@@ -18,63 +17,60 @@ import com.texthip.thip.ui.common.buttons.GenreChipRow
import com.texthip.thip.ui.common.buttons.ToggleSwitchButton
import com.texthip.thip.ui.common.forms.WarningTextField
import com.texthip.thip.ui.common.topappbar.InputTopAppBar
-import com.texthip.thip.ui.group.makeroom.component.GroupSelectBook
import com.texthip.thip.ui.group.makeroom.component.GroupBookSearchBottomSheet
import com.texthip.thip.ui.group.makeroom.component.GroupInputField
import com.texthip.thip.ui.group.makeroom.component.GroupRoomDurationPicker
+import com.texthip.thip.ui.group.makeroom.component.GroupSelectBook
import com.texthip.thip.ui.group.makeroom.component.MemberLimitPicker
+import com.texthip.thip.ui.group.makeroom.component.SectionDivider
import com.texthip.thip.ui.group.makeroom.mock.BookData
-import com.texthip.thip.ui.group.makeroom.mock.dummySavedBooks
+import com.texthip.thip.ui.group.makeroom.mock.GroupMakeRoomRequest
import com.texthip.thip.ui.group.makeroom.mock.dummyGroupBooks
+import com.texthip.thip.ui.group.makeroom.mock.dummySavedBooks
+import com.texthip.thip.ui.group.makeroom.viewmodel.ApiResult
+import com.texthip.thip.ui.group.makeroom.viewmodel.GroupCreateResponse
+import com.texthip.thip.ui.group.makeroom.viewmodel.GroupMakeRoomViewModel
+import com.texthip.thip.ui.group.makeroom.viewmodel.GroupRepository
import com.texthip.thip.ui.theme.ThipTheme
import com.texthip.thip.ui.theme.ThipTheme.colors
import com.texthip.thip.ui.theme.ThipTheme.typography
-import java.time.LocalDate
-import java.time.temporal.ChronoUnit
+
@Composable
-fun GroupMakeRoomScreen(modifier: Modifier = Modifier) {
+fun GroupMakeRoomScreen(
+ viewModel: GroupMakeRoomViewModel,
+ onNavigateBack: () -> Unit,
+ onGroupCreated: () -> Unit,
+ modifier: Modifier = Modifier
+) {
+ val uiState by viewModel.uiState.collectAsState()
val scrollState = rememberScrollState()
-
- var selectedBook by remember { mutableStateOf(null) }
- var showBookSearchSheet by remember { mutableStateOf(false) }
val genres = listOf("문학", "과학·IT", "사회과학", "인문학", "예술")
- var selectedGenreIndex by remember { mutableIntStateOf(-1) }
- var roomTitle by remember { mutableStateOf("") }
- var roomDescription by remember { mutableStateOf("") }
- var meetingStartDate by remember { mutableStateOf(LocalDate.now()) }
- var meetingEndDate by remember { mutableStateOf(LocalDate.now().plusDays(1)) }
- var selectedCount by remember { mutableIntStateOf(30) }
- var isPrivate by remember { mutableStateOf(false) }
- var password by remember { mutableStateOf("") }
- val daysBetween = ChronoUnit.DAYS.between(meetingStartDate, meetingEndDate)
- val isDurationValid = (daysBetween in 1..90)
- val isCountValid = selectedCount in 2..30
- val isPasswordValid = !isPrivate || (password.length == 4)
-
- val isButtonEnabled = selectedBook != null &&
- selectedGenreIndex >= 0 &&
- roomTitle.isNotBlank() &&
- roomDescription.isNotBlank() &&
- isDurationValid &&
- isCountValid &&
- isPasswordValid
+ // 에러 메시지 표시
+ LaunchedEffect(uiState.errorMessage) {
+ uiState.errorMessage?.let { message ->
+ viewModel.clearError()
+ }
+ }
Box {
Column(
modifier = modifier
.fillMaxSize()
- .then(if (showBookSearchSheet) Modifier.blur(5.dp) else Modifier),
+ .then(if (uiState.showBookSearchSheet) Modifier.blur(5.dp) else Modifier),
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.CenterHorizontally
) {
InputTopAppBar(
title = stringResource(R.string.group_making_group),
- isRightButtonEnabled = isButtonEnabled,
- onLeftClick = {},
+ isRightButtonEnabled = uiState.isFormValid && !uiState.isLoading,
+ onLeftClick = onNavigateBack,
onRightClick = {
- // 완료 버튼 클릭 로직
+ viewModel.createGroup(
+ onSuccess = onGroupCreated,
+ onError = { /* 에러는 uiState.errorMessage로 처리 */ }
+ )
}
)
@@ -88,19 +84,12 @@ fun GroupMakeRoomScreen(modifier: Modifier = Modifier) {
Spacer(modifier = Modifier.padding(top = 20.dp))
GroupSelectBook(
- selectedBook = selectedBook,
- onChangeBookClick = { showBookSearchSheet = true },
- onSelectBookClick = { showBookSearchSheet = true }
+ selectedBook = uiState.selectedBook,
+ onChangeBookClick = { viewModel.toggleBookSearchSheet(true) },
+ onSelectBookClick = { viewModel.toggleBookSearchSheet(true) }
)
- Spacer(modifier = Modifier.padding(top = 32.dp))
- Spacer(
- modifier = Modifier
- .fillMaxWidth()
- .height(1.dp)
- .background(colors.DarkGrey02)
- )
- Spacer(modifier = Modifier.padding(top = 32.dp))
+ SectionDivider()
Text(
text = stringResource(R.string.group_book_genre),
@@ -111,8 +100,8 @@ fun GroupMakeRoomScreen(modifier: Modifier = Modifier) {
GenreChipRow(
modifier = Modifier.width(18.dp),
genres = genres,
- selectedIndex = selectedGenreIndex,
- onSelect = { selectedGenreIndex = it }
+ selectedIndex = uiState.selectedGenreIndex,
+ onSelect = viewModel::selectGenre
)
Spacer(modifier = Modifier.height(12.dp))
@@ -127,78 +116,39 @@ fun GroupMakeRoomScreen(modifier: Modifier = Modifier) {
)
}
- Spacer(modifier = Modifier.padding(top = 32.dp))
- Spacer(
- modifier = Modifier
- .fillMaxWidth()
- .height(1.dp)
- .background(colors.DarkGrey02)
- )
- Spacer(modifier = Modifier.padding(top = 32.dp))
+ SectionDivider()
GroupInputField(
title = stringResource(R.string.group_room_title),
hint = stringResource(R.string.group_room_title_hint),
- value = roomTitle,
+ value = uiState.roomTitle,
maxLength = 15,
- onValueChange = { roomTitle = it }
+ onValueChange = viewModel::updateRoomTitle
)
- Spacer(modifier = Modifier.padding(top = 32.dp))
- Spacer(
- modifier = Modifier
- .fillMaxWidth()
- .height(1.dp)
- .background(colors.DarkGrey02)
- )
- Spacer(modifier = Modifier.padding(top = 32.dp))
+ SectionDivider()
GroupInputField(
title = stringResource(R.string.group_room_explain),
hint = stringResource(R.string.group_room_explain_hint),
- value = roomDescription,
- onValueChange = { roomDescription = it }
+ value = uiState.roomDescription,
+ onValueChange = viewModel::updateRoomDescription
)
- Spacer(modifier = Modifier.padding(top = 32.dp))
- Spacer(
- modifier = Modifier
- .fillMaxWidth()
- .height(1.dp)
- .background(colors.DarkGrey02)
- )
- Spacer(modifier = Modifier.padding(top = 32.dp))
+ SectionDivider()
GroupRoomDurationPicker(
- onDateRangeSelected = { startDate, endDate ->
- meetingStartDate = startDate
- meetingEndDate = endDate
- }
+ onDateRangeSelected = viewModel::setDateRange
)
- Spacer(modifier = Modifier.padding(bottom = 32.dp))
- Spacer(
- modifier = Modifier
- .fillMaxWidth()
- .height(1.dp)
- .background(colors.DarkGrey02)
- )
- Spacer(modifier = Modifier.padding(top = 32.dp))
+ SectionDivider()
MemberLimitPicker(
- selectedCount = selectedCount,
- onCountSelected = { selectedCount = it }
+ selectedCount = uiState.memberLimit,
+ onCountSelected = viewModel::setMemberLimit
)
- Spacer(modifier = Modifier.padding(bottom = 32.dp))
- Spacer(
- modifier = Modifier
- .fillMaxWidth()
- .height(1.dp)
- .background(colors.DarkGrey02)
- )
- Spacer(modifier = Modifier.padding(top = 32.dp))
-
+ SectionDivider()
Text(
text = stringResource(R.string.group_private_option),
@@ -217,21 +167,18 @@ fun GroupMakeRoomScreen(modifier: Modifier = Modifier) {
color = colors.White
)
ToggleSwitchButton(
- isChecked = isPrivate,
- onToggleChange = {
- isPrivate = it
- if (!it) password = ""
- }
+ isChecked = uiState.isPrivate,
+ onToggleChange = viewModel::togglePrivate
)
}
- if (isPrivate) {
+ if (uiState.isPrivate) {
Spacer(modifier = Modifier.height(12.dp))
WarningTextField(
- value = password,
- onValueChange = { password = it },
+ value = uiState.password,
+ onValueChange = viewModel::updatePassword,
hint = stringResource(R.string.group_password_hint),
- showWarning = password.isNotEmpty() && password.length < 4,
+ showWarning = uiState.password.isNotEmpty() && uiState.password.length < 4,
warningMessage = stringResource(R.string.group_private_warning_message),
maxLength = 4,
isNumberOnly = true,
@@ -243,27 +190,67 @@ fun GroupMakeRoomScreen(modifier: Modifier = Modifier) {
}
}
- if (showBookSearchSheet) {
+ if (uiState.showBookSearchSheet) {
GroupBookSearchBottomSheet(
- onDismiss = { showBookSearchSheet = false },
+ onDismiss = { viewModel.toggleBookSearchSheet(false) },
onBookSelect = { book: BookData ->
- selectedBook = book
- showBookSearchSheet = false
+ viewModel.selectBook(book)
+ viewModel.toggleBookSearchSheet(false)
},
onRequestBook = {
- showBookSearchSheet = false
+ viewModel.toggleBookSearchSheet(false)
},
savedBooks = dummySavedBooks,
groupBooks = dummyGroupBooks
)
}
+
+ // 로딩 인디케이터
+ /*if (uiState.isLoading) {
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(colors.Black.copy(alpha = 0.5f)),
+ contentAlignment = Alignment.Center
+ ) {
+ CircularProgressIndicator(color = colors.NeonGreen)
+ }
+ }*/
}
}
+
@Preview
@Composable
private fun GroupMakeRoomScreenPreview() {
+ // Preview용 MockViewModel 생성
+ val mockViewModel = object : GroupMakeRoomViewModel(MockGroupRepository()) {
+ // 필요한 경우 Preview용 초기 상태 설정
+ init {
+ // 예시: 미리 선택된 책이 있는 상태로 Preview
+ // selectBook(BookData(id = "1", title = "예시 책", author = "작가"))
+ // selectGenre(0)
+ }
+ }
+
ThipTheme {
- GroupMakeRoomScreen()
+ GroupMakeRoomScreen(
+ viewModel = mockViewModel,
+ onNavigateBack = { },
+ onGroupCreated = { }
+ )
+ }
+}
+
+// Preview용 Mock Repository
+class MockGroupRepository : GroupRepository {
+ override suspend fun createGroup(request: GroupMakeRoomRequest): ApiResult {
+ return ApiResult(
+ isSuccess = true,
+ data = GroupCreateResponse(
+ groupId = "mock_group_id",
+ groupName = "Mock Group"
+ )
+ )
}
}
diff --git a/app/src/main/java/com/texthip/thip/ui/group/makeroom/viewmodel/GroupMakeRoomViewModel.kt b/app/src/main/java/com/texthip/thip/ui/group/makeroom/viewmodel/GroupMakeRoomViewModel.kt
new file mode 100644
index 00000000..2e2c4ef6
--- /dev/null
+++ b/app/src/main/java/com/texthip/thip/ui/group/makeroom/viewmodel/GroupMakeRoomViewModel.kt
@@ -0,0 +1,125 @@
+package com.texthip.thip.ui.group.makeroom.viewmodel
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.texthip.thip.ui.group.makeroom.mock.BookData
+import com.texthip.thip.ui.group.makeroom.mock.GroupMakeRoomRequest
+import com.texthip.thip.ui.group.makeroom.mock.GroupMakeRoomUiState
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.launch
+import java.time.LocalDate
+
+// 나중에 서버와 연동할 때 사용할 뷰모델 예시
+open class GroupMakeRoomViewModel(
+ private val groupRepository: GroupRepository // 의존성 주입
+) : ViewModel() {
+
+ private val _uiState = MutableStateFlow(GroupMakeRoomUiState())
+ val uiState: StateFlow = _uiState.asStateFlow()
+
+ private val genres = listOf("문학", "과학·IT", "사회과학", "인문학", "예술")
+
+ // 책 선택
+ fun selectBook(book: BookData) {
+ _uiState.value = _uiState.value.copy(selectedBook = book)
+ }
+
+ // 책 검색 시트 표시 상태 변경
+ fun toggleBookSearchSheet(show: Boolean) {
+ _uiState.value = _uiState.value.copy(showBookSearchSheet = show)
+ }
+
+ // 장르 선택
+ fun selectGenre(index: Int) {
+ _uiState.value = _uiState.value.copy(selectedGenreIndex = index)
+ }
+
+ // 방 제목 변경
+ fun updateRoomTitle(title: String) {
+ _uiState.value = _uiState.value.copy(roomTitle = title)
+ }
+
+ // 방 설명 변경
+ fun updateRoomDescription(description: String) {
+ _uiState.value = _uiState.value.copy(roomDescription = description)
+ }
+
+ // 모임 날짜 범위 설정
+ fun setDateRange(startDate: LocalDate, endDate: LocalDate) {
+ _uiState.value = _uiState.value.copy(
+ meetingStartDate = startDate,
+ meetingEndDate = endDate
+ )
+ }
+
+ // 인원 수 설정
+ fun setMemberLimit(count: Int) {
+ _uiState.value = _uiState.value.copy(memberLimit = count)
+ }
+
+ // 비밀방 설정
+ fun togglePrivate(isPrivate: Boolean) {
+ _uiState.value = _uiState.value.copy(
+ isPrivate = isPrivate,
+ password = if (!isPrivate) "" else _uiState.value.password
+ )
+ }
+
+ // 비밀번호 설정
+ fun updatePassword(password: String) {
+ _uiState.value = _uiState.value.copy(password = password)
+ }
+
+ // 그룹 생성 요청
+ fun createGroup(onSuccess: () -> Unit, onError: (String) -> Unit) {
+ val currentState = _uiState.value
+
+ if (!currentState.isFormValid) {
+ onError("입력 정보를 확인해주세요")
+ return
+ }
+
+ viewModelScope.launch {
+ try {
+ _uiState.value = currentState.copy(isLoading = true, errorMessage = null)
+
+ val request = currentState.toRequest()
+ val result = groupRepository.createGroup(request)
+
+ if (result.isSuccess) {
+ onSuccess()
+ } else {
+ onError(result.message ?: "그룹 생성에 실패했습니다")
+ }
+ } catch (e: Exception) {
+ onError("네트워크 오류가 발생했습니다: ${e.message}")
+ } finally {
+ _uiState.value = _uiState.value.copy(isLoading = false)
+ }
+ }
+ }
+
+ // 에러 메시지 클리어
+ fun clearError() {
+ _uiState.value = _uiState.value.copy(errorMessage = null)
+ }
+}
+
+// Repository 예시
+interface GroupRepository {
+ suspend fun createGroup(request: GroupMakeRoomRequest): ApiResult
+}
+
+// API 응답 클래스 예시
+data class ApiResult(
+ val isSuccess: Boolean,
+ val data: T? = null,
+ val message: String? = null
+)
+
+data class GroupCreateResponse(
+ val groupId: String,
+ val groupName: String
+)
\ No newline at end of file
From 5bfd72d8675a05df7a0eb7cd39d58af940819837 Mon Sep 17 00:00:00 2001
From: Gyubin
Date: Wed, 9 Jul 2025 20:48:37 +0900
Subject: [PATCH 21/21] =?UTF-8?q?[refactor]:=20=EC=9E=A5=EB=A5=B4=EB=A5=BC?=
=?UTF-8?q?=20viewModel=EC=9D=98=20=EC=9E=A5=EB=A5=B4=EB=A5=BC=20=EC=82=AC?=
=?UTF-8?q?=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95=20(#3?=
=?UTF-8?q?5)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../thip/ui/group/makeroom/screen/GroupMakeRoomScreen.kt | 2 +-
.../ui/group/makeroom/viewmodel/GroupMakeRoomViewModel.kt | 8 ++++----
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/app/src/main/java/com/texthip/thip/ui/group/makeroom/screen/GroupMakeRoomScreen.kt b/app/src/main/java/com/texthip/thip/ui/group/makeroom/screen/GroupMakeRoomScreen.kt
index 7acab2cc..8211d6c4 100644
--- a/app/src/main/java/com/texthip/thip/ui/group/makeroom/screen/GroupMakeRoomScreen.kt
+++ b/app/src/main/java/com/texthip/thip/ui/group/makeroom/screen/GroupMakeRoomScreen.kt
@@ -45,7 +45,7 @@ fun GroupMakeRoomScreen(
) {
val uiState by viewModel.uiState.collectAsState()
val scrollState = rememberScrollState()
- val genres = listOf("문학", "과학·IT", "사회과학", "인문학", "예술")
+ val genres = viewModel.genres
// 에러 메시지 표시
LaunchedEffect(uiState.errorMessage) {
diff --git a/app/src/main/java/com/texthip/thip/ui/group/makeroom/viewmodel/GroupMakeRoomViewModel.kt b/app/src/main/java/com/texthip/thip/ui/group/makeroom/viewmodel/GroupMakeRoomViewModel.kt
index 2e2c4ef6..20e28599 100644
--- a/app/src/main/java/com/texthip/thip/ui/group/makeroom/viewmodel/GroupMakeRoomViewModel.kt
+++ b/app/src/main/java/com/texthip/thip/ui/group/makeroom/viewmodel/GroupMakeRoomViewModel.kt
@@ -19,7 +19,7 @@ open class GroupMakeRoomViewModel(
private val _uiState = MutableStateFlow(GroupMakeRoomUiState())
val uiState: StateFlow = _uiState.asStateFlow()
- private val genres = listOf("문학", "과학·IT", "사회과학", "인문학", "예술")
+ val genres = listOf("문학", "과학·IT", "사회과학", "인문학", "예술")
// 책 선택
fun selectBook(book: BookData) {
@@ -77,7 +77,7 @@ open class GroupMakeRoomViewModel(
val currentState = _uiState.value
if (!currentState.isFormValid) {
- onError("입력 정보를 확인해주세요")
+ //onError("입력 정보를 확인해주세요")
return
}
@@ -91,10 +91,10 @@ open class GroupMakeRoomViewModel(
if (result.isSuccess) {
onSuccess()
} else {
- onError(result.message ?: "그룹 생성에 실패했습니다")
+ //onError(result.message ?: "그룹 생성에 실패했습니다")
}
} catch (e: Exception) {
- onError("네트워크 오류가 발생했습니다: ${e.message}")
+ //onError("네트워크 오류가 발생했습니다: ${e.message}")
} finally {
_uiState.value = _uiState.value.copy(isLoading = false)
}