From 091f181da60ff9247bb47b596ae97815adea9c11 Mon Sep 17 00:00:00 2001 From: kimjw Date: Fri, 8 May 2026 01:13:23 +0900 Subject: [PATCH 1/9] =?UTF-8?q?feat:=20=EC=B6=94=EC=B2=9C=20=EC=BB=AC?= =?UTF-8?q?=EB=A0=89=EC=85=98=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20UI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../flint/core/designsystem/theme/Color.kt | 47 ++++++ .../model/collection/CollectionListModel.kt | 24 +++ .../com/flint/presentation/home/HomeScreen.kt | 16 +- .../presentation/home/component/HomeBanner.kt | 17 +- .../component/HomeRecommendCollectionList.kt | 107 ++++++++++++ .../home/component/RecommendCollectionCard.kt | 152 ++++++++++++++++++ app/src/main/res/drawable/img_home_banner.png | Bin 0 -> 53199 bytes 7 files changed, 349 insertions(+), 14 deletions(-) create mode 100644 app/src/main/java/com/flint/presentation/home/component/HomeRecommendCollectionList.kt create mode 100644 app/src/main/java/com/flint/presentation/home/component/RecommendCollectionCard.kt create mode 100644 app/src/main/res/drawable/img_home_banner.png diff --git a/app/src/main/java/com/flint/core/designsystem/theme/Color.kt b/app/src/main/java/com/flint/core/designsystem/theme/Color.kt index 3ffa9f8c..96aaea4d 100644 --- a/app/src/main/java/com/flint/core/designsystem/theme/Color.kt +++ b/app/src/main/java/com/flint/core/designsystem/theme/Color.kt @@ -72,8 +72,15 @@ data class Colors( val yellow: Color, val blue: Color, val kakao: Color, + val transparent: Color, val buttonStroke: Brush, val myGradient: Brush, + val blueGradient: Brush, + val primary400Gradient: Brush, + val grayGradient: Brush, + val gray800Gradient: Brush, + val userBadgeGradient: Brush, + val userBadgeStroke: Brush, ) val FlintColors = @@ -160,6 +167,7 @@ val FlintColors = yellow = Color(0xFFF9B902), blue = Color(0xFF38A5FF), kakao = Color(0xFFFEE500), + transparent = Color.Transparent, buttonStroke = Brush.verticalGradient( colors = listOf(Color(0xFFAEAEAE), Color(0xFF666666)), @@ -168,6 +176,45 @@ val FlintColors = Brush.verticalGradient( colors = listOf(Color(0xFF424BBD).copy(1f), Color(0xFF121212).copy(alpha = 0.04f)), ), + blueGradient = + Brush.verticalGradient( + colors = listOf(Color(0xFF062845).copy(alpha = 0f), Color(0xFF062845).copy(1f)), + ), + primary400Gradient = + Brush.verticalGradient( + colors = listOf(Color(0xFF1ABFF2).copy(0f), Color(0xFF1ABFF2).copy(0.35f)), + ), + grayGradient = + Brush.verticalGradient( + colors = listOf(Color(0xFF21242C).copy(alpha = 0f), Color(0xFF21242C).copy(alpha = 1f)), + ), + gray800Gradient = + Brush.verticalGradient( + colors = listOf(Color(0xFF21242C).copy(alpha = 0f), Color(0xFF21242C).copy(alpha = 0.35f)), + ), + userBadgeGradient = object : ShaderBrush() { + override fun createShader(size: Size): Shader { + return LinearGradientShader( + from = Offset(size.width * 0.095f, size.height * 0.109f), + to = Offset(size.width * 0.921f, size.height * 0.844f), + colors = listOf(Color.White.copy(alpha = 0f), Color.White.copy(alpha = 0.1f)), + ) + } + }, + userBadgeStroke = object : ShaderBrush() { + override fun createShader(size: Size): Shader { + return LinearGradientShader( + from = Offset(size.width * 0.429f, 0f), + to = Offset(size.width * 0.738f, size.height), + colors = listOf( + Color.White.copy(alpha = 0.7f), + Color.White.copy(alpha = 0f), + Color.White.copy(alpha = 0.7f), + ), + colorStops = listOf(0f, 0.52f, 1f), + ) + } + }, ) @Preview(device = Devices.DESKTOP) diff --git a/app/src/main/java/com/flint/domain/model/collection/CollectionListModel.kt b/app/src/main/java/com/flint/domain/model/collection/CollectionListModel.kt index b31791ea..c9b56ba3 100644 --- a/app/src/main/java/com/flint/domain/model/collection/CollectionListModel.kt +++ b/app/src/main/java/com/flint/domain/model/collection/CollectionListModel.kt @@ -21,6 +21,30 @@ data class CollectionListModel( userId = "0", nickname = "nickname", profileUrl = null + ), + CollectionItemModel( + id = "1", + thumbnailUrl = "", + title = "드라마 제목", + description = "드라마 제목 드라마 제목 드라마 제목 드라마 제목 드라마 제목", + imageList = emptyList(), + bookmarkCount = 0, + isBookmarked = false, + userId = "0", + nickname = "nickname", + profileUrl = null + ), + CollectionItemModel( + id = "2", + thumbnailUrl = "", + title = "드라마 제목", + description = "드라마 제목 드라마 제목 드라마 제목 드라마 제목 드라마 제목", + imageList = emptyList(), + bookmarkCount = 0, + isBookmarked = false, + userId = "0", + nickname = "nickname", + profileUrl = null ) ) ) diff --git a/app/src/main/java/com/flint/presentation/home/HomeScreen.kt b/app/src/main/java/com/flint/presentation/home/HomeScreen.kt index 0910c6ca..30b307bf 100644 --- a/app/src/main/java/com/flint/presentation/home/HomeScreen.kt +++ b/app/src/main/java/com/flint/presentation/home/HomeScreen.kt @@ -19,6 +19,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -37,6 +38,7 @@ import com.flint.core.navigation.model.CollectionListRouteType import com.flint.presentation.home.component.HomeBanner import com.flint.presentation.home.component.HomeFab import com.flint.presentation.home.component.HomeRecentCollectionEmpty +import com.flint.presentation.home.component.HomeRecommendCollectionList import com.flint.presentation.home.sideeffect.HomeSideEffect @OptIn(ExperimentalMaterial3Api::class) @@ -149,10 +151,6 @@ private fun HomeScreen( contentPadding = PaddingValues(bottom = 20.dp), modifier = Modifier.fillMaxSize(), ) { - stickyHeader { - FlintLogoTopAppbar() - } - item { Spacer(Modifier.height(5.dp)) @@ -164,11 +162,7 @@ private fun HomeScreen( item { Spacer(Modifier.height(48.dp)) - CollectionSection( - title = "Fliner의 추천 컬렉션을 만나보세요", - description = "Fliner는 콘텐츠에 진심인, 플린트의 큐레이터들이에요", - isAllVisible = false, - onAllClick = {}, + HomeRecommendCollectionList( collectionListModel = recommendCollectionModelList, onItemClick = onRecommendCollectionItemClick, ) @@ -194,8 +188,8 @@ private fun HomeScreen( HomeRecentCollectionEmpty(navigateToExplore = navigateToExplore) } else { CollectionSection( - title = "눈여겨보고 있는 컬렉션", - description = "${userName}님이 최근 살펴본 컬렉션이에요", + title = "인기 컬렉션", + description = "사람들이 눈여겨보는 컬렉션들이에요", isAllVisible = true, onAllClick = onRecentCollectionAllClick, collectionListModel = recentCollectionModelList, diff --git a/app/src/main/java/com/flint/presentation/home/component/HomeBanner.kt b/app/src/main/java/com/flint/presentation/home/component/HomeBanner.kt index f7b6f59e..3e65a0ac 100644 --- a/app/src/main/java/com/flint/presentation/home/component/HomeBanner.kt +++ b/app/src/main/java/com/flint/presentation/home/component/HomeBanner.kt @@ -1,9 +1,11 @@ package com.flint.presentation.home.component +import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -25,12 +27,21 @@ fun HomeBanner( modifier = modifier .fillMaxWidth() - .height(270.dp) + .height(230.dp) .paint( - painter = painterResource(id = R.drawable.img_collection_bg2), + painter = painterResource(id = R.drawable.img_home_banner), contentScale = ContentScale.FillBounds, ), ) { + Image( + painter = painterResource(id = R.drawable.img_textlogo), + contentDescription = null, + modifier = Modifier + .padding(top = 54.dp) + .width(90.dp) + .height(20.dp) + ) + Text( text = "반가워요, $userName 님\n오늘은 어떤 작품이 끌리세요?", style = FlintTheme.typography.head1Sb22, @@ -38,7 +49,7 @@ fun HomeBanner( modifier = Modifier .align(Alignment.BottomStart) - .padding(start = 16.dp, bottom = 30.dp), + .padding(start = 16.dp, bottom = 20.dp), ) } } diff --git a/app/src/main/java/com/flint/presentation/home/component/HomeRecommendCollectionList.kt b/app/src/main/java/com/flint/presentation/home/component/HomeRecommendCollectionList.kt new file mode 100644 index 00000000..b913cbec --- /dev/null +++ b/app/src/main/java/com/flint/presentation/home/component/HomeRecommendCollectionList.kt @@ -0,0 +1,107 @@ +package com.flint.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.BoxWithConstraints +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.size +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.PageSize +import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.foundation.shape.CircleShape +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.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.flint.core.designsystem.theme.FlintTheme +import com.flint.domain.model.collection.CollectionListModel + +private val CARD_WIDTH = 270.dp + +@Composable +fun HomeRecommendCollectionList( + collectionListModel: CollectionListModel, + onItemClick: (id: String) -> Unit, + modifier: Modifier = Modifier, +) { + if (collectionListModel.collections.isEmpty()) return + + val pagerState = rememberPagerState(pageCount = { collectionListModel.collections.size }) + + Column(modifier = modifier.fillMaxWidth()) { + Column( + modifier = Modifier.padding(horizontal = 16.dp), + verticalArrangement = Arrangement.spacedBy(4.dp), + ) { + Text( + text = "Fliner의 추천 컬렉션", + style = FlintTheme.typography.head3Sb18, + color = FlintTheme.colors.white, + ) + Text( + text = "Fliner는 콘텐츠에 진심인, 플린트의 큐레이터들이에요", + style = FlintTheme.typography.body2R14, + color = FlintTheme.colors.gray100, + ) + } + + Spacer(Modifier.height(24.dp)) + + BoxWithConstraints(modifier = Modifier.fillMaxWidth()) { + val horizontalPadding = (maxWidth - CARD_WIDTH) / 2 + HorizontalPager( + state = pagerState, + pageSize = PageSize.Fixed(CARD_WIDTH), + pageSpacing = 12.dp, + contentPadding = PaddingValues(horizontal = horizontalPadding), + modifier = Modifier.fillMaxWidth(), + ) { page -> + RecommendCollectionCard( + item = collectionListModel.collections[page], + isCurrentPage = page == pagerState.currentPage, + onItemClick = onItemClick, + ) + } + } + + Spacer(Modifier.height(12.dp)) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(4.dp, Alignment.CenterHorizontally), + ) { + repeat(collectionListModel.collections.size) { index -> + Box( + modifier = Modifier + .size(8.dp) + .clip(CircleShape) + .background( + if (index == pagerState.currentPage) FlintTheme.colors.secondary400 + else FlintTheme.colors.gray500 + ), + ) + } + } + } +} + +@Preview(showBackground = true, backgroundColor = 0xFF121212) +@Composable +private fun HomeRecommendCollectionListPreview() { + FlintTheme { + HomeRecommendCollectionList( + collectionListModel = CollectionListModel.FakeList, + onItemClick = {}, + ) + } +} diff --git a/app/src/main/java/com/flint/presentation/home/component/RecommendCollectionCard.kt b/app/src/main/java/com/flint/presentation/home/component/RecommendCollectionCard.kt new file mode 100644 index 00000000..c16701b0 --- /dev/null +++ b/app/src/main/java/com/flint/presentation/home/component/RecommendCollectionCard.kt @@ -0,0 +1,152 @@ +package com.flint.presentation.home.component + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.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.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import com.flint.core.common.extension.noRippleClickable +import com.flint.core.designsystem.component.image.NetworkImage +import com.flint.core.designsystem.component.image.ProfileImage +import com.flint.core.designsystem.theme.FlintTheme +import com.flint.domain.model.collection.CollectionItemModel + +@Composable +fun RecommendCollectionCard( + item: CollectionItemModel, + onItemClick: (id: String) -> Unit, + isCurrentPage: Boolean, + modifier: Modifier = Modifier, +) { + val backgroundColor = if (isCurrentPage) FlintTheme.colors.primary900 else FlintTheme.colors.gray800 + val midGradient = if (isCurrentPage) FlintTheme.colors.blueGradient else FlintTheme.colors.grayGradient + val bottomGradient = if (isCurrentPage) FlintTheme.colors.primary400Gradient else FlintTheme.colors.gray800Gradient + + Box( + modifier = modifier + .width(270.dp) + .height(320.dp) + .clip(RoundedCornerShape(12.dp)) + .background(backgroundColor) + .noRippleClickable { onItemClick(item.id) }, + ) { + NetworkImage( + imageUrl = item.thumbnailUrl, + contentDescription = null, + contentScale = ContentScale.Crop, + modifier = Modifier + .fillMaxWidth() + .height(202.dp), + ) + + Box( + modifier = Modifier + .padding(top = 70.dp) + .fillMaxWidth() + .height(132.dp) + .background(midGradient), + ) + + Row( + modifier = Modifier + .padding(top = 52.dp) + .height(32.dp) + .clip(RoundedCornerShape(16.dp)) + .background(brush = FlintTheme.colors.userBadgeGradient) + .border(width = 0.5.dp, brush = FlintTheme.colors.userBadgeStroke, shape = RoundedCornerShape(16.dp)) + .padding(top = 4.dp, bottom = 4.dp, start = 6.dp, end = 8.dp) + .align(Alignment.Center), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + ProfileImage( + imageUrl = item.profileUrl, + contentDescription = null, + modifier = Modifier.size(24.dp), + ) + Text( + text = item.nickname, + style = FlintTheme.typography.caption1R12, + color = FlintTheme.colors.gray200, + maxLines = 1, + ) + } + + Column( + modifier = Modifier + .fillMaxWidth() + .align(Alignment.BottomCenter) + .padding(bottom = 35.dp), + verticalArrangement = Arrangement.spacedBy(4.dp), + ) { + Text( + text = item.title, + style = FlintTheme.typography.head3Sb18, + color = FlintTheme.colors.gray50, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 48.dp) + ) + Text( + text = item.description, + style = FlintTheme.typography.caption1R12, + color = FlintTheme.colors.gray200, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 34.dp) + ) + } + + Box( + modifier = Modifier + .fillMaxWidth() + .height(34.dp) + .align(Alignment.BottomCenter) + .background(bottomGradient) + ) + } +} + +@Preview +@Composable +private fun RecommendCollectionCardPreview() { + FlintTheme { + RecommendCollectionCard( + item = CollectionItemModel( + id = "1", + thumbnailUrl = null, + title = "추천 컬렉션 제목", + description = "추천 컬렉션에 대한 설명입니다. 여러 줄일 경우 어떻게 보이는지 확인하기 위해 길게 작성합니다.", + nickname = "작성자 닉네임", + profileUrl = null + ), + isCurrentPage = true, + onItemClick = {} + ) + } +} diff --git a/app/src/main/res/drawable/img_home_banner.png b/app/src/main/res/drawable/img_home_banner.png new file mode 100644 index 0000000000000000000000000000000000000000..35e02707d532d1a4438f471d0fa8bac71b2840f1 GIT binary patch literal 53199 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 literal 0 HcmV?d00001 From 980ec3ad90e9d9d0ac322f52d6f563338e809484 Mon Sep 17 00:00:00 2001 From: kimjw Date: Fri, 8 May 2026 01:20:46 +0900 Subject: [PATCH 2/9] =?UTF-8?q?feat:=20OTT=20=EB=B0=94=ED=85=80=EC=8B=9C?= =?UTF-8?q?=ED=8A=B8=20UI=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bottomsheet/OttListBottomSheet.kt | 28 +++++++++++++------ .../component/listItem/OttShortCutListItem.kt | 21 +------------- .../com/flint/presentation/home/HomeScreen.kt | 3 -- .../presentation/profile/ProfileScreen.kt | 3 -- 4 files changed, 21 insertions(+), 34 deletions(-) diff --git a/app/src/main/java/com/flint/core/designsystem/component/bottomsheet/OttListBottomSheet.kt b/app/src/main/java/com/flint/core/designsystem/component/bottomsheet/OttListBottomSheet.kt index 862a05c9..a9961e4c 100644 --- a/app/src/main/java/com/flint/core/designsystem/component/bottomsheet/OttListBottomSheet.kt +++ b/app/src/main/java/com/flint/core/designsystem/component/bottomsheet/OttListBottomSheet.kt @@ -1,13 +1,18 @@ package com.flint.core.designsystem.component.bottomsheet import androidx.compose.foundation.layout.Arrangement +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.lazy.LazyColumn import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.SheetState +import androidx.compose.material3.Text import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.flint.core.designsystem.component.listItem.OttShortCutListItem @@ -20,7 +25,6 @@ import com.flint.domain.type.OttType fun OttListBottomSheet( ottList: OttListModel, onDismiss: () -> Unit, - onMoveClick: (String) -> Unit, modifier: Modifier = Modifier, sheetState: SheetState = rememberModalBottomSheetState(), ) { @@ -29,17 +33,26 @@ fun OttListBottomSheet( onDismiss = onDismiss, ) { LazyColumn( - modifier = modifier.padding(top = 24.dp, bottom = 32.dp), - verticalArrangement = Arrangement.spacedBy(8.dp), + modifier = modifier.padding(bottom = 30.dp), ) { + item { + Text( + text = "이 작품을 볼 수 있는 OTT", + style = FlintTheme.typography.head3Sb18, + color = FlintTheme.colors.white, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + ) + + Spacer(Modifier.height(12.dp)) + } + items(ottList.otts.size) { OttShortCutListItem( ottModel = ottList.otts[it], - onMoveClick = { - onMoveClick(ottList.otts[it].contentUrl) - onDismiss() - }, ) + Spacer(Modifier.height(8.dp)) } } } @@ -57,7 +70,6 @@ private fun PreviewOttListBottomSheet() { OttListBottomSheet( ottList = ottList, onDismiss = {}, - onMoveClick = {}, modifier = Modifier, sheetState = sheetState, ) diff --git a/app/src/main/java/com/flint/core/designsystem/component/listItem/OttShortCutListItem.kt b/app/src/main/java/com/flint/core/designsystem/component/listItem/OttShortCutListItem.kt index ed4cba31..785da19c 100644 --- a/app/src/main/java/com/flint/core/designsystem/component/listItem/OttShortCutListItem.kt +++ b/app/src/main/java/com/flint/core/designsystem/component/listItem/OttShortCutListItem.kt @@ -31,7 +31,6 @@ import com.flint.domain.type.OttType @Composable fun OttShortCutListItem( ottModel: OttModel, - onMoveClick: () -> Unit, modifier: Modifier = Modifier, ) { Row( @@ -57,23 +56,6 @@ fun OttShortCutListItem( ) Spacer(Modifier.weight(1f)) - - Box( - modifier = - Modifier - .clip(RoundedCornerShape(8.dp)) - .background(FlintTheme.colors.primary400) - .clickable { - onMoveClick() - }.padding(vertical = 7.dp, horizontal = 12.dp), - contentAlignment = Alignment.Center, - ) { - Text( - text = "바로 보러가기", - style = FlintTypography.body2M14, - color = FlintTheme.colors.white, - ) - } } } @@ -88,8 +70,7 @@ private fun PreviewOttShortCutListItem() { name = "Netflix", logoUrl = "", contentUrl = "", - ), - onMoveClick = {}, + ) ) } } diff --git a/app/src/main/java/com/flint/presentation/home/HomeScreen.kt b/app/src/main/java/com/flint/presentation/home/HomeScreen.kt index 30b307bf..96846d2d 100644 --- a/app/src/main/java/com/flint/presentation/home/HomeScreen.kt +++ b/app/src/main/java/com/flint/presentation/home/HomeScreen.kt @@ -116,9 +116,6 @@ fun HomeRoute( OttListBottomSheet( ottList = ottListModel, onDismiss = { showOttListBottomSheet = false }, - onMoveClick = { url -> - uriHandler.openUri(url) - }, sheetState = sheetState, ) } diff --git a/app/src/main/java/com/flint/presentation/profile/ProfileScreen.kt b/app/src/main/java/com/flint/presentation/profile/ProfileScreen.kt index f9b0f971..aa55343f 100644 --- a/app/src/main/java/com/flint/presentation/profile/ProfileScreen.kt +++ b/app/src/main/java/com/flint/presentation/profile/ProfileScreen.kt @@ -124,9 +124,6 @@ fun ProfileRoute( OttListBottomSheet( ottList = ottListModel, onDismiss = { showOttListBottomSheet = false }, - onMoveClick = { url -> - uriHandler.openUri(url) - }, sheetState = sheetState, ) } From 2d1a3d86036bc6d314fc4adc0f864fb3cef0a844 Mon Sep 17 00:00:00 2001 From: kimjw Date: Fri, 8 May 2026 01:30:52 +0900 Subject: [PATCH 3/9] =?UTF-8?q?feat:=20=EC=B6=94=EC=B2=9C=20=EC=BB=AC?= =?UTF-8?q?=EB=A0=89=EC=85=98=20=EB=AC=B4=ED=95=9C=EC=8A=A4=ED=81=AC?= =?UTF-8?q?=EB=A1=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/flint/presentation/home/HomeScreen.kt | 6 +++--- .../component/HomeRecommendCollectionList.kt | 20 +++++++++++++++---- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/flint/presentation/home/HomeScreen.kt b/app/src/main/java/com/flint/presentation/home/HomeScreen.kt index 96846d2d..f784899c 100644 --- a/app/src/main/java/com/flint/presentation/home/HomeScreen.kt +++ b/app/src/main/java/com/flint/presentation/home/HomeScreen.kt @@ -157,7 +157,7 @@ private fun HomeScreen( } item { - Spacer(Modifier.height(48.dp)) + Spacer(Modifier.height(24.dp)) HomeRecommendCollectionList( collectionListModel = recommendCollectionModelList, @@ -166,7 +166,7 @@ private fun HomeScreen( } item { - Spacer(Modifier.height(48.dp)) + Spacer(Modifier.height(42.dp)) SavedContentsSection( title = "최근 저장한 콘텐츠", @@ -179,7 +179,7 @@ private fun HomeScreen( } item { - Spacer(Modifier.height(48.dp)) + Spacer(Modifier.height(42.dp)) if (recentCollectionModelList.collections.isEmpty()) { HomeRecentCollectionEmpty(navigateToExplore = navigateToExplore) diff --git a/app/src/main/java/com/flint/presentation/home/component/HomeRecommendCollectionList.kt b/app/src/main/java/com/flint/presentation/home/component/HomeRecommendCollectionList.kt index b913cbec..c39233c6 100644 --- a/app/src/main/java/com/flint/presentation/home/component/HomeRecommendCollectionList.kt +++ b/app/src/main/java/com/flint/presentation/home/component/HomeRecommendCollectionList.kt @@ -27,6 +27,7 @@ import com.flint.core.designsystem.theme.FlintTheme import com.flint.domain.model.collection.CollectionListModel private val CARD_WIDTH = 270.dp +private const val INFINITE_PAGE_COUNT = Int.MAX_VALUE @Composable fun HomeRecommendCollectionList( @@ -36,7 +37,16 @@ fun HomeRecommendCollectionList( ) { if (collectionListModel.collections.isEmpty()) return - val pagerState = rememberPagerState(pageCount = { collectionListModel.collections.size }) + val actualCount = collectionListModel.collections.size + + // 초기 페이지를 중간 아이템으로 설정하면서 양방향 무한 스크롤 가능하도록 중앙 정렬 + val half = INFINITE_PAGE_COUNT / 2 + val initialPage = half - (half % actualCount) + (actualCount / 2) + + val pagerState = rememberPagerState( + initialPage = initialPage, + pageCount = { INFINITE_PAGE_COUNT }, + ) Column(modifier = modifier.fillMaxWidth()) { Column( @@ -66,8 +76,9 @@ fun HomeRecommendCollectionList( contentPadding = PaddingValues(horizontal = horizontalPadding), modifier = Modifier.fillMaxWidth(), ) { page -> + val actualIndex = page % actualCount RecommendCollectionCard( - item = collectionListModel.collections[page], + item = collectionListModel.collections[actualIndex], isCurrentPage = page == pagerState.currentPage, onItemClick = onItemClick, ) @@ -76,17 +87,18 @@ fun HomeRecommendCollectionList( Spacer(Modifier.height(12.dp)) + val currentIndex = pagerState.currentPage % actualCount Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(4.dp, Alignment.CenterHorizontally), ) { - repeat(collectionListModel.collections.size) { index -> + repeat(actualCount) { index -> Box( modifier = Modifier .size(8.dp) .clip(CircleShape) .background( - if (index == pagerState.currentPage) FlintTheme.colors.secondary400 + if (index == currentIndex) FlintTheme.colors.secondary400 else FlintTheme.colors.gray500 ), ) From cb0faa52df8e132b691bfb106d9b56a1ff17c433 Mon Sep 17 00:00:00 2001 From: kimjw Date: Fri, 8 May 2026 01:46:47 +0900 Subject: [PATCH 4/9] =?UTF-8?q?feat:=20RECENT=20->=20FAMOUS=20=EB=9D=BC?= =?UTF-8?q?=EC=9A=B0=ED=8A=B8=20=ED=83=80=EC=9E=85=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?=EB=B0=8F=20=ED=99=88=20=EC=9D=B8=EA=B8=B0=20=EC=BB=AC=EB=A0=89?= =?UTF-8?q?=EC=85=98=20UI=20=EA=B0=9C=ED=8E=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- .../model/CollectionListRouteType.kt | 2 +- .../collectionlist/CollectionListViewModel.kt | 2 +- .../com/flint/presentation/home/HomeScreen.kt | 48 +++++++------------ 3 files changed, 20 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/com/flint/core/navigation/model/CollectionListRouteType.kt b/app/src/main/java/com/flint/core/navigation/model/CollectionListRouteType.kt index 01b19ea2..fc450358 100644 --- a/app/src/main/java/com/flint/core/navigation/model/CollectionListRouteType.kt +++ b/app/src/main/java/com/flint/core/navigation/model/CollectionListRouteType.kt @@ -5,5 +5,5 @@ enum class CollectionListRouteType( ) { CREATED(title = "전체 컬렉션"), SAVED(title = "저장 컬렉션"), - RECENT(title = "눈여겨보고 있는 컬렉션"), + FAMOUS(title = "인기 컬렉션") } \ No newline at end of file diff --git a/app/src/main/java/com/flint/presentation/collectionlist/CollectionListViewModel.kt b/app/src/main/java/com/flint/presentation/collectionlist/CollectionListViewModel.kt index 5e6c34c6..d2f3ffcc 100644 --- a/app/src/main/java/com/flint/presentation/collectionlist/CollectionListViewModel.kt +++ b/app/src/main/java/com/flint/presentation/collectionlist/CollectionListViewModel.kt @@ -60,7 +60,7 @@ class CollectionListViewModel @Inject constructor( when (data.routeType) { CollectionListRouteType.CREATED -> userRepository.getUserCreatedCollections(userId = data.userId) CollectionListRouteType.SAVED -> userRepository.getUserBookmarkedCollections(userId = data.userId) - CollectionListRouteType.RECENT -> collectionRepository.getRecentCollectionList() // 홈 + CollectionListRouteType.FAMOUS -> collectionRepository.getRecentCollectionList() // TODO 종우 Api 나오면 붙이기 }.onSuccess { result -> _uiState.update { it.copy(collectionList = UiState.Success(result)) } }.onFailure { diff --git a/app/src/main/java/com/flint/presentation/home/HomeScreen.kt b/app/src/main/java/com/flint/presentation/home/HomeScreen.kt index f784899c..48bbcc1d 100644 --- a/app/src/main/java/com/flint/presentation/home/HomeScreen.kt +++ b/app/src/main/java/com/flint/presentation/home/HomeScreen.kt @@ -19,7 +19,6 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -29,7 +28,6 @@ import com.flint.core.common.util.UiState import com.flint.core.designsystem.component.bottomsheet.OttListBottomSheet import com.flint.core.designsystem.component.listView.CollectionSection import com.flint.core.designsystem.component.listView.SavedContentsSection -import com.flint.core.designsystem.component.topappbar.FlintLogoTopAppbar import com.flint.core.designsystem.theme.FlintTheme import com.flint.domain.model.collection.CollectionListModel import com.flint.domain.model.content.BookmarkedContentListModel @@ -37,7 +35,6 @@ import com.flint.domain.model.ott.OttListModel import com.flint.core.navigation.model.CollectionListRouteType import com.flint.presentation.home.component.HomeBanner import com.flint.presentation.home.component.HomeFab -import com.flint.presentation.home.component.HomeRecentCollectionEmpty import com.flint.presentation.home.component.HomeRecommendCollectionList import com.flint.presentation.home.sideeffect.HomeSideEffect @@ -82,24 +79,21 @@ fun HomeRoute( is UiState.Success -> { val recommendedCollectionList = (uiState.recommendedCollectionListLoadState as? UiState.Success)?.data ?: CollectionListModel() val bookmarkedContentList = (uiState.bookmarkedContentListLoadState as? UiState.Success)?.data ?: BookmarkedContentListModel() + // TODO 종우 recent -> famous val recentCollectionList = (uiState.recentCollectionListLoadState as? UiState.Success)?.data ?: CollectionListModel() HomeScreen( userName = uiState.userName, recommendCollectionModelList = recommendedCollectionList, - recentCollectionModelList = recentCollectionList, + famousCollectionModelList = recentCollectionList, savedContentModelList = bookmarkedContentList, navigateToCollectionCreate = { navigateToCollectionCreate() }, - navigateToExplore = { - // TODO navigate to explore - navigateToExplore() - }, - onRecentCollectionItemClick = { collectionId -> + onFamousCollectionItemClick = { collectionId -> navigateToCollectionDetail(collectionId) }, - onRecentCollectionAllClick = { navigateToCollectionList(CollectionListRouteType.RECENT) }, + onFamousCollectionAllClick = { navigateToCollectionList(CollectionListRouteType.FAMOUS) }, onRecommendCollectionItemClick = { collectionId -> navigateToCollectionDetail(collectionId) }, @@ -127,12 +121,11 @@ private fun HomeScreen( userName: String, recommendCollectionModelList: CollectionListModel, savedContentModelList: BookmarkedContentListModel, - recentCollectionModelList: CollectionListModel, + famousCollectionModelList: CollectionListModel, onRecommendCollectionItemClick: (collectionId: String) -> Unit, onSavedContentItemClick: (contentId: String) -> Unit, - onRecentCollectionItemClick: (collectionId: String) -> Unit, - onRecentCollectionAllClick: () -> Unit, - navigateToExplore: () -> Unit, + onFamousCollectionItemClick: (collectionId: String) -> Unit, + onFamousCollectionAllClick: () -> Unit, navigateToCollectionCreate: () -> Unit, modifier: Modifier = Modifier, ) { @@ -181,18 +174,14 @@ private fun HomeScreen( item { Spacer(Modifier.height(42.dp)) - if (recentCollectionModelList.collections.isEmpty()) { - HomeRecentCollectionEmpty(navigateToExplore = navigateToExplore) - } else { - CollectionSection( - title = "인기 컬렉션", - description = "사람들이 눈여겨보는 컬렉션들이에요", - isAllVisible = true, - onAllClick = onRecentCollectionAllClick, - collectionListModel = recentCollectionModelList, - onItemClick = onRecentCollectionItemClick, - ) - } + CollectionSection( + title = "인기 컬렉션", + description = "사람들이 눈여겨보는 컬렉션들이에요", + isAllVisible = true, + onAllClick = onFamousCollectionAllClick, + collectionListModel = famousCollectionModelList, + onItemClick = onFamousCollectionItemClick, + ) } } @@ -217,12 +206,11 @@ private fun PreviewHomeScreen() { userName = "종우", recommendCollectionModelList = collectionModelList, savedContentModelList = contentModelList, - recentCollectionModelList = collectionModelList, + famousCollectionModelList = collectionModelList, onRecommendCollectionItemClick = {}, onSavedContentItemClick = {}, - onRecentCollectionItemClick = {}, - onRecentCollectionAllClick = {}, - navigateToExplore = {}, + onFamousCollectionItemClick = {}, + onFamousCollectionAllClick = {}, navigateToCollectionCreate = {}, ) } From be08a7c16e06975e86653890193c435f8d55b57f Mon Sep 17 00:00:00 2001 From: kimjw Date: Sun, 10 May 2026 23:30:21 +0900 Subject: [PATCH 5/9] =?UTF-8?q?feat:=20=ED=99=88=20=EC=9D=B8=EA=B8=B0=20?= =?UTF-8?q?=EC=BB=AC=EB=A0=89=EC=85=98=20API=20=EC=97=B0=EB=8F=99=20?= =?UTF-8?q?=EB=B0=8F=20=EA=B2=80=EC=83=89=20=EA=B3=B5=EA=B0=9C=20=EC=97=94?= =?UTF-8?q?=EB=93=9C=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?=EC=A0=9C=EC=99=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - GET /api/v1/home/popular-collections 엔드포인트 추가 - PopularCollectionResponseDto 신규 생성 및 CollectionListModel 매퍼 추가 - HomeViewModel recentCollection → popularCollection 교체, CollectionRepository 의존성 제거 - CollectionListViewModel FAMOUS 라우트 popular API로 교체 - TokenInterceptor에서 /api/v1/search/contents 요청 시 토큰 헤더 제외 Co-Authored-By: Claude Sonnet 4.6 --- .../main/java/com/flint/data/api/HomeApi.kt | 6 ++++- .../data/di/interceptor/TokenInterceptor.kt | 3 ++- .../response/PopularCollectionResponseDto.kt | 24 +++++++++++++++++++ .../mapper/collection/CollectionMapper.kt | 18 ++++++++++++++ .../flint/domain/repository/HomeRepository.kt | 3 +++ .../collectionlist/CollectionListViewModel.kt | 4 +++- .../com/flint/presentation/home/HomeScreen.kt | 7 +++--- .../flint/presentation/home/HomeViewModel.kt | 18 +++++++------- .../presentation/home/uistate/HomeUiState.kt | 8 +++---- 9 files changed, 70 insertions(+), 21 deletions(-) create mode 100644 app/src/main/java/com/flint/data/dto/home/response/PopularCollectionResponseDto.kt diff --git a/app/src/main/java/com/flint/data/api/HomeApi.kt b/app/src/main/java/com/flint/data/api/HomeApi.kt index e9f41708..1e26530a 100644 --- a/app/src/main/java/com/flint/data/api/HomeApi.kt +++ b/app/src/main/java/com/flint/data/api/HomeApi.kt @@ -1,11 +1,15 @@ package com.flint.data.api import com.flint.data.dto.base.BaseResponse +import com.flint.data.dto.home.response.PopularCollectionResponseDto import com.flint.data.dto.home.response.RecommendCollectionResponseDto import retrofit2.http.GET interface HomeApi { @GET("/api/v1/home/recommended-collections") - suspend fun getRecommendedCollections() : BaseResponse + suspend fun getRecommendedCollections(): BaseResponse + + @GET("/api/v1/home/popular-collections") + suspend fun getPopularCollections(): BaseResponse } diff --git a/app/src/main/java/com/flint/data/di/interceptor/TokenInterceptor.kt b/app/src/main/java/com/flint/data/di/interceptor/TokenInterceptor.kt index dcd107f0..8b49fd00 100644 --- a/app/src/main/java/com/flint/data/di/interceptor/TokenInterceptor.kt +++ b/app/src/main/java/com/flint/data/di/interceptor/TokenInterceptor.kt @@ -23,7 +23,8 @@ class TokenInterceptor val requestBuilder = originalRequest.newBuilder() - if (accessToken.isNotEmpty()) { + val isPublicEndpoint = originalRequest.url.encodedPath == "/api/v1/search/contents" + if (accessToken.isNotEmpty() && !isPublicEndpoint) { requestBuilder.header("Authorization", "Bearer $accessToken") } diff --git a/app/src/main/java/com/flint/data/dto/home/response/PopularCollectionResponseDto.kt b/app/src/main/java/com/flint/data/dto/home/response/PopularCollectionResponseDto.kt new file mode 100644 index 00000000..394b634b --- /dev/null +++ b/app/src/main/java/com/flint/data/dto/home/response/PopularCollectionResponseDto.kt @@ -0,0 +1,24 @@ +package com.flint.data.dto.home.response + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class PopularCollectionResponseDto( + @SerialName("collections") + val collections: List +) + +@Serializable +data class PopularCollectionItemResponseDto( + @SerialName("id") + val id: String, + @SerialName("thumbnailUrl") + val thumbnailUrl: String?, + @SerialName("title") + val title: String, + @SerialName("nickname") + val nickname: String, + @SerialName("profileImageUrl") + val profileUrl: String? +) diff --git a/app/src/main/java/com/flint/domain/mapper/collection/CollectionMapper.kt b/app/src/main/java/com/flint/domain/mapper/collection/CollectionMapper.kt index 0c88cde2..2ceb3a38 100644 --- a/app/src/main/java/com/flint/domain/mapper/collection/CollectionMapper.kt +++ b/app/src/main/java/com/flint/domain/mapper/collection/CollectionMapper.kt @@ -3,6 +3,8 @@ package com.flint.domain.mapper.collection import com.flint.data.dto.collection.response.CollectionsResponseDto import com.flint.data.dto.collection.response.RecentCollectionItemResponseDto import com.flint.data.dto.collection.response.RecentCollectionListResponseDto +import com.flint.data.dto.home.response.PopularCollectionItemResponseDto +import com.flint.data.dto.home.response.PopularCollectionResponseDto import com.flint.data.dto.home.response.RecommendCollectionItemResponseDto import com.flint.data.dto.home.response.RecommendCollectionResponseDto import com.flint.data.dto.user.response.BookmarkedCollectionItemResponseDto @@ -22,6 +24,22 @@ fun CollectionsResponseDto.toModel(): CollectionsModel { } +fun PopularCollectionResponseDto.toModel(): CollectionListModel { + return CollectionListModel( + collections = collections.map { it.toModel() }.toImmutableList() + ) +} + +private fun PopularCollectionItemResponseDto.toModel(): CollectionItemModel { + return CollectionItemModel( + id = id, + thumbnailUrl = thumbnailUrl, + title = title, + nickname = nickname, + profileUrl = profileUrl + ) +} + fun RecommendCollectionResponseDto.toModel(): CollectionListModel { return CollectionListModel( collections = collections.map { it.toModel() }.toImmutableList() diff --git a/app/src/main/java/com/flint/domain/repository/HomeRepository.kt b/app/src/main/java/com/flint/domain/repository/HomeRepository.kt index 80aef558..a3e37341 100644 --- a/app/src/main/java/com/flint/domain/repository/HomeRepository.kt +++ b/app/src/main/java/com/flint/domain/repository/HomeRepository.kt @@ -12,4 +12,7 @@ class HomeRepository @Inject constructor( ) { suspend fun getRecommendedCollectionList(): Result = suspendRunCatching { apiService.getRecommendedCollections().data.toModel() } + + suspend fun getPopularCollectionList(): Result = + suspendRunCatching { apiService.getPopularCollections().data.toModel() } } diff --git a/app/src/main/java/com/flint/presentation/collectionlist/CollectionListViewModel.kt b/app/src/main/java/com/flint/presentation/collectionlist/CollectionListViewModel.kt index d2f3ffcc..55f268da 100644 --- a/app/src/main/java/com/flint/presentation/collectionlist/CollectionListViewModel.kt +++ b/app/src/main/java/com/flint/presentation/collectionlist/CollectionListViewModel.kt @@ -9,6 +9,7 @@ import com.flint.core.navigation.Route import com.flint.domain.model.collection.CollectionListModel import com.flint.domain.repository.BookmarkRepository import com.flint.domain.repository.CollectionRepository +import com.flint.domain.repository.HomeRepository import com.flint.domain.repository.UserRepository import com.flint.core.navigation.model.CollectionListRouteType import com.flint.presentation.collectionlist.sideeffect.CollectionListSideEffect @@ -31,6 +32,7 @@ class CollectionListViewModel @Inject constructor( savedStateHandle: SavedStateHandle, private val userRepository: UserRepository, private val collectionRepository: CollectionRepository, + private val homeRepository: HomeRepository, private val bookmarkRepository: BookmarkRepository, ) : ViewModel() { private val _uiState = MutableStateFlow(CollectionListUiState()) @@ -60,7 +62,7 @@ class CollectionListViewModel @Inject constructor( when (data.routeType) { CollectionListRouteType.CREATED -> userRepository.getUserCreatedCollections(userId = data.userId) CollectionListRouteType.SAVED -> userRepository.getUserBookmarkedCollections(userId = data.userId) - CollectionListRouteType.FAMOUS -> collectionRepository.getRecentCollectionList() // TODO 종우 Api 나오면 붙이기 + CollectionListRouteType.FAMOUS -> homeRepository.getPopularCollectionList() }.onSuccess { result -> _uiState.update { it.copy(collectionList = UiState.Success(result)) } }.onFailure { diff --git a/app/src/main/java/com/flint/presentation/home/HomeScreen.kt b/app/src/main/java/com/flint/presentation/home/HomeScreen.kt index 48bbcc1d..e3b4d83e 100644 --- a/app/src/main/java/com/flint/presentation/home/HomeScreen.kt +++ b/app/src/main/java/com/flint/presentation/home/HomeScreen.kt @@ -58,7 +58,7 @@ fun HomeRoute( LaunchedEffect(Unit) { viewModel.getRecommendedCollectionList() viewModel.getBookmarkedContentList() - viewModel.getRecentCollectionList() + viewModel.getPopularCollectionList() } LaunchedEffect(Unit) { @@ -79,13 +79,12 @@ fun HomeRoute( is UiState.Success -> { val recommendedCollectionList = (uiState.recommendedCollectionListLoadState as? UiState.Success)?.data ?: CollectionListModel() val bookmarkedContentList = (uiState.bookmarkedContentListLoadState as? UiState.Success)?.data ?: BookmarkedContentListModel() - // TODO 종우 recent -> famous - val recentCollectionList = (uiState.recentCollectionListLoadState as? UiState.Success)?.data ?: CollectionListModel() + val popularCollectionList = (uiState.popularCollectionListLoadState as? UiState.Success)?.data ?: CollectionListModel() HomeScreen( userName = uiState.userName, recommendCollectionModelList = recommendedCollectionList, - famousCollectionModelList = recentCollectionList, + famousCollectionModelList = popularCollectionList, savedContentModelList = bookmarkedContentList, navigateToCollectionCreate = { navigateToCollectionCreate() diff --git a/app/src/main/java/com/flint/presentation/home/HomeViewModel.kt b/app/src/main/java/com/flint/presentation/home/HomeViewModel.kt index b2b44062..59956b28 100644 --- a/app/src/main/java/com/flint/presentation/home/HomeViewModel.kt +++ b/app/src/main/java/com/flint/presentation/home/HomeViewModel.kt @@ -7,7 +7,6 @@ import com.flint.core.common.util.UiState import com.flint.data.local.PreferencesManager import com.flint.domain.model.collection.CollectionListModel import com.flint.domain.model.content.BookmarkedContentListModel -import com.flint.domain.repository.CollectionRepository import com.flint.domain.repository.ContentRepository import com.flint.domain.repository.HomeRepository import com.flint.presentation.home.sideeffect.HomeSideEffect @@ -31,13 +30,12 @@ class HomeViewModel @Inject constructor( private val preferencesManager: PreferencesManager, private val homeRepository: HomeRepository, private val contentRepository: ContentRepository, - private val collectionRepository: CollectionRepository ) : ViewModel() { private val _userName = preferencesManager.getString(USER_NAME) private val _recommendCollectionListLoadState = MutableStateFlow>(UiState.Loading) private val _bookmarkedContentListLoadState = MutableStateFlow>(UiState.Loading) - private val _recentCollectionListLoadState = MutableStateFlow>(UiState.Loading) + private val _popularCollectionListLoadState = MutableStateFlow>(UiState.Loading) private val _homeSideEffect = MutableSharedFlow() val homeSideEffect = _homeSideEffect.asSharedFlow() @@ -46,13 +44,13 @@ class HomeViewModel @Inject constructor( _userName, _recommendCollectionListLoadState, _bookmarkedContentListLoadState, - _recentCollectionListLoadState - ) { userName, recommendedCollectionList, bookmarkedContentList, recentCollectionList -> + _popularCollectionListLoadState + ) { userName, recommendedCollectionList, bookmarkedContentList, popularCollectionList -> HomeUiState( userName = userName, recommendedCollectionListLoadState = recommendedCollectionList, bookmarkedContentListLoadState = bookmarkedContentList, - recentCollectionListLoadState = recentCollectionList + popularCollectionListLoadState = popularCollectionList ) }.stateIn( scope = viewModelScope, @@ -61,7 +59,7 @@ class HomeViewModel @Inject constructor( userName = "", recommendedCollectionListLoadState = UiState.Loading, bookmarkedContentListLoadState = UiState.Loading, - recentCollectionListLoadState = UiState.Loading + popularCollectionListLoadState = UiState.Loading ) ) @@ -85,10 +83,10 @@ class HomeViewModel @Inject constructor( } } - fun getRecentCollectionList() = viewModelScope.launch { - collectionRepository.getRecentCollectionList() + fun getPopularCollectionList() = viewModelScope.launch { + homeRepository.getPopularCollectionList() .onSuccess { - _recentCollectionListLoadState.emit(UiState.Success(it)) + _popularCollectionListLoadState.emit(UiState.Success(it)) } .onFailure { Timber.e(it.message) diff --git a/app/src/main/java/com/flint/presentation/home/uistate/HomeUiState.kt b/app/src/main/java/com/flint/presentation/home/uistate/HomeUiState.kt index 571cc21f..0af55e73 100644 --- a/app/src/main/java/com/flint/presentation/home/uistate/HomeUiState.kt +++ b/app/src/main/java/com/flint/presentation/home/uistate/HomeUiState.kt @@ -8,21 +8,21 @@ data class HomeUiState( val userName: String = "", val recommendedCollectionListLoadState: UiState = UiState.Loading, val bookmarkedContentListLoadState: UiState = UiState.Loading, - val recentCollectionListLoadState: UiState = UiState.Loading + val popularCollectionListLoadState: UiState = UiState.Loading ) { val loadState: UiState get() = when { recommendedCollectionListLoadState is UiState.Loading && bookmarkedContentListLoadState is UiState.Loading && - recentCollectionListLoadState is UiState.Loading -> UiState.Loading + popularCollectionListLoadState is UiState.Loading -> UiState.Loading recommendedCollectionListLoadState is UiState.Failure || bookmarkedContentListLoadState is UiState.Failure || - recentCollectionListLoadState is UiState.Failure -> UiState.Failure + popularCollectionListLoadState is UiState.Failure -> UiState.Failure recommendedCollectionListLoadState is UiState.Success && bookmarkedContentListLoadState is UiState.Success && - recentCollectionListLoadState is UiState.Success -> UiState.Success(Unit) + popularCollectionListLoadState is UiState.Success -> UiState.Success(Unit) else -> UiState.Loading } From ca4a6b609e0a7dd920227df4445c9181c4100071 Mon Sep 17 00:00:00 2001 From: kimjw Date: Fri, 12 Jun 2026 01:24:34 +0900 Subject: [PATCH 6/9] =?UTF-8?q?feat:=20=ED=99=88=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?=EB=B6=81=EB=A7=88=ED=81=AC=20=EC=BD=98=ED=85=90=EC=B8=A0=20API?= =?UTF-8?q?=EB=A5=BC=20=EB=B6=81=EB=A7=88=ED=81=AC=20=EC=BB=AC=EB=A0=89?= =?UTF-8?q?=EC=85=98=20API=EB=A1=9C=20=EA=B5=90=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - /api/v1/contents/bookmarks(ContentRepository) → GET /api/v1/users/me/bookmarked-collections(UserRepository) - HomeViewModel: ContentRepository 제거, UserRepository 주입, getBookmarkedCollectionList()로 변경 - HomeUiState: bookmarkedContentListLoadState → bookmarkedCollectionListLoadState(CollectionListModel) - HomeScreen: SavedContentsSection → CollectionSection("저장한 컬렉션"), OTT 바텀시트 제거 - 저장 컬렉션 클릭 시 컬렉션 상세 화면으로 이동 Co-Authored-By: Claude Sonnet 4.6 --- .../com/flint/presentation/home/HomeScreen.kt | 68 ++++--------------- .../flint/presentation/home/HomeViewModel.kt | 39 +++-------- .../presentation/home/uistate/HomeUiState.kt | 9 ++- 3 files changed, 28 insertions(+), 88 deletions(-) diff --git a/app/src/main/java/com/flint/presentation/home/HomeScreen.kt b/app/src/main/java/com/flint/presentation/home/HomeScreen.kt index e3b4d83e..ef5f6d47 100644 --- a/app/src/main/java/com/flint/presentation/home/HomeScreen.kt +++ b/app/src/main/java/com/flint/presentation/home/HomeScreen.kt @@ -9,36 +9,24 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect 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.platform.LocalUriHandler import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.flint.core.common.util.UiState -import com.flint.core.designsystem.component.bottomsheet.OttListBottomSheet import com.flint.core.designsystem.component.listView.CollectionSection -import com.flint.core.designsystem.component.listView.SavedContentsSection import com.flint.core.designsystem.theme.FlintTheme import com.flint.domain.model.collection.CollectionListModel -import com.flint.domain.model.content.BookmarkedContentListModel -import com.flint.domain.model.ott.OttListModel import com.flint.core.navigation.model.CollectionListRouteType import com.flint.presentation.home.component.HomeBanner import com.flint.presentation.home.component.HomeFab import com.flint.presentation.home.component.HomeRecommendCollectionList -import com.flint.presentation.home.sideeffect.HomeSideEffect -@OptIn(ExperimentalMaterial3Api::class) @Composable fun HomeRoute( paddingValues: PaddingValues, @@ -49,43 +37,24 @@ fun HomeRoute( viewModel: HomeViewModel = hiltViewModel() ) { val uiState by viewModel.homeUiState.collectAsStateWithLifecycle() - val uriHandler = LocalUriHandler.current - - var showOttListBottomSheet by remember { mutableStateOf(false) } - var ottListModel by remember { mutableStateOf(OttListModel()) } - val sheetState = rememberModalBottomSheetState() LaunchedEffect(Unit) { viewModel.getRecommendedCollectionList() - viewModel.getBookmarkedContentList() + viewModel.getBookmarkedCollectionList() viewModel.getPopularCollectionList() } - LaunchedEffect(Unit) { - viewModel.homeSideEffect.collect { sideEffect -> - when (sideEffect) { - is HomeSideEffect.ShowOttListBottomSheet -> { - ottListModel = sideEffect.ottListModel - - if(ottListModel.otts.isNotEmpty()) { - showOttListBottomSheet = true - } - } - } - } - } - when (uiState.loadState) { is UiState.Success -> { val recommendedCollectionList = (uiState.recommendedCollectionListLoadState as? UiState.Success)?.data ?: CollectionListModel() - val bookmarkedContentList = (uiState.bookmarkedContentListLoadState as? UiState.Success)?.data ?: BookmarkedContentListModel() + val bookmarkedCollectionList = (uiState.bookmarkedCollectionListLoadState as? UiState.Success)?.data ?: CollectionListModel() val popularCollectionList = (uiState.popularCollectionListLoadState as? UiState.Success)?.data ?: CollectionListModel() HomeScreen( userName = uiState.userName, recommendCollectionModelList = recommendedCollectionList, famousCollectionModelList = popularCollectionList, - savedContentModelList = bookmarkedContentList, + savedCollectionModelList = bookmarkedCollectionList, navigateToCollectionCreate = { navigateToCollectionCreate() }, @@ -96,22 +65,14 @@ fun HomeRoute( onRecommendCollectionItemClick = { collectionId -> navigateToCollectionDetail(collectionId) }, - onSavedContentItemClick = { contentId -> - viewModel.getOttListPerContent(contentId) + onSavedCollectionItemClick = { collectionId -> + navigateToCollectionDetail(collectionId) }, modifier = Modifier.padding(paddingValues), ) } else -> {} } - - if (showOttListBottomSheet) { - OttListBottomSheet( - ottList = ottListModel, - onDismiss = { showOttListBottomSheet = false }, - sheetState = sheetState, - ) - } } @OptIn(ExperimentalFoundationApi::class) @@ -119,10 +80,10 @@ fun HomeRoute( private fun HomeScreen( userName: String, recommendCollectionModelList: CollectionListModel, - savedContentModelList: BookmarkedContentListModel, + savedCollectionModelList: CollectionListModel, famousCollectionModelList: CollectionListModel, onRecommendCollectionItemClick: (collectionId: String) -> Unit, - onSavedContentItemClick: (contentId: String) -> Unit, + onSavedCollectionItemClick: (collectionId: String) -> Unit, onFamousCollectionItemClick: (collectionId: String) -> Unit, onFamousCollectionAllClick: () -> Unit, navigateToCollectionCreate: () -> Unit, @@ -160,13 +121,13 @@ private fun HomeScreen( item { Spacer(Modifier.height(42.dp)) - SavedContentsSection( - title = "최근 저장한 콘텐츠", - description = "현재 구독 중인 OTT에서 볼 수 있는 작품들이에요", + CollectionSection( + title = "저장한 컬렉션", + description = "내가 저장한 컬렉션들이에요", isAllVisible = false, onAllClick = {}, - contentModelList = savedContentModelList, - onItemClick = onSavedContentItemClick, + collectionListModel = savedCollectionModelList, + onItemClick = onSavedCollectionItemClick, ) } @@ -199,15 +160,14 @@ private fun HomeScreen( private fun PreviewHomeScreen() { FlintTheme { val collectionModelList = CollectionListModel.FakeList - val contentModelList = BookmarkedContentListModel.FakeList HomeScreen( userName = "종우", recommendCollectionModelList = collectionModelList, - savedContentModelList = contentModelList, + savedCollectionModelList = collectionModelList, famousCollectionModelList = collectionModelList, onRecommendCollectionItemClick = {}, - onSavedContentItemClick = {}, + onSavedCollectionItemClick = {}, onFamousCollectionItemClick = {}, onFamousCollectionAllClick = {}, navigateToCollectionCreate = {}, diff --git a/app/src/main/java/com/flint/presentation/home/HomeViewModel.kt b/app/src/main/java/com/flint/presentation/home/HomeViewModel.kt index 59956b28..d4b8b319 100644 --- a/app/src/main/java/com/flint/presentation/home/HomeViewModel.kt +++ b/app/src/main/java/com/flint/presentation/home/HomeViewModel.kt @@ -6,22 +6,16 @@ import com.flint.core.common.util.DataStoreKey.USER_NAME import com.flint.core.common.util.UiState import com.flint.data.local.PreferencesManager import com.flint.domain.model.collection.CollectionListModel -import com.flint.domain.model.content.BookmarkedContentListModel -import com.flint.domain.repository.ContentRepository import com.flint.domain.repository.HomeRepository -import com.flint.presentation.home.sideeffect.HomeSideEffect +import com.flint.domain.repository.UserRepository import com.flint.presentation.home.uistate.HomeUiState import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking import timber.log.Timber import javax.inject.Inject @@ -29,27 +23,24 @@ import javax.inject.Inject class HomeViewModel @Inject constructor( private val preferencesManager: PreferencesManager, private val homeRepository: HomeRepository, - private val contentRepository: ContentRepository, + private val userRepository: UserRepository, ) : ViewModel() { private val _userName = preferencesManager.getString(USER_NAME) private val _recommendCollectionListLoadState = MutableStateFlow>(UiState.Loading) - private val _bookmarkedContentListLoadState = MutableStateFlow>(UiState.Loading) + private val _bookmarkedCollectionListLoadState = MutableStateFlow>(UiState.Loading) private val _popularCollectionListLoadState = MutableStateFlow>(UiState.Loading) - private val _homeSideEffect = MutableSharedFlow() - val homeSideEffect = _homeSideEffect.asSharedFlow() - val homeUiState: StateFlow = combine( _userName, _recommendCollectionListLoadState, - _bookmarkedContentListLoadState, + _bookmarkedCollectionListLoadState, _popularCollectionListLoadState - ) { userName, recommendedCollectionList, bookmarkedContentList, popularCollectionList -> + ) { userName, recommendedCollectionList, bookmarkedCollectionList, popularCollectionList -> HomeUiState( userName = userName, recommendedCollectionListLoadState = recommendedCollectionList, - bookmarkedContentListLoadState = bookmarkedContentList, + bookmarkedCollectionListLoadState = bookmarkedCollectionList, popularCollectionListLoadState = popularCollectionList ) }.stateIn( @@ -58,7 +49,7 @@ class HomeViewModel @Inject constructor( initialValue = HomeUiState( userName = "", recommendedCollectionListLoadState = UiState.Loading, - bookmarkedContentListLoadState = UiState.Loading, + bookmarkedCollectionListLoadState = UiState.Loading, popularCollectionListLoadState = UiState.Loading ) ) @@ -73,10 +64,10 @@ class HomeViewModel @Inject constructor( } } - fun getBookmarkedContentList() = viewModelScope.launch { - contentRepository.getBookmarkedContentList() + fun getBookmarkedCollectionList() = viewModelScope.launch { + userRepository.getUserBookmarkedCollections(userId = null) .onSuccess { - _bookmarkedContentListLoadState.emit(UiState.Success(it)) + _bookmarkedCollectionListLoadState.emit(UiState.Success(it)) } .onFailure { Timber.e(it.message) @@ -92,14 +83,4 @@ class HomeViewModel @Inject constructor( Timber.e(it.message) } } - - fun getOttListPerContent(contentId: String) = viewModelScope.launch { - contentRepository.getOttListPerContent(contentId) - .onSuccess { - _homeSideEffect.emit(HomeSideEffect.ShowOttListBottomSheet(it)) - } - .onFailure { - Timber.e(it.message) - } - } } \ No newline at end of file diff --git a/app/src/main/java/com/flint/presentation/home/uistate/HomeUiState.kt b/app/src/main/java/com/flint/presentation/home/uistate/HomeUiState.kt index 0af55e73..97849d1e 100644 --- a/app/src/main/java/com/flint/presentation/home/uistate/HomeUiState.kt +++ b/app/src/main/java/com/flint/presentation/home/uistate/HomeUiState.kt @@ -2,26 +2,25 @@ package com.flint.presentation.home.uistate import com.flint.core.common.util.UiState import com.flint.domain.model.collection.CollectionListModel -import com.flint.domain.model.content.BookmarkedContentListModel data class HomeUiState( val userName: String = "", val recommendedCollectionListLoadState: UiState = UiState.Loading, - val bookmarkedContentListLoadState: UiState = UiState.Loading, + val bookmarkedCollectionListLoadState: UiState = UiState.Loading, val popularCollectionListLoadState: UiState = UiState.Loading ) { val loadState: UiState get() = when { recommendedCollectionListLoadState is UiState.Loading && - bookmarkedContentListLoadState is UiState.Loading && + bookmarkedCollectionListLoadState is UiState.Loading && popularCollectionListLoadState is UiState.Loading -> UiState.Loading recommendedCollectionListLoadState is UiState.Failure || - bookmarkedContentListLoadState is UiState.Failure || + bookmarkedCollectionListLoadState is UiState.Failure || popularCollectionListLoadState is UiState.Failure -> UiState.Failure recommendedCollectionListLoadState is UiState.Success && - bookmarkedContentListLoadState is UiState.Success && + bookmarkedCollectionListLoadState is UiState.Success && popularCollectionListLoadState is UiState.Success -> UiState.Success(Unit) else -> UiState.Loading From 5952267eef2500e80a50462990e1a4bd7a516e3a Mon Sep 17 00:00:00 2001 From: kimjw Date: Fri, 12 Jun 2026 01:34:18 +0900 Subject: [PATCH 7/9] =?UTF-8?q?fix:=20=ED=99=88=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?=EC=B5=9C=EA=B7=BC=20=EC=A0=80=EC=9E=A5=ED=95=9C=20=EC=BD=98?= =?UTF-8?q?=ED=85=90=EC=B8=A0=20API=EB=A5=BC=20=EC=8B=A0=EA=B7=9C=20?= =?UTF-8?q?=EC=97=94=EB=93=9C=ED=8F=AC=EC=9D=B8=ED=8A=B8=EB=A1=9C=20?= =?UTF-8?q?=EA=B5=90=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - UserApi: /api/v1/contents/{userId} → /api/v1/users/{userId}/bookmarked-contents 경로 수정 - BookmarkedContentListResponseDto: totalCount, bookmarkCount, isBookmarked 필드 추가 - HomeViewModel: getUserBookmarkedContents(null) 호출, OTT 바텀시트 복구 - HomeUiState: bookmarkedContentListLoadState(BookmarkedContentListModel) 복구 - HomeScreen: SavedContentsSection 복구, 아이템 0개일 때 섹션 숨김 Co-Authored-By: Claude Sonnet 4.6 --- .../main/java/com/flint/data/api/UserApi.kt | 2 +- .../BookmarkedContentListResponseDto.kt | 8 +- .../com/flint/presentation/home/HomeScreen.kt | 94 ++++++++++++------- .../flint/presentation/home/HomeViewModel.kt | 53 ++++++----- .../presentation/home/uistate/HomeUiState.kt | 9 +- 5 files changed, 101 insertions(+), 65 deletions(-) diff --git a/app/src/main/java/com/flint/data/api/UserApi.kt b/app/src/main/java/com/flint/data/api/UserApi.kt index 7f5ec759..192f7360 100644 --- a/app/src/main/java/com/flint/data/api/UserApi.kt +++ b/app/src/main/java/com/flint/data/api/UserApi.kt @@ -36,7 +36,7 @@ interface UserApi { ): BaseResponse // 사용자별 북마크한 콘텐츠 목록 조회 - @GET("/api/v1/contents/{userId}/bookmarked-contents") + @GET("/api/v1/users/{userId}/bookmarked-contents") suspend fun getBookmarkedContentListByUserId( @Path("userId") userId: String ): BaseResponse diff --git a/app/src/main/java/com/flint/data/dto/content/response/BookmarkedContentListResponseDto.kt b/app/src/main/java/com/flint/data/dto/content/response/BookmarkedContentListResponseDto.kt index 9066b570..69e6549b 100644 --- a/app/src/main/java/com/flint/data/dto/content/response/BookmarkedContentListResponseDto.kt +++ b/app/src/main/java/com/flint/data/dto/content/response/BookmarkedContentListResponseDto.kt @@ -5,6 +5,8 @@ import kotlinx.serialization.Serializable @Serializable data class BookmarkedContentListResponseDto( + @SerialName("totalCount") + val totalCount: Int = 0, @SerialName("contents") val contents: List ) @@ -19,6 +21,10 @@ data class BookmarkedContentResponseDto( val year: Int, @SerialName("imageUrl") val imageUrl: String, + @SerialName("bookmarkCount") + val bookmarkCount: Int = 0, + @SerialName("isBookmarked") + val isBookmarked: Boolean = false, @SerialName("getOttSimpleList") val getOttSimpleList: List ) @@ -29,4 +35,4 @@ data class OttSimpleResponseDto( val ottName: String, @SerialName("logoUrl") val logoUrl: String -) \ No newline at end of file +) diff --git a/app/src/main/java/com/flint/presentation/home/HomeScreen.kt b/app/src/main/java/com/flint/presentation/home/HomeScreen.kt index ef5f6d47..4394a743 100644 --- a/app/src/main/java/com/flint/presentation/home/HomeScreen.kt +++ b/app/src/main/java/com/flint/presentation/home/HomeScreen.kt @@ -9,9 +9,14 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect 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.tooling.preview.Preview @@ -19,14 +24,20 @@ import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.flint.core.common.util.UiState +import com.flint.core.designsystem.component.bottomsheet.OttListBottomSheet import com.flint.core.designsystem.component.listView.CollectionSection +import com.flint.core.designsystem.component.listView.SavedContentsSection import com.flint.core.designsystem.theme.FlintTheme import com.flint.domain.model.collection.CollectionListModel +import com.flint.domain.model.content.BookmarkedContentListModel +import com.flint.domain.model.ott.OttListModel import com.flint.core.navigation.model.CollectionListRouteType import com.flint.presentation.home.component.HomeBanner import com.flint.presentation.home.component.HomeFab import com.flint.presentation.home.component.HomeRecommendCollectionList +import com.flint.presentation.home.sideeffect.HomeSideEffect +@OptIn(ExperimentalMaterial3Api::class) @Composable fun HomeRoute( paddingValues: PaddingValues, @@ -38,41 +49,56 @@ fun HomeRoute( ) { val uiState by viewModel.homeUiState.collectAsStateWithLifecycle() + var showOttListBottomSheet by remember { mutableStateOf(false) } + var ottListModel by remember { mutableStateOf(OttListModel()) } + val sheetState = rememberModalBottomSheetState() + LaunchedEffect(Unit) { viewModel.getRecommendedCollectionList() - viewModel.getBookmarkedCollectionList() + viewModel.getBookmarkedContentList() viewModel.getPopularCollectionList() } + LaunchedEffect(Unit) { + viewModel.homeSideEffect.collect { sideEffect -> + when (sideEffect) { + is HomeSideEffect.ShowOttListBottomSheet -> { + ottListModel = sideEffect.ottListModel + if (ottListModel.otts.isNotEmpty()) showOttListBottomSheet = true + } + } + } + } + when (uiState.loadState) { is UiState.Success -> { val recommendedCollectionList = (uiState.recommendedCollectionListLoadState as? UiState.Success)?.data ?: CollectionListModel() - val bookmarkedCollectionList = (uiState.bookmarkedCollectionListLoadState as? UiState.Success)?.data ?: CollectionListModel() + val bookmarkedContentList = (uiState.bookmarkedContentListLoadState as? UiState.Success)?.data ?: BookmarkedContentListModel() val popularCollectionList = (uiState.popularCollectionListLoadState as? UiState.Success)?.data ?: CollectionListModel() HomeScreen( userName = uiState.userName, recommendCollectionModelList = recommendedCollectionList, famousCollectionModelList = popularCollectionList, - savedCollectionModelList = bookmarkedCollectionList, - navigateToCollectionCreate = { - navigateToCollectionCreate() - }, - onFamousCollectionItemClick = { collectionId -> - navigateToCollectionDetail(collectionId) - }, + savedContentModelList = bookmarkedContentList, + navigateToCollectionCreate = { navigateToCollectionCreate() }, + onFamousCollectionItemClick = { navigateToCollectionDetail(it) }, onFamousCollectionAllClick = { navigateToCollectionList(CollectionListRouteType.FAMOUS) }, - onRecommendCollectionItemClick = { collectionId -> - navigateToCollectionDetail(collectionId) - }, - onSavedCollectionItemClick = { collectionId -> - navigateToCollectionDetail(collectionId) - }, + onRecommendCollectionItemClick = { navigateToCollectionDetail(it) }, + onSavedContentItemClick = { viewModel.getOttListPerContent(it) }, modifier = Modifier.padding(paddingValues), ) } else -> {} } + + if (showOttListBottomSheet) { + OttListBottomSheet( + ottList = ottListModel, + onDismiss = { showOttListBottomSheet = false }, + sheetState = sheetState, + ) + } } @OptIn(ExperimentalFoundationApi::class) @@ -80,10 +106,10 @@ fun HomeRoute( private fun HomeScreen( userName: String, recommendCollectionModelList: CollectionListModel, - savedCollectionModelList: CollectionListModel, + savedContentModelList: BookmarkedContentListModel, famousCollectionModelList: CollectionListModel, onRecommendCollectionItemClick: (collectionId: String) -> Unit, - onSavedCollectionItemClick: (collectionId: String) -> Unit, + onSavedContentItemClick: (contentId: String) -> Unit, onFamousCollectionItemClick: (collectionId: String) -> Unit, onFamousCollectionAllClick: () -> Unit, navigateToCollectionCreate: () -> Unit, @@ -118,17 +144,19 @@ private fun HomeScreen( ) } - item { - Spacer(Modifier.height(42.dp)) - - CollectionSection( - title = "저장한 컬렉션", - description = "내가 저장한 컬렉션들이에요", - isAllVisible = false, - onAllClick = {}, - collectionListModel = savedCollectionModelList, - onItemClick = onSavedCollectionItemClick, - ) + if (savedContentModelList.contents.isNotEmpty()) { + item { + Spacer(Modifier.height(42.dp)) + + SavedContentsSection( + title = "최근 저장한 콘텐츠", + description = "현재 구독 중인 OTT에서 볼 수 있는 작품들이에요", + isAllVisible = false, + onAllClick = {}, + contentModelList = savedContentModelList, + onItemClick = onSavedContentItemClick, + ) + } } item { @@ -159,15 +187,13 @@ private fun HomeScreen( @Composable private fun PreviewHomeScreen() { FlintTheme { - val collectionModelList = CollectionListModel.FakeList - HomeScreen( userName = "종우", - recommendCollectionModelList = collectionModelList, - savedCollectionModelList = collectionModelList, - famousCollectionModelList = collectionModelList, + recommendCollectionModelList = CollectionListModel.FakeList, + savedContentModelList = BookmarkedContentListModel.FakeList, + famousCollectionModelList = CollectionListModel.FakeList, onRecommendCollectionItemClick = {}, - onSavedCollectionItemClick = {}, + onSavedContentItemClick = {}, onFamousCollectionItemClick = {}, onFamousCollectionAllClick = {}, navigateToCollectionCreate = {}, diff --git a/app/src/main/java/com/flint/presentation/home/HomeViewModel.kt b/app/src/main/java/com/flint/presentation/home/HomeViewModel.kt index d4b8b319..e2ffdf53 100644 --- a/app/src/main/java/com/flint/presentation/home/HomeViewModel.kt +++ b/app/src/main/java/com/flint/presentation/home/HomeViewModel.kt @@ -6,13 +6,18 @@ import com.flint.core.common.util.DataStoreKey.USER_NAME import com.flint.core.common.util.UiState import com.flint.data.local.PreferencesManager import com.flint.domain.model.collection.CollectionListModel +import com.flint.domain.model.content.BookmarkedContentListModel +import com.flint.domain.repository.ContentRepository import com.flint.domain.repository.HomeRepository import com.flint.domain.repository.UserRepository +import com.flint.presentation.home.sideeffect.HomeSideEffect import com.flint.presentation.home.uistate.HomeUiState import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch @@ -24,23 +29,27 @@ class HomeViewModel @Inject constructor( private val preferencesManager: PreferencesManager, private val homeRepository: HomeRepository, private val userRepository: UserRepository, + private val contentRepository: ContentRepository, ) : ViewModel() { private val _userName = preferencesManager.getString(USER_NAME) private val _recommendCollectionListLoadState = MutableStateFlow>(UiState.Loading) - private val _bookmarkedCollectionListLoadState = MutableStateFlow>(UiState.Loading) + private val _bookmarkedContentListLoadState = MutableStateFlow>(UiState.Loading) private val _popularCollectionListLoadState = MutableStateFlow>(UiState.Loading) + private val _homeSideEffect = MutableSharedFlow() + val homeSideEffect = _homeSideEffect.asSharedFlow() + val homeUiState: StateFlow = combine( _userName, _recommendCollectionListLoadState, - _bookmarkedCollectionListLoadState, + _bookmarkedContentListLoadState, _popularCollectionListLoadState - ) { userName, recommendedCollectionList, bookmarkedCollectionList, popularCollectionList -> + ) { userName, recommendedCollectionList, bookmarkedContentList, popularCollectionList -> HomeUiState( userName = userName, recommendedCollectionListLoadState = recommendedCollectionList, - bookmarkedCollectionListLoadState = bookmarkedCollectionList, + bookmarkedContentListLoadState = bookmarkedContentList, popularCollectionListLoadState = popularCollectionList ) }.stateIn( @@ -49,38 +58,32 @@ class HomeViewModel @Inject constructor( initialValue = HomeUiState( userName = "", recommendedCollectionListLoadState = UiState.Loading, - bookmarkedCollectionListLoadState = UiState.Loading, + bookmarkedContentListLoadState = UiState.Loading, popularCollectionListLoadState = UiState.Loading ) ) fun getRecommendedCollectionList() = viewModelScope.launch { homeRepository.getRecommendedCollectionList() - .onSuccess { - _recommendCollectionListLoadState.emit(UiState.Success(it)) - } - .onFailure { - Timber.e(it.message) - } + .onSuccess { _recommendCollectionListLoadState.emit(UiState.Success(it)) } + .onFailure { Timber.e(it.message) } } - fun getBookmarkedCollectionList() = viewModelScope.launch { - userRepository.getUserBookmarkedCollections(userId = null) - .onSuccess { - _bookmarkedCollectionListLoadState.emit(UiState.Success(it)) - } - .onFailure { - Timber.e(it.message) - } + fun getBookmarkedContentList() = viewModelScope.launch { + userRepository.getUserBookmarkedContents(userId = null) + .onSuccess { _bookmarkedContentListLoadState.emit(UiState.Success(it)) } + .onFailure { Timber.e(it.message) } } fun getPopularCollectionList() = viewModelScope.launch { homeRepository.getPopularCollectionList() - .onSuccess { - _popularCollectionListLoadState.emit(UiState.Success(it)) - } - .onFailure { - Timber.e(it.message) - } + .onSuccess { _popularCollectionListLoadState.emit(UiState.Success(it)) } + .onFailure { Timber.e(it.message) } + } + + fun getOttListPerContent(contentId: String) = viewModelScope.launch { + contentRepository.getOttListPerContent(contentId) + .onSuccess { _homeSideEffect.emit(HomeSideEffect.ShowOttListBottomSheet(it)) } + .onFailure { Timber.e(it.message) } } } \ No newline at end of file diff --git a/app/src/main/java/com/flint/presentation/home/uistate/HomeUiState.kt b/app/src/main/java/com/flint/presentation/home/uistate/HomeUiState.kt index 97849d1e..0af55e73 100644 --- a/app/src/main/java/com/flint/presentation/home/uistate/HomeUiState.kt +++ b/app/src/main/java/com/flint/presentation/home/uistate/HomeUiState.kt @@ -2,25 +2,26 @@ package com.flint.presentation.home.uistate import com.flint.core.common.util.UiState import com.flint.domain.model.collection.CollectionListModel +import com.flint.domain.model.content.BookmarkedContentListModel data class HomeUiState( val userName: String = "", val recommendedCollectionListLoadState: UiState = UiState.Loading, - val bookmarkedCollectionListLoadState: UiState = UiState.Loading, + val bookmarkedContentListLoadState: UiState = UiState.Loading, val popularCollectionListLoadState: UiState = UiState.Loading ) { val loadState: UiState get() = when { recommendedCollectionListLoadState is UiState.Loading && - bookmarkedCollectionListLoadState is UiState.Loading && + bookmarkedContentListLoadState is UiState.Loading && popularCollectionListLoadState is UiState.Loading -> UiState.Loading recommendedCollectionListLoadState is UiState.Failure || - bookmarkedCollectionListLoadState is UiState.Failure || + bookmarkedContentListLoadState is UiState.Failure || popularCollectionListLoadState is UiState.Failure -> UiState.Failure recommendedCollectionListLoadState is UiState.Success && - bookmarkedCollectionListLoadState is UiState.Success && + bookmarkedContentListLoadState is UiState.Success && popularCollectionListLoadState is UiState.Success -> UiState.Success(Unit) else -> UiState.Loading From 579bc1951f802a9880452381e6154e7298938eba Mon Sep 17 00:00:00 2001 From: kimjw Date: Fri, 12 Jun 2026 01:44:47 +0900 Subject: [PATCH 8/9] =?UTF-8?q?feat:=20=EC=BB=AC=EB=A0=89=EC=85=98=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=ED=99=94=EB=A9=B4=20=ED=95=98=EB=8B=A8=20?= =?UTF-8?q?=EA=B7=B8=EB=9D=BC=EB=8D=B0=EC=9D=B4=EC=85=98=20=EA=B7=B8?= =?UTF-8?q?=EB=A6=BC=EC=9E=90=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 스크롤 가능한 콘텐츠가 있음을 암시하는 하단 페이드 오버레이 추가 (투명 → 배경색, 80dp) Co-Authored-By: Claude Sonnet 4.6 --- .../collectionlist/CollectionListScreen.kt | 127 ++++++++++-------- 1 file changed, 71 insertions(+), 56 deletions(-) diff --git a/app/src/main/java/com/flint/presentation/collectionlist/CollectionListScreen.kt b/app/src/main/java/com/flint/presentation/collectionlist/CollectionListScreen.kt index 1a6ad0d4..65ccbe5e 100644 --- a/app/src/main/java/com/flint/presentation/collectionlist/CollectionListScreen.kt +++ b/app/src/main/java/com/flint/presentation/collectionlist/CollectionListScreen.kt @@ -23,6 +23,8 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel @@ -112,76 +114,89 @@ private fun CollectionListScreen( onBookmarkClick: (collectionId: String) -> Unit, modifier: Modifier = Modifier, ) { - Column( - modifier = - modifier - .fillMaxSize() - .background(FlintTheme.colors.background), + Box( + modifier = modifier + .fillMaxSize() + .background(FlintTheme.colors.background), ) { - FlintBackTopAppbar( - onClick = onBackClick, - title = title, - backgroundColor = FlintTheme.colors.background, - ) + Column(modifier = Modifier.fillMaxSize()) { + FlintBackTopAppbar( + onClick = onBackClick, + title = title, + backgroundColor = FlintTheme.colors.background, + ) - when (collectionList) { - is UiState.Loading -> { - FlintLoadingIndicator() - } + when (collectionList) { + is UiState.Loading -> { + FlintLoadingIndicator() + } - is UiState.Success -> { - with(collectionList.data) { - LazyVerticalGrid( - contentPadding = PaddingValues(10.dp), - columns = GridCells.Fixed(2), - horizontalArrangement = Arrangement.spacedBy(12.dp, Alignment.CenterHorizontally), - verticalArrangement = Arrangement.spacedBy(12.dp), - modifier = Modifier.padding(horizontal = 10.dp), - ) { - item( - span = { GridItemSpan(maxLineSpan) } + is UiState.Success -> { + with(collectionList.data) { + LazyVerticalGrid( + contentPadding = PaddingValues(10.dp), + columns = GridCells.Fixed(2), + horizontalArrangement = Arrangement.spacedBy(12.dp, Alignment.CenterHorizontally), + verticalArrangement = Arrangement.spacedBy(12.dp), + modifier = Modifier.padding(horizontal = 10.dp), ) { - Spacer(Modifier.height(12.dp)) - - Text( - text = "총 ${collections.size}개", - color = FlintTheme.colors.gray100, - modifier = - Modifier - .fillMaxWidth() - .padding(horizontal = 6.dp) - .padding(bottom = 14.dp), - ) - } - - items( - items = collections, - key = { it.id }, - ) { collection -> - Box( - modifier = - Modifier - .fillMaxSize(1f) - .align(Alignment.CenterHorizontally), + item( + span = { GridItemSpan(maxLineSpan) } ) { - CollectionFileItem( - collection = collection, - onBookmarkClick = { onBookmarkClick(collection.id) }, + Spacer(Modifier.height(12.dp)) + + Text( + text = "총 ${collections.size}개", + color = FlintTheme.colors.gray100, modifier = Modifier - .align(Alignment.Center) - .noRippleClickable( - onClick = { onCollectionItemClick(collection.id) }, - ), + .fillMaxWidth() + .padding(horizontal = 6.dp) + .padding(bottom = 14.dp), ) } + + items( + items = collections, + key = { it.id }, + ) { collection -> + Box( + modifier = + Modifier + .fillMaxSize(1f) + .align(Alignment.CenterHorizontally), + ) { + CollectionFileItem( + collection = collection, + onBookmarkClick = { onBookmarkClick(collection.id) }, + modifier = + Modifier + .align(Alignment.Center) + .noRippleClickable( + onClick = { onCollectionItemClick(collection.id) }, + ), + ) + } + } } } } - } - else -> {} + else -> {} + } } + + Box( + modifier = Modifier + .fillMaxWidth() + .height(80.dp) + .align(Alignment.BottomCenter) + .background( + Brush.verticalGradient( + colors = listOf(Color.Transparent, FlintTheme.colors.background) + ) + ) + ) } } From 3a938c301325a5f7e384ffd4ebf4642b27c87a42 Mon Sep 17 00:00:00 2001 From: kimjw Date: Fri, 12 Jun 2026 01:47:19 +0900 Subject: [PATCH 9/9] =?UTF-8?q?style:=20=EC=BB=AC=EB=A0=89=EC=85=98=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=ED=95=98=EB=8B=A8=20=EA=B7=B8=EB=9D=BC?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=EC=85=98=20=EB=86=92=EC=9D=B4=20148dp?= =?UTF-8?q?=EB=A1=9C=20=EC=A1=B0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- .../flint/presentation/collectionlist/CollectionListScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/flint/presentation/collectionlist/CollectionListScreen.kt b/app/src/main/java/com/flint/presentation/collectionlist/CollectionListScreen.kt index 65ccbe5e..97d4f51c 100644 --- a/app/src/main/java/com/flint/presentation/collectionlist/CollectionListScreen.kt +++ b/app/src/main/java/com/flint/presentation/collectionlist/CollectionListScreen.kt @@ -189,7 +189,7 @@ private fun CollectionListScreen( Box( modifier = Modifier .fillMaxWidth() - .height(80.dp) + .height(148.dp) .align(Alignment.BottomCenter) .background( Brush.verticalGradient(