diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml index db5ab89b..8bc271fe 100644 --- a/.idea/deploymentTargetSelector.xml +++ b/.idea/deploymentTargetSelector.xml @@ -2,10 +2,7 @@ - - - + diff --git a/.idea/git_toolbox_prj.xml b/.idea/git_toolbox_prj.xml new file mode 100644 index 00000000..02b915b8 --- /dev/null +++ b/.idea/git_toolbox_prj.xml @@ -0,0 +1,15 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/material_theme_project_new.xml b/.idea/material_theme_project_new.xml new file mode 100644 index 00000000..2cb81d57 --- /dev/null +++ b/.idea/material_theme_project_new.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/ui/common/cards/CardAlarm.kt b/app/src/main/java/com/texthip/thip/ui/common/cards/CardAlarm.kt new file mode 100644 index 00000000..afdeb777 --- /dev/null +++ b/app/src/main/java/com/texthip/thip/ui/common/cards/CardAlarm.kt @@ -0,0 +1,168 @@ +package com.texthip.thip.ui.common.cards + +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.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.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.texthip.thip.R +import com.texthip.thip.ui.theme.ThipTheme.colors +import com.texthip.thip.ui.theme.ThipTheme.typography + + +@Composable +fun NotificationCard( + modifier: Modifier = Modifier, + title: String, + message: String, + timeAgo: String, + isRead: Boolean = false, + onClick: () -> Unit = {} +) { + Card( + modifier = modifier + .fillMaxWidth() + .clickable { onClick() }, + colors = CardDefaults.cardColors( + containerColor = if (isRead) colors.DarkGrey else colors.DarkGrey02 + ), + elevation = CardDefaults.cardElevation(defaultElevation = 2.dp), + shape = RoundedCornerShape(12.dp) + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + ) { + Row( + modifier = Modifier + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + // 뱃지 + Box( + modifier = Modifier + .size(width = 40.dp, height = 24.dp) + .clip(RoundedCornerShape(13.dp)) + .border( + width = 1.dp, + color = if (isRead) colors.Grey02 else colors.Grey01, + shape = RoundedCornerShape(13.dp) + ), + contentAlignment = Alignment.Center + ) { + Text( + text = stringResource(R.string.group), + color = if (isRead) colors.Grey01 else colors.Grey, + style = typography.menu_sb600_s12_h20 + ) + } + + Spacer(modifier = Modifier.width(12.dp)) + + // 내용 (제목, 빨간 점, 시간, 메시지) + Column( + modifier = Modifier.weight(1f) + ) { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = title, + style = typography.menu_sb600_s14_h24, + color = if (isRead) colors.Grey01 else colors.White, + modifier = Modifier.weight(1f), + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + + Column( + horizontalAlignment = Alignment.End + ) { + // 안읽음 상태일 때만 빨간 점 + if (!isRead) { + Spacer(modifier = Modifier.width(8.dp)) + Box( + modifier = Modifier + .size(6.dp) + .clip(RoundedCornerShape(3.dp)) + .background(color = colors.Red) + ) + } + + Text( + text = timeAgo + stringResource(R.string.time_ago), + style = typography.view_m500_s12_h20, + color = if (isRead) colors.Grey02 else colors.Grey01, + ) + } + } + } + } + + Spacer(modifier = Modifier.width(12.dp)) + + Text( + text = message, + style = typography.copy_r400_s12_h20, + color = if (isRead) colors.Grey02 else colors.Grey01, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } + } +} + +@Preview +@Composable +fun PreviewNotificationCards() { + var isRead by remember { mutableStateOf(false) } + + Column( + modifier = Modifier.padding(16.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + // 안읽은 알림 + NotificationCard( + title = "같이 읽기를 시작했어요!", + message = "한줄만 입력이 가능합니다. 한줄만 입력이 가능합니다. 한줄만 입력이 가능합니다.", + timeAgo = "12", + isRead = isRead + ) { + isRead = true + } + + // 읽은 알림 + NotificationCard( + title = "같이 읽기를 시작했어요!", + message = "한줄만 입력이 가능합니다. 한줄만 입력이 가능합니다. 한줄만 입력이 가능합니다.", + timeAgo = "12", + isRead = true + ) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/ui/common/cards/CardBookList.kt b/app/src/main/java/com/texthip/thip/ui/common/cards/CardBookList.kt new file mode 100644 index 00000000..544a6555 --- /dev/null +++ b/app/src/main/java/com/texthip/thip/ui/common/cards/CardBookList.kt @@ -0,0 +1,124 @@ +package com.texthip.thip.ui.common.cards + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +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.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.texthip.thip.R +import com.texthip.thip.ui.theme.ThipTheme.colors +import com.texthip.thip.ui.theme.ThipTheme.typography + +@Composable +fun CardBookList( + modifier: Modifier = Modifier, + title: String, + author: String, + publisher: String, + imageRes: Int? = R.drawable.bookcover_sample, // 기본 이미지 리소스 + isBookmarked: Boolean = false, + onBookmarkClick: () -> Unit = {} +) { + Row( + modifier = modifier + .fillMaxWidth() + .background(Color.Transparent), + ) { + // 책 이미지 + Box( + modifier = Modifier + .size(width = 80.dp, height = 108.dp) + ) { + + imageRes?.let { + Image( + painter = painterResource(id = it), + contentDescription = null, + modifier = Modifier.fillMaxSize(), + contentScale = ContentScale.Crop + ) + } + } + + Spacer(modifier = Modifier.width(12.dp)) + + // 텍스트 정보 + Column( + modifier = Modifier.weight(1f), + verticalArrangement = Arrangement.Top + ) { + Text( + text = title, + style = typography.smalltitle_sb600_s16_h20, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + color = colors.White + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = "$author 저 · $publisher", + style = typography.view_m500_s12_h20, + color = colors.Grey01 + ) + } + + Spacer(modifier = Modifier.width(12.dp)) + + IconButton( + onClick = onBookmarkClick, + modifier = Modifier.size(24.dp) + ) { + Icon( + imageVector = if (isBookmarked) ImageVector.vectorResource(R.drawable.ic_save_filled) else ImageVector.vectorResource(R.drawable.ic_save), + contentDescription = "북마크", + tint = if (isBookmarked) colors.Purple else colors.Grey01 + ) + } + } +} + +// 프리뷰들 +@Preview +@Composable +fun PreviewBookTitleCard() { + var isBookmarked by remember { mutableStateOf(false) } + + Column( + modifier = Modifier.padding(16.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + CardBookList( + title = "책제목입니다.책제목입니다.책제목입니다.책제목입니다.책제목입니다.책제목입니다.", + author = "리처드 도킨스", + publisher = "을유문화사", + isBookmarked = isBookmarked, + onBookmarkClick = { isBookmarked = !isBookmarked } + ) + } + +} \ No newline at end of file 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 new file mode 100644 index 00000000..1bc7fa03 --- /dev/null +++ b/app/src/main/java/com/texthip/thip/ui/common/cards/CardBookSearch.kt @@ -0,0 +1,92 @@ +package com.texthip.thip.ui.common.cards + +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +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.colors +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 = {} +) { + Row( + modifier = modifier + .fillMaxWidth() + .clickable { onClick() } + .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 + .size(width = 45.dp, height = 60.dp) + ) { + imageRes?.let { + Image( + painter = painterResource(id = it), + contentDescription = null, + modifier = Modifier.fillMaxSize(), + contentScale = ContentScale.Crop + ) + } + } + + Spacer(modifier = Modifier.width(8.dp)) + + // 제목 + Text( + text = title, + style = typography.feedcopy_r400_s14_h20, + color = colors.White, + modifier = Modifier.weight(1f) + ) + } +} + +@Preview +@Composable +fun CardBookSearchPreview() { + + Column( + modifier = Modifier.padding(16.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + CardBookSearch( + number = 1, + title = "단 한번의 삶" + ) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/ui/common/cards/CardInputBook.kt b/app/src/main/java/com/texthip/thip/ui/common/cards/CardInputBook.kt new file mode 100644 index 00000000..e6b322af --- /dev/null +++ b/app/src/main/java/com/texthip/thip/ui/common/cards/CardInputBook.kt @@ -0,0 +1,112 @@ +package com.texthip.thip.ui.common.cards + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.texthip.thip.R +import com.texthip.thip.ui.common.buttons.OutlinedButton +import com.texthip.thip.ui.theme.ThipTheme.colors +import com.texthip.thip.ui.theme.ThipTheme.typography + +@Composable +fun CardInputBook( + modifier: Modifier = Modifier, + title: String, + author: String, + imageRes: Int? = R.drawable.bookcover_sample, // 기본 이미지 리소스 + onChangeClick: () -> Unit = {} +) { + Row( + modifier = modifier + .fillMaxWidth() + .background(Color.Transparent), + ) { + // 책 이미지 + Box( + modifier = Modifier + .size(width = 60.dp, height = 80.dp) + ) { + + imageRes?.let { + Image( + painter = painterResource(id = it), + contentDescription = null, + modifier = Modifier.fillMaxSize(), + contentScale = ContentScale.Crop + ) + } + } + + Spacer(modifier = Modifier.width(12.dp)) + + // 텍스트 정보 + Column( + modifier = Modifier.weight(.1f), + verticalArrangement = Arrangement.Top + ) { + Text( + text = title, + style = typography.menu_sb600_s14_h24, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + color = colors.White + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = "$author 저", + style = typography.view_m500_s12_h20, + color = colors.Grey01 + ) + } + // 텍스트 정보와 버튼 사이 19dp 고정 간격 + Spacer(modifier = Modifier.width(19.dp)) + + OutlinedButton( + modifier = Modifier + .size(width = 49.dp, height = 33.dp) + .align(Alignment.Bottom), + text = stringResource(R.string.change), + textStyle = typography.view_m500_s14, + onClick = onChangeClick + ) + } +} + +// 프리뷰들 +@Preview +@Composable +fun CardInputBookPreview() { + + Column( + modifier = Modifier.padding(16.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + CardInputBook( + title = "책제목입니다.책제목입니다.책제목입니다.책제목입니다.책제목입니다.", + author = "리처드 도킨스", + onChangeClick = {} + ) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/ui/common/cards/CardRoomBook.kt b/app/src/main/java/com/texthip/thip/ui/common/cards/CardRoomBook.kt new file mode 100644 index 00000000..068a4ce5 --- /dev/null +++ b/app/src/main/java/com/texthip/thip/ui/common/cards/CardRoomBook.kt @@ -0,0 +1,158 @@ +package com.texthip.thip.ui.common.cards + +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.texthip.thip.R +import com.texthip.thip.ui.theme.ThipTheme.colors +import com.texthip.thip.ui.theme.ThipTheme.typography + +@Composable +fun DetailedDarkCard( + modifier: Modifier = Modifier, + title: String, + author: String, + publisher: String, + description: String, + imageRes: Int? = R.drawable.bookcover_sample, + onClick: () -> Unit = {} +) { + Card( + modifier = modifier + .fillMaxWidth() + .clickable { onClick() }, + colors = CardDefaults.cardColors( + containerColor = colors.DarkGrey02 + ), + elevation = CardDefaults.cardElevation(defaultElevation = 4.dp), + shape = RoundedCornerShape(12.dp) + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(20.dp) + ) { + // 헤더 행 + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.Top + ) { + Text( + text = title, + style = typography.menu_m500_s16_h24, + color = Color.White, + maxLines = 1, + modifier = Modifier.weight(1f), + ) + + Spacer(modifier = Modifier.width(12.dp)) + Icon( + imageVector = ImageVector.vectorResource(R.drawable.ic_chevron_right), + contentDescription = "더보기", + tint = Color.White, + ) + } + + Spacer(modifier = Modifier.height(24.dp)) + + // 내용 행 + Row( + modifier = Modifier.fillMaxWidth() + ) { + // 책 이미지 + Box( + modifier = Modifier + .size(width = 80.dp, height = 107.dp) + ) { + imageRes?.let { + Image( + painter = painterResource(id = it), + contentDescription = null, + modifier = Modifier.fillMaxSize(), + contentScale = ContentScale.Crop + ) + } + } + + Spacer(modifier = Modifier.width(16.dp)) + + // 텍스트 정보 + Column( + modifier = Modifier.weight(1f) + ) { + Spacer(modifier = Modifier.height(7.dp)) + + Text( + text = "$author 저 · $publisher", + color = colors.White, + style = typography.info_m500_s12, + ) + + Spacer(modifier = Modifier.height(21.dp)) + + Text( + text = "도서 소개", + color = colors.White, + style = typography.info_m500_s12, + ) + + Spacer(modifier = Modifier.height(5.dp)) + + Text( + text = description, + color = colors.Grey01, + style = typography.timedate_r400_s11, + maxLines = 3, + overflow = TextOverflow.Ellipsis + ) + } + } + } + } +} + + +@Preview +@Composable +fun PreviewDetailedDarkCard() { + + Column( + modifier = Modifier.padding(16.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + DetailedDarkCard( + title = "도서명을 입력, 예시까지 최대 입력 후...", + author = "저자명", + publisher = "출판사", + description = "세 줄로 내용을 입력합니다. 세 줄로 내용을 입력합니다. 세 줄로 내용을 입력합니다. 세 줄로 내용을 입력합니다. 세 줄로 내용을 입력합니다. 세 줄로 내용을 입력합니다." + ) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/ui/common/forms/BookPageTextField.kt b/app/src/main/java/com/texthip/thip/ui/common/forms/BookPageTextField.kt new file mode 100644 index 00000000..fe832cae --- /dev/null +++ b/app/src/main/java/com/texthip/thip/ui/common/forms/BookPageTextField.kt @@ -0,0 +1,158 @@ +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.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 +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.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.OffsetMapping +import androidx.compose.ui.text.input.TransformedText +import androidx.compose.ui.text.input.VisualTransformation +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 BookPageTextField( + modifier: Modifier = Modifier, + bookPage: Int +) { + var text by rememberSaveable { mutableStateOf("") } + var isError by rememberSaveable { mutableStateOf(false) } + var errorMessageRes by rememberSaveable { mutableStateOf(null) } + var errorMessageParam by rememberSaveable { mutableStateOf(0) } + + Column { + OutlinedTextField( + value = text, + onValueChange = { newText: String -> + if (newText.isEmpty() || newText.all { it.isDigit() }) { + text = newText + if (newText.isNotEmpty()) { + val pageNum = newText.toInt() + isError = pageNum > bookPage + if (isError) { + errorMessageRes = R.string.error_page_over + errorMessageParam = bookPage + } else { + errorMessageRes = null + } + } else { + isError = false + errorMessageRes = null + } + } + }, + visualTransformation = SuffixTransformation( + suffix = "/${bookPage}p", + suffixColor = colors.Grey02 + ), + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + modifier = modifier.size(width = 320.dp, height = 48.dp), + textStyle = typography.menu_r400_s14_h24.copy(lineHeight = 12.sp), + maxLines = 1, + shape = RoundedCornerShape(12.dp), + colors = TextFieldDefaults.colors( + focusedTextColor = colors.White, + focusedIndicatorColor = if (isError) colors.Red else Color.Transparent, + unfocusedIndicatorColor = if (isError) 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 = "" + isError = false + errorMessageRes = null + }, + tint = Color.Unspecified + ) + } else { + Icon( + painter = painterResource(id = R.drawable.ic_x_circle), + contentDescription = "Clear text" + ) + } + } + ) + + if (isError && errorMessageRes != null) { + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = stringResource(id = errorMessageRes!!, errorMessageParam), + color = colors.Red, + style = typography.menu_r400_s14_h24.copy(lineHeight = 12.sp) + ) + } + } +} + +class SuffixTransformation( + private val suffix: String, + private val suffixColor: Color +) : VisualTransformation { + override fun filter(text: AnnotatedString): TransformedText { + val original = text.text + + val transformed = buildAnnotatedString { + append(original) + pushStyle(SpanStyle(color = suffixColor)) + append(suffix) + pop() + } + + val offsetMapping = object : OffsetMapping { + override fun originalToTransformed(offset: Int): Int = + offset.coerceAtMost(original.length) + override fun transformedToOriginal(offset: Int): Int = + offset.coerceAtMost(original.length) + } + + return TransformedText(transformed, offsetMapping) + } +} + + +@Composable +@Preview(showBackground = true, backgroundColor = 0xFF000000, widthDp = 360, heightDp = 200) +fun BookPageTextFieldPreviewEmpty() { + Box( + modifier = Modifier.size(width = 360.dp, height = 200.dp), + contentAlignment = Alignment.Center + ) { + BookPageTextField( + bookPage = 456 + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/ui/common/forms/BorderedTextField.kt b/app/src/main/java/com/texthip/thip/ui/common/forms/BorderedTextField.kt new file mode 100644 index 00000000..6307dd2a --- /dev/null +++ b/app/src/main/java/com/texthip/thip/ui/common/forms/BorderedTextField.kt @@ -0,0 +1,86 @@ +package com.texthip.thip.ui.common.forms + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.material3.TextFieldDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.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 BorderedTextField( + modifier: Modifier = Modifier, + hint: String +) { + var text by rememberSaveable { mutableStateOf("") } + val myStyle = typography.menu_r400_s14_h24.copy(lineHeight = 14.sp) + + OutlinedTextField( + value = text, + onValueChange = { text = it }, + placeholder = { + Text( + text = hint, + color = colors.Grey02, + style = myStyle + ) + }, + textStyle = myStyle, + modifier = modifier.size(width = 320.dp, height = 48.dp), + shape = RoundedCornerShape(12.dp), + colors = TextFieldDefaults.colors( + focusedTextColor = colors.White, + focusedIndicatorColor = colors.Grey02, + unfocusedIndicatorColor = colors.Grey02, + focusedContainerColor = colors.Black00, + unfocusedContainerColor = colors.Black00, + 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" + ) + } + } + ) +} + +@Composable +@Preview(showBackground = true, backgroundColor = 0xFF000000, widthDp = 360, heightDp = 200) +fun BorderedTextFieldPreview() { + Box( + modifier = Modifier.size(width = 360.dp, height = 200.dp), + contentAlignment = Alignment.Center + ) { + BorderedTextField( + hint = "가이드 텍스트를 입력" + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/ui/common/forms/FormTextFieldDefault.kt b/app/src/main/java/com/texthip/thip/ui/common/forms/FormTextFieldDefault.kt new file mode 100644 index 00000000..cc8f2285 --- /dev/null +++ b/app/src/main/java/com/texthip/thip/ui/common/forms/FormTextFieldDefault.kt @@ -0,0 +1,86 @@ +package com.texthip.thip.ui.common.forms + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.material3.TextFieldDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.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 BaseInputTextField( + modifier: Modifier = Modifier, + hint: String +) { + var text by rememberSaveable { mutableStateOf("") } + val myStyle = typography.menu_r400_s14_h24.copy(lineHeight = 14.sp) + + OutlinedTextField( + value = text, + onValueChange = { text = it }, + placeholder = { + Text( + text = hint, + color = colors.Grey02, + style = myStyle + ) + }, + textStyle = myStyle, + modifier = modifier.size(width = 320.dp, height = 48.dp), + shape = RoundedCornerShape(12.dp), + colors = TextFieldDefaults.colors( + focusedTextColor = colors.White, + focusedIndicatorColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent, + focusedContainerColor = colors.Black, + unfocusedContainerColor = colors.Black, + cursorColor = colors.NeonGreen + ), + trailingIcon = { + if (text.isNotEmpty()) { + Icon( + painter = painterResource(id = R.drawable.ic_x_circle_white), + contentDescription = "Clear text", + modifier = Modifier.clickable { text = "" }, + tint = Color.Unspecified + ) + } else { + Icon( + painter = painterResource(id = R.drawable.ic_x_circle), + contentDescription = "Clear text" + ) + } + }, + singleLine = true + ) +} + +@Composable +@Preview(showBackground = true, backgroundColor = 0xFF000000, widthDp = 360, heightDp = 200) +fun InputTextFieldPreviewEmpty() { + Box( + modifier = Modifier.size(width = 360.dp, height = 200.dp), + contentAlignment = Alignment.Center + ) { + BaseInputTextField( + hint = "이곳에 텍스트를 입력하세요" + ) + } +} \ No newline at end of file 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 new file mode 100644 index 00000000..533cfcea --- /dev/null +++ b/app/src/main/java/com/texthip/thip/ui/common/forms/WarningTextField.kt @@ -0,0 +1,118 @@ +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.height +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.material3.TextFieldDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.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 WarningTextField( + modifier: Modifier = Modifier, + hint: String, + warningMessage: String = "경고 메시지를 입력해주세요.", + showWarning: Boolean +) { + 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)) + 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() { + Box( + modifier = Modifier.size(width = 360.dp, height = 200.dp), + contentAlignment = Alignment.Center + ) { + WarningTextField( + hint = "인풋 텍스트", + showWarning = true, + warningMessage = "경고 메시지를 입력해주세요." + ) + } +} + +@Composable +@Preview(showBackground = true, backgroundColor = 0xFF000000, widthDp = 360, heightDp = 200) +fun WarningTextFieldPreviewNormal() { + Box( + modifier = Modifier.size(width = 360.dp, height = 200.dp), + contentAlignment = Alignment.Center + ) { + WarningTextField( + hint = "인풋 텍스트", + showWarning = false + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/ui/theme/Color.kt b/app/src/main/java/com/texthip/thip/ui/theme/Color.kt index 4ecc11e6..3bbcdba8 100644 --- a/app/src/main/java/com/texthip/thip/ui/theme/Color.kt +++ b/app/src/main/java/com/texthip/thip/ui/theme/Color.kt @@ -33,6 +33,7 @@ val Grey03 = Color(0xFF525252) val DarkGrey = Color(0xFF3D3D3D) val DarkGrey50 = Color(0x803D3D3D) val DarkGrey02 = Color(0xFF282828) +val DarkGrey01 = Color(0x4B4B4B4B) val Black = Color(0xFF121212) val Black50 = Color(0x80121212) @@ -63,6 +64,7 @@ data class ThipColors( val Grey02: Color, val Grey03: Color, val DarkGrey: Color, + val darkGray01: Color, val DarkGrey50: Color, val DarkGrey02: Color, val Black: Color, @@ -94,6 +96,7 @@ val defaultThipColors = ThipColors( Grey02 = Grey02, Grey03 = Grey03, DarkGrey = DarkGrey, + darkGray01 = DarkGrey01, DarkGrey50 = DarkGrey50, DarkGrey02 = DarkGrey02, Black = Black, diff --git a/app/src/main/res/drawable/bookcover_sample.png b/app/src/main/res/drawable/bookcover_sample.png new file mode 100644 index 00000000..7ff24359 Binary files /dev/null and b/app/src/main/res/drawable/bookcover_sample.png differ diff --git a/app/src/main/res/drawable/ic_save_filled.xml b/app/src/main/res/drawable/ic_save_filled.xml new file mode 100644 index 00000000..ff962de7 --- /dev/null +++ b/app/src/main/res/drawable/ic_save_filled.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_x_circle_white.xml b/app/src/main/res/drawable/ic_x_circle_white.xml new file mode 100644 index 00000000..126b24dc --- /dev/null +++ b/app/src/main/res/drawable/ic_x_circle_white.xml @@ -0,0 +1,16 @@ + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 60af47c3..46514a2b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -20,4 +20,13 @@ 인물관계도 보기 기록 작성 투표 생성 + + + 시간 전 + 모임 + 변경 + + + 해당 도서는 %1$dp까지만 있습니다. + \ No newline at end of file