From 60c862ad02d3aabaeb17bec3012f08e0f4b06335 Mon Sep 17 00:00:00 2001 From: Dongjoo Seo <83579348+etama123@users.noreply.github.com> Date: Fri, 19 Dec 2025 23:29:26 +0900 Subject: [PATCH 01/21] =?UTF-8?q?feat(home):=20HomeHeader=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EC=A0=80=EB=B8=94=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/home/component/HomeHeader.kt | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 app/src/main/java/com/daedan/festabook/presentation/home/component/HomeHeader.kt diff --git a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeHeader.kt b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeHeader.kt new file mode 100644 index 00000000..8a564aeb --- /dev/null +++ b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeHeader.kt @@ -0,0 +1,63 @@ +package com.daedan.festabook.presentation.home.component + +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material3.Text +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.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.daedan.festabook.R +import com.daedan.festabook.presentation.theme.FestabookColor +import com.daedan.festabook.presentation.theme.FestabookTypography + +@Composable +fun HomeHeader( + schoolName: String, + onExpandClick: () -> Unit, + modifier: Modifier = Modifier, +) { + Box( + modifier = + modifier + .fillMaxWidth() + .padding(horizontal = 20.dp, vertical = 12.dp), + ) { + Row( + modifier = Modifier.clickable { onExpandClick() }, + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = schoolName, + style = FestabookTypography.displayLarge, + color = FestabookColor.black, + ) + + Spacer(modifier = Modifier.width(4.dp)) + + Image( + painter = painterResource(id = R.drawable.ic_dropdown), + contentDescription = stringResource(id = R.string.home_navigate_to_explore_desc), + ) + } + } +} + +@Preview(showBackground = true) +@Composable +private fun HomeHeaderPreview() { + HomeHeader( + schoolName = "가천대학교", + onExpandClick = {}, + ) +} From 3af132ee53b99e0647c256ce88e839abdcfef32f Mon Sep 17 00:00:00 2001 From: Dongjoo Seo <83579348+etama123@users.noreply.github.com> Date: Fri, 19 Dec 2025 23:42:14 +0900 Subject: [PATCH 02/21] =?UTF-8?q?feat(Home):=20HomeFestivalInfo=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EC=A0=80=EB=B8=94=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../home/component/HomeFestivalInfo.kt | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 app/src/main/java/com/daedan/festabook/presentation/home/component/HomeFestivalInfo.kt diff --git a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeFestivalInfo.kt b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeFestivalInfo.kt new file mode 100644 index 00000000..7d852615 --- /dev/null +++ b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeFestivalInfo.kt @@ -0,0 +1,52 @@ +package com.daedan.festabook.presentation.home.component + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.daedan.festabook.presentation.theme.FestabookColor +import com.daedan.festabook.presentation.theme.FestabookTypography + +@Composable +fun HomeFestivalInfo( + festivalName: String, + festivalDate: String, + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier.fillMaxWidth(), + ) { + Text( + text = festivalName, + style = FestabookTypography.displayMedium, + color = FestabookColor.black, + modifier = Modifier.padding(horizontal = 20.dp), + ) + + Spacer(modifier = Modifier.height(8.dp)) + + Text( + text = festivalDate, + style = FestabookTypography.bodyLarge, + color = FestabookColor.gray500, + modifier = Modifier.padding(horizontal = 20.dp), + ) + } +} + +@Preview(showBackground = true) +@Composable +private fun HomeFestivalInfoPreview() { + HomeFestivalInfo( + festivalName = "2025 가천 Water Festival\n: AQUA WAVE", + festivalDate = "2025년 10월 15일 - 10월 17일", + ) +} From bb90581e4fbe0cab2e797c90bea894c8ea3e5dd7 Mon Sep 17 00:00:00 2001 From: Dongjoo Seo <83579348+etama123@users.noreply.github.com> Date: Fri, 19 Dec 2025 23:51:11 +0900 Subject: [PATCH 03/21] =?UTF-8?q?feat(home):=20HomeLineupHeader=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EC=A0=80=EB=B8=94=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../home/component/HomeLineupHeader.kt | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 app/src/main/java/com/daedan/festabook/presentation/home/component/HomeLineupHeader.kt diff --git a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeLineupHeader.kt b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeLineupHeader.kt new file mode 100644 index 00000000..e0a9471a --- /dev/null +++ b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeLineupHeader.kt @@ -0,0 +1,78 @@ +package com.daedan.festabook.presentation.home.component + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Arrangement +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.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.daedan.festabook.R +import com.daedan.festabook.presentation.theme.FestabookColor +import com.daedan.festabook.presentation.theme.FestabookTypography + +@Composable +fun HomeLineupHeader( + onScheduleClick: () -> Unit, + modifier: Modifier = Modifier, +) { + Row( + modifier = + modifier + .fillMaxWidth() + .padding(start = 16.dp, top = 16.dp, end = 16.dp, bottom = 16.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = stringResource(R.string.home_lineup_title), + style = FestabookTypography.displayMedium, + color = FestabookColor.black, + ) + + Row( + modifier = + Modifier + .clickable( + onClick = onScheduleClick, + ) + .padding(4.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = stringResource(R.string.home_check_schedule_text), + style = FestabookTypography.bodySmall, + color = FestabookColor.gray400, + ) + + Spacer(modifier = Modifier.width(4.dp)) + + Icon( + painter = painterResource(id = R.drawable.ic_arrow_forward_right), + contentDescription = null, + tint = FestabookColor.gray400, + modifier = Modifier.size(12.dp), + ) + } + } +} + +@Preview(showBackground = true) +@Composable +private fun HomeLineupHeaderPreview() { + HomeLineupHeader( + onScheduleClick = {}, + ) +} From 19e9842cd0e646c214080dcd15ca835df70ecb59 Mon Sep 17 00:00:00 2001 From: Dongjoo Seo <83579348+etama123@users.noreply.github.com> Date: Sat, 20 Dec 2025 00:23:00 +0900 Subject: [PATCH 04/21] =?UTF-8?q?feat(home):=20HomePosterList=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EC=A0=80=EB=B8=94=20=EA=B5=AC=ED=98=84=20=EB=B0=8F=20?= =?UTF-8?q?=EB=AC=B4=ED=95=9C=20=EC=8A=A4=ED=81=AC=EB=A1=A4=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A0=80=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../home/component/HomePosterList.kt | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 app/src/main/java/com/daedan/festabook/presentation/home/component/HomePosterList.kt diff --git a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomePosterList.kt b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomePosterList.kt new file mode 100644 index 00000000..1ce0af70 --- /dev/null +++ b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomePosterList.kt @@ -0,0 +1,112 @@ +package com.daedan.festabook.presentation.home.component + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.PageSize +import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.util.lerp +import com.daedan.festabook.presentation.common.component.CoilImage +import kotlin.math.absoluteValue + +@Composable +fun HomePosterList( + posterUrls: List, + modifier: Modifier = Modifier, +) { + if (posterUrls.isEmpty()) return + + // 무한 스크롤을 위한 큰 수 설정 + val initialPage = (Int.MAX_VALUE / 2) - ((Int.MAX_VALUE / 2) % posterUrls.size) + val pagerState = + rememberPagerState( + initialPage = initialPage, + pageCount = { Int.MAX_VALUE }, + ) + + val configuration = LocalConfiguration.current + val screenWidth = configuration.screenWidthDp.dp + val itemWidth = 300.dp + // 화면 중앙에 아이템이 오도록 패딩 계산 + val horizontalPadding = (screenWidth - itemWidth) / 2 + + HorizontalPager( + state = pagerState, + pageSize = PageSize.Fixed(itemWidth), + contentPadding = PaddingValues(horizontal = horizontalPadding), + pageSpacing = 12.dp, + modifier = + modifier + .fillMaxWidth() + .height(400.dp), // item_home_poster 높이 + verticalAlignment = Alignment.CenterVertically, + ) { page -> + val actualIndex = page % posterUrls.size + val imageUrl = posterUrls[actualIndex] + + // 스크롤 위치에 따른 Scale 계산 + val pageOffset = + ((pagerState.currentPage - page) + pagerState.currentPageOffsetFraction).absoluteValue + + // 중앙(0)이면 1.0f, 멀어질수록 작아짐 (최소 0.9f) + val scale = + lerp( + start = 1.0f, + stop = 0.9f, + fraction = pageOffset.coerceIn(0f, 1f), + ) + + // 투명도 조절 (중앙은 1.0, 멀어지면 약간 투명하게) + val alpha = + lerp( + start = 1.0f, + stop = 0.6f, + fraction = pageOffset.coerceIn(0f, 1f), + ) + + Box( + modifier = + Modifier + .width(itemWidth) + .height(400.dp) + .graphicsLayer { + scaleX = scale + scaleY = scale + this.alpha = alpha + } + .clip(RoundedCornerShape(10.dp)), + ) { + CoilImage( + url = imageUrl, + contentDescription = null, + modifier = Modifier.fillMaxSize(), + ) + } + } +} + +@Preview +@Composable +private fun HomePosterListPreview() { + HomePosterList( + posterUrls = + listOf( + "sample", + "sample", + "sample", + ), + ) +} \ No newline at end of file From c73796eef6ce716b783f6b3fe6d6ea6d8ff02335 Mon Sep 17 00:00:00 2001 From: Dongjoo Seo <83579348+etama123@users.noreply.github.com> Date: Mon, 22 Dec 2025 22:46:56 +0900 Subject: [PATCH 05/21] =?UTF-8?q?feat(home):=20=ED=99=88=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20=EC=95=84=ED=8B=B0=EC=8A=A4=ED=8A=B8=20=EB=B0=8F=20?= =?UTF-8?q?=EB=9D=BC=EC=9D=B8=EC=97=85=20=EC=95=84=EC=9D=B4=ED=85=9C=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EC=A0=80=EB=B8=94=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../home/component/HomeArtistItem.kt | 70 ++++++++ .../home/component/HomeLineupItem.kt | 150 ++++++++++++++++++ 2 files changed, 220 insertions(+) create mode 100644 app/src/main/java/com/daedan/festabook/presentation/home/component/HomeArtistItem.kt create mode 100644 app/src/main/java/com/daedan/festabook/presentation/home/component/HomeLineupItem.kt diff --git a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeArtistItem.kt b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeArtistItem.kt new file mode 100644 index 00000000..478baa80 --- /dev/null +++ b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeArtistItem.kt @@ -0,0 +1,70 @@ +package com.daedan.festabook.presentation.home.component + +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.daedan.festabook.presentation.common.component.CoilImage +import com.daedan.festabook.presentation.theme.FestabookColor +import com.daedan.festabook.presentation.theme.FestabookTypography + +@Composable +fun HomeArtistItem( + artistName: String, + artistImageUrl: String, + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier.width(68.dp), + ) { + CoilImage( + url = artistImageUrl, + contentDescription = null, + modifier = + Modifier + .fillMaxWidth() + .aspectRatio(1f) + .clip(HomeArtistItem.ArtistImage) + .border(1.dp, FestabookColor.gray300, HomeArtistItem.ArtistImage), + ) + + Spacer(modifier = Modifier.height(4.dp)) + + Text( + text = artistName, + style = FestabookTypography.labelLarge, + color = FestabookColor.gray700, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } +} + +object HomeArtistItem { + val ArtistImage = RoundedCornerShape( + topStartPercent = 50, + topEndPercent = 50, + bottomEndPercent = 50, + bottomStartPercent = 5, + ) +} + +@Preview +@Composable +private fun HomeArtistItemPreview() { + HomeArtistItem( + artistName = "실리카겔", + artistImageUrl = "sample", + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeLineupItem.kt b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeLineupItem.kt new file mode 100644 index 00000000..af9efc0b --- /dev/null +++ b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeLineupItem.kt @@ -0,0 +1,150 @@ +package com.daedan.festabook.presentation.home.component + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +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.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.daedan.festabook.R +import com.daedan.festabook.presentation.home.LineUpItemOfDayUiModel +import com.daedan.festabook.presentation.home.LineupItemUiModel +import com.daedan.festabook.presentation.theme.FestabookColor +import com.daedan.festabook.presentation.theme.FestabookTypography +import java.time.LocalDate +import java.time.LocalDateTime + +@Composable +fun HomeLineupItem( + uiModel: LineUpItemOfDayUiModel, + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier.fillMaxWidth(), + ) { + // 날짜 + 배지 영역 + Row( + modifier = Modifier.padding(horizontal = 16.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = "${uiModel.date.monthValue}.${uiModel.date.dayOfMonth}", + style = FestabookTypography.titleLarge, + color = FestabookColor.black, + ) + + if (uiModel.isDDay) { + Spacer(modifier = Modifier.width(6.dp)) + Box( + modifier = + Modifier + .clip(RoundedCornerShape(20.dp)) + .background(FestabookColor.black) + .padding(horizontal = 6.dp, vertical = 2.dp), + ) { + Text( + text = stringResource(id = R.string.home_is_d_day), + style = FestabookTypography.labelSmall, + color = FestabookColor.white, + ) + } + } + } + + Spacer(modifier = Modifier.height(4.dp)) + + Box( + modifier = + Modifier + .padding(horizontal = 16.dp) + .width(75.dp) + .height(1.dp) + .background(FestabookColor.gray700), + ) + + Spacer(modifier = Modifier.height(8.dp)) + + // 아티스트 가로 리스트 + LazyRow( + contentPadding = PaddingValues(horizontal = 16.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp), + ) { + items(uiModel.lineupItems) { item -> + HomeArtistItem( + artistName = item.name, + artistImageUrl = item.imageUrl, + ) + } + } + + Spacer(modifier = Modifier.height(16.dp)) + } +} + +@Preview(showBackground = true) +@Composable +private fun HomeLineupItemPreview() { + HomeLineupItem( + uiModel = + LineUpItemOfDayUiModel( + id = 1L, + date = LocalDate.now(), + isDDay = true, + lineupItems = + listOf( + LineupItemUiModel( + id = 1, + name = "실리카겔", + imageUrl = "sample", + performanceAt = LocalDateTime.now(), + ), + LineupItemUiModel( + id = 2, + name = "한로로", + imageUrl = "sample", + performanceAt = LocalDateTime.now(), + ), + LineupItemUiModel( + id = 3, + name = "실리카겔", + imageUrl = "sample", + performanceAt = LocalDateTime.now(), + ), + LineupItemUiModel( + id = 4, + name = "한로로", + imageUrl = "sample", + performanceAt = LocalDateTime.now(), + ), + LineupItemUiModel( + id = 5, + name = "실리카겔", + imageUrl = "sample", + performanceAt = LocalDateTime.now(), + ), + LineupItemUiModel( + id = 6, + name = "한로로", + imageUrl = "sample", + performanceAt = LocalDateTime.now(), + ), + ), + ), + ) +} From 9ef8418688be4c25a7a42360d9aee3612801914a Mon Sep 17 00:00:00 2001 From: Dongjoo Seo <83579348+etama123@users.noreply.github.com> Date: Fri, 26 Dec 2025 17:40:17 +0900 Subject: [PATCH 06/21] =?UTF-8?q?feat(home):=20=ED=99=88=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20Compose=20=EC=A0=84=ED=99=98=20=EB=B0=8F=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EB=A1=9C=EC=A7=81=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/home/HomeFragment.kt | 175 ++-------------- .../presentation/home/HomeViewModel.kt | 25 ++- .../presentation/home/component/HomeHeader.kt | 9 +- .../presentation/home/component/HomeScreen.kt | 196 ++++++++++++++++++ .../presentation/main/MainActivity.kt | 16 +- .../presentation/setting/SettingFragment.kt | 38 ++-- 6 files changed, 269 insertions(+), 190 deletions(-) create mode 100644 app/src/main/java/com/daedan/festabook/presentation/home/component/HomeScreen.kt diff --git a/app/src/main/java/com/daedan/festabook/presentation/home/HomeFragment.kt b/app/src/main/java/com/daedan/festabook/presentation/home/HomeFragment.kt index 2b0b8349..ed10622d 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/home/HomeFragment.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/home/HomeFragment.kt @@ -1,184 +1,49 @@ package com.daedan.festabook.presentation.home import android.os.Bundle +import android.view.LayoutInflater import android.view.View -import androidx.fragment.app.Fragment +import android.view.ViewGroup +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.fragment.app.viewModels import androidx.lifecycle.ViewModelProvider -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.PagerSnapHelper import androidx.recyclerview.widget.RecyclerView import com.daedan.festabook.R import com.daedan.festabook.databinding.FragmentHomeBinding import com.daedan.festabook.di.fragment.FragmentKey -import com.daedan.festabook.logging.logger -import com.daedan.festabook.logging.model.home.ExploreClickLogData -import com.daedan.festabook.logging.model.home.HomeViewLogData -import com.daedan.festabook.logging.model.home.ScheduleClickLogData import com.daedan.festabook.presentation.common.BaseFragment -import com.daedan.festabook.presentation.common.formatFestivalPeriod -import com.daedan.festabook.presentation.common.showErrorSnackBar import com.daedan.festabook.presentation.explore.ExploreActivity -import com.daedan.festabook.presentation.home.adapter.CenterItemMotionEnlarger -import com.daedan.festabook.presentation.home.adapter.FestivalUiState -import com.daedan.festabook.presentation.home.adapter.LineUpItemOfDayAdapter -import com.daedan.festabook.presentation.home.adapter.PosterAdapter +import com.daedan.festabook.presentation.home.component.HomeScreen import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesIntoMap import dev.zacsweers.metro.Inject import dev.zacsweers.metro.binding -import timber.log.Timber -@ContributesIntoMap(scope = AppScope::class, binding = binding()) +@ContributesIntoMap(scope = AppScope::class, binding = binding()) @FragmentKey(HomeFragment::class) -class HomeFragment @Inject constructor( - private val centerItemMotionEnlarger: RecyclerView.OnScrollListener, -) : BaseFragment() { +class HomeFragment @Inject constructor() : BaseFragment() { override val layoutId: Int = R.layout.fragment_home @Inject override lateinit var defaultViewModelProviderFactory: ViewModelProvider.Factory private val viewModel: HomeViewModel by viewModels({ requireActivity() }) - private val posterAdapter: PosterAdapter by lazy { - PosterAdapter() - } - - private val lineupOfDayAdapter: LineUpItemOfDayAdapter by lazy { - LineUpItemOfDayAdapter() - } - - override fun onViewCreated( - view: View, + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, savedInstanceState: Bundle?, - ) { - super.onViewCreated(view, savedInstanceState) - binding.lifecycleOwner = viewLifecycleOwner - setupObservers() - setupAdapters() - setupNavigateToScheduleButton() - setupNavigateToExploreButton() - } - - private fun setupNavigateToExploreButton() { - binding.layoutTitleWithIcon.setOnClickListener { - binding.logger.log(ExploreClickLogData(binding.logger.getBaseLogData())) - - startActivity(ExploreActivity.newIntent(requireContext())) - } - } - - private fun setupNavigateToScheduleButton() { - binding.btnNavigateToSchedule.setOnClickListener { - binding.logger.log( - ScheduleClickLogData( - baseLogData = binding.logger.getBaseLogData(), - ), - ) - - viewModel.navigateToScheduleClick() - } - } - - private fun setupObservers() { - viewModel.festivalUiState.observe(viewLifecycleOwner) { festivalUiState -> - when (festivalUiState) { - is FestivalUiState.Loading -> {} - is FestivalUiState.Success -> handleSuccessState(festivalUiState) - is FestivalUiState.Error -> { - showErrorSnackBar(festivalUiState.throwable) - Timber.w( - festivalUiState.throwable, - "HomeFragment: ${festivalUiState.throwable.message}", - ) - } - } - } - viewModel.lineupUiState.observe(viewLifecycleOwner) { lineupUiState -> - when (lineupUiState) { - is LineupUiState.Loading -> {} - is LineupUiState.Success -> { - lineupOfDayAdapter.submitList(lineupUiState.lineups.getLineupItems()) - } - - is LineupUiState.Error -> { - showErrorSnackBar(lineupUiState.throwable) - Timber.w( - lineupUiState.throwable, - "HomeFragment: ${lineupUiState.throwable.message}", - ) - } - } - } - } - - private fun setupAdapters() { - binding.rvHomePoster.adapter = posterAdapter - binding.rvHomeLineup.adapter = lineupOfDayAdapter - attachSnapHelper() - addScrollEffectListener() - } - - private fun handleSuccessState(festivalUiState: FestivalUiState.Success) { - binding.tvHomeOrganizationTitle.text = - festivalUiState.organization.universityName - binding.tvHomeFestivalTitle.text = - festivalUiState.organization.festival.festivalName - binding.tvHomeFestivalDate.text = - formatFestivalPeriod( - festivalUiState.organization.festival.startDate, - festivalUiState.organization.festival.endDate, - ) - - val posterUrls = - festivalUiState.organization.festival.festivalImages - .sortedBy { it.sequence } - .map { it.imageUrl } - - if (posterUrls.isNotEmpty()) { - posterAdapter.submitList(posterUrls) { - scrollToInitialPosition(posterUrls.size) + ): View { + return ComposeView(requireContext()).apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + setContent { + HomeScreen( + viewModel = viewModel, + onNavigateToExplore = { + startActivity(ExploreActivity.newIntent(requireContext())) + }, + ) } } - binding.logger.log( - HomeViewLogData( - baseLogData = binding.logger.getBaseLogData(), - universityName = festivalUiState.organization.universityName, - festivalId = festivalUiState.organization.id, - ), - ) - } - - private fun attachSnapHelper() { - PagerSnapHelper().attachToRecyclerView(binding.rvHomePoster) - } - - private fun scrollToInitialPosition(size: Int) { - val safeMaxValue = Int.MAX_VALUE / INFINITE_SCROLL_SAFETY_FACTOR - val initialPosition = safeMaxValue - (safeMaxValue % size) - - val layoutManager = binding.rvHomePoster.layoutManager as? LinearLayoutManager ?: return - - val itemWidth = resources.getDimensionPixelSize(R.dimen.poster_item_width) - val offset = (binding.rvHomePoster.width / 2) - (itemWidth / 2) - - layoutManager.scrollToPositionWithOffset(initialPosition, offset) - - binding.rvHomePoster.post { - (centerItemMotionEnlarger as CenterItemMotionEnlarger).expandCenterItem(binding.rvHomePoster) - } - } - - private fun addScrollEffectListener() { - binding.rvHomePoster.addOnScrollListener(centerItemMotionEnlarger) - } - - override fun onDestroyView() { - binding.rvHomePoster.clearOnScrollListeners() - super.onDestroyView() - } - - companion object { - private const val INFINITE_SCROLL_SAFETY_FACTOR = 4 } } diff --git a/app/src/main/java/com/daedan/festabook/presentation/home/HomeViewModel.kt b/app/src/main/java/com/daedan/festabook/presentation/home/HomeViewModel.kt index 85fb947f..64caaff1 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/home/HomeViewModel.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/home/HomeViewModel.kt @@ -1,17 +1,19 @@ package com.daedan.festabook.presentation.home -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.daedan.festabook.di.viewmodel.ViewModelKey -import com.daedan.festabook.di.viewmodel.ViewModelScope import com.daedan.festabook.domain.repository.FestivalRepository -import com.daedan.festabook.presentation.common.SingleLiveData import com.daedan.festabook.presentation.home.adapter.FestivalUiState import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesIntoMap import dev.zacsweers.metro.Inject +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch @ContributesIntoMap(AppScope::class) @@ -19,14 +21,15 @@ import kotlinx.coroutines.launch class HomeViewModel @Inject constructor( private val festivalRepository: FestivalRepository, ) : ViewModel() { - private val _festivalUiState = MutableLiveData() - val festivalUiState: LiveData get() = _festivalUiState + private val _festivalUiState = MutableStateFlow(FestivalUiState.Loading) + val festivalUiState: StateFlow = _festivalUiState.asStateFlow() - private val _lineupUiState = MutableLiveData() - val lineupUiState: LiveData get() = _lineupUiState + private val _lineupUiState = MutableStateFlow(LineupUiState.Loading) + val lineupUiState: StateFlow = _lineupUiState.asStateFlow() - private val _navigateToScheduleEvent: SingleLiveData = SingleLiveData() - val navigateToScheduleEvent: LiveData get() = _navigateToScheduleEvent + private val _navigateToScheduleEvent = + MutableSharedFlow(replay = 0, extraBufferCapacity = 1) + val navigateToScheduleEvent: SharedFlow = _navigateToScheduleEvent.asSharedFlow() init { loadFestival() @@ -48,7 +51,7 @@ class HomeViewModel @Inject constructor( } fun navigateToScheduleClick() { - _navigateToScheduleEvent.setValue(Unit) + _navigateToScheduleEvent.tryEmit(Unit) } private fun loadLineup() { diff --git a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeHeader.kt b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeHeader.kt index 8a564aeb..836e48c8 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeHeader.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeHeader.kt @@ -15,8 +15,10 @@ 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.PlatformTextStyle import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import com.daedan.festabook.R import com.daedan.festabook.presentation.theme.FestabookColor import com.daedan.festabook.presentation.theme.FestabookTypography @@ -31,7 +33,7 @@ fun HomeHeader( modifier = modifier .fillMaxWidth() - .padding(horizontal = 20.dp, vertical = 12.dp), + .padding(horizontal = 16.dp) ) { Row( modifier = Modifier.clickable { onExpandClick() }, @@ -39,7 +41,10 @@ fun HomeHeader( ) { Text( text = schoolName, - style = FestabookTypography.displayLarge, + style = FestabookTypography.displayLarge.copy( + platformStyle = PlatformTextStyle(includeFontPadding = false), + lineHeight = 34.sp + ), color = FestabookColor.black, ) diff --git a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeScreen.kt b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeScreen.kt new file mode 100644 index 00000000..e9e5aeca --- /dev/null +++ b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeScreen.kt @@ -0,0 +1,196 @@ +package com.daedan.festabook.presentation.home.component + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.daedan.festabook.presentation.common.formatFestivalPeriod +import com.daedan.festabook.presentation.home.HomeViewModel +import com.daedan.festabook.presentation.home.LineUpItemGroupUiModel +import com.daedan.festabook.presentation.home.LineupItemUiModel +import com.daedan.festabook.presentation.home.adapter.FestivalUiState +import com.daedan.festabook.domain.model.Festival +import com.daedan.festabook.domain.model.Organization +import com.daedan.festabook.domain.model.Poster +import com.daedan.festabook.presentation.home.LineupUiState +import com.daedan.festabook.presentation.theme.FestabookColor +import kotlinx.coroutines.flow.collectLatest +import java.time.LocalDate +import java.time.LocalDateTime + +@Composable +fun HomeScreen( + viewModel: HomeViewModel, + onNavigateToExplore: () -> Unit, + modifier: Modifier = Modifier, +) { + val festivalUiState by viewModel.festivalUiState.collectAsState() + val lineupUiState by viewModel.lineupUiState.collectAsState() + + FestivalOverview( + festivalUiState = festivalUiState, + lineupUiState = lineupUiState, + onNavigateToExplore = onNavigateToExplore, + onNavigateToSchedule = viewModel::navigateToScheduleClick, + modifier = modifier, + ) +} + +@Composable +fun FestivalOverview( + festivalUiState: FestivalUiState, + lineupUiState: LineupUiState, + onNavigateToExplore: () -> Unit, + onNavigateToSchedule: () -> Unit, + modifier: Modifier = Modifier, +) { + Scaffold( + modifier = modifier.fillMaxSize(), + containerColor = Color.White, + ) { + LazyColumn( + modifier = + Modifier.fillMaxSize() + ) { + // 헤더 (학교 이름) + item { + if (festivalUiState is FestivalUiState.Success) { + HomeHeader( + schoolName = festivalUiState.organization.universityName, + onExpandClick = onNavigateToExplore, + modifier = Modifier.padding(top = 40.dp), + ) + } + } + + // 포스터 리스트 + item { + if (festivalUiState is FestivalUiState.Success) { + val posterUrls = + festivalUiState.organization.festival.festivalImages + .sortedBy { it.sequence } + .map { it.imageUrl } + + HomePosterList( + posterUrls = posterUrls, + modifier = Modifier.padding(vertical = 12.dp), + ) + } + } + + // 축제 정보 + item { + if (festivalUiState is FestivalUiState.Success) { + val festival = festivalUiState.organization.festival + HomeFestivalInfo( + festivalName = festival.festivalName, + festivalDate = + formatFestivalPeriod( + festival.startDate, + festival.endDate, + ), + modifier = Modifier.padding(top = 16.dp), + ) + } + } + + + // 구분선 + item { + if (festivalUiState is FestivalUiState.Success) { + HorizontalDivider( + thickness = 4.dp, + color = FestabookColor.gray200, + modifier = + Modifier + .padding(top = 16.dp), + ) + } + } + + // 라인업 헤더 + item { + HomeLineupHeader( + onScheduleClick = onNavigateToSchedule, + ) + } + + // 라인업 리스트 + when (lineupUiState) { + is LineupUiState.Success -> { + val lineups = lineupUiState.lineups.getLineupItems() + items(lineups) { lineupItem -> + HomeLineupItem(uiModel = lineupItem) + } + } + + is LineupUiState.Loading -> { + // 로딩 시 동작 논의 후 추가 + } + + is LineupUiState.Error -> { + // 에러 표시 + } + } + + // 하단 여백 추가 + item { + Box(modifier = Modifier.padding(bottom = 60.dp)) + } + } + } +} + +@Preview(showBackground = true) +@Composable +private fun FestivalOverviewPreview() { + val sampleFestival = + Organization( + id = 1, + universityName = "가천대학교", + festival = + Festival( + festivalName = "2025 가천 Water Festival\n: AQUA WAVE", + startDate = LocalDate.now(), + endDate = LocalDate.now().plusDays(2), + festivalImages = + listOf( + Poster(1, "sample", 1), + Poster(2, "sample", 2), + ), + ), + ) + + val sampleLineups = + LineUpItemGroupUiModel( + group = + mapOf( + LocalDate.now() to + listOf( + LineupItemUiModel(1, "sample", "실리카겔", LocalDateTime.now()), + LineupItemUiModel(2, "sample", "아이유", LocalDateTime.now()), + ), + LocalDate.now().plusDays(1) to + listOf( + LineupItemUiModel(3, "sample", "뉴진스", LocalDateTime.now()), + ), + ), + ) + + FestivalOverview( + festivalUiState = FestivalUiState.Success(sampleFestival), + lineupUiState = LineupUiState.Success(sampleLineups), + onNavigateToExplore = {}, + onNavigateToSchedule = {}, + ) +} diff --git a/app/src/main/java/com/daedan/festabook/presentation/main/MainActivity.kt b/app/src/main/java/com/daedan/festabook/presentation/main/MainActivity.kt index 8df33ef2..17e2b8cd 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/main/MainActivity.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/main/MainActivity.kt @@ -19,7 +19,10 @@ import androidx.fragment.app.FragmentFactory import androidx.fragment.app.add import androidx.fragment.app.commit import androidx.fragment.app.commitNow +import androidx.lifecycle.Lifecycle import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import com.daedan.festabook.R import com.daedan.festabook.databinding.ActivityMainBinding import com.daedan.festabook.di.appGraph @@ -39,6 +42,8 @@ import com.daedan.festabook.presentation.setting.SettingFragment import com.daedan.festabook.presentation.setting.SettingViewModel import com.google.android.material.dialog.MaterialAlertDialogBuilder import dev.zacsweers.metro.Inject +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch import timber.log.Timber class MainActivity : @@ -158,8 +163,13 @@ class MainActivity : if (isDoublePress) finish() else showToast(getString(R.string.back_press_exit_message)) } } - homeViewModel.navigateToScheduleEvent.observe(this) { - binding.bnvMenu.selectedItemId = R.id.item_menu_schedule + + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + homeViewModel.navigateToScheduleEvent.collectLatest { + binding.bnvMenu.selectedItemId = R.id.item_menu_schedule + } + } } mainViewModel.isFirstVisit.observe(this) { isFirstVisit -> @@ -298,4 +308,4 @@ class MainActivity : flags = Intent.FLAG_ACTIVITY_SINGLE_TOP } } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/daedan/festabook/presentation/setting/SettingFragment.kt b/app/src/main/java/com/daedan/festabook/presentation/setting/SettingFragment.kt index 05477317..50119653 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/setting/SettingFragment.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/setting/SettingFragment.kt @@ -112,25 +112,25 @@ class SettingFragment( binding.btnNoticeAllow.isEnabled = !loading } - homeViewModel.festivalUiState.observe(viewLifecycleOwner) { state -> - when (state) { - is FestivalUiState.Error -> { - showErrorSnackBar(state.throwable) - Timber.w( - state.throwable, - "${this::class.simpleName}: ${state.throwable.message}", - ) - } - - FestivalUiState.Loading -> { - binding.tvSettingCurrentUniversityNotice.text = "" - } - - is FestivalUiState.Success -> { - binding.tvSettingCurrentUniversity.text = state.organization.universityName - } - } - } +// homeViewModel.festivalUiState.observe(viewLifecycleOwner) { state -> +// when (state) { +// is FestivalUiState.Error -> { +// showErrorSnackBar(state.throwable) +// Timber.w( +// state.throwable, +// "${this::class.simpleName}: ${state.throwable.message}", +// ) +// } +// +// FestivalUiState.Loading -> { +// binding.tvSettingCurrentUniversityNotice.text = "" +// } +// +// is FestivalUiState.Success -> { +// binding.tvSettingCurrentUniversity.text = state.organization.universityName +// } +// } +// } } private fun setupServicePolicyClickListener() { From 920ea5cb630c0b1e28372f979a055b09c55bea7c Mon Sep 17 00:00:00 2001 From: Dongjoo Seo <83579348+etama123@users.noreply.github.com> Date: Sat, 27 Dec 2025 17:06:51 +0900 Subject: [PATCH 07/21] =?UTF-8?q?refactor(home):=20=ED=99=88=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20=EC=BB=B4=ED=8F=AC=EC=A0=80=EB=B8=94=20=EB=B0=8F=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/daedan/festabook/presentation/home/HomeFragment.kt | 4 ++-- .../presentation/home/component/HomeFestivalInfo.kt | 2 -- .../festabook/presentation/home/component/HomeHeader.kt | 1 - .../presentation/home/component/HomeLineupHeader.kt | 4 +--- .../festabook/presentation/home/component/HomeScreen.kt | 5 ++--- 5 files changed, 5 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/daedan/festabook/presentation/home/HomeFragment.kt b/app/src/main/java/com/daedan/festabook/presentation/home/HomeFragment.kt index ed10622d..fa548e63 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/home/HomeFragment.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/home/HomeFragment.kt @@ -6,9 +6,9 @@ import android.view.View import android.view.ViewGroup import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.lifecycle.ViewModelProvider -import androidx.recyclerview.widget.RecyclerView import com.daedan.festabook.R import com.daedan.festabook.databinding.FragmentHomeBinding import com.daedan.festabook.di.fragment.FragmentKey @@ -20,7 +20,7 @@ import dev.zacsweers.metro.ContributesIntoMap import dev.zacsweers.metro.Inject import dev.zacsweers.metro.binding -@ContributesIntoMap(scope = AppScope::class, binding = binding()) +@ContributesIntoMap(scope = AppScope::class, binding = binding()) @FragmentKey(HomeFragment::class) class HomeFragment @Inject constructor() : BaseFragment() { override val layoutId: Int = R.layout.fragment_home diff --git a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeFestivalInfo.kt b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeFestivalInfo.kt index 7d852615..50989193 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeFestivalInfo.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeFestivalInfo.kt @@ -5,11 +5,9 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.daedan.festabook.presentation.theme.FestabookColor diff --git a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeHeader.kt b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeHeader.kt index 836e48c8..379b644c 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeHeader.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeHeader.kt @@ -12,7 +12,6 @@ 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.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.PlatformTextStyle diff --git a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeLineupHeader.kt b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeLineupHeader.kt index e0a9471a..da16b702 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeLineupHeader.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeLineupHeader.kt @@ -1,7 +1,6 @@ package com.daedan.festabook.presentation.home.component import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -12,7 +11,6 @@ import androidx.compose.foundation.layout.width import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource @@ -32,7 +30,7 @@ fun HomeLineupHeader( modifier = modifier .fillMaxWidth() - .padding(start = 16.dp, top = 16.dp, end = 16.dp, bottom = 16.dp), + .padding(16.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, ) { diff --git a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeScreen.kt b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeScreen.kt index e9e5aeca..aaf0ea71 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeScreen.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeScreen.kt @@ -24,7 +24,6 @@ import com.daedan.festabook.domain.model.Organization import com.daedan.festabook.domain.model.Poster import com.daedan.festabook.presentation.home.LineupUiState import com.daedan.festabook.presentation.theme.FestabookColor -import kotlinx.coroutines.flow.collectLatest import java.time.LocalDate import java.time.LocalDateTime @@ -47,7 +46,7 @@ fun HomeScreen( } @Composable -fun FestivalOverview( +private fun FestivalOverview( festivalUiState: FestivalUiState, lineupUiState: LineupUiState, onNavigateToExplore: () -> Unit, @@ -56,7 +55,7 @@ fun FestivalOverview( ) { Scaffold( modifier = modifier.fillMaxSize(), - containerColor = Color.White, + containerColor = Color.White ) { LazyColumn( modifier = From 0ce44cf0554d2ab89bb6e63d36f93ae16528fd37 Mon Sep 17 00:00:00 2001 From: Dongjoo Seo <83579348+etama123@users.noreply.github.com> Date: Mon, 29 Dec 2025 00:58:43 +0900 Subject: [PATCH 08/21] =?UTF-8?q?refactor(Home):=20=ED=99=88=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20UI=20=EC=BB=B4=ED=8F=AC=EC=A6=88=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=A6=AC=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A7=81=20=EB=B0=8F=20=EC=8A=A4=ED=83=80=EC=9D=BC=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 홈 화면을 구성하는 주요 컴포저블(`HomeHeader`, `HomePosterList` 등)의 파라미터 네이밍을 정리하고, 디자인 시스템 및 커스텀 Modifier를 적용하여 코드 일관성을 높였습니다. - **`HomeHeader.kt` 수정:** - 파라미터 이름을 `schoolName`에서 `universityName`으로 변경하여 도메인 모델과의 일관성을 맞췄습니다. - 드롭다운 아이콘을 `Image`에서 `Icon`으로 변경하고, `FestabookColor`와 고정 크기(24.dp)를 적용했습니다. - **`HomePosterList.kt` 수정:** - 포스터 카드에 커스텀 Modifier인 `cardBackground`를 적용하여 배경 스타일을 개선했습니다. - **`HomeScreen.kt` 수정:** - `HomeHeader` 호출 시 변경된 파라미터 명(`universityName`)을 반영했습니다. - 리스트 하단의 여백 처리를 위해 의미상 더 적절한 `Spacer`를 사용하도록 변경했습니다. - **`HomeLineupHeader.kt` 수정:** - 일정 클릭 영역(`Row`)에서 불필요한 `padding(4.dp)`을 제거하여 레이아웃을 정돈했습니다. --- .../presentation/home/component/HomeHeader.kt | 15 +++++++++------ .../home/component/HomeLineupHeader.kt | 3 +-- .../presentation/home/component/HomePosterList.kt | 4 +++- .../presentation/home/component/HomeScreen.kt | 5 +++-- 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeHeader.kt b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeHeader.kt index 379b644c..5677ce19 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeHeader.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeHeader.kt @@ -1,13 +1,14 @@ package com.daedan.festabook.presentation.home.component -import androidx.compose.foundation.Image import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box 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.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -24,7 +25,7 @@ import com.daedan.festabook.presentation.theme.FestabookTypography @Composable fun HomeHeader( - schoolName: String, + universityName: String, onExpandClick: () -> Unit, modifier: Modifier = Modifier, ) { @@ -39,7 +40,7 @@ fun HomeHeader( verticalAlignment = Alignment.CenterVertically, ) { Text( - text = schoolName, + text = universityName, style = FestabookTypography.displayLarge.copy( platformStyle = PlatformTextStyle(includeFontPadding = false), lineHeight = 34.sp @@ -49,9 +50,11 @@ fun HomeHeader( Spacer(modifier = Modifier.width(4.dp)) - Image( + Icon( painter = painterResource(id = R.drawable.ic_dropdown), - contentDescription = stringResource(id = R.string.home_navigate_to_explore_desc), + tint = FestabookColor.black, + contentDescription = stringResource(R.string.home_navigate_to_explore_desc), + modifier = Modifier.size(24.dp) ) } } @@ -61,7 +64,7 @@ fun HomeHeader( @Composable private fun HomeHeaderPreview() { HomeHeader( - schoolName = "가천대학교", + universityName = "가천대학교", onExpandClick = {}, ) } diff --git a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeLineupHeader.kt b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeLineupHeader.kt index da16b702..3679a4a0 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeLineupHeader.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeLineupHeader.kt @@ -45,8 +45,7 @@ fun HomeLineupHeader( Modifier .clickable( onClick = onScheduleClick, - ) - .padding(4.dp), + ), verticalAlignment = Alignment.CenterVertically, ) { Text( diff --git a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomePosterList.kt b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomePosterList.kt index 1ce0af70..fd8b8c0e 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomePosterList.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomePosterList.kt @@ -20,6 +20,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.util.lerp import com.daedan.festabook.presentation.common.component.CoilImage +import com.daedan.festabook.presentation.common.component.cardBackground import kotlin.math.absoluteValue @Composable @@ -87,7 +88,8 @@ fun HomePosterList( scaleY = scale this.alpha = alpha } - .clip(RoundedCornerShape(10.dp)), + .cardBackground(roundedCornerShape = 10.dp) + .clip(RoundedCornerShape(10.dp)) ) { CoilImage( url = imageUrl, diff --git a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeScreen.kt b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeScreen.kt index aaf0ea71..f98f657b 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeScreen.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeScreen.kt @@ -1,6 +1,7 @@ package com.daedan.festabook.presentation.home.component import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn @@ -65,7 +66,7 @@ private fun FestivalOverview( item { if (festivalUiState is FestivalUiState.Success) { HomeHeader( - schoolName = festivalUiState.organization.universityName, + universityName = festivalUiState.organization.universityName, onExpandClick = onNavigateToExplore, modifier = Modifier.padding(top = 40.dp), ) @@ -144,7 +145,7 @@ private fun FestivalOverview( // 하단 여백 추가 item { - Box(modifier = Modifier.padding(bottom = 60.dp)) + Spacer(modifier = Modifier.padding(bottom = 60.dp)) } } } From e0c981e63f970a9ba50e25591240b5e3bda5b21f Mon Sep 17 00:00:00 2001 From: Dongjoo Seo <83579348+etama123@users.noreply.github.com> Date: Mon, 29 Dec 2025 01:08:18 +0900 Subject: [PATCH 09/21] =?UTF-8?q?refactor(Home):=20LineupUiState=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EA=B5=AC=EC=A1=B0=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../daedan/festabook/presentation/home/HomeViewModel.kt | 6 ++---- .../daedan/festabook/presentation/home/LineupUiState.kt | 2 +- .../festabook/presentation/home/component/HomeScreen.kt | 8 +++++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/daedan/festabook/presentation/home/HomeViewModel.kt b/app/src/main/java/com/daedan/festabook/presentation/home/HomeViewModel.kt index 64caaff1..6dbb1c1d 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/home/HomeViewModel.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/home/HomeViewModel.kt @@ -61,10 +61,8 @@ class HomeViewModel @Inject constructor( val result = festivalRepository.getLineUpGroupByDate() result .onSuccess { lineups -> - _lineupUiState.value = - LineupUiState.Success( - lineups.toUiModel(), - ) + val lineupItems = lineups.toUiModel().getLineupItems() + _lineupUiState.value = LineupUiState.Success(lineupItems) }.onFailure { _lineupUiState.value = LineupUiState.Error(it) } diff --git a/app/src/main/java/com/daedan/festabook/presentation/home/LineupUiState.kt b/app/src/main/java/com/daedan/festabook/presentation/home/LineupUiState.kt index f2b84299..dcfc03e8 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/home/LineupUiState.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/home/LineupUiState.kt @@ -4,7 +4,7 @@ sealed interface LineupUiState { data object Loading : LineupUiState data class Success( - val lineups: LineUpItemGroupUiModel, + val lineups: List, ) : LineupUiState data class Error( diff --git a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeScreen.kt b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeScreen.kt index f98f657b..fe8e790b 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeScreen.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeScreen.kt @@ -128,8 +128,10 @@ private fun FestivalOverview( // 라인업 리스트 when (lineupUiState) { is LineupUiState.Success -> { - val lineups = lineupUiState.lineups.getLineupItems() - items(lineups) { lineupItem -> + items( + items = lineupUiState.lineups, + key = { it.id }, + ) { lineupItem -> HomeLineupItem(uiModel = lineupItem) } } @@ -189,7 +191,7 @@ private fun FestivalOverviewPreview() { FestivalOverview( festivalUiState = FestivalUiState.Success(sampleFestival), - lineupUiState = LineupUiState.Success(sampleLineups), + lineupUiState = LineupUiState.Success(sampleLineups.getLineupItems()), onNavigateToExplore = {}, onNavigateToSchedule = {}, ) From a9d84b4f70d0df6c8daac327aec9d2413f5c56d0 Mon Sep 17 00:00:00 2001 From: Dongjoo Seo <83579348+etama123@users.noreply.github.com> Date: Mon, 29 Dec 2025 01:17:54 +0900 Subject: [PATCH 10/21] =?UTF-8?q?refactor(HomeLineupItem):=20=EB=82=A0?= =?UTF-8?q?=EC=A7=9C=20=EC=98=81=EC=97=AD=20=EB=A0=88=EC=9D=B4=EC=95=84?= =?UTF-8?q?=EC=9B=83=20=EB=B0=8F=20=EB=94=94=EB=B0=94=EC=9D=B4=EB=8D=94=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20=EB=B0=A9=EC=8B=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../home/component/HomeLineupItem.kt | 68 ++++++++++--------- 1 file changed, 37 insertions(+), 31 deletions(-) diff --git a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeLineupItem.kt b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeLineupItem.kt index af9efc0b..bbb106dd 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeLineupItem.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeLineupItem.kt @@ -4,6 +4,7 @@ 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.IntrinsicSize import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -14,6 +15,8 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.DividerDefaults +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -39,44 +42,47 @@ fun HomeLineupItem( modifier = modifier.fillMaxWidth(), ) { // 날짜 + 배지 영역 - Row( - modifier = Modifier.padding(horizontal = 16.dp), - verticalAlignment = Alignment.CenterVertically, + Column( + modifier = Modifier.padding(horizontal = 16.dp).width(IntrinsicSize.Max) ) { - Text( - text = "${uiModel.date.monthValue}.${uiModel.date.dayOfMonth}", - style = FestabookTypography.titleLarge, - color = FestabookColor.black, - ) + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth(), + ) { + Text( + text = "${uiModel.date.monthValue}.${uiModel.date.dayOfMonth}", + style = FestabookTypography.titleLarge, + color = FestabookColor.black, + ) - if (uiModel.isDDay) { - Spacer(modifier = Modifier.width(6.dp)) - Box( - modifier = - Modifier - .clip(RoundedCornerShape(20.dp)) - .background(FestabookColor.black) - .padding(horizontal = 6.dp, vertical = 2.dp), - ) { - Text( - text = stringResource(id = R.string.home_is_d_day), - style = FestabookTypography.labelSmall, - color = FestabookColor.white, - ) + if (uiModel.isDDay) { + Spacer(modifier = Modifier.width(6.dp)) + Box( + modifier = + Modifier + .clip(RoundedCornerShape(20.dp)) + .background(FestabookColor.black) + .padding(horizontal = 6.dp, vertical = 2.dp), + ) { + Text( + text = stringResource(id = R.string.home_is_d_day), + style = FestabookTypography.labelSmall, + color = FestabookColor.white, + ) + } } } + Spacer(modifier = Modifier.height(4.dp)) + + HorizontalDivider( + thickness = 1.dp, + color = FestabookColor.gray700, + modifier = Modifier.fillMaxWidth(), + ) + } - Spacer(modifier = Modifier.height(4.dp)) - Box( - modifier = - Modifier - .padding(horizontal = 16.dp) - .width(75.dp) - .height(1.dp) - .background(FestabookColor.gray700), - ) Spacer(modifier = Modifier.height(8.dp)) From f849f6f084e299637cd3c9f23d6ebb1d19cdb746 Mon Sep 17 00:00:00 2001 From: Dongjoo Seo <83579348+etama123@users.noreply.github.com> Date: Mon, 29 Dec 2025 01:26:59 +0900 Subject: [PATCH 11/21] =?UTF-8?q?refactor(Home):=20=ED=99=88=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20=EB=AC=B8=EC=9E=90=EC=97=B4=20=EB=A6=AC=EC=86=8C?= =?UTF-8?q?=EC=8A=A4=20=EC=A0=95=EB=A6=AC=20=EB=B0=8F=20=EC=A0=91=EA=B7=BC?= =?UTF-8?q?=EC=84=B1=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/home/component/HomeArtistItem.kt | 2 +- .../presentation/home/component/HomeLineupHeader.kt | 4 ++-- app/src/main/res/layout/fragment_home.xml | 2 +- app/src/main/res/values/strings.xml | 9 ++++++--- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeArtistItem.kt b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeArtistItem.kt index 478baa80..7d451cca 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeArtistItem.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeArtistItem.kt @@ -51,7 +51,7 @@ fun HomeArtistItem( } } -object HomeArtistItem { +private object HomeArtistItem { val ArtistImage = RoundedCornerShape( topStartPercent = 50, topEndPercent = 50, diff --git a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeLineupHeader.kt b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeLineupHeader.kt index 3679a4a0..11b4d092 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeLineupHeader.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeLineupHeader.kt @@ -49,7 +49,7 @@ fun HomeLineupHeader( verticalAlignment = Alignment.CenterVertically, ) { Text( - text = stringResource(R.string.home_check_schedule_text), + text = stringResource(R.string.home_navigate_to_schedule_text), style = FestabookTypography.bodySmall, color = FestabookColor.gray400, ) @@ -58,7 +58,7 @@ fun HomeLineupHeader( Icon( painter = painterResource(id = R.drawable.ic_arrow_forward_right), - contentDescription = null, + contentDescription = stringResource(R.string.home_navigate_to_schedule_desc), tint = FestabookColor.gray400, modifier = Modifier.size(12.dp), ) diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index e0b46feb..76e048fc 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -113,7 +113,7 @@ android:background="@color/transparent" android:paddingHorizontal="16dp" android:paddingVertical="4dp" - android:text="@string/home_check_schedule_text" + android:text="@string/home_navigate_to_schedule_text" android:textColor="@color/gray400" app:icon="@drawable/ic_arrow_forward_right" app:iconGravity="end" diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cca637b8..5c45fca2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -16,6 +16,11 @@ poster_image 오늘 + 일정 화면으로 이동하는 버튼 + 일정 확인하기 + 축제 라인업 + 탐색 화면으로 이동하는 버튼 + 한 눈에 보기 @@ -123,8 +128,7 @@ 알림 받기 다음에 item_lineup_image - 일정 확인하기 - 축제 라인업 + 뒤로가기를 한 번 더 누르면 종료됩니다. @@ -135,7 +139,6 @@ 알림 새로운 소식이 있습니다. - 탐색 화면으로 이동하는 버튼 탐색 화면 닫기 버튼 From 1e98ebba8522b723b2f6bf85e77d8ba0323cb5a9 Mon Sep 17 00:00:00 2001 From: Dongjoo Seo <83579348+etama123@users.noreply.github.com> Date: Fri, 2 Jan 2026 15:52:07 +0900 Subject: [PATCH 12/21] =?UTF-8?q?refactor(HomeViewModel):=20`@Inject`=20?= =?UTF-8?q?=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20=EC=9C=84?= =?UTF-8?q?=EC=B9=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/daedan/festabook/presentation/home/HomeViewModel.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/daedan/festabook/presentation/home/HomeViewModel.kt b/app/src/main/java/com/daedan/festabook/presentation/home/HomeViewModel.kt index 6dbb1c1d..99ebae04 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/home/HomeViewModel.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/home/HomeViewModel.kt @@ -18,7 +18,8 @@ import kotlinx.coroutines.launch @ContributesIntoMap(AppScope::class) @ViewModelKey(HomeViewModel::class) -class HomeViewModel @Inject constructor( +@Inject +class HomeViewModel( private val festivalRepository: FestivalRepository, ) : ViewModel() { private val _festivalUiState = MutableStateFlow(FestivalUiState.Loading) From 9427a8f06a348b85a7bc63252bd40e4a8f17c99c Mon Sep 17 00:00:00 2001 From: Dongjoo Seo <83579348+etama123@users.noreply.github.com> Date: Fri, 2 Jan 2026 16:02:40 +0900 Subject: [PATCH 13/21] =?UTF-8?q?refactor(MainActivity):=20BottomNavigatio?= =?UTF-8?q?nView=EC=9D=98=20=EC=A4=91=EB=B3=B5=20=EC=84=A0=ED=83=9D=20?= =?UTF-8?q?=EB=B0=A9=EC=A7=80=20=EB=B0=8F=20flow=20=EC=88=98=EC=A7=91=20?= =?UTF-8?q?=EB=B0=A9=EC=8B=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/daedan/festabook/presentation/main/MainActivity.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/daedan/festabook/presentation/main/MainActivity.kt b/app/src/main/java/com/daedan/festabook/presentation/main/MainActivity.kt index 17e2b8cd..283e07f4 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/main/MainActivity.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/main/MainActivity.kt @@ -166,8 +166,10 @@ class MainActivity : lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { - homeViewModel.navigateToScheduleEvent.collectLatest { - binding.bnvMenu.selectedItemId = R.id.item_menu_schedule + homeViewModel.navigateToScheduleEvent.collect { + if (binding.bnvMenu.selectedItemId != R.id.item_menu_schedule) { + binding.bnvMenu.selectedItemId = R.id.item_menu_schedule + } } } } From c8325fe5954c59fcec4e41518c837a702639e9d0 Mon Sep 17 00:00:00 2001 From: Dongjoo Seo <83579348+etama123@users.noreply.github.com> Date: Fri, 2 Jan 2026 16:03:34 +0900 Subject: [PATCH 14/21] =?UTF-8?q?refactor(HomeFestivalInfo):=20Column=20?= =?UTF-8?q?=EB=82=B4=EB=B6=80=20padding=20=EC=A0=81=EC=9A=A9=20=EB=B0=8F?= =?UTF-8?q?=20=EA=B0=9C=EB=B3=84=20Text=20padding=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../festabook/presentation/home/component/HomeFestivalInfo.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeFestivalInfo.kt b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeFestivalInfo.kt index 50989193..37e15d6d 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeFestivalInfo.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeFestivalInfo.kt @@ -20,13 +20,12 @@ fun HomeFestivalInfo( modifier: Modifier = Modifier, ) { Column( - modifier = modifier.fillMaxWidth(), + modifier = modifier.fillMaxWidth().padding(horizontal = 20.dp), ) { Text( text = festivalName, style = FestabookTypography.displayMedium, color = FestabookColor.black, - modifier = Modifier.padding(horizontal = 20.dp), ) Spacer(modifier = Modifier.height(8.dp)) @@ -35,7 +34,6 @@ fun HomeFestivalInfo( text = festivalDate, style = FestabookTypography.bodyLarge, color = FestabookColor.gray500, - modifier = Modifier.padding(horizontal = 20.dp), ) } } From dba3acbf5e894fc8f342290ce602b70824ea58c0 Mon Sep 17 00:00:00 2001 From: Dongjoo Seo <83579348+etama123@users.noreply.github.com> Date: Wed, 7 Jan 2026 20:38:39 +0900 Subject: [PATCH 15/21] =?UTF-8?q?build:=20Landscapist=20=EB=9D=BC=EC=9D=B4?= =?UTF-8?q?=EB=B8=8C=EB=9F=AC=EB=A6=AC=20=EC=9D=98=EC=A1=B4=EC=84=B1=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 3 +++ gradle/libs.versions.toml | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index dac82a61..75bbf326 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -181,6 +181,9 @@ dependencies { implementation(libs.ui.tooling) implementation(libs.androidx.material3) implementation(libs.photoview.dialog) + implementation(libs.landscapist.coil3) + implementation(libs.landscapist.placeholder) + implementation(libs.landscapist.zoomable) testImplementation(libs.junit) testImplementation(libs.mockk) testImplementation(libs.androidx.core.testing) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 59f07add..2b6cfc74 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -15,6 +15,9 @@ junitVersion = "1.2.1" espressoCore = "3.6.1" appcompat = "1.7.0" kotlinxCoroutinesTest = "1.10.2" +landscapistCoil3 = "2.8.2" +landscapistPlaceholder = "2.8.2" +landscapistZoomable = "2.8.2" loggingInterceptor = "5.1.0" lottie = "6.6.6" mapSdk = "3.22.1" @@ -59,6 +62,9 @@ androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "j androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinxCoroutinesTest" } +landscapist-coil3 = { module = "com.github.skydoves:landscapist-coil3", version.ref = "landscapistCoil3" } +landscapist-placeholder = { module = "com.github.skydoves:landscapist-placeholder", version.ref = "landscapistPlaceholder" } +landscapist-zoomable = { module = "com.github.skydoves:landscapist-zoomable", version.ref = "landscapistZoomable" } logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "loggingInterceptor" } lottie = { module = "com.airbnb.android:lottie", version.ref = "lottie" } lottie-compose = { module = "com.airbnb.android:lottie-compose", version.ref = "lottie" } From db9268696e9a42fb1bf8560a26d9bcd0b1808380 Mon Sep 17 00:00:00 2001 From: Dongjoo Seo <83579348+etama123@users.noreply.github.com> Date: Wed, 7 Jan 2026 20:39:10 +0900 Subject: [PATCH 16/21] =?UTF-8?q?feat(common):=20FestabookImage=20?= =?UTF-8?q?=EA=B3=B5=ED=86=B5=20=EC=BB=B4=ED=8F=AC=EC=A0=80=EB=B8=94=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/component/FestabookImage.kt | 160 ++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 app/src/main/java/com/daedan/festabook/presentation/common/component/FestabookImage.kt diff --git a/app/src/main/java/com/daedan/festabook/presentation/common/component/FestabookImage.kt b/app/src/main/java/com/daedan/festabook/presentation/common/component/FestabookImage.kt new file mode 100644 index 00000000..8815053b --- /dev/null +++ b/app/src/main/java/com/daedan/festabook/presentation/common/component/FestabookImage.kt @@ -0,0 +1,160 @@ +package com.daedan.festabook.presentation.common.component + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Close +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +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.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties +import coil3.request.ImageRequest +import com.daedan.festabook.BuildConfig +import com.daedan.festabook.presentation.theme.FestabookColor +import com.skydoves.landscapist.ImageOptions +import com.skydoves.landscapist.coil3.CoilImage +import com.skydoves.landscapist.components.rememberImageComponent +import com.skydoves.landscapist.placeholder.shimmer.Shimmer +import com.skydoves.landscapist.placeholder.shimmer.ShimmerPlugin +import com.skydoves.landscapist.zoomable.ZoomablePlugin +import com.skydoves.landscapist.zoomable.rememberZoomableState +import com.daedan.festabook.R + +@Composable +fun FestabookImage( + modifier: Modifier = Modifier, + imageUrl: String?, + contentDescription: String? = null, + contentScale: ContentScale = ContentScale.Crop, + isZoomable: Boolean = false, + enablePopUp: Boolean = false, + builder: ImageRequest.Builder.() -> Unit = {}, +) { + val context = LocalContext.current + val zoomableState = rememberZoomableState() + val convertedUrl = imageUrl.convertImageUrl() + + var isPopUpOpen by remember { mutableStateOf(false) } + + Box( + modifier = modifier.then( + if (enablePopUp) Modifier.clickable { isPopUpOpen = true } + else Modifier + ) + ) { + CoilImage( + imageRequest = { + ImageRequest.Builder(context) + .data(convertedUrl) + .apply(builder) + .build() + + }, + modifier = modifier.fillMaxSize(), + imageOptions = ImageOptions( + contentScale = contentScale, + alignment = Alignment.Center, + contentDescription = contentDescription + ), + component = rememberImageComponent { + +ShimmerPlugin( + Shimmer.Flash( + baseColor = FestabookColor.gray100.copy(alpha = 0.5f), + highlightColor = FestabookColor.gray200.copy(alpha = 0.3f)), + ) + if (isZoomable) { + +ZoomablePlugin(state = zoomableState) + } + }, + failure = { + Image( + painter = painterResource(id = R.drawable.img_fallback), + contentDescription = "fallback_image", + modifier = Modifier.align(Alignment.Center), + contentScale = contentScale + ) + } + ) + } + if (isPopUpOpen && enablePopUp) { + FestabookImageZoomPopup( + imageUrl = imageUrl, + onDismiss = { isPopUpOpen = false } + ) + } +} + +@Composable +private fun FestabookImageZoomPopup( + imageUrl: String?, + onDismiss: () -> Unit +) { + Dialog( + onDismissRequest = onDismiss, + properties = DialogProperties(usePlatformDefaultWidth = false) + ) { + Box( + modifier = Modifier + .fillMaxSize() + .background(FestabookColor.black.copy(alpha = 0.8f)) + ) { + FestabookImage( + imageUrl = imageUrl, + modifier = Modifier.fillMaxSize(), + contentScale = ContentScale.Fit, + isZoomable = true, + enablePopUp = false + ) + + IconButton( + onClick = onDismiss, + modifier = Modifier + .align(Alignment.TopEnd) + .padding(16.dp) + ) { + Icon( + imageVector = Icons.Default.Close, + contentDescription = "close the popup", + tint = FestabookColor.white, + ) + } + } + } +} + +@Preview(showBackground = true) +@Composable +fun FestabookImageTestPreview() { + FestabookImage( + imageUrl = "" + ) +} +@Preview(showBackground = true) +@Composable +fun DiaplogPreview() { + FestabookImageZoomPopup( + imageUrl = "" + ) { } +} + +fun String?.convertImageUrl() = if (this != null && this.startsWith("/images/")) { + BuildConfig.FESTABOOK_URL.removeSuffix("/api/") + this +} else { + this +} \ No newline at end of file From aec51d3143d32e2c6574bc87df921b9d24488a7e Mon Sep 17 00:00:00 2001 From: Dongjoo Seo <83579348+etama123@users.noreply.github.com> Date: Wed, 7 Jan 2026 21:06:59 +0900 Subject: [PATCH 17/21] =?UTF-8?q?feat(home):=20=ED=99=88=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20UI=20=EC=83=81=ED=83=9C=20=EA=B4=80=EB=A6=AC=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EA=B0=9C=EC=84=A0=20=EB=B0=8F=20`Festaboo?= =?UTF-8?q?kImage`=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../home/component/HomePosterList.kt | 7 +- .../presentation/home/component/HomeScreen.kt | 145 ++++++++++-------- 2 files changed, 86 insertions(+), 66 deletions(-) diff --git a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomePosterList.kt b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomePosterList.kt index fd8b8c0e..84fd80a7 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomePosterList.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomePosterList.kt @@ -20,6 +20,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.util.lerp import com.daedan.festabook.presentation.common.component.CoilImage +import com.daedan.festabook.presentation.common.component.FestabookImage import com.daedan.festabook.presentation.common.component.cardBackground import kotlin.math.absoluteValue @@ -91,10 +92,10 @@ fun HomePosterList( .cardBackground(roundedCornerShape = 10.dp) .clip(RoundedCornerShape(10.dp)) ) { - CoilImage( - url = imageUrl, - contentDescription = null, + FestabookImage( + imageUrl = imageUrl, modifier = Modifier.fillMaxSize(), + enablePopUp = true ) } } diff --git a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeScreen.kt b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeScreen.kt index fe8e790b..8082e85e 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeScreen.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeScreen.kt @@ -1,6 +1,7 @@ package com.daedan.festabook.presentation.home.component import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding @@ -8,13 +9,16 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.daedan.festabook.presentation.common.component.LoadingStateScreen import com.daedan.festabook.presentation.common.formatFestivalPeriod import com.daedan.festabook.presentation.home.HomeViewModel import com.daedan.festabook.presentation.home.LineUpItemGroupUiModel @@ -34,48 +38,66 @@ fun HomeScreen( onNavigateToExplore: () -> Unit, modifier: Modifier = Modifier, ) { - val festivalUiState by viewModel.festivalUiState.collectAsState() - val lineupUiState by viewModel.lineupUiState.collectAsState() + val festivalUiState by viewModel.festivalUiState.collectAsStateWithLifecycle() + val lineupUiState by viewModel.lineupUiState.collectAsStateWithLifecycle() - FestivalOverview( - festivalUiState = festivalUiState, - lineupUiState = lineupUiState, - onNavigateToExplore = onNavigateToExplore, - onNavigateToSchedule = viewModel::navigateToScheduleClick, - modifier = modifier, - ) + when (val state = festivalUiState) { + is FestivalUiState.Loading -> { + LoadingStateScreen(modifier = modifier) + } + + is FestivalUiState.Error -> { + Box(modifier = modifier.fillMaxSize()) { + Text( + text = "데이터를 불러오는데 실패했습니다.", + modifier = Modifier.align(Alignment.Center), + ) + } + } + + is FestivalUiState.Success -> { + FestivalOverview( + festivalUiState = state, + lineupUiState = lineupUiState, + onNavigateToExplore = onNavigateToExplore, + onNavigateToSchedule = viewModel::navigateToScheduleClick, + modifier = modifier, + ) + } + } } @Composable private fun FestivalOverview( - festivalUiState: FestivalUiState, + festivalUiState: FestivalUiState.Success, lineupUiState: LineupUiState, onNavigateToExplore: () -> Unit, onNavigateToSchedule: () -> Unit, modifier: Modifier = Modifier, ) { + val universityName = festivalUiState.organization.universityName + Scaffold( modifier = modifier.fillMaxSize(), containerColor = Color.White - ) { - LazyColumn( - modifier = - Modifier.fillMaxSize() + ) { innerPadding -> + Column( + modifier = Modifier + .fillMaxSize() ) { - // 헤더 (학교 이름) - item { - if (festivalUiState is FestivalUiState.Success) { - HomeHeader( - universityName = festivalUiState.organization.universityName, - onExpandClick = onNavigateToExplore, - modifier = Modifier.padding(top = 40.dp), - ) - } - } - - // 포스터 리스트 - item { - if (festivalUiState is FestivalUiState.Success) { + HomeHeader( + universityName = universityName, + onExpandClick = onNavigateToExplore, + modifier = Modifier.padding(top = 40.dp, bottom = 12.dp), + ) + + LazyColumn( + modifier = + Modifier + .fillMaxSize() + ) { + // 포스터 리스트 + item { val posterUrls = festivalUiState.organization.festival.festivalImages .sortedBy { it.sequence } @@ -86,11 +108,9 @@ private fun FestivalOverview( modifier = Modifier.padding(vertical = 12.dp), ) } - } - // 축제 정보 - item { - if (festivalUiState is FestivalUiState.Success) { + // 축제 정보 + item { val festival = festivalUiState.organization.festival HomeFestivalInfo( festivalName = festival.festivalName, @@ -102,12 +122,9 @@ private fun FestivalOverview( modifier = Modifier.padding(top = 16.dp), ) } - } - - // 구분선 - item { - if (festivalUiState is FestivalUiState.Success) { + // 구분선 + item { HorizontalDivider( thickness = 4.dp, color = FestabookColor.gray200, @@ -115,39 +132,40 @@ private fun FestivalOverview( Modifier .padding(top = 16.dp), ) + } - } - // 라인업 헤더 - item { - HomeLineupHeader( - onScheduleClick = onNavigateToSchedule, - ) - } + // 라인업 헤더 + item { + HomeLineupHeader( + onScheduleClick = onNavigateToSchedule, + ) + } - // 라인업 리스트 - when (lineupUiState) { - is LineupUiState.Success -> { - items( - items = lineupUiState.lineups, - key = { it.id }, - ) { lineupItem -> - HomeLineupItem(uiModel = lineupItem) + // 라인업 리스트 + when (lineupUiState) { + is LineupUiState.Success -> { + items( + items = lineupUiState.lineups, + key = { it.id }, + ) { lineupItem -> + HomeLineupItem(uiModel = lineupItem) + } } - } - is LineupUiState.Loading -> { - // 로딩 시 동작 논의 후 추가 - } + is LineupUiState.Loading -> { + // 로딩 시 동작 논의 후 추가 + } - is LineupUiState.Error -> { - // 에러 표시 + is LineupUiState.Error -> { + // 에러 표시 + } } - } - // 하단 여백 추가 - item { - Spacer(modifier = Modifier.padding(bottom = 60.dp)) + // 하단 여백 추가 + item { + Spacer(modifier = Modifier.padding(bottom = 60.dp)) + } } } } @@ -196,3 +214,4 @@ private fun FestivalOverviewPreview() { onNavigateToSchedule = {}, ) } + From 14522f3d23255207331ab7757e7e6d9f22d5c3cb Mon Sep 17 00:00:00 2001 From: Dongjoo Seo <83579348+etama123@users.noreply.github.com> Date: Wed, 7 Jan 2026 23:04:31 +0900 Subject: [PATCH 18/21] =?UTF-8?q?style(MainActivity,=20HomeFragment,=20etc?= =?UTF-8?q?):=20=EC=BD=94=EB=93=9C=20=ED=8F=AC=EB=A7=B7=ED=8C=85=20?= =?UTF-8?q?=EB=B0=8F=20=EC=8A=A4=ED=83=80=EC=9D=BC=20=EA=B0=80=EC=9D=B4?= =?UTF-8?q?=EB=93=9C=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/home/HomeFragment.kt | 10 +++--- .../presentation/home/component/HomeHeader.kt | 13 ++++---- .../presentation/home/component/HomeScreen.kt | 33 +++++++++---------- .../presentation/main/MainActivity.kt | 33 ++++++++++++++----- .../presentation/setting/SettingFragment.kt | 1 - 5 files changed, 53 insertions(+), 37 deletions(-) diff --git a/app/src/main/java/com/daedan/festabook/presentation/home/HomeFragment.kt b/app/src/main/java/com/daedan/festabook/presentation/home/HomeFragment.kt index c49774d2..6fefc37c 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/home/HomeFragment.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/home/HomeFragment.kt @@ -35,9 +35,12 @@ class HomeFragment( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?, - ): View { - return ComposeView(requireContext()).apply { - setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + ): View = + ComposeView(requireContext()).apply { + setViewCompositionStrategy( + ViewCompositionStrategy + .DisposeOnViewTreeLifecycleDestroyed, + ) setContent { HomeScreen( viewModel = viewModel, @@ -47,5 +50,4 @@ class HomeFragment( ) } } - } } diff --git a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeHeader.kt b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeHeader.kt index 5677ce19..1d9450de 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeHeader.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeHeader.kt @@ -33,7 +33,7 @@ fun HomeHeader( modifier = modifier .fillMaxWidth() - .padding(horizontal = 16.dp) + .padding(horizontal = 16.dp), ) { Row( modifier = Modifier.clickable { onExpandClick() }, @@ -41,10 +41,11 @@ fun HomeHeader( ) { Text( text = universityName, - style = FestabookTypography.displayLarge.copy( - platformStyle = PlatformTextStyle(includeFontPadding = false), - lineHeight = 34.sp - ), + style = + FestabookTypography.displayLarge.copy( + platformStyle = PlatformTextStyle(includeFontPadding = false), + lineHeight = 34.sp, + ), color = FestabookColor.black, ) @@ -54,7 +55,7 @@ fun HomeHeader( painter = painterResource(id = R.drawable.ic_dropdown), tint = FestabookColor.black, contentDescription = stringResource(R.string.home_navigate_to_explore_desc), - modifier = Modifier.size(24.dp) + modifier = Modifier.size(24.dp), ) } } diff --git a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeScreen.kt b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeScreen.kt index 8082e85e..0214a9c3 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeScreen.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeScreen.kt @@ -18,16 +18,16 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.daedan.festabook.domain.model.Festival +import com.daedan.festabook.domain.model.Organization +import com.daedan.festabook.domain.model.Poster import com.daedan.festabook.presentation.common.component.LoadingStateScreen import com.daedan.festabook.presentation.common.formatFestivalPeriod import com.daedan.festabook.presentation.home.HomeViewModel import com.daedan.festabook.presentation.home.LineUpItemGroupUiModel import com.daedan.festabook.presentation.home.LineupItemUiModel -import com.daedan.festabook.presentation.home.adapter.FestivalUiState -import com.daedan.festabook.domain.model.Festival -import com.daedan.festabook.domain.model.Organization -import com.daedan.festabook.domain.model.Poster import com.daedan.festabook.presentation.home.LineupUiState +import com.daedan.festabook.presentation.home.adapter.FestivalUiState import com.daedan.festabook.presentation.theme.FestabookColor import java.time.LocalDate import java.time.LocalDateTime @@ -79,11 +79,12 @@ private fun FestivalOverview( Scaffold( modifier = modifier.fillMaxSize(), - containerColor = Color.White + containerColor = Color.White, ) { innerPadding -> Column( - modifier = Modifier - .fillMaxSize() + modifier = + Modifier + .fillMaxSize(), ) { HomeHeader( universityName = universityName, @@ -94,7 +95,7 @@ private fun FestivalOverview( LazyColumn( modifier = Modifier - .fillMaxSize() + .fillMaxSize(), ) { // 포스터 리스트 item { @@ -132,7 +133,6 @@ private fun FestivalOverview( Modifier .padding(top = 16.dp), ) - } // 라인업 헤더 @@ -196,14 +196,14 @@ private fun FestivalOverviewPreview() { group = mapOf( LocalDate.now() to - listOf( - LineupItemUiModel(1, "sample", "실리카겔", LocalDateTime.now()), - LineupItemUiModel(2, "sample", "아이유", LocalDateTime.now()), - ), + listOf( + LineupItemUiModel(1, "sample", "실리카겔", LocalDateTime.now()), + LineupItemUiModel(2, "sample", "아이유", LocalDateTime.now()), + ), LocalDate.now().plusDays(1) to - listOf( - LineupItemUiModel(3, "sample", "뉴진스", LocalDateTime.now()), - ), + listOf( + LineupItemUiModel(3, "sample", "뉴진스", LocalDateTime.now()), + ), ), ) @@ -214,4 +214,3 @@ private fun FestivalOverviewPreview() { onNavigateToSchedule = {}, ) } - diff --git a/app/src/main/java/com/daedan/festabook/presentation/main/MainActivity.kt b/app/src/main/java/com/daedan/festabook/presentation/main/MainActivity.kt index 020e37f1..528af417 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/main/MainActivity.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/main/MainActivity.kt @@ -43,7 +43,6 @@ import com.daedan.festabook.presentation.setting.SettingFragment import com.daedan.festabook.presentation.setting.SettingViewModel import com.google.android.material.dialog.MaterialAlertDialogBuilder import dev.zacsweers.metro.Inject -import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import timber.log.Timber @@ -227,19 +226,27 @@ class MainActivity : private fun onMenuItemClick() { binding.bnvMenu.setOnItemSelectedListener { icon -> when (icon.itemId) { - R.id.item_menu_home -> switchFragment(HomeFragment::class.java, TAG_HOME_FRAGMENT) - R.id.item_menu_schedule -> + R.id.item_menu_home -> { + switchFragment(HomeFragment::class.java, TAG_HOME_FRAGMENT) + } + + R.id.item_menu_schedule -> { switchFragment( ScheduleFragment::class.java, TAG_SCHEDULE_FRAGMENT, ) + } + + R.id.item_menu_news -> { + switchFragment(NewsFragment::class.java, TAG_NEWS_FRAGMENT) + } - R.id.item_menu_news -> switchFragment(NewsFragment::class.java, TAG_NEWS_FRAGMENT) - R.id.item_menu_setting -> + R.id.item_menu_setting -> { switchFragment( SettingFragment::class.java, TAG_SETTING_FRAGMENT, ) + } } true } @@ -254,14 +261,22 @@ class MainActivity : private fun onMenuItemReClick() { binding.bnvMenu.setOnItemReselectedListener { icon -> when (icon.itemId) { - R.id.item_menu_home -> Unit + R.id.item_menu_home -> { + Unit + } + R.id.item_menu_schedule -> { val fragment = supportFragmentManager.findFragmentByTag(TAG_SCHEDULE_FRAGMENT) if (fragment is OnMenuItemReClickListener) fragment.onMenuItemReClick() } - R.id.item_menu_news -> Unit - R.id.item_menu_setting -> Unit + R.id.item_menu_news -> { + Unit + } + + R.id.item_menu_setting -> { + Unit + } } } } @@ -310,4 +325,4 @@ class MainActivity : flags = Intent.FLAG_ACTIVITY_SINGLE_TOP } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/daedan/festabook/presentation/setting/SettingFragment.kt b/app/src/main/java/com/daedan/festabook/presentation/setting/SettingFragment.kt index c24afadf..783a4a79 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/setting/SettingFragment.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/setting/SettingFragment.kt @@ -20,7 +20,6 @@ import com.daedan.festabook.presentation.common.showErrorSnackBar import com.daedan.festabook.presentation.common.showNotificationDeniedSnackbar import com.daedan.festabook.presentation.common.showSnackBar import com.daedan.festabook.presentation.home.HomeViewModel -import com.daedan.festabook.presentation.home.adapter.FestivalUiState import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesIntoMap import dev.zacsweers.metro.Inject From 88cbab00a92ee0fbb39736acc138fe289824cb90 Mon Sep 17 00:00:00 2001 From: Dongjoo Seo <83579348+etama123@users.noreply.github.com> Date: Wed, 7 Jan 2026 23:15:36 +0900 Subject: [PATCH 19/21] =?UTF-8?q?style(HomeArtistItem,=20HomeLineupItem,?= =?UTF-8?q?=20HomePosterList,=20FestabookImage):=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=ED=8F=AC=EB=A7=B7=ED=8C=85=20=EB=B0=8F=20=EC=8A=A4=ED=83=80?= =?UTF-8?q?=EC=9D=BC=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 프로젝트 전반의 UI 컴포저블 코드를 정리하고, 불필요한 임포트 제거 및 일관된 코드 스타일을 적용했습니다. - **`FestabookImage.kt`:** - `modifier`와 `imageUrl` 파라미터의 순서를 변경하고 코드 가독성을 위해 들여쓰기를 조정했습니다. - `CoilImage` 내부의 `modifier` 설정을 `Modifier.fillMaxSize()`로 단순화했습니다. - `FestabookImageZoomPopup` 및 미리보기(Preview) 함수의 접근 제어자를 `private`으로 수정했습니다. - 불필요한 빈 줄을 제거하고 다중 라인 호출 구조를 정돈했습니다. - **`HomePosterList.kt`:** - 더 이상 사용되지 않는 `CoilImage` 임포트를 제거했습니다. - `Modifier` 체이닝의 줄바꿈과 쉼표 누락 등 스타일을 수정했습니다. - **`HomeLineupItem.kt` & `HomeArtistItem.kt`:** - 사용하지 않는 `DividerDefaults` 임포트를 제거했습니다. - `RoundedCornerShape` 정의 및 컴포저블 내 불필요한 공백을 제거하여 코드 포맷을 정돈했습니다. --- .../common/component/FestabookImage.kt | 101 ++++++++++-------- .../home/component/HomeArtistItem.kt | 15 +-- .../home/component/HomeLineupItem.kt | 8 +- .../home/component/HomePosterList.kt | 10 +- 4 files changed, 70 insertions(+), 64 deletions(-) diff --git a/app/src/main/java/com/daedan/festabook/presentation/common/component/FestabookImage.kt b/app/src/main/java/com/daedan/festabook/presentation/common/component/FestabookImage.kt index 8815053b..734f6163 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/common/component/FestabookImage.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/common/component/FestabookImage.kt @@ -26,6 +26,7 @@ import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties import coil3.request.ImageRequest import com.daedan.festabook.BuildConfig +import com.daedan.festabook.R import com.daedan.festabook.presentation.theme.FestabookColor import com.skydoves.landscapist.ImageOptions import com.skydoves.landscapist.coil3.CoilImage @@ -34,12 +35,11 @@ import com.skydoves.landscapist.placeholder.shimmer.Shimmer import com.skydoves.landscapist.placeholder.shimmer.ShimmerPlugin import com.skydoves.landscapist.zoomable.ZoomablePlugin import com.skydoves.landscapist.zoomable.rememberZoomableState -import com.daedan.festabook.R @Composable fun FestabookImage( - modifier: Modifier = Modifier, imageUrl: String?, + modifier: Modifier = Modifier, contentDescription: String? = null, contentScale: ContentScale = ContentScale.Crop, isZoomable: Boolean = false, @@ -53,49 +53,56 @@ fun FestabookImage( var isPopUpOpen by remember { mutableStateOf(false) } Box( - modifier = modifier.then( - if (enablePopUp) Modifier.clickable { isPopUpOpen = true } - else Modifier - ) + modifier = + modifier.then( + if (enablePopUp) { + Modifier.clickable { isPopUpOpen = true } + } else { + Modifier + }, + ), ) { CoilImage( imageRequest = { - ImageRequest.Builder(context) + ImageRequest + .Builder(context) .data(convertedUrl) .apply(builder) .build() - - }, - modifier = modifier.fillMaxSize(), - imageOptions = ImageOptions( - contentScale = contentScale, - alignment = Alignment.Center, - contentDescription = contentDescription - ), - component = rememberImageComponent { - +ShimmerPlugin( - Shimmer.Flash( - baseColor = FestabookColor.gray100.copy(alpha = 0.5f), - highlightColor = FestabookColor.gray200.copy(alpha = 0.3f)), - ) - if (isZoomable) { - +ZoomablePlugin(state = zoomableState) - } }, + modifier = Modifier.fillMaxSize(), + imageOptions = + ImageOptions( + contentScale = contentScale, + alignment = Alignment.Center, + contentDescription = contentDescription, + ), + component = + rememberImageComponent { + +ShimmerPlugin( + Shimmer.Flash( + baseColor = FestabookColor.gray100.copy(alpha = 0.5f), + highlightColor = FestabookColor.gray200.copy(alpha = 0.3f), + ), + ) + if (isZoomable) { + +ZoomablePlugin(state = zoomableState) + } + }, failure = { Image( painter = painterResource(id = R.drawable.img_fallback), contentDescription = "fallback_image", modifier = Modifier.align(Alignment.Center), - contentScale = contentScale + contentScale = contentScale, ) - } + }, ) } if (isPopUpOpen && enablePopUp) { FestabookImageZoomPopup( imageUrl = imageUrl, - onDismiss = { isPopUpOpen = false } + onDismiss = { isPopUpOpen = false }, ) } } @@ -103,30 +110,32 @@ fun FestabookImage( @Composable private fun FestabookImageZoomPopup( imageUrl: String?, - onDismiss: () -> Unit + onDismiss: () -> Unit, ) { Dialog( onDismissRequest = onDismiss, - properties = DialogProperties(usePlatformDefaultWidth = false) + properties = DialogProperties(usePlatformDefaultWidth = false), ) { Box( - modifier = Modifier - .fillMaxSize() - .background(FestabookColor.black.copy(alpha = 0.8f)) + modifier = + Modifier + .fillMaxSize() + .background(FestabookColor.black.copy(alpha = 0.8f)), ) { FestabookImage( imageUrl = imageUrl, modifier = Modifier.fillMaxSize(), contentScale = ContentScale.Fit, isZoomable = true, - enablePopUp = false + enablePopUp = false, ) IconButton( onClick = onDismiss, - modifier = Modifier - .align(Alignment.TopEnd) - .padding(16.dp) + modifier = + Modifier + .align(Alignment.TopEnd) + .padding(16.dp), ) { Icon( imageVector = Icons.Default.Close, @@ -140,21 +149,23 @@ private fun FestabookImageZoomPopup( @Preview(showBackground = true) @Composable -fun FestabookImageTestPreview() { +private fun FestabookImageTestPreview() { FestabookImage( - imageUrl = "" + imageUrl = "", ) } + @Preview(showBackground = true) @Composable -fun DiaplogPreview() { +private fun DiaplogPreview() { FestabookImageZoomPopup( - imageUrl = "" + imageUrl = "", ) { } } -fun String?.convertImageUrl() = if (this != null && this.startsWith("/images/")) { - BuildConfig.FESTABOOK_URL.removeSuffix("/api/") + this -} else { - this -} \ No newline at end of file +fun String?.convertImageUrl() = + if (this != null && this.startsWith("/images/")) { + BuildConfig.FESTABOOK_URL.removeSuffix("/api/") + this + } else { + this + } diff --git a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeArtistItem.kt b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeArtistItem.kt index 7d451cca..0c3cc779 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeArtistItem.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeArtistItem.kt @@ -52,12 +52,13 @@ fun HomeArtistItem( } private object HomeArtistItem { - val ArtistImage = RoundedCornerShape( - topStartPercent = 50, - topEndPercent = 50, - bottomEndPercent = 50, - bottomStartPercent = 5, - ) + val ArtistImage = + RoundedCornerShape( + topStartPercent = 50, + topEndPercent = 50, + bottomEndPercent = 50, + bottomStartPercent = 5, + ) } @Preview @@ -67,4 +68,4 @@ private fun HomeArtistItemPreview() { artistName = "실리카겔", artistImageUrl = "sample", ) -} \ No newline at end of file +} diff --git a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeLineupItem.kt b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeLineupItem.kt index bbb106dd..9efdb59d 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeLineupItem.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeLineupItem.kt @@ -15,7 +15,6 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.DividerDefaults import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -43,7 +42,7 @@ fun HomeLineupItem( ) { // 날짜 + 배지 영역 Column( - modifier = Modifier.padding(horizontal = 16.dp).width(IntrinsicSize.Max) + modifier = Modifier.padding(horizontal = 16.dp).width(IntrinsicSize.Max), ) { Row( verticalAlignment = Alignment.CenterVertically, @@ -79,11 +78,8 @@ fun HomeLineupItem( color = FestabookColor.gray700, modifier = Modifier.fillMaxWidth(), ) - } - - Spacer(modifier = Modifier.height(8.dp)) // 아티스트 가로 리스트 @@ -98,7 +94,7 @@ fun HomeLineupItem( ) } } - + Spacer(modifier = Modifier.height(16.dp)) } } diff --git a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomePosterList.kt b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomePosterList.kt index 84fd80a7..b7c910f9 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomePosterList.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomePosterList.kt @@ -19,7 +19,6 @@ import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.util.lerp -import com.daedan.festabook.presentation.common.component.CoilImage import com.daedan.festabook.presentation.common.component.FestabookImage import com.daedan.festabook.presentation.common.component.cardBackground import kotlin.math.absoluteValue @@ -88,14 +87,13 @@ fun HomePosterList( scaleX = scale scaleY = scale this.alpha = alpha - } - .cardBackground(roundedCornerShape = 10.dp) - .clip(RoundedCornerShape(10.dp)) + }.cardBackground(roundedCornerShape = 10.dp) + .clip(RoundedCornerShape(10.dp)), ) { FestabookImage( imageUrl = imageUrl, modifier = Modifier.fillMaxSize(), - enablePopUp = true + enablePopUp = true, ) } } @@ -112,4 +110,4 @@ private fun HomePosterListPreview() { "sample", ), ) -} \ No newline at end of file +} From 85477d6f51637e7679f2493e00cfb1eb2ec37511 Mon Sep 17 00:00:00 2001 From: Dongjoo Seo <83579348+etama123@users.noreply.github.com> Date: Wed, 7 Jan 2026 23:37:52 +0900 Subject: [PATCH 20/21] =?UTF-8?q?refactor(Home):=20=EB=94=94=EC=9E=90?= =?UTF-8?q?=EC=9D=B8=20=EC=8B=9C=EC=8A=A4=ED=85=9C=20=EC=A0=81=EC=9A=A9=20?= =?UTF-8?q?=EB=B0=8F=20Fragment=20=EC=A3=BC=EC=9E=85=20=EB=B0=A9=EC=8B=9D?= =?UTF-8?q?=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../daedan/festabook/presentation/home/HomeFragment.kt | 8 +++----- .../presentation/home/component/HomePosterList.kt | 6 +++--- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/daedan/festabook/presentation/home/HomeFragment.kt b/app/src/main/java/com/daedan/festabook/presentation/home/HomeFragment.kt index 6fefc37c..24d4a0e4 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/home/HomeFragment.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/home/HomeFragment.kt @@ -22,13 +22,11 @@ import dev.zacsweers.metro.binding @ContributesIntoMap(scope = AppScope::class, binding = binding()) @FragmentKey(HomeFragment::class) -@Inject -class HomeFragment( - private val centerItemMotionEnlarger: RecyclerView.OnScrollListener, - override val defaultViewModelProviderFactory: ViewModelProvider.Factory, -) : BaseFragment() { +class HomeFragment @Inject constructor() : BaseFragment() { override val layoutId: Int = R.layout.fragment_home + @Inject + override lateinit var defaultViewModelProviderFactory: ViewModelProvider.Factory private val viewModel: HomeViewModel by viewModels({ requireActivity() }) override fun onCreateView( diff --git a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomePosterList.kt b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomePosterList.kt index b7c910f9..4e9bda6f 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomePosterList.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomePosterList.kt @@ -9,7 +9,6 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.PageSize import androidx.compose.foundation.pager.rememberPagerState -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -21,6 +20,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.util.lerp import com.daedan.festabook.presentation.common.component.FestabookImage import com.daedan.festabook.presentation.common.component.cardBackground +import com.daedan.festabook.presentation.theme.festabookShapes import kotlin.math.absoluteValue @Composable @@ -87,8 +87,8 @@ fun HomePosterList( scaleX = scale scaleY = scale this.alpha = alpha - }.cardBackground(roundedCornerShape = 10.dp) - .clip(RoundedCornerShape(10.dp)), + }.cardBackground(shape = festabookShapes.radius2) + .clip(festabookShapes.radius2), ) { FestabookImage( imageUrl = imageUrl, From 69775f6950c2f678d6d676d0cbf93b9def6b0f4b Mon Sep 17 00:00:00 2001 From: Dongjoo Seo <83579348+etama123@users.noreply.github.com> Date: Thu, 8 Jan 2026 00:01:46 +0900 Subject: [PATCH 21/21] =?UTF-8?q?test(HomeViewModelTest):=20LiveData?= =?UTF-8?q?=EB=A5=BC=20StateFlow=EB=A1=9C=20=EC=A0=84=ED=99=98=ED=95=A8?= =?UTF-8?q?=EC=97=90=20=EB=94=B0=EB=A5=B8=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../festabook/home/HomeViewModelTest.kt | 74 +++++++++++-------- 1 file changed, 44 insertions(+), 30 deletions(-) diff --git a/app/src/test/java/com/daedan/festabook/home/HomeViewModelTest.kt b/app/src/test/java/com/daedan/festabook/home/HomeViewModelTest.kt index d44b7c31..d1a7d1b9 100644 --- a/app/src/test/java/com/daedan/festabook/home/HomeViewModelTest.kt +++ b/app/src/test/java/com/daedan/festabook/home/HomeViewModelTest.kt @@ -2,18 +2,18 @@ package com.daedan.festabook.home import androidx.arch.core.executor.testing.InstantTaskExecutorRule import com.daedan.festabook.domain.repository.FestivalRepository -import com.daedan.festabook.getOrAwaitValue import com.daedan.festabook.presentation.home.HomeViewModel -import com.daedan.festabook.presentation.home.LineUpItemGroupUiModel +import com.daedan.festabook.presentation.home.LineUpItemOfDayUiModel import com.daedan.festabook.presentation.home.LineupUiState import com.daedan.festabook.presentation.home.adapter.FestivalUiState -import com.daedan.festabook.presentation.home.adapter.FestivalUiState.Loading import com.daedan.festabook.presentation.home.toUiModel import io.mockk.coEvery import io.mockk.mockk import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.launch import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.runTest @@ -64,80 +64,94 @@ class HomeViewModelTest { advanceUntilIdle() // then - val actual = homeViewModel.festivalUiState.getOrAwaitValue() - assertThat(actual).isEqualTo(expect) + val actual = homeViewModel.festivalUiState.value + assertThat(actual).isInstanceOf(FestivalUiState.Success::class.java) + assertThat((actual as FestivalUiState.Success).organization).isEqualTo(FAKE_ORGANIZATION) } @Test fun `연예인 정보를 불러올 수 있다`() = runTest { // given - val expect = - LineupUiState.Success( - LineUpItemGroupUiModel( - mapOf( - FAKE_LINEUP[0].performanceAt.toLocalDate() to FAKE_LINEUP.map { it.toUiModel() }, - ), + val expectedLineup = + listOf( + LineUpItemOfDayUiModel( + id = 0, + date = FAKE_LINEUP[0].performanceAt.toLocalDate(), + isDDay = false, + lineupItems = FAKE_LINEUP.map { it.toUiModel() }, ), ) // when - HomeViewModel(festivalRepository) advanceUntilIdle() // then - val actual = homeViewModel.lineupUiState.getOrAwaitValue() - assertThat(actual).isEqualTo(expect) + val actual = homeViewModel.lineupUiState.value + assertThat(actual).isInstanceOf(LineupUiState.Success::class.java) + + val actualItems = (actual as LineupUiState.Success).lineups + assertThat(actualItems) + .usingRecursiveComparison() + .ignoringFields("id") + .isEqualTo(expectedLineup) } @Test fun `축제 정보를 불러오는 동안은 Loading 상태로 전환한다`() = runTest { // given - var wasLoadingState = false - homeViewModel.festivalUiState.observeForever { state -> - if (state == Loading) { - wasLoadingState = true + val results = mutableListOf() + val job = + launch(UnconfinedTestDispatcher()) { + homeViewModel.festivalUiState.collect { results.add(it) } } - } // when homeViewModel.loadFestival() - advanceUntilIdle() // then - assertThat(wasLoadingState).isTrue() + testScheduler.runCurrent() + assertThat(results).contains(FestivalUiState.Loading) + + advanceUntilIdle() + assertThat(results.last()).isInstanceOf(FestivalUiState.Success::class.java) + + job.cancel() } @Test fun `축제 정보를 불러오는 데 실패하면 Error 상태로 전환한다`() = runTest { // given - val exception = Throwable("test") + val exception = Throwable("Network Error") coEvery { festivalRepository.getFestivalInfo() } returns Result.failure(exception) - // when + // when: 정보를 불러옴 homeViewModel.loadFestival() advanceUntilIdle() // then - val expect = FestivalUiState.Error(exception) - val actual = homeViewModel.festivalUiState.getOrAwaitValue() - assertThat(actual).isEqualTo(expect) + val actual = homeViewModel.festivalUiState.value + assertThat(actual).isInstanceOf(FestivalUiState.Error::class.java) } @Test fun `스케줄 이동 이벤트를 발생시킬 수 있다`() = runTest { // given - val expect = Unit + val events = mutableListOf() + val job = + launch(UnconfinedTestDispatcher()) { + homeViewModel.navigateToScheduleEvent.collect { events.add(it) } + } // when homeViewModel.navigateToScheduleClick() - advanceUntilIdle() // then - val actual = homeViewModel.navigateToScheduleEvent.value - assertThat(actual).isEqualTo(expect) + assertThat(events).hasSize(1) + + job.cancel() } }