diff --git a/app/src/main/java/com/texthip/thip/data/group/repository/GroupRepository.kt b/app/src/main/java/com/texthip/thip/data/group/repository/GroupRepository.kt new file mode 100644 index 00000000..4fbd66fa --- /dev/null +++ b/app/src/main/java/com/texthip/thip/data/group/repository/GroupRepository.kt @@ -0,0 +1,324 @@ +package com.texthip.thip.data.group.repository + +import com.texthip.thip.R +import com.texthip.thip.ui.group.myroom.mock.GroupBookData +import com.texthip.thip.ui.group.myroom.mock.GroupCardData +import com.texthip.thip.ui.group.myroom.mock.GroupCardItemRoomData +import com.texthip.thip.ui.group.myroom.mock.GroupRoomData +import com.texthip.thip.ui.group.myroom.mock.GroupRoomSectionData +import kotlinx.coroutines.delay + +// 그룹 데이터를 제공하는 Repository +// 실제로는 서버의 API와 통신할 거라서 다 삭제하고 함수 구조만 유지한 채 수정하면 될 듯 합니다. + +class GroupRepository { + + private val genres = listOf("문학", "과학·IT", "사회과학", "인문학", "예술") + private val roomDetailsCache = mutableMapOf() + + suspend fun getUserName(): Result { + return try { + Result.success("규빈") // 임시 이름 + } catch (e: Exception) { + Result.failure(e) + } + } + + suspend fun getMyGroups(): Result> { + return try { + delay(200) + val myGroups = listOf( + GroupCardData(23, "호르몬 체인지 완독하는 방", 22, R.drawable.bookcover_sample, 40, "uibowl1"), + GroupCardData(24, "명작 읽기방", 10, R.drawable.bookcover_sample, 70, "joyce"), + GroupCardData(25, "또 다른 방", 13, R.drawable.bookcover_sample, 10, "other") + ) + Result.success(myGroups) + } catch (e: Exception) { + Result.failure(e) + } + } + + suspend fun getRoomSections(): Result> { + return try { + + // 마감 임박한 독서 모임방 + val deadlineRooms = listOf( + GroupCardItemRoomData(1, "시집만 읽는 사람들 3월", 22, 30, true, 3, R.drawable.bookcover_sample, 0), + GroupCardItemRoomData(2, "일본 소설 좋아하는 사람들", 15, 20, true, 2, R.drawable.bookcover_sample, 0), + GroupCardItemRoomData(3, "명작 같이 읽기방", 22, 30, true, 3, R.drawable.bookcover_sample, 0), + GroupCardItemRoomData(4, "물리책 읽는 방", 13, 20, true, 1, R.drawable.bookcover_sample, 1), + GroupCardItemRoomData(5, "코딩 과학 동아리", 12, 15, true, 5, R.drawable.bookcover_sample, 1), + GroupCardItemRoomData(6, "사회과학 인문 탐구", 8, 12, true, 4, R.drawable.bookcover_sample, 2) + ) + + // 인기 있는 독서 모임방 + val popularRooms = listOf( + GroupCardItemRoomData(7, "베스트셀러 토론방", 28, 30, true, 7, R.drawable.bookcover_sample, 0), + GroupCardItemRoomData(8, "인기 소설 완독방", 25, 25, false, 5, R.drawable.bookcover_sample, 0), + GroupCardItemRoomData(9, "트렌드 과학서 읽기", 20, 25, true, 10, R.drawable.bookcover_sample, 1), + GroupCardItemRoomData(10, "화제의 경영서", 18, 20, true, 8, R.drawable.bookcover_sample, 2), + GroupCardItemRoomData(11, "인기 철학서 모임", 15, 18, true, 12, R.drawable.bookcover_sample, 3), + GroupCardItemRoomData(12, "예술서 베스트", 12, 15, true, 6, R.drawable.bookcover_sample, 4) + ) + + // 인플루언서, 작가 독서 모임방 + val influencerRooms = listOf( + GroupCardItemRoomData(13, "작가와 함께하는 독서방", 30, 30, false, 14, R.drawable.bookcover_sample, 0), + GroupCardItemRoomData(14, "유명 북튜버와 읽기", 18, 20, true, 8, R.drawable.bookcover_sample, 2), + GroupCardItemRoomData(15, "작가 초청 인문학방", 15, 20, true, 12, R.drawable.bookcover_sample, 3), + GroupCardItemRoomData(16, "인플루언서 과학책", 22, 25, true, 9, R.drawable.bookcover_sample, 1), + GroupCardItemRoomData(17, "유명작가 예술론", 16, 18, true, 11, R.drawable.bookcover_sample, 4) + ) + + val sections = listOf( + GroupRoomSectionData( + title = "마감 임박한 독서 모임방", + rooms = deadlineRooms, + genres = genres + ), + GroupRoomSectionData( + title = "인기 있는 독서 모임방", + rooms = popularRooms, + genres = genres + ), + GroupRoomSectionData( + title = "인플루언서·작가 독서 모임방", + rooms = influencerRooms, + genres = genres + ) + ) + + // 상세 데이터 캐시에 저장 + (deadlineRooms + popularRooms + influencerRooms).forEach { room -> + initializeRoomDetail(room) + } + + Result.success(sections) + } catch (e: Exception) { + Result.failure(e) + } + } + + suspend fun getDoneGroups(): Result> { + return try { + val doneGroups = listOf( + GroupCardItemRoomData(18, "완료된 독서 모임방 1", 15, 20, false, null, R.drawable.bookcover_sample, 0), + GroupCardItemRoomData(19, "완료된 독서 모임방 2", 25, 30, false, null, R.drawable.bookcover_sample, 1), + GroupCardItemRoomData(20, "완료된 독서 모임방 3", 12, 15, false, null, R.drawable.bookcover_sample, 2), + GroupCardItemRoomData(21, "호르몬 체인지 완독한 방", 22, 22, false, null, R.drawable.bookcover_sample, 0), + GroupCardItemRoomData(22, "명작 읽기방 완료", 10, 10, false, null, R.drawable.bookcover_sample, 0) + ) + + // 상세 데이터 캐시에 저장 + doneGroups.forEach { room -> + initializeRoomDetail(room) + } + + Result.success(doneGroups) + } catch (e: Exception) { + Result.failure(e) + } + } + + suspend fun getMyRoomGroups(): Result> { + return try { + val myRoomGroups = listOf( + GroupCardItemRoomData(23, "호르몬 체인지 완독하는 방", 22, 30, true, 5, R.drawable.bookcover_sample, 0), + GroupCardItemRoomData(24, "명작 읽기방", 10, 20, true, 3, R.drawable.bookcover_sample, 0), + GroupCardItemRoomData(25, "또 다른 방", 13, 25, false, 10, R.drawable.bookcover_sample, 1), + GroupCardItemRoomData(26, "내가 참여한 과학책방", 18, 25, true, 7, R.drawable.bookcover_sample, 1), + GroupCardItemRoomData(27, "인문학 토론방", 12, 20, true, 2, R.drawable.bookcover_sample, 3) + ) + + // 상세 데이터 캐시에 저장 + myRoomGroups.forEach { room -> + initializeRoomDetail(room) + } + + Result.success(myRoomGroups) + } catch (e: Exception) { + Result.failure(e) + } + } + + suspend fun getSearchGroups(): Result> { + return try { + // 기존에 로드된 섹션 데이터들을 합쳐서 반환 + val sectionsResult = getRoomSections() + if (sectionsResult.isSuccess) { + val allRooms = sectionsResult.getOrThrow().flatMap { it.rooms } + Result.success(allRooms) + } else { + Result.failure(sectionsResult.exceptionOrNull() ?: Exception("Failed to load search groups")) + } + } catch (e: Exception) { + Result.failure(e) + } + } + + suspend fun getRoomDetail(roomId: Int): Result { + return try { + delay(150) + val roomDetail = roomDetailsCache[roomId] + if (roomDetail != null) { + Result.success(roomDetail) + } else { + Result.failure(Exception("Room not found: $roomId")) + } + } catch (e: Exception) { + Result.failure(e) + } + } + + suspend fun searchRooms(query: String): Result> { + return try { + val searchResult = getSearchGroups() + if (searchResult.isSuccess) { + val filteredRooms = searchResult.getOrThrow().filter { room -> + room.title.contains(query, ignoreCase = true) + } + Result.success(filteredRooms) + } else { + searchResult + } + } catch (e: Exception) { + Result.failure(e) + } + } + + suspend fun getGenres(): Result> { + return try { + delay(50) + Result.success(genres) + } catch (e: Exception) { + Result.failure(e) + } + } + + + private fun initializeRoomDetail(room: GroupCardItemRoomData) { + val bookData = GroupBookData( + title = "심장보다 단단한 토마토 한 알", + author = "고선지", + publisher = "푸른출판사", + description = "${room.title}에서 읽는 책입니다. 감동적인 이야기로 가득한 작품입니다.", + imageRes = room.imageRes ?: R.drawable.bookcover_sample + ) + + val recommendations = getRecommendations(room.id) + + val roomDetail = GroupRoomData( + id = room.id, + title = room.title, + isSecret = room.isSecret, + description = "${room.title} 모임입니다. 함께 책을 읽고 토론해요.", + startDate = "2025.01.12", + endDate = "2025.02.12", + members = room.participants, + maxMembers = room.maxParticipants, + daysLeft = room.endDate ?: 0, + genre = genres[room.genreIndex], + bookData = bookData, + recommendations = recommendations + ) + + roomDetailsCache[room.id] = roomDetail + + // 추천 모임방들의 상세 정보도 캐시에 추가 + recommendations.forEach { recommendedRoom -> + if (!roomDetailsCache.containsKey(recommendedRoom.id)) { + initializeRecommendedRoomDetail(recommendedRoom) + } + } + } + + // 추천 모임방 예시 by gpt + private fun initializeRecommendedRoomDetail(room: GroupCardItemRoomData) { + val bookTitles = listOf( + "데미안", "1984", "노인과 바다", "위대한 개츠비", "햄릿", + "코스모스", "이기적 유전자", "블랙홀과 시간여행", "총균쇠", + "국부론", "자본론", "사피엔스", "총균쇠", "정의란 무엇인가", + "예술의 역사", "음악의 역사", "미학 오디세이" + ) + + val authors = listOf( + "헤르만 헤세", "조지 오웰", "어니스트 헤밍웨이", "스콧 피츠제럴드", + "칼 세이건", "리처드 도킨스", "킵 손", "재레드 다이아몬드", + "아담 스미스", "칼 마르크스", "유발 하라리", "마이클 샌델" + ) + + val publishers = listOf("푸른출판사", "문학동네", "민음사", "창비", "열린책들", "김영사") + + val bookData = GroupBookData( + title = bookTitles.random(), + author = authors.random(), + publisher = publishers.random(), + description = "${room.title}에서 읽는 흥미로운 책입니다. 함께 읽으며 깊이 있는 토론을 나눠보세요.", + imageRes = room.imageRes ?: R.drawable.bookcover_sample + ) + + val roomDetail = GroupRoomData( + id = room.id, + title = room.title, + isSecret = room.isSecret, + description = "${room.title} 모임입니다. 다양한 관점으로 책을 읽고 의견을 나눠보세요.", + startDate = "2025.01.15", + endDate = "2025.02.15", + members = room.participants, + maxMembers = room.maxParticipants, + daysLeft = room.endDate ?: 0, + genre = genres.getOrElse(room.genreIndex) { genres[0] }, + bookData = bookData, + recommendations = getRecommendations(room.id) // 추천 모임방에도 추천 제공 + ) + + roomDetailsCache[room.id] = roomDetail + } + + private fun getRecommendations(roomId: Int): List { + // 추천 모임방 더미데이터 풀 + val recommendationPool = listOf( + // 문학 관련 추천 + GroupCardItemRoomData(1001, "한국 근현대 소설 읽기", 18, 25, true, 3, R.drawable.bookcover_sample, 0), + GroupCardItemRoomData(1002, "일본 문학 애호가들", 22, 30, true, 1, R.drawable.bookcover_sample, 0), + GroupCardItemRoomData(1003, "시 읽기 모임", 16, 25, true, 2, R.drawable.bookcover_sample, 0), + GroupCardItemRoomData(1004, "해외문학 번역서 읽기", 15, 22, true, 3, R.drawable.bookcover_sample, 0, true), + GroupCardItemRoomData(1005, "고전 문학 탐구", 20, 25, true, 5, R.drawable.bookcover_sample, 0), + + // 과학·IT 관련 추천 + GroupCardItemRoomData(1006, "SF 소설 탐험대", 12, 20, true, 7, R.drawable.bookcover_sample, 1), + GroupCardItemRoomData(1007, "과학도서 함께 읽기", 7, 15, true, 9, R.drawable.bookcover_sample, 1), + GroupCardItemRoomData(1008, "컴퓨터 과학 스터디", 14, 18, true, 4, R.drawable.bookcover_sample, 1), + GroupCardItemRoomData(1009, "물리학 입문서 모임", 10, 16, true, 6, R.drawable.bookcover_sample, 1), + + // 사회과학 관련 추천 + GroupCardItemRoomData(1010, "경제경영서 스터디", 9, 12, true, 6, R.drawable.bookcover_sample, 2), + GroupCardItemRoomData(1011, "사회학 도서 토론", 13, 18, true, 4, R.drawable.bookcover_sample, 2), + GroupCardItemRoomData(1012, "정치학 입문 모임", 11, 15, true, 8, R.drawable.bookcover_sample, 2), + + // 인문학 관련 추천 + GroupCardItemRoomData(1013, "철학 에세이 읽기 모임", 8, 15, true, 5, R.drawable.bookcover_sample, 3), + GroupCardItemRoomData(1014, "인문학 고전 읽기", 20, 25, true, 5, R.drawable.bookcover_sample, 3, true), + GroupCardItemRoomData(1015, "심리학 도서 스터디", 10, 16, true, 7, R.drawable.bookcover_sample, 3), + GroupCardItemRoomData(1016, "역사서 탐구 모임", 11, 16, true, 8, R.drawable.bookcover_sample, 3), + + // 예술 관련 추천 + GroupCardItemRoomData(1017, "미술사 도서 읽기", 14, 20, true, 3, R.drawable.bookcover_sample, 4), + GroupCardItemRoomData(1018, "음악 관련 서적 모임", 12, 18, true, 5, R.drawable.bookcover_sample, 4), + + // 기타 장르 + GroupCardItemRoomData(1019, "로맨스 소설 감상회", 14, 20, true, 4, R.drawable.bookcover_sample, 0), + GroupCardItemRoomData(1020, "미스터리 소설 동호회", 15, 18, true, 2, R.drawable.bookcover_sample, 0, true), + GroupCardItemRoomData(1021, "자기계발서 함께 읽기", 25, 30, true, 3, R.drawable.bookcover_sample, 2, true), + GroupCardItemRoomData(1022, "판타지 소설 동호회", 24, 30, true, 1, R.drawable.bookcover_sample, 0), + GroupCardItemRoomData(1023, "여행 에세이 모임", 13, 18, true, 4, R.drawable.bookcover_sample, 3), + GroupCardItemRoomData(1024, "추리소설 마니아", 19, 24, true, 6, R.drawable.bookcover_sample, 0) + ) + + // 현재 방과 관련 없는 추천을 제공하기 위해 현재 roomId와 다른 것들만 필터링 + val filteredRecommendations = recommendationPool.filter { it.id != roomId } + + // 랜덤하게 3-5개의 추천 반환 + return filteredRecommendations.shuffled().take(5) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/ui/common/alarmpage/screen/AlarmScreen.kt b/app/src/main/java/com/texthip/thip/ui/common/alarmpage/screen/AlarmScreen.kt index 44f4e486..9d80da0e 100644 --- a/app/src/main/java/com/texthip/thip/ui/common/alarmpage/screen/AlarmScreen.kt +++ b/app/src/main/java/com/texthip/thip/ui/common/alarmpage/screen/AlarmScreen.kt @@ -1,6 +1,5 @@ package com.texthip.thip.ui.common.alarmpage.screen -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues @@ -10,7 +9,6 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -19,7 +17,6 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -34,7 +31,9 @@ import com.texthip.thip.ui.theme.ThipTheme.typography @Composable fun AlarmScreen( - alarmItems: List, onCardClick: (AlarmItem) -> Unit = {} + alarmItems: List, + onCardClick: (AlarmItem) -> Unit = {}, // 나중에 서버랑 연동할 때 사용 + onNavigateBack: () -> Unit = {} ) { var selectedStates by remember { mutableStateOf(booleanArrayOf(false, false)) } var alarms by remember { mutableStateOf(alarmItems) } @@ -51,7 +50,7 @@ fun AlarmScreen( ) { DefaultTopAppBar( title = stringResource(R.string.alarm_string), - onLeftClick = {}, + onLeftClick = onNavigateBack, ) Column( Modifier diff --git a/app/src/main/java/com/texthip/thip/ui/common/alarmpage/viewmodel/AlarmViewModel.kt b/app/src/main/java/com/texthip/thip/ui/common/alarmpage/viewmodel/AlarmViewModel.kt new file mode 100644 index 00000000..c1e31f27 --- /dev/null +++ b/app/src/main/java/com/texthip/thip/ui/common/alarmpage/viewmodel/AlarmViewModel.kt @@ -0,0 +1,28 @@ +package com.texthip.thip.ui.common.alarmpage.viewmodel + +import androidx.lifecycle.ViewModel +import com.texthip.thip.ui.common.alarmpage.mock.AlarmItem +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow + +class AlarmViewModel : ViewModel() { + private val _alarmItems = MutableStateFlow>(emptyList()) + val alarmItems: StateFlow> = _alarmItems.asStateFlow() + + // 알림 더미 데이터 + init { + _alarmItems.value = listOf( + AlarmItem(1, "피드", "내 글을 좋아합니다.", "user123님이 내 글에 좋아요를 눌렀어요.", "2시간 전", false), + AlarmItem(2, "모임", "같이 읽기를 시작했어요!", "모임방에서 20분 동안 같이 읽기가 시작되었어요!", "7시간 전", false), + AlarmItem(3, "피드", "내 글에 댓글이 달렸어요.", "user1: 진짜 공감합니다!", "2025.01.12", true), + AlarmItem(4, "모임", "투표가 시작되었어요!", "투표지를 먼저 열람합니다.", "17시간 전", false), + AlarmItem(5, "피드", "팔로워가 새 글을 올렸어요.", "user456님이 새 리뷰를 작성했습니다.", "1일 전", true), + AlarmItem(6, "모임", "새로운 모임방 초대", "호르몬 체인지 완독하는 방에 초대되었습니다.", "2일 전", false) + ) + } + + fun onCardClick(item: AlarmItem) { + // TODO: 알림 카드 클릭 처리 + } +} \ No newline at end of file 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 index 11233ceb..066ada9d 100644 --- 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 @@ -53,7 +53,7 @@ fun SearchBookTextField( .fillMaxWidth() .height(40.dp) .clip(shape) - .background(colors.DarkGrey02), + .background(colors.DarkGrey), contentAlignment = Alignment.CenterStart ) { Row( 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 bf956155..fa6984c4 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 @@ -189,6 +189,7 @@ fun PreviewGroupRoomPagerSection() { // 마감 임박한 독서 모임방 val deadlineRooms = listOf( GroupCardItemRoomData( + id = 1, title = "시집만 읽는 사람들 3월", participants = 22, maxParticipants = 30, @@ -197,6 +198,7 @@ fun PreviewGroupRoomPagerSection() { genreIndex = 0 ), GroupCardItemRoomData( + id = 2, title = "일본 소설 좋아하는 사람들", participants = 15, maxParticipants = 20, @@ -205,6 +207,7 @@ fun PreviewGroupRoomPagerSection() { genreIndex = 0 ), GroupCardItemRoomData( + id = 3, title = "명작 같이 읽기방", participants = 22, maxParticipants = 30, @@ -213,6 +216,7 @@ fun PreviewGroupRoomPagerSection() { genreIndex = 0 ), GroupCardItemRoomData( + id = 4, title = "명작 같이 읽기방", participants = 22, maxParticipants = 30, @@ -221,6 +225,7 @@ fun PreviewGroupRoomPagerSection() { genreIndex = 0 ), GroupCardItemRoomData( + id = 5, title = "물리책 읽는 방", participants = 13, maxParticipants = 20, @@ -233,6 +238,7 @@ fun PreviewGroupRoomPagerSection() { // 인기 있는 독서 모임방 val popularRooms = listOf( GroupCardItemRoomData( + id = 6, title = "베스트셀러 토론방", participants = 28, maxParticipants = 30, @@ -241,6 +247,7 @@ fun PreviewGroupRoomPagerSection() { genreIndex = 0 ), GroupCardItemRoomData( + id = 7, title = "인기 소설 완독방", participants = 25, maxParticipants = 25, @@ -249,6 +256,7 @@ fun PreviewGroupRoomPagerSection() { genreIndex = 0 ), GroupCardItemRoomData( + id = 8, title = "트렌드 과학서 읽기", participants = 20, maxParticipants = 25, @@ -261,6 +269,7 @@ fun PreviewGroupRoomPagerSection() { // 인플루언서, 작가 독서 모임방 val influencerRooms = listOf( GroupCardItemRoomData( + id = 9, title = "작가와 함께하는 독서방", participants = 30, maxParticipants = 30, @@ -269,6 +278,7 @@ fun PreviewGroupRoomPagerSection() { genreIndex = 0 ), GroupCardItemRoomData( + id = 10, title = "유명 북튜버와 읽기", participants = 18, maxParticipants = 20, @@ -277,6 +287,7 @@ fun PreviewGroupRoomPagerSection() { genreIndex = 2 ), GroupCardItemRoomData( + id = 11, title = "작가 초청 인문학방", participants = 15, maxParticipants = 20, @@ -320,6 +331,7 @@ fun PreviewGroupRoomPagerSectionEmptyGenre() { // 특정 장르에만 데이터가 있는 경우 (문학 장르만 데이터 존재) val deadlineRooms = listOf( GroupCardItemRoomData( + id = 12, title = "시집만 읽는 사람들 3월", participants = 22, maxParticipants = 30, diff --git a/app/src/main/java/com/texthip/thip/ui/group/myroom/component/GroupSearchTextField.kt b/app/src/main/java/com/texthip/thip/ui/group/myroom/component/GroupSearchTextField.kt index 22b2d107..e2a644f6 100644 --- a/app/src/main/java/com/texthip/thip/ui/group/myroom/component/GroupSearchTextField.kt +++ b/app/src/main/java/com/texthip/thip/ui/group/myroom/component/GroupSearchTextField.kt @@ -1,6 +1,7 @@ package com.texthip.thip.ui.group.myroom.component import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize @@ -8,18 +9,12 @@ 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.foundation.text.BasicTextField import androidx.compose.material3.Icon 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.graphics.SolidColor import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview @@ -32,18 +27,11 @@ import com.texthip.thip.ui.theme.ThipTheme.typography @Composable fun GroupSearchTextField( modifier: Modifier = Modifier, - value: String, placeholder: String = stringResource(R.string.group_search_placeholder), - onValueChange: (String) -> Unit, - onClick: (() -> Unit)? = null + onClick: () -> Unit ) { - val textStyle = typography.menu_r400_s14_h24.copy( - lineHeight = 16.sp, - color = colors.White - ) val shape = RoundedCornerShape(12.dp) val backgroundColor = colors.DarkGrey - val cursorColor = colors.NeonGreen Box( modifier @@ -51,44 +39,29 @@ fun GroupSearchTextField( .fillMaxWidth() .height(40.dp) .clip(shape) - .background(backgroundColor), + .background(backgroundColor) + .clickable { onClick() }, contentAlignment = Alignment.CenterStart ) { Row( verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxSize() + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 12.dp) ) { - BasicTextField( - value = value, - onValueChange = onValueChange, - singleLine = true, - textStyle = textStyle, - cursorBrush = SolidColor(cursorColor), - modifier = Modifier - .weight(1f) - .padding(start = 12.dp, end = 8.dp), - decorationBox = { innerTextField -> - Box(contentAlignment = Alignment.CenterStart) { - if (value.isEmpty()) { - Text( - text = placeholder, - color = colors.Grey02, - style = typography.menu_r400_s14_h24.copy( - fontSize = 14.sp, - lineHeight = 16.sp - ) - ) - } - innerTextField() - } - } + Text( + text = placeholder, + color = colors.Grey02, + style = typography.menu_r400_s14_h24.copy( + fontSize = 14.sp, + lineHeight = 16.sp + ), + modifier = Modifier.weight(1f) ) Icon( painter = painterResource(id = R.drawable.ic_search), contentDescription = "검색", - tint = colors.White, - modifier = Modifier - .padding(end = 12.dp) + tint = colors.White ) } } @@ -97,9 +70,7 @@ fun GroupSearchTextField( @Preview(showBackground = true, backgroundColor = 0xFF000000, widthDp = 360) @Composable fun PreviewGroupSearchTextField() { - var value by remember { mutableStateOf("") } GroupSearchTextField( - value = value, - onValueChange = { value = it } + onClick = { } ) } diff --git a/app/src/main/java/com/texthip/thip/ui/group/myroom/mock/GroupCardData.kt b/app/src/main/java/com/texthip/thip/ui/group/myroom/mock/GroupCardData.kt index dab51e6b..79b6b34b 100644 --- a/app/src/main/java/com/texthip/thip/ui/group/myroom/mock/GroupCardData.kt +++ b/app/src/main/java/com/texthip/thip/ui/group/myroom/mock/GroupCardData.kt @@ -3,6 +3,7 @@ package com.texthip.thip.ui.group.myroom.mock import com.texthip.thip.R data class GroupCardData( + val id: Int = 0, // 모임방 ID 추가 val title: String, val members: Int, val imageRes: Int = R.drawable.bookcover_sample, diff --git a/app/src/main/java/com/texthip/thip/ui/group/myroom/mock/GroupCardItemRoomData.kt b/app/src/main/java/com/texthip/thip/ui/group/myroom/mock/GroupCardItemRoomData.kt index 11f779c9..00a64c30 100644 --- a/app/src/main/java/com/texthip/thip/ui/group/myroom/mock/GroupCardItemRoomData.kt +++ b/app/src/main/java/com/texthip/thip/ui/group/myroom/mock/GroupCardItemRoomData.kt @@ -3,6 +3,7 @@ package com.texthip.thip.ui.group.myroom.mock import com.texthip.thip.R data class GroupCardItemRoomData( + val id: Int, val title: String, val participants: Int, val maxParticipants: Int, diff --git a/app/src/main/java/com/texthip/thip/ui/group/myroom/mock/GroupRoomData.kt b/app/src/main/java/com/texthip/thip/ui/group/myroom/mock/GroupRoomData.kt index 55468f20..4a227a4c 100644 --- a/app/src/main/java/com/texthip/thip/ui/group/myroom/mock/GroupRoomData.kt +++ b/app/src/main/java/com/texthip/thip/ui/group/myroom/mock/GroupRoomData.kt @@ -1,6 +1,7 @@ package com.texthip.thip.ui.group.myroom.mock data class GroupRoomData( + val id: Int, val title: String, val isSecret: Boolean, val description: String, 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 4df8b8fb..2e436a72 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 @@ -1,41 +1,40 @@ package com.texthip.thip.ui.group.myroom.screen +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.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.ui.common.cards.CardItemRoom -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.ui.Modifier import com.texthip.thip.R -import com.texthip.thip.ui.theme.ThipTheme.colors -import androidx.compose.foundation.lazy.items -import androidx.compose.material3.Icon -import androidx.compose.material3.Text -import androidx.compose.ui.Alignment -import androidx.compose.ui.res.painterResource +import com.texthip.thip.ui.common.cards.CardItemRoom import com.texthip.thip.ui.common.topappbar.DefaultTopAppBar -import com.texthip.thip.ui.group.myroom.mock.GroupCardItemRoomData import com.texthip.thip.ui.group.myroom.component.GroupMyRoomFilterRow +import com.texthip.thip.ui.group.myroom.mock.GroupCardItemRoomData import com.texthip.thip.ui.theme.ThipTheme +import com.texthip.thip.ui.theme.ThipTheme.colors import com.texthip.thip.ui.theme.ThipTheme.typography @Composable fun GroupMyScreen( allDataList: List, - onCardClick: (GroupCardItemRoomData) -> Unit = {} + onCardClick: (GroupCardItemRoomData) -> Unit = {}, + onNavigateBack: () -> Unit = {} ) { var selectedStates by remember { mutableStateOf(booleanArrayOf(false, false)) } @@ -58,7 +57,7 @@ fun GroupMyScreen( ) { DefaultTopAppBar( title = stringResource(R.string.my_group_room), - onLeftClick = {}, + onLeftClick = onNavigateBack, ) Column( Modifier @@ -120,13 +119,13 @@ fun GroupMyScreen( } } - -@Preview() +@Preview @Composable fun MyGroupListFilterScreenPreview() { ThipTheme { val dataList = listOf( GroupCardItemRoomData( + id = 1, title = "모임방 이름입니다. 모임방...", participants = 22, maxParticipants = 30, @@ -135,6 +134,7 @@ fun MyGroupListFilterScreenPreview() { genreIndex = 0 ), GroupCardItemRoomData( + id = 2, title = "모임방 이름입니다. 모임방...", participants = 22, maxParticipants = 30, @@ -143,6 +143,7 @@ fun MyGroupListFilterScreenPreview() { genreIndex = 0 ), GroupCardItemRoomData( + id = 3, title = "모임방 이름입니다. 모임방...", participants = 22, maxParticipants = 30, @@ -151,6 +152,7 @@ fun MyGroupListFilterScreenPreview() { genreIndex = 0 ), GroupCardItemRoomData( + id = 4, title = "모임방 이름입니다. 모임방...", participants = 22, maxParticipants = 30, @@ -159,6 +161,7 @@ fun MyGroupListFilterScreenPreview() { genreIndex = 0 ), GroupCardItemRoomData( + id = 5, title = "모임방 이름입니다. 모임방...", participants = 22, maxParticipants = 30, @@ -167,6 +170,7 @@ fun MyGroupListFilterScreenPreview() { genreIndex = 0 ), GroupCardItemRoomData( + id = 6, title = "모임방 이름입니다. 모임방...", participants = 22, maxParticipants = 30, @@ -175,6 +179,7 @@ fun MyGroupListFilterScreenPreview() { genreIndex = 0 ), GroupCardItemRoomData( + id = 7, title = "모임방 이름입니다. 모임방...", participants = 22, maxParticipants = 30, @@ -183,6 +188,7 @@ fun MyGroupListFilterScreenPreview() { genreIndex = 0 ), GroupCardItemRoomData( + id = 8, title = "모임방 이름입니다. 모임방...", participants = 22, maxParticipants = 30, @@ -191,6 +197,7 @@ fun MyGroupListFilterScreenPreview() { genreIndex = 0 ), GroupCardItemRoomData( + id = 9, title = "모임방 이름입니다. 모임방...", participants = 22, maxParticipants = 30, @@ -199,6 +206,7 @@ fun MyGroupListFilterScreenPreview() { genreIndex = 0 ), GroupCardItemRoomData( + id = 10, title = "모임방 이름입니다. 모임방...", participants = 22, maxParticipants = 30, @@ -207,6 +215,7 @@ fun MyGroupListFilterScreenPreview() { genreIndex = 0 ), GroupCardItemRoomData( + id = 11, title = "모임방 이름입니다. 모임방...", participants = 22, maxParticipants = 30, @@ -215,6 +224,7 @@ fun MyGroupListFilterScreenPreview() { genreIndex = 0 ), GroupCardItemRoomData( + id = 12, title = "모임방 이름입니다. 모임방...", participants = 22, maxParticipants = 30, diff --git a/app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomRecruitScreen.kt b/app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomRecruitScreen.kt index a0cf9125..4ff90801 100644 --- a/app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomRecruitScreen.kt +++ b/app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomRecruitScreen.kt @@ -436,6 +436,7 @@ fun GroupRoomRecruitScreenPreviewJoin() { ThipTheme { val recommendations = listOf( GroupCardItemRoomData( + id = 1, title = "일본 소설 좋아하는 사람들 일본 소설 좋아하는 사람들", participants = 19, maxParticipants = 25, @@ -444,6 +445,7 @@ fun GroupRoomRecruitScreenPreviewJoin() { genreIndex = 0 ), GroupCardItemRoomData( + id = 2, title = "일본 소설 좋아하는 사람들 일본 소설 좋아하는 사람들", participants = 12, maxParticipants = 16, @@ -452,6 +454,7 @@ fun GroupRoomRecruitScreenPreviewJoin() { genreIndex = 0 ), GroupCardItemRoomData( + id = 3, title = "일본 소설 좋아하는 사람들 일본 소설 좋아하는 사람들", participants = 30, maxParticipants = 30, @@ -460,6 +463,7 @@ fun GroupRoomRecruitScreenPreviewJoin() { genreIndex = 0 ), GroupCardItemRoomData( + id = 4, title = "일본 소설 좋아하는 사람들 일본 소설 좋아하는 사람들", participants = 10, maxParticipants = 12, @@ -468,6 +472,7 @@ fun GroupRoomRecruitScreenPreviewJoin() { genreIndex = 0 ), GroupCardItemRoomData( + id = 5, title = "에세이 나눔방", participants = 14, maxParticipants = 20, @@ -486,6 +491,7 @@ fun GroupRoomRecruitScreenPreviewJoin() { ) val detailJoin = GroupRoomData( + id = 1, title = "시집만 읽는 사람들 3월", isSecret = true, description = "'시집만 읽는 사람들' 3월 모임입니다. 이번 달 모임에서는 심장보다 단단한 토마토 한 알을 함께 읽어요.", @@ -517,6 +523,7 @@ fun GroupRoomRecruitScreenPreviewCancel() { ThipTheme { val recommendations = listOf( GroupCardItemRoomData( + id = 6, title = "일본 소설 좋아하는 사람들 일본 소설 좋아하는 사람들", participants = 19, maxParticipants = 25, @@ -525,6 +532,7 @@ fun GroupRoomRecruitScreenPreviewCancel() { genreIndex = 0 ), GroupCardItemRoomData( + id = 7, title = "일본 소설 좋아하는 사람들 일본 소설 좋아하는 사람들", participants = 12, maxParticipants = 16, @@ -533,6 +541,7 @@ fun GroupRoomRecruitScreenPreviewCancel() { genreIndex = 0 ), GroupCardItemRoomData( + id = 8, title = "에세이 나눔방", participants = 14, maxParticipants = 20, @@ -551,6 +560,7 @@ fun GroupRoomRecruitScreenPreviewCancel() { ) val detailCancel = GroupRoomData( + id = 2, title = "시집만 읽는 사람들 3월", isSecret = true, description = "'시집만 읽는 사람들' 3월 모임입니다. 이번 달 모임에서는 심장보다 단단한 토마토 한 알을 함께 읽어요.", @@ -582,6 +592,7 @@ fun GroupRoomRecruitScreenClose() { ThipTheme { val recommendations = listOf( GroupCardItemRoomData( + id = 9, title = "일본 소설 좋아하는 사람들 일본 소설 좋아하는 사람들", participants = 19, maxParticipants = 25, @@ -590,6 +601,7 @@ fun GroupRoomRecruitScreenClose() { genreIndex = 0 ), GroupCardItemRoomData( + id = 10, title = "일본 소설 좋아하는 사람들 일본 소설 좋아하는 사람들", participants = 12, maxParticipants = 16, @@ -598,6 +610,7 @@ fun GroupRoomRecruitScreenClose() { genreIndex = 0 ), GroupCardItemRoomData( + id = 11, title = "미스터리 소설 탐구", participants = 8, maxParticipants = 15, @@ -616,6 +629,7 @@ fun GroupRoomRecruitScreenClose() { ) val detailClose = GroupRoomData( + id = 3, title = "시집만 읽는 사람들 3월", isSecret = false, // 오픈방으로 변경 description = "'시집만 읽는 사람들' 3월 모임입니다. 이번 달 모임에서는 심장보다 단단한 토마토 한 알을 함께 읽어요. 모임장이 모집을 마감할 수 있는 상태입니다.", diff --git a/app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomScreen.kt b/app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomScreen.kt index 9b42ef22..bd97b6d2 100644 --- a/app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomScreen.kt +++ b/app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomScreen.kt @@ -39,7 +39,9 @@ import com.texthip.thip.ui.theme.ThipTheme.colors @OptIn(ExperimentalMaterial3Api::class) @Composable -fun GroupRoomScreen() { +fun GroupRoomScreen( + onBackClick: () -> Unit = {} +) { val scrollState = rememberScrollState() var isBottomSheetVisible by remember { mutableStateOf(false) } @@ -98,7 +100,7 @@ fun GroupRoomScreen() { } GradationTopAppBar( - onLeftClick = {}, + onLeftClick = onBackClick, onRightClick = { isBottomSheetVisible = true }, ) } diff --git a/app/src/main/java/com/texthip/thip/ui/group/screen/GroupDoneScreen.kt b/app/src/main/java/com/texthip/thip/ui/group/screen/GroupDoneScreen.kt index e55b4d51..205b0423 100644 --- a/app/src/main/java/com/texthip/thip/ui/group/screen/GroupDoneScreen.kt +++ b/app/src/main/java/com/texthip/thip/ui/group/screen/GroupDoneScreen.kt @@ -27,9 +27,8 @@ import com.texthip.thip.ui.theme.ThipTheme.typography fun GroupDoneScreen( name: String, allDataList: List, - onCardClick: (GroupCardItemRoomData) -> Unit = {} + onNavigateBack: () -> Unit = {} ) { - // isRecruiting == false 인 방만 필터링 (혹시 몰라서 넣어둡니다) val doneList = remember(allDataList) { allDataList.filter { !it.isRecruiting } } @@ -40,7 +39,7 @@ fun GroupDoneScreen( ) { DefaultTopAppBar( title = stringResource(R.string.group_done_title), - onLeftClick = {}, + onLeftClick = onNavigateBack, ) Column( Modifier @@ -72,7 +71,7 @@ fun GroupDoneScreen( maxParticipants = item.maxParticipants, isRecruiting = item.isRecruiting, imageRes = item.imageRes, - onClick = { onCardClick(item) } + onClick = { /* 완료된 모임방은 클릭 불가 */ } ) } } @@ -82,12 +81,13 @@ fun GroupDoneScreen( -@Preview() +@Preview @Composable fun MyGroupListFilterScreenPreview() { ThipTheme { val dataList = listOf( GroupCardItemRoomData( + id = 1, title = "모임방 이름입니다. 모임방...", participants = 22, maxParticipants = 30, @@ -95,6 +95,7 @@ fun MyGroupListFilterScreenPreview() { genreIndex = 0 ), GroupCardItemRoomData( + id = 2, title = "모임방 이름입니다. 모임방...", participants = 22, maxParticipants = 30, @@ -102,6 +103,7 @@ fun MyGroupListFilterScreenPreview() { genreIndex = 0 ), GroupCardItemRoomData( + id = 3, title = "모임방 이름입니다. 모임방...", participants = 22, maxParticipants = 30, @@ -109,6 +111,7 @@ fun MyGroupListFilterScreenPreview() { genreIndex = 0 ), GroupCardItemRoomData( + id = 4, title = "모임방 이름입니다. 모임방...", participants = 22, maxParticipants = 30, @@ -116,6 +119,7 @@ fun MyGroupListFilterScreenPreview() { genreIndex = 0 ), GroupCardItemRoomData( + id = 5, title = "모임방 이름입니다. 모임방...", participants = 22, maxParticipants = 30, @@ -123,6 +127,7 @@ fun MyGroupListFilterScreenPreview() { genreIndex = 0 ), GroupCardItemRoomData( + id = 6, title = "모임방 이름입니다. 모임방...", participants = 22, maxParticipants = 30, 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 95f8ecb7..ff2fb15d 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 @@ -13,7 +13,6 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource @@ -34,17 +33,20 @@ import com.texthip.thip.ui.theme.ThipTheme.colors @Composable fun GroupScreen( onNavigateToMakeRoom: () -> Unit = {}, + onNavigateToGroupDone: () -> Unit = {}, // 완료된 화면으로 이동 + onNavigateToAlarm: () -> Unit = {}, // 알림 화면으로 이동 + onNavigateToGroupSearch: () -> Unit = {}, // 검색 화면으로 이동 + onNavigateToGroupMy: () -> Unit = {}, // 내 모임방 화면으로 이동 + onNavigateToGroupRecruit: (Int) -> Unit = {}, // 모집 중인 모임방 화면으로 이동 + onNavigateToGroupRoom: (Int) -> Unit = {}, // 기록장 화면으로 이동 viewModel: GroupViewModel = viewModel() ) { val myGroups by viewModel.myGroups.collectAsState() val roomSections by viewModel.roomSections.collectAsState() val scrollState = rememberScrollState() - val searchText by remember { mutableStateOf("") } Box( - Modifier - .background(colors.Black) - .fillMaxSize() + modifier = Modifier.fillMaxSize() ) { Column( Modifier @@ -55,27 +57,27 @@ fun GroupScreen( LogoTopAppBar( leftIcon = painterResource(R.drawable.ic_done), hasNotification = false, - onLeftClick = { }, - onRightClick = { } + onLeftClick = onNavigateToGroupDone, + onRightClick = onNavigateToAlarm ) // 검색창 GroupSearchTextField( modifier = Modifier.padding(top = 16.dp, bottom = 32.dp), - value = searchText, - onValueChange = {}, - onClick = {} + onClick = onNavigateToGroupSearch ) // 내 모임방 헤더 + 카드 GroupMySectionHeader( - onClick = { viewModel.onMyGroupHeaderClick() } + onClick = onNavigateToGroupMy ) Spacer(Modifier.height(20.dp)) GroupPager( groupCards = myGroups, - onCardClick = { viewModel.onMyGroupCardClick(it) } + onCardClick = { groupCard -> + onNavigateToGroupRoom(groupCard.id) + } ) Spacer(Modifier.height(32.dp)) @@ -90,7 +92,13 @@ fun GroupScreen( // 마감 임박한 독서 모임방 GroupRoomDeadlineSection( roomSections = roomSections, - onRoomClick = { viewModel.onRoomCardClick(it) } + onRoomClick = { room -> + if (room.isRecruiting) { + onNavigateToGroupRecruit(room.id) + } else { + onNavigateToGroupRoom(room.id) + } + } ) Spacer(Modifier.height(102.dp)) } diff --git a/app/src/main/java/com/texthip/thip/ui/group/search/component/GroupFilteredSearchResult.kt b/app/src/main/java/com/texthip/thip/ui/group/search/component/GroupFilteredSearchResult.kt index 554e9482..ea039d5d 100644 --- a/app/src/main/java/com/texthip/thip/ui/group/search/component/GroupFilteredSearchResult.kt +++ b/app/src/main/java/com/texthip/thip/ui/group/search/component/GroupFilteredSearchResult.kt @@ -36,7 +36,8 @@ fun GroupFilteredSearchResult( selectedGenreIndex: Int, onGenreSelect: (Int) -> Unit, resultCount: Int, - roomList: List + roomList: List, + onRoomClick: (GroupCardItemRoomData) -> Unit = {} ) { Column { GenreChipRow( @@ -79,7 +80,8 @@ fun GroupFilteredSearchResult( endDate = room.endDate, imageRes = room.imageRes, isWide = true, - isSecret = room.isSecret + isSecret = room.isSecret, + onClick = { onRoomClick(room) } ) if (index < roomList.size - 1) { Spacer( @@ -113,6 +115,7 @@ fun GroupFilteredSearchResultPreview() { resultCount = 3, roomList = listOf( GroupCardItemRoomData( + id = 1, title = "해리포터 독서모임", participants = 5, maxParticipants = 10, @@ -122,6 +125,7 @@ fun GroupFilteredSearchResultPreview() { genreIndex = 1, isSecret = false ), GroupCardItemRoomData( + id = 2, title = "소설 읽기 모임", participants = 8, maxParticipants = 12, diff --git a/app/src/main/java/com/texthip/thip/ui/group/search/component/GroupLiveSearchResult.kt b/app/src/main/java/com/texthip/thip/ui/group/search/component/GroupLiveSearchResult.kt index 817626d9..7674e39e 100644 --- a/app/src/main/java/com/texthip/thip/ui/group/search/component/GroupLiveSearchResult.kt +++ b/app/src/main/java/com/texthip/thip/ui/group/search/component/GroupLiveSearchResult.kt @@ -21,7 +21,8 @@ import com.texthip.thip.ui.theme.ThipTheme.colors @Composable fun GroupLiveSearchResult( - roomList: List + roomList: List, + onRoomClick: (GroupCardItemRoomData) -> Unit = {} ) { LazyColumn { itemsIndexed(roomList) { index, room -> @@ -32,7 +33,8 @@ fun GroupLiveSearchResult( endDate = room.endDate, imageRes = room.imageRes, isWide = true, - isSecret = room.isSecret + isSecret = room.isSecret, + onClick = { onRoomClick(room) } ) if (index < roomList.size - 1) { Spacer( @@ -58,6 +60,7 @@ fun GroupLiveSearchResultPreview() { GroupLiveSearchResult( roomList = listOf( GroupCardItemRoomData( + id = 1, title = "해리포터 독서모임", participants = 5, maxParticipants = 10, @@ -68,6 +71,7 @@ fun GroupLiveSearchResultPreview() { isSecret = false ), GroupCardItemRoomData( + id = 2, title = "소설 읽기 모임", participants = 8, maxParticipants = 12, @@ -78,6 +82,7 @@ fun GroupLiveSearchResultPreview() { isSecret = true ), GroupCardItemRoomData( + id = 3, title = "비즈니스 서적 스터디", participants = 3, maxParticipants = 8, diff --git a/app/src/main/java/com/texthip/thip/ui/group/search/screen/GroupSearchScreen.kt b/app/src/main/java/com/texthip/thip/ui/group/search/screen/GroupSearchScreen.kt index 00691975..bfcaedb8 100644 --- a/app/src/main/java/com/texthip/thip/ui/group/search/screen/GroupSearchScreen.kt +++ b/app/src/main/java/com/texthip/thip/ui/group/search/screen/GroupSearchScreen.kt @@ -1,5 +1,6 @@ package com.texthip.thip.ui.group.search.screen +import android.content.Context import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer @@ -20,6 +21,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview @@ -31,17 +33,47 @@ import com.texthip.thip.ui.common.topappbar.DefaultTopAppBar import com.texthip.thip.ui.group.myroom.component.GroupRecentSearch import com.texthip.thip.ui.group.myroom.mock.GroupCardItemRoomData import com.texthip.thip.ui.group.search.component.GroupEmptyResult -import com.texthip.thip.ui.group.search.component.GroupLiveSearchResult import com.texthip.thip.ui.group.search.component.GroupFilteredSearchResult +import com.texthip.thip.ui.group.search.component.GroupLiveSearchResult import com.texthip.thip.ui.theme.ThipTheme +import kotlinx.serialization.json.Json +import androidx.core.content.edit +import kotlinx.serialization.builtins.ListSerializer +import kotlinx.serialization.builtins.serializer @Composable fun GroupSearchScreen( modifier: Modifier = Modifier, - roomList: List + roomList: List, + onNavigateBack: () -> Unit = {}, + onRoomClick: (GroupCardItemRoomData) -> Unit = {} ) { - var recentSearches by rememberSaveable { - mutableStateOf(listOf("user.02", "ㅇㅇ", "훽후ㅣㅣ", "검색4", "검색5", "검색6")) + val context = LocalContext.current + val sharedPrefs = remember { + context.getSharedPreferences("group_search_prefs", Context.MODE_PRIVATE) + } + + var recentSearches by remember { + mutableStateOf( + try { + val jsonString = sharedPrefs.getString("recent_searches", "[]") ?: "[]" + Json.decodeFromString>(jsonString) + } catch (e: Exception) { + emptyList() + } + ) + } + + fun saveRecentSearches(searches: List) { + try { + val jsonString = Json.encodeToString(ListSerializer(String.serializer()), searches) + sharedPrefs.edit { + putString("recent_searches", jsonString) + } + recentSearches = searches + } catch (e: Exception) { + recentSearches = emptyList() + } } var searchText by rememberSaveable { mutableStateOf("") } var isSearched by rememberSaveable { mutableStateOf(false) } @@ -107,7 +139,7 @@ fun GroupSearchScreen( ) { DefaultTopAppBar( title = stringResource(R.string.group_room_search_topappbar), - onLeftClick = {}, + onLeftClick = onNavigateBack, ) Column( modifier = Modifier @@ -128,7 +160,8 @@ fun GroupSearchScreen( }, onSearch = { query -> if (query.isNotBlank() && !recentSearches.contains(query)) { - recentSearches = listOf(query) + recentSearches + val newSearches = listOf(query) + recentSearches.take(9) // 최대 10개 유지 + saveRecentSearches(newSearches) } isSearched = true selectedGenreIndex = -1 @@ -153,7 +186,8 @@ fun GroupSearchScreen( isSearched = true }, onRemove = { keyword -> - recentSearches = recentSearches.filterNot { it == keyword } + val updatedSearches = recentSearches.filterNot { it == keyword } + saveRecentSearches(updatedSearches) } ) } @@ -166,7 +200,8 @@ fun GroupSearchScreen( ) } else { GroupLiveSearchResult( - roomList = liveFilteredRoomList + roomList = liveFilteredRoomList, + onRoomClick = onRoomClick ) } } @@ -178,6 +213,7 @@ fun GroupSearchScreen( onGenreSelect = { selectedGenreIndex = it }, resultCount = filteredRoomList.size, roomList = filteredRoomList, + onRoomClick = onRoomClick ) } } @@ -206,13 +242,13 @@ fun PreviewGroupSearchScreen() { ThipTheme { GroupSearchScreen( roomList = listOf( - GroupCardItemRoomData("aaa", 22, 30, true, 3, R.drawable.bookcover_sample, 0), - GroupCardItemRoomData("abc", 15, 20, true, 7, R.drawable.bookcover_sample, 1, true), - GroupCardItemRoomData("abcd", 10, 15, true, 5, R.drawable.bookcover_sample, 2, true), - GroupCardItemRoomData("abcde", 8, 12, false, 2, R.drawable.bookcover_sample, 3, true), - GroupCardItemRoomData("abcdef", 18, 25, true, 4, R.drawable.bookcover_sample, 4), - GroupCardItemRoomData("abcdefg", 12, 20, true, 1, R.drawable.bookcover_sample, 0), - GroupCardItemRoomData("abcdefgh", 10, 14, true, 6, R.drawable.bookcover_sample, 1) + GroupCardItemRoomData(1, "aaa", 22, 30, true, 3, R.drawable.bookcover_sample, 0), + GroupCardItemRoomData(2, "abc", 15, 20, true, 7, R.drawable.bookcover_sample, 1, true), + GroupCardItemRoomData(3, "abcd", 10, 15, true, 5, R.drawable.bookcover_sample, 2, true), + GroupCardItemRoomData(4, "abcde", 8, 12, false, 2, R.drawable.bookcover_sample, 3, true), + GroupCardItemRoomData(5, "abcdef", 18, 25, true, 4, R.drawable.bookcover_sample, 4), + GroupCardItemRoomData(6, "abcdefg", 12, 20, true, 1, R.drawable.bookcover_sample, 0), + GroupCardItemRoomData(7, "abcdefgh", 10, 14, true, 6, R.drawable.bookcover_sample, 1) ) ) } diff --git a/app/src/main/java/com/texthip/thip/ui/group/viewmodel/GroupViewModel.kt b/app/src/main/java/com/texthip/thip/ui/group/viewmodel/GroupViewModel.kt index ded195d8..561adffe 100644 --- a/app/src/main/java/com/texthip/thip/ui/group/viewmodel/GroupViewModel.kt +++ b/app/src/main/java/com/texthip/thip/ui/group/viewmodel/GroupViewModel.kt @@ -1,90 +1,116 @@ package com.texthip.thip.ui.group.viewmodel import androidx.lifecycle.ViewModel -import com.texthip.thip.R +import androidx.lifecycle.viewModelScope +import com.texthip.thip.data.group.repository.GroupRepository import com.texthip.thip.ui.group.myroom.mock.GroupCardData import com.texthip.thip.ui.group.myroom.mock.GroupCardItemRoomData import com.texthip.thip.ui.group.myroom.mock.GroupRoomSectionData +import com.texthip.thip.ui.group.myroom.mock.GroupRoomData import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch -class GroupViewModel : ViewModel() { +class GroupViewModel( + private val repository: GroupRepository = GroupRepository() +) : ViewModel() { private val _myGroups = MutableStateFlow>(emptyList()) - val myGroups: StateFlow> = _myGroups + val myGroups: StateFlow> = _myGroups.asStateFlow() private val _roomSections = MutableStateFlow>(emptyList()) - val roomSections: StateFlow> = _roomSections + val roomSections: StateFlow> = _roomSections.asStateFlow() - private val _genres = listOf("문학", "과학·IT", "사회과학", "인문학", "예술") - val genres: List get() = _genres + private val _userName = MutableStateFlow("") + val userName: StateFlow = _userName.asStateFlow() - // 초기 데이터 세팅 (실제에선 repository/remote에서 받아옴) - init { - _myGroups.value = listOf( - GroupCardData("호르몬 체인지 완독하는 방", 22, R.drawable.bookcover_sample, 40, "uibowl1"), - GroupCardData("명작 읽기방", 10, R.drawable.bookcover_sample, 70, "joyce"), - GroupCardData("또 다른 방", 13, R.drawable.bookcover_sample, 10, "other") - ) - - // 마감 임박한 독서 모임방 - val deadlineRooms = listOf( - GroupCardItemRoomData("시집만 읽는 사람들 3월", 22, 30, true, 3, R.drawable.bookcover_sample, 0), - GroupCardItemRoomData("일본 소설 좋아하는 사람들", 15, 20, true, 2, R.drawable.bookcover_sample, 0), - GroupCardItemRoomData("명작 같이 읽기방", 22, 30, true, 3, R.drawable.bookcover_sample, 0), - GroupCardItemRoomData("물리책 읽는 방", 13, 20, true, 1, R.drawable.bookcover_sample, 1), - GroupCardItemRoomData("코딩 과학 동아리", 12, 15, true, 5, R.drawable.bookcover_sample, 1), - GroupCardItemRoomData("사회과학 인문 탐구", 8, 12, true, 4, R.drawable.bookcover_sample, 2) - ) + private val _doneGroups = MutableStateFlow>(emptyList()) + val doneGroups: StateFlow> = _doneGroups.asStateFlow() - // 인기 있는 독서 모임방 - val popularRooms = listOf( - GroupCardItemRoomData("베스트셀러 토론방", 28, 30, true, 7, R.drawable.bookcover_sample, 0), - GroupCardItemRoomData("인기 소설 완독방", 25, 25, false, 5, R.drawable.bookcover_sample, 0), - GroupCardItemRoomData("트렌드 과학서 읽기", 20, 25, true, 10, R.drawable.bookcover_sample, 1), - GroupCardItemRoomData("화제의 경영서", 18, 20, true, 8, R.drawable.bookcover_sample, 2), - GroupCardItemRoomData("인기 철학서 모임", 15, 18, true, 12, R.drawable.bookcover_sample, 3), - GroupCardItemRoomData("예술서 베스트", 12, 15, true, 6, R.drawable.bookcover_sample, 4) - ) + private val _myRoomGroups = MutableStateFlow>(emptyList()) + val myRoomGroups: StateFlow> = _myRoomGroups.asStateFlow() - // 인플루언서, 작가 독서 모임방 - val influencerRooms = listOf( - GroupCardItemRoomData("작가와 함께하는 독서방", 30, 30, false, 14, R.drawable.bookcover_sample, 0), - GroupCardItemRoomData("유명 북튜버와 읽기", 18, 20, true, 8, R.drawable.bookcover_sample, 2), - GroupCardItemRoomData("작가 초청 인문학방", 15, 20, true, 12, R.drawable.bookcover_sample, 3), - GroupCardItemRoomData("인플루언서 과학책", 22, 25, true, 9, R.drawable.bookcover_sample, 1), - GroupCardItemRoomData("유명작가 예술론", 16, 18, true, 11, R.drawable.bookcover_sample, 4) - ) + private val _searchGroups = MutableStateFlow>(emptyList()) + val searchGroups: StateFlow> = _searchGroups.asStateFlow() - _roomSections.value = listOf( - GroupRoomSectionData( - title = "마감 임박한 독서 모임방", - rooms = deadlineRooms, - genres = _genres - ), - GroupRoomSectionData( - title = "인기 있는 독서 모임방", - rooms = popularRooms, - genres = _genres - ), - GroupRoomSectionData( - title = "인플루언서·작가 독서 모임방", - rooms = influencerRooms, - genres = _genres - ) - ) + private val _genres = MutableStateFlow>(emptyList()) + val genres: StateFlow> = _genres.asStateFlow() + + init { + loadInitialData() } - fun onMyGroupHeaderClick() { - // 내 모임방 리스트로 이동 (Nav 이벤트 트리거 등) + private fun loadInitialData() { + loadUserName() + loadMyGroups() + loadRoomSections() + loadDoneGroups() + loadMyRoomGroups() + loadSearchGroups() } - - fun onMyGroupCardClick(data: GroupCardData) { - // 내 모임방 카드 클릭 (상세 진입) + + private fun loadUserName() { + viewModelScope.launch { + repository.getUserName() + .onSuccess { userName -> + _userName.value = userName + } + } } - - fun onRoomCardClick(data: GroupCardItemRoomData) { - // 방 카드 클릭 (상세 진입) + + private fun loadMyGroups() { + viewModelScope.launch { + repository.getMyGroups() + .onSuccess { groups -> + _myGroups.value = groups + } + } + } + + private fun loadRoomSections() { + viewModelScope.launch { + repository.getRoomSections() + .onSuccess { sections -> + _roomSections.value = sections + } + } + } + + private fun loadDoneGroups() { + viewModelScope.launch { + repository.getDoneGroups() + .onSuccess { groups -> + _doneGroups.value = groups + } + } + } + + private fun loadMyRoomGroups() { + viewModelScope.launch { + repository.getMyRoomGroups() + .onSuccess { groups -> + _myRoomGroups.value = groups + } + } + } + + private fun loadSearchGroups() { + viewModelScope.launch { + repository.getSearchGroups() + .onSuccess { groups -> + _searchGroups.value = groups + } + } + } + + fun refreshGroupData() { + loadInitialData() + } + + + suspend fun getRoomDetail(roomId: Int): GroupRoomData? { + return repository.getRoomDetail(roomId).getOrNull() } } \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/ui/navigator/MainNavHost.kt b/app/src/main/java/com/texthip/thip/ui/navigator/MainNavHost.kt index 4fdeae34..894a09d8 100644 --- a/app/src/main/java/com/texthip/thip/ui/navigator/MainNavHost.kt +++ b/app/src/main/java/com/texthip/thip/ui/navigator/MainNavHost.kt @@ -8,14 +8,22 @@ import com.texthip.thip.ui.navigator.navigations.feedNavigation import com.texthip.thip.ui.navigator.navigations.groupNavigation import com.texthip.thip.ui.navigator.navigations.myPageNavigation import com.texthip.thip.ui.navigator.navigations.searchNavigation +import com.texthip.thip.ui.navigator.navigations.commonNavigation // 메인 네비게이션 @Composable fun MainNavHost(navController: NavHostController) { NavHost(navController = navController, startDestination = MainTabRoutes.Feed) { feedNavigation(navController) - groupNavigation(navController) + groupNavigation( + navController = navController, + navigateBack = navController::popBackStack + ) searchNavigation(navController) myPageNavigation(navController) + commonNavigation( + navController = navController, + navigateBack = navController::popBackStack + ) } } \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/ui/navigator/extensions/CommonNavigationExtensions.kt b/app/src/main/java/com/texthip/thip/ui/navigator/extensions/CommonNavigationExtensions.kt index 61ff596b..1e16cea8 100644 --- a/app/src/main/java/com/texthip/thip/ui/navigator/extensions/CommonNavigationExtensions.kt +++ b/app/src/main/java/com/texthip/thip/ui/navigator/extensions/CommonNavigationExtensions.kt @@ -7,10 +7,6 @@ import com.texthip.thip.ui.navigator.routes.MainTabRoutes // 공통 네비게이션 확장 함수들 -// 뒤로가기 -fun NavHostController.navigateBack() { - popBackStack() -} // Bottom Navigation용 Tab 이동 (메인 탭에만 사용) fun NavHostController.navigateToTab(route: MainTabRoutes) { @@ -23,7 +19,7 @@ fun NavHostController.navigateToTab(route: MainTabRoutes) { } } -// 라우트 매칭 헬퍼 함수들 +// 메인 루트 헬퍼 함수들 fun NavDestination.isMainTabRoute(): Boolean { return when (route) { MainTabRoutes.Feed::class.qualifiedName, @@ -34,6 +30,9 @@ fun NavDestination.isMainTabRoute(): Boolean { } } +// 매인 루트인지 확인 fun NavDestination.isRoute(targetRoute: MainTabRoutes): Boolean { return route == targetRoute::class.qualifiedName -} \ No newline at end of file +} + + diff --git a/app/src/main/java/com/texthip/thip/ui/navigator/extensions/GroupNavigationExtensions.kt b/app/src/main/java/com/texthip/thip/ui/navigator/extensions/GroupNavigationExtensions.kt index 0e1aaa75..48fc3ddb 100644 --- a/app/src/main/java/com/texthip/thip/ui/navigator/extensions/GroupNavigationExtensions.kt +++ b/app/src/main/java/com/texthip/thip/ui/navigator/extensions/GroupNavigationExtensions.kt @@ -1,16 +1,58 @@ package com.texthip.thip.ui.navigator.extensions import androidx.navigation.NavHostController -import com.texthip.thip.ui.navigator.routes.MainTabRoutes +import com.texthip.thip.ui.navigator.routes.CommonRoutes import com.texthip.thip.ui.navigator.routes.GroupRoutes +import com.texthip.thip.ui.navigator.routes.MainTabRoutes // Group 관련 네비게이션 확장 함수들 - fun NavHostController.navigateToGroup() { navigate(MainTabRoutes.Group) } +// 모임방 만들기 화면으로 이동 fun NavHostController.navigateToGroupMakeRoom() { navigate(GroupRoutes.MakeRoom) -} \ No newline at end of file +} + +// 완료된 모임방 목록으로 이동 +fun NavHostController.navigateToGroupDone() { + navigate(GroupRoutes.Done) +} + +// 모임방 검색 화면으로 이동 +fun NavHostController.navigateToGroupSearch() { + navigate(GroupRoutes.Search) +} + +// 내 모임방 화면으로 이동 +fun NavHostController.navigateToGroupMy() { + navigate(GroupRoutes.My) +} + +// 알람 화면으로 이동 +fun NavHostController.navigateToAlarm() { + navigate(CommonRoutes.Alarm) +} + +// 모집중인 모임방 상세 화면으로 이동 +fun NavHostController.navigateToGroupRecruit(roomId: Int) { + navigate(GroupRoutes.Recruit(roomId)) +} + +// 추천 모임방으로 이동 (현재 화면을 대체) +fun NavHostController.navigateToRecommendedGroupRecruit(roomId: Int) { + navigate(GroupRoutes.Recruit(roomId)) { + popUpTo(currentDestination?.route ?: return@navigate) { + inclusive = true + } + } +} + +// 진행중인 모임방 화면으로 이동 +fun NavHostController.navigateToGroupRoom(roomId: Int) { + navigate(GroupRoutes.Room(roomId)) +} + + diff --git a/app/src/main/java/com/texthip/thip/ui/navigator/navigations/CommonNavigation.kt b/app/src/main/java/com/texthip/thip/ui/navigator/navigations/CommonNavigation.kt new file mode 100644 index 00000000..d2cd398c --- /dev/null +++ b/app/src/main/java/com/texthip/thip/ui/navigator/navigations/CommonNavigation.kt @@ -0,0 +1,29 @@ +package com.texthip.thip.ui.navigator.navigations + +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavHostController +import androidx.navigation.compose.composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import com.texthip.thip.ui.common.alarmpage.screen.AlarmScreen +import com.texthip.thip.ui.common.alarmpage.viewmodel.AlarmViewModel +import com.texthip.thip.ui.navigator.routes.CommonRoutes + +// Common 관련 네비게이션 +fun NavGraphBuilder.commonNavigation( + navController: NavHostController, + navigateBack: () -> Unit +) { + // Alarm 화면 + composable { + val alarmViewModel: AlarmViewModel = viewModel() + val alarmItems by alarmViewModel.alarmItems.collectAsState() + + AlarmScreen( + alarmItems = alarmItems, + onCardClick = { alarmViewModel.onCardClick(it) }, + onNavigateBack = navigateBack + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/ui/navigator/navigations/GroupNavigation.kt b/app/src/main/java/com/texthip/thip/ui/navigator/navigations/GroupNavigation.kt index 711840ff..0c8adce7 100644 --- a/app/src/main/java/com/texthip/thip/ui/navigator/navigations/GroupNavigation.kt +++ b/app/src/main/java/com/texthip/thip/ui/navigator/navigations/GroupNavigation.kt @@ -1,24 +1,73 @@ package com.texthip.thip.ui.navigator.navigations +import android.annotation.SuppressLint +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController import androidx.navigation.compose.composable +import androidx.navigation.toRoute +import com.texthip.thip.ui.group.viewmodel.GroupViewModel import com.texthip.thip.ui.group.makeroom.screen.GroupMakeRoomScreen import com.texthip.thip.ui.group.makeroom.viewmodel.GroupMakeRoomViewModel +import com.texthip.thip.ui.group.myroom.mock.GroupBottomButtonType +import com.texthip.thip.ui.group.myroom.mock.GroupRoomData +import com.texthip.thip.ui.group.myroom.screen.GroupMyScreen +import com.texthip.thip.ui.group.room.screen.GroupRoomRecruitScreen +import com.texthip.thip.ui.group.room.screen.GroupRoomScreen +import com.texthip.thip.ui.group.screen.GroupDoneScreen import com.texthip.thip.ui.group.screen.GroupScreen -import com.texthip.thip.ui.navigator.routes.MainTabRoutes -import com.texthip.thip.ui.navigator.routes.GroupRoutes -import com.texthip.thip.ui.navigator.extensions.navigateBack +import com.texthip.thip.ui.group.search.screen.GroupSearchScreen +import com.texthip.thip.ui.navigator.extensions.navigateToAlarm +import com.texthip.thip.ui.navigator.extensions.navigateToGroupDone import com.texthip.thip.ui.navigator.extensions.navigateToGroupMakeRoom +import com.texthip.thip.ui.navigator.extensions.navigateToGroupMy +import com.texthip.thip.ui.navigator.extensions.navigateToGroupRecruit +import com.texthip.thip.ui.navigator.extensions.navigateToRecommendedGroupRecruit +import com.texthip.thip.ui.navigator.extensions.navigateToGroupRoom +import com.texthip.thip.ui.navigator.extensions.navigateToGroupSearch +import com.texthip.thip.ui.navigator.routes.GroupRoutes +import com.texthip.thip.ui.navigator.routes.MainTabRoutes // Group -fun NavGraphBuilder.groupNavigation(navController: NavHostController) { +@SuppressLint("UnrememberedGetBackStackEntry") +fun NavGraphBuilder.groupNavigation( + navController: NavHostController, + navigateBack: () -> Unit +) { // 메인 Group 화면 - composable { + composable { backStackEntry -> + val groupViewModel: GroupViewModel = viewModel( + viewModelStoreOwner = backStackEntry + ) + GroupScreen( + viewModel = groupViewModel, onNavigateToMakeRoom = { navController.navigateToGroupMakeRoom() + }, + onNavigateToGroupDone = { + navController.navigateToGroupDone() + }, + onNavigateToAlarm = { + navController.navigateToAlarm() + }, + onNavigateToGroupSearch = { + navController.navigateToGroupSearch() + }, + onNavigateToGroupMy = { + navController.navigateToGroupMy() + }, + onNavigateToGroupRecruit = { roomId -> + navController.navigateToGroupRecruit(roomId) + }, + onNavigateToGroupRoom = { roomId -> + navController.navigateToGroupRoom(roomId) } ) } @@ -29,11 +78,181 @@ fun NavGraphBuilder.groupNavigation(navController: NavHostController) { GroupMakeRoomScreen( viewModel = viewModel, onNavigateBack = { - navController.navigateBack() + navigateBack() }, onGroupCreated = { - navController.navigateBack() + navigateBack() } ) } + + // Group Done 화면 + composable { + val parentEntry = remember(navController) { + try { + navController.getBackStackEntry(MainTabRoutes.Group) + } catch (e: Exception) { + null + } + } + val groupViewModel: GroupViewModel = if (parentEntry != null) { + viewModel(viewModelStoreOwner = parentEntry) + } else { + viewModel() + } + val userName by groupViewModel.userName.collectAsState() + val doneGroups by groupViewModel.doneGroups.collectAsState() + + GroupDoneScreen( + name = userName, + allDataList = doneGroups, + onNavigateBack = { + navigateBack() + } + ) + } + + // Group My 화면 + composable { + val parentEntry = remember(navController) { + try { + navController.getBackStackEntry(MainTabRoutes.Group) + } catch (e: Exception) { + null + } + } + val groupViewModel: GroupViewModel = if (parentEntry != null) { + viewModel(viewModelStoreOwner = parentEntry) + } else { + viewModel() + } + val myRoomGroups by groupViewModel.myRoomGroups.collectAsState() + + GroupMyScreen( + allDataList = myRoomGroups, + onCardClick = { room -> + if (room.isRecruiting) { + navController.navigateToGroupRecruit(room.id) + } else { + navController.navigateToGroupRoom(room.id) + } + }, + onNavigateBack = { + navigateBack() + } + ) + } + + // Group Search 화면 + composable { + val parentEntry = remember(navController) { + try { + navController.getBackStackEntry(MainTabRoutes.Group) + } catch (e: Exception) { + null + } + } + val groupViewModel: GroupViewModel = if (parentEntry != null) { + viewModel(viewModelStoreOwner = parentEntry) + } else { + viewModel() + } + val searchGroups by groupViewModel.searchGroups.collectAsState() + + GroupSearchScreen( + roomList = searchGroups, + onNavigateBack = { + navigateBack() + }, + onRoomClick = { room -> + if (room.isRecruiting) { + navController.navigateToGroupRecruit(room.id) + } else { + navController.navigateToGroupRoom(room.id) + } + } + ) + } + + // Group Recruit 화면 + composable { backStackEntry -> + val route = backStackEntry.toRoute() + val roomId = route.roomId + val parentEntry = remember(navController) { + try { + navController.getBackStackEntry(MainTabRoutes.Group) + } catch (e: Exception) { + null + } + } + val groupViewModel: GroupViewModel = if (parentEntry != null) { + viewModel(viewModelStoreOwner = parentEntry) + } else { + viewModel() + } + + // suspend 함수를 위한 LaunchedEffect 사용 + var roomDetail by remember { mutableStateOf(null) } + LaunchedEffect(roomId) { + roomDetail = groupViewModel.getRoomDetail(roomId) + } + + roomDetail?.let { detail -> + GroupRoomRecruitScreen( + detail = detail, + buttonType = GroupBottomButtonType.JOIN, // 기본값, 실제로는 사용자 상태에 따라 결정 + onRecommendationClick = { recommendation -> + navController.navigateToRecommendedGroupRecruit(recommendation.id) + }, + onParticipation = { + // 참여 로직 + }, + onCancelParticipation = { + // 참여 취소 로직 + }, + onCloseRecruitment = { + // 모집 마감 로직 + }, + onBackClick = { + navigateBack() + } + ) + } ?: run { + // 로딩 중이거나 데이터를 찾을 수 없는 경우 + } + } + + // Group Room 화면 + composable { backStackEntry -> + val route = backStackEntry.toRoute() + val roomId = route.roomId + val parentEntry = remember(navController) { + try { + navController.getBackStackEntry(MainTabRoutes.Group) + } catch (e: Exception) { + null + } + } + val groupViewModel: GroupViewModel = if (parentEntry != null) { + viewModel(viewModelStoreOwner = parentEntry) + } else { + viewModel() + } + + // suspend 함수를 위한 LaunchedEffect 사용 + var roomDetail by remember { mutableStateOf(null) } + LaunchedEffect(roomId) { + roomDetail = groupViewModel.getRoomDetail(roomId) + } + + roomDetail?.let { + GroupRoomScreen( + onBackClick = { + navigateBack() + } + ) + } ?: run { + // 로딩 중이거나 데이터를 찾을 수 없는 경우 + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/ui/navigator/routes/CommonRoutes.kt b/app/src/main/java/com/texthip/thip/ui/navigator/routes/CommonRoutes.kt new file mode 100644 index 00000000..623bb43f --- /dev/null +++ b/app/src/main/java/com/texthip/thip/ui/navigator/routes/CommonRoutes.kt @@ -0,0 +1,9 @@ +package com.texthip.thip.ui.navigator.routes + +import kotlinx.serialization.Serializable + +@Serializable +sealed class CommonRoutes : Routes() { + @Serializable + data object Alarm : CommonRoutes() +} \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/ui/navigator/routes/GroupRoutes.kt b/app/src/main/java/com/texthip/thip/ui/navigator/routes/GroupRoutes.kt index e68d8268..7c327d3c 100644 --- a/app/src/main/java/com/texthip/thip/ui/navigator/routes/GroupRoutes.kt +++ b/app/src/main/java/com/texthip/thip/ui/navigator/routes/GroupRoutes.kt @@ -7,9 +7,18 @@ sealed class GroupRoutes : Routes() { @Serializable data object MakeRoom : GroupRoutes() - // 향후 추가될 Group 관련 화면들 - // @Serializable data object Room : GroupRoutes - // @Serializable data object RoomChat : GroupRoutes - // @Serializable data object Note : GroupRoutes - // @Serializable data object NoteCreate : GroupRoutes + @Serializable + data object Done : GroupRoutes() + + @Serializable + data object Search : GroupRoutes() + + @Serializable + data object My : GroupRoutes() + + @Serializable + data class Recruit(val roomId: Int) : GroupRoutes() + + @Serializable + data class Room(val roomId: Int) : GroupRoutes() } \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/ui/search/screen/SearchBookGroupScreen.kt b/app/src/main/java/com/texthip/thip/ui/search/screen/SearchBookGroupScreen.kt index f21c0ff3..c181a487 100644 --- a/app/src/main/java/com/texthip/thip/ui/search/screen/SearchBookGroupScreen.kt +++ b/app/src/main/java/com/texthip/thip/ui/search/screen/SearchBookGroupScreen.kt @@ -145,6 +145,7 @@ fun GroupRecruitingScreenPreview() { ThipTheme { val dataList = listOf( GroupCardItemRoomData( + id = 1, title = "모임방 이름입니다. 모임방...", participants = 22, maxParticipants = 30, @@ -153,6 +154,7 @@ fun GroupRecruitingScreenPreview() { genreIndex = 0 ), GroupCardItemRoomData( + id = 2, title = "모임방 이름입니다. 모임방...", participants = 22, maxParticipants = 30, @@ -161,6 +163,7 @@ fun GroupRecruitingScreenPreview() { genreIndex = 0 ), GroupCardItemRoomData( + id = 3, title = "모임방 이름입니다. 모임방...", participants = 22, maxParticipants = 30, @@ -169,6 +172,7 @@ fun GroupRecruitingScreenPreview() { genreIndex = 0 ), GroupCardItemRoomData( + id = 4, title = "모임방 이름입니다. 모임방...", participants = 22, maxParticipants = 30, @@ -177,6 +181,7 @@ fun GroupRecruitingScreenPreview() { genreIndex = 0 ), GroupCardItemRoomData( + id = 5, title = "모임방 이름입니다. 모임방...", participants = 22, maxParticipants = 30, @@ -185,6 +190,7 @@ fun GroupRecruitingScreenPreview() { genreIndex = 0 ), GroupCardItemRoomData( + id = 6, title = "모임방 이름입니다. 모임방...", participants = 22, maxParticipants = 30, @@ -193,6 +199,7 @@ fun GroupRecruitingScreenPreview() { genreIndex = 0 ), GroupCardItemRoomData( + id = 7, title = "모임방 이름입니다. 모임방...", participants = 22, maxParticipants = 30, @@ -201,6 +208,7 @@ fun GroupRecruitingScreenPreview() { genreIndex = 0 ), GroupCardItemRoomData( + id = 8, title = "모임방 이름입니다. 모임방...", participants = 22, maxParticipants = 30, diff --git a/app/src/main/java/com/texthip/thip/ui/search/screen/SearchBookScreen.kt b/app/src/main/java/com/texthip/thip/ui/search/screen/SearchBookScreen.kt index efa3f02d..7062aa3a 100644 --- a/app/src/main/java/com/texthip/thip/ui/search/screen/SearchBookScreen.kt +++ b/app/src/main/java/com/texthip/thip/ui/search/screen/SearchBookScreen.kt @@ -1,5 +1,6 @@ package com.texthip.thip.ui.search.screen +import android.content.Context import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer @@ -18,20 +19,25 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.navigation.NavHostController import com.texthip.thip.R -import com.texthip.thip.ui.search.component.SearchEmptyResult -import com.texthip.thip.ui.search.component.SearchBookFilteredResult +import com.texthip.thip.ui.common.forms.SearchBookTextField +import com.texthip.thip.ui.common.topappbar.LeftNameTopAppBar import com.texthip.thip.ui.search.component.SearchActiveField +import com.texthip.thip.ui.search.component.SearchBookFilteredResult +import com.texthip.thip.ui.search.component.SearchEmptyResult import com.texthip.thip.ui.search.component.SearchRecentBook import com.texthip.thip.ui.search.mock.BookData -import com.texthip.thip.ui.common.forms.SearchBookTextField -import com.texthip.thip.ui.common.topappbar.LeftNameTopAppBar import com.texthip.thip.ui.theme.ThipTheme +import kotlinx.serialization.json.Json +import androidx.core.content.edit +import kotlinx.serialization.builtins.ListSerializer +import kotlinx.serialization.builtins.serializer @Composable fun SearchBookScreen( @@ -40,8 +46,32 @@ fun SearchBookScreen( bookList: List = emptyList(), popularBooks: List = emptyList() ) { - var recentSearches by rememberSaveable { - mutableStateOf(listOf("asd", "qwe", "xcv", "dfggfd", "asdasd", "gfhjghj")) + val context = LocalContext.current + val sharedPrefs = remember { + context.getSharedPreferences("book_search_prefs", Context.MODE_PRIVATE) + } + + var recentSearches by remember { + mutableStateOf( + try { + val jsonString = sharedPrefs.getString("recent_book_searches", "[]") ?: "[]" + Json.decodeFromString>(jsonString) + } catch (e: Exception) { + emptyList() + } + ) + } + + fun saveRecentSearches(searches: List) { + try { + val jsonString = Json.encodeToString(ListSerializer(String.serializer()), searches) + sharedPrefs.edit { + putString("recent_book_searches", jsonString) + } + recentSearches = searches + } catch (e: Exception) { + recentSearches = emptyList() + } } var searchText by rememberSaveable { mutableStateOf("") } var isSearched by rememberSaveable { mutableStateOf(false) } @@ -107,7 +137,8 @@ fun SearchBookScreen( }, onSearch = { query -> if (query.isNotBlank() && !recentSearches.contains(query)) { - recentSearches = listOf(query) + recentSearches + val newSearches = listOf(query) + recentSearches.take(9) // 최대 10개 유지 + saveRecentSearches(newSearches) } isSearched = true } @@ -125,7 +156,8 @@ fun SearchBookScreen( isSearched = true }, onRemove = { keyword -> - recentSearches = recentSearches.filterNot { it == keyword } + val updatedSearches = recentSearches.filterNot { it == keyword } + saveRecentSearches(updatedSearches) }, onBookClick = { book -> // 책 클릭 시 처리 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 12d1cd00..19e50af7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] -agp = "8.9.3" -kotlin = "2.2.0" +agp = "8.11.1" +kotlin = "2.0.21" coreKtx = "1.16.0" junit = "4.13.2" junitVersion = "1.2.1" @@ -8,10 +8,10 @@ espressoCore = "3.6.1" lifecycleRuntimeKtx = "2.8.7" activityCompose = "1.10.1" composeBom = "2024.09.00" -navigationCompose = "2.9.2" +navigationCompose = "2.9.0" foundation = "1.9.0-beta01" -lifecycleViewmodelCompose = "2.9.2" -navigationRuntimeAndroid = "2.9.2" +lifecycleViewmodelCompose = "2.9.1" +navigationRuntimeAndroid = "2.9.0" accompanistPager = "0.36.0" accompanistPagerIndicators = "0.36.0" coilCompose = "2.7.0" @@ -44,5 +44,4 @@ kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx- android-application = { id = "com.android.application", version.ref = "agp" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } -kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } - +kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index c686c423..bc55b151 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Mon Apr 28 18:50:30 KST 2025 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists