From 01e35c7e8ecbb61011823d05d2e5ff1499664cf9 Mon Sep 17 00:00:00 2001 From: gmin Date: Fri, 30 Jan 2026 21:50:30 +0900 Subject: [PATCH 01/13] =?UTF-8?q?=F0=9F=93=A6=20feature/file=20=EB=AA=A8?= =?UTF-8?q?=EB=93=88=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EB=B3=80=EA=B2=BD:?= =?UTF-8?q?=20foundation-layout=EC=9D=84=20foundation=EC=9C=BC=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 - `feature/file/build.gradle.kts`에서 `androidx.compose.foundation.layout` 의존성을 `androidx.compose.foundation`으로 변경 --- feature/file/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feature/file/build.gradle.kts b/feature/file/build.gradle.kts index 7355fce..7be8d8f 100644 --- a/feature/file/build.gradle.kts +++ b/feature/file/build.gradle.kts @@ -53,7 +53,7 @@ dependencies { implementation(libs.androidx.ui.tooling.preview) implementation(libs.androidx.material3) implementation(libs.androidx.navigation.compose) - implementation(libs.androidx.compose.foundation.layout) + implementation(libs.androidx.compose.foundation) testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) From 50a844eb37dba359cfd15e706e9dab95857e91bc Mon Sep 17 00:00:00 2001 From: gmin Date: Mon, 2 Feb 2026 19:30:55 +0900 Subject: [PATCH 02/13] =?UTF-8?q?=E2=9C=A8=20feature/file:=20=EB=A7=81?= =?UTF-8?q?=ED=81=AC=20=EC=82=AD=EC=A0=9C=20=EB=AA=A8=EB=8B=AC=20=EB=AC=B8?= =?UTF-8?q?=EA=B5=AC=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20=EB=AF=B8=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=20=EC=9E=84=ED=8F=AC=ED=8A=B8=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `LinksGrid.kt` 내 삭제 확인 모달의 버튼 텍스트를 "삭제" -> "삭제하기", "취소" -> "취소하기"로 변경 - 미사용 임포트(`lifecycleScope`, `rememberCoroutineScope`, `launch` 등) 제거 및 임포트 최적화 --- .../java/com/example/file/ui/content/LinksGrid.kt | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/feature/file/src/main/java/com/example/file/ui/content/LinksGrid.kt b/feature/file/src/main/java/com/example/file/ui/content/LinksGrid.kt index 179d243..91591ab 100644 --- a/feature/file/src/main/java/com/example/file/ui/content/LinksGrid.kt +++ b/feature/file/src/main/java/com/example/file/ui/content/LinksGrid.kt @@ -1,6 +1,5 @@ package com.example.file.ui.content -import androidx.lifecycle.lifecycleScope import android.util.Log import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement @@ -12,7 +11,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -28,16 +26,15 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import com.cheonjaeung.compose.grid.SimpleGridCells import com.cheonjaeung.compose.grid.VerticalGrid +import com.example.design.modifier.noRippleClickable import com.example.file.FileViewModel import com.example.file.R -import com.example.design.modifier.noRippleClickable import com.example.file.ui.item.LinkItemLayout import com.example.file.ui.modal.FileModalWindow -import com.example.file.viewmodel.folder.state.FolderStateViewModel import com.example.file.ui.theme.Black import com.example.file.ui.theme.DefaultFont import com.example.file.ui.theme.Gray600 -import kotlinx.coroutines.launch +import com.example.file.viewmodel.folder.state.FolderStateViewModel @Composable fun LinksGrid( @@ -164,8 +161,8 @@ fun LinksGrid( }, onDismiss = { deleteModalWindowVisible = false }, title = "해당 링크를 삭제하시겠습니까?", - positiveText = "삭제", - negativeText = "취소" + positiveText = "삭제하기", + negativeText = "취소하기" ) { Text( text = "삭제 시 해당 링크가 영구적으로 제거되며\n복구가 불가능합니다.", From 1326445a4a34ad579927e3f9427b513d04b2881e Mon Sep 17 00:00:00 2001 From: gmin Date: Mon, 2 Feb 2026 19:33:19 +0900 Subject: [PATCH 03/13] =?UTF-8?q?=E2=9C=A8=20feature/file:=20FileBottomShe?= =?UTF-8?q?et=20UI=20=EC=8A=A4=ED=83=80=EC=9D=BC=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=EB=B0=8F=20=EB=B0=B0=EA=B2=BD=20=EB=94=A4(scrim)=20=ED=9A=A8?= =?UTF-8?q?=EA=B3=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `FileBottomSheet`에 0.5 투명도의 블랙 딤 컬러(`scrimColor`) 적용 - 텍스트 간격 조정을 위해 `Spacer` 추가 및 불필요한 `lineHeight`, `padding` 제거 --- .../file/ui/bottom/sheet/FileBottomSheet.kt | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/feature/file/src/main/java/com/example/file/ui/bottom/sheet/FileBottomSheet.kt b/feature/file/src/main/java/com/example/file/ui/bottom/sheet/FileBottomSheet.kt index 97b93ce..1cc305b 100644 --- a/feature/file/src/main/java/com/example/file/ui/bottom/sheet/FileBottomSheet.kt +++ b/feature/file/src/main/java/com/example/file/ui/bottom/sheet/FileBottomSheet.kt @@ -10,7 +10,6 @@ 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.layout.wrapContentHeight import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape @@ -38,9 +37,9 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import com.example.file.R import com.example.design.modifier.noRippleClickable import com.example.design.theme.color.Basic +import com.example.file.R import com.example.file.ui.theme.Black import com.example.file.ui.theme.DefaultFont import com.example.file.ui.theme.Gray300 @@ -73,6 +72,9 @@ fun FileBottomSheet( color = Basic.gray[300] ) }, + + // 딤 효과 수치 + scrimColor = Basic.black.copy(alpha = 0.5f), sheetState = sheetState, onDismissRequest = onDismiss, tonalElevation = 8.dp, @@ -85,26 +87,29 @@ fun FileBottomSheet( .padding(bottom = 20.dp) .padding(horizontal = 20.dp), ) { + Text( modifier = Modifier .padding(start = 10.dp), text = title, fontSize = 18.sp, - lineHeight = 22.sp, fontFamily = DefaultFont, fontWeight = FontWeight(500), color = Black, ) + + Spacer(modifier = Modifier.height(11.dp)) + Text( modifier = Modifier - .padding(start = 10.dp, top = 14.dp), + .padding(start = 10.dp), text = body, fontSize = 15.sp, - lineHeight = 22.sp, fontFamily = DefaultFont, fontWeight = FontWeight.Normal, color = Gray600, ) + Spacer(modifier = Modifier.height(24.dp)) content() From 69c19e0ff1d0119b8f9da7d4f70fa84559b29aa6 Mon Sep 17 00:00:00 2001 From: gmin Date: Mon, 2 Feb 2026 19:34:09 +0900 Subject: [PATCH 04/13] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20feature/file:=20Text?= =?UTF-8?q?FieldFileBottomSheet=20=ED=99=94=EC=82=B4=ED=91=9C=20=ED=9A=8C?= =?UTF-8?q?=EC=A0=84=20=EC=95=A0=EB=8B=88=EB=A9=94=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EB=9D=BC=EB=B2=A8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `animateFloatAsState`의 `label` 파라미터에 "화살표 회전 애니메이션" 명시 및 `targetValue` 네임드 파라미터 적용 --- .../example/file/ui/bottom/sheet/TextFieldFileBottomSheet.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/feature/file/src/main/java/com/example/file/ui/bottom/sheet/TextFieldFileBottomSheet.kt b/feature/file/src/main/java/com/example/file/ui/bottom/sheet/TextFieldFileBottomSheet.kt index 81c74d2..ff546f4 100644 --- a/feature/file/src/main/java/com/example/file/ui/bottom/sheet/TextFieldFileBottomSheet.kt +++ b/feature/file/src/main/java/com/example/file/ui/bottom/sheet/TextFieldFileBottomSheet.kt @@ -185,7 +185,10 @@ fun TextFieldFileBottomSheet( ) } - val rotation by animateFloatAsState(if (expanded) 180f else 0f, label = "") + val rotation by animateFloatAsState( + targetValue = if (expanded) 180f else 0f, + label = "화살표 회전 애니메이션" + ) val modifier = if(expanded) Modifier .padding(start = 10.dp) From 6d04654c309959f35c08bfdb2f5383c92e32c5b9 Mon Sep 17 00:00:00 2001 From: gmin Date: Mon, 2 Feb 2026 19:36:05 +0900 Subject: [PATCH 05/13] =?UTF-8?q?=E2=9C=A8=20feature/file:=20LinkItemLayou?= =?UTF-8?q?t=20UI=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 - `LinkItemLayout`에 그림자 효과(`shadow`)를 적용하고 기존 `shadowElevation` 속성 제거 - 태그 UI 레이아웃 수정: `padding` 값 조정, 폰트 크기 변경(10sp -> 12sp), `includeFontPadding = false` 적용 - 컴포넌트 간 간격 조정을 위해 `Spacer` 추가 및 기존 `padding` 로직 수정 - 불필요한 `showDialog` 상태 및 미사용 import 제거 - 클릭 이벤트 로직 내 불필요한 null safety 체크 최적화 --- .../example/file/ui/item/LinkItemLayout.kt | 89 ++++++++++--------- 1 file changed, 49 insertions(+), 40 deletions(-) diff --git a/feature/file/src/main/java/com/example/file/ui/item/LinkItemLayout.kt b/feature/file/src/main/java/com/example/file/ui/item/LinkItemLayout.kt index 306ae4e..8bc9fb3 100644 --- a/feature/file/src/main/java/com/example/file/ui/item/LinkItemLayout.kt +++ b/feature/file/src/main/java/com/example/file/ui/item/LinkItemLayout.kt @@ -6,6 +6,8 @@ import android.util.Log import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image import androidx.compose.foundation.background +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -18,7 +20,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.CircleShape @@ -28,25 +29,24 @@ import androidx.compose.material3.Icon import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.shadow import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.ExperimentalTextApi +import androidx.compose.ui.text.PlatformTextStyle +import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.lifecycle.viewmodel.compose.viewModel -import androidx.navigation.compose.rememberNavController import coil3.compose.AsyncImage import coil3.request.ImageRequest import coil3.request.crossfade @@ -54,8 +54,6 @@ import coil3.request.error import coil3.request.fallback import coil3.request.placeholder import com.example.core.model.LinkItemInfo -import com.example.design.modifier.noRippleClickable -import com.example.file.FileViewModel import com.example.file.R import com.example.file.ui.theme.Black import com.example.file.ui.theme.DefaultFont @@ -66,11 +64,12 @@ import com.example.file.ui.theme.Gray600 import com.example.file.ui.theme.Gray800 import com.example.file.ui.theme.White import com.example.file.ui.theme.domainLogoPainterOrNull -import androidx.compose.foundation.combinedClickable -import androidx.compose.foundation.interaction.MutableInteractionSource -import java.time.OffsetDateTime -@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) +@OptIn( + ExperimentalMaterial3Api::class, + ExperimentalFoundationApi::class, + ExperimentalTextApi::class +) @Composable fun LinkItemLayout( link: LinkItemInfo? = null, @@ -78,7 +77,6 @@ fun LinkItemLayout( onLongClick: (Long) -> Unit = {}, ) { val tags = link?.tags?:emptyList() - var showDialog by remember { mutableStateOf(false) } val domainIcon = link?.let{ domainLogoPainterOrNull(it.url) } @@ -99,28 +97,38 @@ fun LinkItemLayout( Box ( // 태그 배경: 크기 wrap, 둥근 모서리(6dp), Gray100 배경색 modifier = Modifier - .wrapContentSize() .background( shape = RoundedCornerShape(size = 6.dp), color = Gray100 - ), + ) + .padding(horizontal = 6.dp, vertical = 1.dp), + contentAlignment = Alignment.Center ) { + // 태그 텍스트 Text( - // 태그 텍스트를 Box 중앙에 정렬, 내부 여백(가로 6dp, 세로 3dp) + // 태그 텍스트를 Box 중앙에 정렬, 내부 여백(가로 1dp, 세로 2dp) modifier = Modifier - .align(Alignment.Center) - .padding(horizontal = 6.dp, vertical = 1.dp), + .padding(horizontal = 1.dp, vertical = 2.dp), text = tag, - // 폰트 크기(10sp) - fontSize = 10.sp, + // 폰트 크기(12sp) + fontSize = 12.sp, // 폰트 fontFamily = DefaultFont, // 폰트 굵기(Normal) fontWeight = FontWeight.Normal, // 글자색(Gray600) color = Gray600, + + textAlign = TextAlign.Center, + + style = TextStyle( + platformStyle = PlatformTextStyle( + includeFontPadding = false + ) + ) ) + } } @@ -128,7 +136,7 @@ fun LinkItemLayout( indication = null, interactionSource = remember { MutableInteractionSource() }, onClick = { - link?.linkuId?.let { + link.linkuId.let { Log.d("LinkItemLayout", "아이템 클릭: \"savelinkresult/${it}\"") onClick(link) } @@ -144,21 +152,17 @@ fun LinkItemLayout( // 카드 크기: 가로 181dp, 세로 267dp modifier = Modifier .width(181.dp) - //.height(267.dp) - then(modifier) - /*.pointerInput(Unit) { - detectTapGestures( - onLongPress = { - showDialog = true // 꾹 누르면 Dialog 띄우기 - } - ) - }*/, + .then(modifier) + .shadow( + elevation = 4.dp, + shape = RoundedCornerShape(18.dp) + ), // 모서리 둥글게(18dp) - shape = RoundedCornerShape(18.dp), + //shape = RoundedCornerShape(18.dp), // 카드 배경색(White) color = White, // 그림자(입체감) 효과(20dp) - shadowElevation = 5.dp//(if(painter == null) 0 else 5).dp, + //shadowElevation = 5.dp//(if(painter == null) 0 else 5).dp, ) { // 내용 전체를 세로로 배치하는 Column Column ( @@ -197,11 +201,13 @@ fun LinkItemLayout( } } + // 링크 메인 이미지, 제목 사이 간격 + Spacer(modifier = Modifier.height(10.dp)) + // (2) 링크 제목 Text( // 위쪽 여백(10dp) - modifier = Modifier - .padding(top = 10.dp), + modifier = Modifier, text = link?.title?:"제목", // 폰트 크기(15sp) fontSize = 15.sp, @@ -215,12 +221,14 @@ fun LinkItemLayout( overflow = TextOverflow.Ellipsis // 잘리면 ... 표시 ) + // 링크 제목, 태그 사이 간격 + Spacer(modifier = Modifier.height(1.dp)) + // (3) 링크 분류 태그(여러 개를 Row에 배치) LazyRow( // 가로 전체 채우기, 위쪽 여백(8dp) modifier = Modifier - .height(30.dp) - .padding(top = 8.dp), + .height(30.dp), // 태그 간 5dp 간격, 왼쪽 정렬 horizontalArrangement = Arrangement.spacedBy(5.dp, Alignment.Start), // 세로 중앙 정렬 @@ -232,8 +240,8 @@ fun LinkItemLayout( } } - // (4) 남은 공간 모두 차지하는 Spacer (아래로 밀어내기) - Spacer(modifier = Modifier.height(8.dp)) + // 링크 태그, 설명 프레임 사이 간격 + Spacer(modifier = Modifier.height(5.dp)) // (5) 링크 설명 프레임 (도메인, 아이콘 등) Row( @@ -300,7 +308,7 @@ private fun LinkItemTest() { contentAlignment = Alignment.TopCenter ){ Box( - modifier = Modifier.alpha(0.35f), + //modifier = Modifier.alpha(0.35f), ){ LinkItemLayout( link = null @@ -323,5 +331,6 @@ private fun LinkItemTest() { textAlign = TextAlign.Center, ) } -} + LinkItemLayout { } +} \ No newline at end of file From b27b3ed32a7343c638920d0ef5d82d2fb38cc88b Mon Sep 17 00:00:00 2001 From: gmin Date: Mon, 2 Feb 2026 19:38:40 +0900 Subject: [PATCH 06/13] =?UTF-8?q?=E2=9C=A8=20=EB=A7=81=ED=81=AC=20?= =?UTF-8?q?=EB=B6=84=EB=A5=98=20=EB=B0=94=ED=85=80=EC=8B=9C=ED=8A=B8=20UI?= =?UTF-8?q?=20=EB=B0=8F=20=EC=84=A0=ED=83=9D=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `selectedLinks`를 `mutableStateListOf`로 변경하여 상태 추적 지원 - 바텀시트 '추가' 버튼 활성화 상태(`isReady`) 조건 추가 (선택된 링크가 있을 때만 활성화) - 링크 아이템 레이아웃 수정: `offset`, `padding`, `shape` 및 정렬 조정 - 체크박스 미선택 시 색상(`uncheckedColor`) 변경 - 링크 선택 시 데이터 정합성을 위해 `onOkay` 콜백 내 `selectedLinks.clear()` 로직 추가 --- .../sheet/LinkCategorizationBottomSheet.kt | 73 +++++++++++-------- 1 file changed, 43 insertions(+), 30 deletions(-) diff --git a/feature/file/src/main/java/com/example/file/ui/bottom/sheet/LinkCategorizationBottomSheet.kt b/feature/file/src/main/java/com/example/file/ui/bottom/sheet/LinkCategorizationBottomSheet.kt index ffe5148..5d04405 100644 --- a/feature/file/src/main/java/com/example/file/ui/bottom/sheet/LinkCategorizationBottomSheet.kt +++ b/feature/file/src/main/java/com/example/file/ui/bottom/sheet/LinkCategorizationBottomSheet.kt @@ -12,7 +12,7 @@ import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items @@ -25,6 +25,7 @@ import androidx.compose.material3.Text import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope @@ -45,6 +46,7 @@ import com.example.core.model.LinkItemInfo import com.example.file.FileViewModel import com.example.file.R import com.example.design.modifier.noRippleClickable +import com.example.design.theme.color.Basic import com.example.file.ui.theme.Black import com.example.file.ui.theme.DefaultFont import com.example.file.ui.theme.Gray100 @@ -62,9 +64,9 @@ fun LinkCategorizationBottomSheet( ) { val links by fileViewModel.notCategorizationLinks.collectAsStateWithLifecycle() - var link by remember { mutableStateOf(null) } + //var link by remember { mutableStateOf(null) } - var selectedLinks = mutableListOf() + val selectedLinks = remember { mutableStateListOf() } val scope = rememberCoroutineScope() @@ -75,15 +77,17 @@ fun LinkCategorizationBottomSheet( title = "${folderStateViewModel.selectedTopFolder?.folderName?:""} 폴더의 미분류 링크 목록", body = "하위폴더에 추가하실 링크를 선택해주세요!", buttonText = "추가", + isReady = selectedLinks.isNotEmpty(), visible = folderStateViewModel.linkCategorizationBottomSheetVisible, onOkay = { scope.launch{ - selectedLinks.map { - fileViewModel.updateLinkFolder( - it, - folderStateViewModel.selectedBottomFolder?.folderId!! - ) + val folderId = requireNotNull(folderStateViewModel.selectedBottomFolder?.folderId) + + selectedLinks.forEach { + fileViewModel.updateLinkFolder(it, folderId) } + + selectedLinks.clear() } }, onDismiss = { folderStateViewModel.updateLinkCategorizationBottomSheetVisible(false) } @@ -95,7 +99,7 @@ fun LinkCategorizationBottomSheet( verticalArrangement = Arrangement.spacedBy(10.dp) ) { items(links) { - val l = it + val link = it val title = it.title val url = it.url val icon = domainLogoPainterOrNull(it.url)?:painterResource(R.drawable.link_categorization_default) @@ -104,64 +108,71 @@ fun LinkCategorizationBottomSheet( var checked by remember { mutableStateOf(false)} Row( modifier = Modifier - //.height(60.dp) + .height(60.dp) + .offset(x = (-20).dp) .noRippleClickable{ if(checked){ Log.d("LinkCategorizationBottomSheet", "checked: $it") - selectedLinks.remove(l) - checked = selectedLinks.contains(l) + selectedLinks.remove(link) + checked = selectedLinks.contains(link) } else { Log.d("LinkCategorizationBottomSheet", "checked: $it") - selectedLinks.add(l) - checked = selectedLinks.contains(l) + selectedLinks.add(link) + checked = selectedLinks.contains(link) } }, - horizontalArrangement = Arrangement.spacedBy(14.dp) + horizontalArrangement = Arrangement.spacedBy(10.dp), + verticalAlignment = Alignment.CenterVertically ) { Checkbox( - modifier = Modifier - .clip(RoundedCornerShape(18.dp)), checked = checked, onCheckedChange = { if(checked){ Log.d("LinkCategorizationBottomSheet", "checked: $it") - selectedLinks.remove(l) - checked = selectedLinks.contains(l) + selectedLinks.remove(link) + checked = selectedLinks.contains(link) } else { Log.d("LinkCategorizationBottomSheet", "checked: $it") - selectedLinks.add(l) - checked = selectedLinks.contains(l) + selectedLinks.add(link) + checked = selectedLinks.contains(link) } }, colors = CheckboxDefaults.colors( checkedColor = Purple200, + uncheckedColor = Basic.gray[200], ) ) Box( modifier = Modifier - .height(60.dp) - .background(Gray100), + //.height(60.dp) + .background( + color = Gray100, + shape = RoundedCornerShape(18.dp) + ) + /*.clip(RoundedCornerShape(18.dp))*/, contentAlignment = Alignment.Center ) { Image( modifier = Modifier - .fillMaxHeight() - .clip(RoundedCornerShape(18.dp)), + .fillMaxHeight(), painter = img, contentDescription = null ) } + Column( - modifier = Modifier - .fillMaxHeight() - .padding(vertical = 8.dp), + modifier = Modifier.fillMaxHeight(), + //verticalArrangement = Arrangement.SpaceBetween + ) { + // 링크 상단 패딩 + Spacer(modifier = Modifier.height(7.dp)) + Text( text = title, fontSize = 15.sp, - lineHeight = 22.sp, fontFamily = DefaultFont, fontWeight = FontWeight(500), color = Black, @@ -194,7 +205,6 @@ fun LinkCategorizationBottomSheet( Text( text = url, fontSize = 12.sp, - lineHeight = 14.sp, fontFamily = DefaultFont, fontWeight = FontWeight(400), color = Gray800, @@ -202,7 +212,10 @@ fun LinkCategorizationBottomSheet( overflow = TextOverflow.Ellipsis ) } + // 링크 하단 패딩 + Spacer(modifier = Modifier.height(7.dp)) } + } } } From e9fbcb649a14b2792b9ea7e851302ad9fa32ea44 Mon Sep 17 00:00:00 2001 From: gmin Date: Mon, 2 Feb 2026 19:39:07 +0900 Subject: [PATCH 07/13] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20feature/file:=20?= =?UTF-8?q?=EC=95=84=EC=9D=B4=EC=BD=98=20=EC=BB=B4=ED=8F=AC=EC=A0=80?= =?UTF-8?q?=EB=B8=94=20=EC=A0=91=EA=B7=BC=20=EC=A0=9C=ED=95=9C=EC=9E=90=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20(`internal`=20=EC=B6=94=EA=B0=80)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `ShareFolderIcon`, `LockFolderIcon`, `PencilIcon`, `BookMarkStar` 함수에 `internal` 접근 제한자 적용 --- .../main/java/com/example/file/ui/content/FolderIcons.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/feature/file/src/main/java/com/example/file/ui/content/FolderIcons.kt b/feature/file/src/main/java/com/example/file/ui/content/FolderIcons.kt index 5cd753a..65b6cd0 100644 --- a/feature/file/src/main/java/com/example/file/ui/content/FolderIcons.kt +++ b/feature/file/src/main/java/com/example/file/ui/content/FolderIcons.kt @@ -19,7 +19,7 @@ import com.example.file.ui.theme.White // 공유 폴더 사람 아이콘 @Composable -fun ShareFolderIcon( +internal fun ShareFolderIcon( tint: Color ) { Icon( @@ -31,7 +31,7 @@ fun ShareFolderIcon( // 잠금 폴더 자물쇠 아이콘 @Composable -fun LockFolderIcon( +internal fun LockFolderIcon( tint: Color ) { Icon( @@ -43,7 +43,7 @@ fun LockFolderIcon( // 수정 연필 아이콘 @Composable -fun PencilIcon( +internal fun PencilIcon( tint: Color ) { Icon( @@ -55,7 +55,7 @@ fun PencilIcon( // 북마크 별 아이콘 @Composable -fun BookMarkStar( +internal fun BookMarkStar( isBookmarked: Boolean ) { val modifier = if(isBookmarked) Modifier From 5af7142d5a859cf1ab82c7fe763fad181378650b Mon Sep 17 00:00:00 2001 From: gmin Date: Tue, 3 Feb 2026 17:50:33 +0900 Subject: [PATCH 08/13] =?UTF-8?q?=F0=9F=90=9B=20=EA=B2=80=EC=83=89=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95:=20=EC=B4=88=EA=B8=B0?= =?UTF-8?q?=ED=99=94=20=EC=8B=9C=20=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20?= =?UTF-8?q?=ED=98=B8=EC=B6=9C=20=EB=B0=A9=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `LaunchedEffect`의 key를 `text`에서 `Unit`으로 변경하여, 컴포저블이 처음 생성될 때만 검색 로직 관련 Flow가 생성되도록 수정했습니다. 이를 통해 불필요한 초기 호출을 방지합니다. --- .../design/top/search/SearchBarTopSheet.kt | 2 +- .../com/example/curation/ui/CurationScreen.kt | 10 +--------- .../src/main/java/com/example/file/FileScreen.kt | 2 +- .../java/com/example/home/screen/HomeScreen.kt | 16 +--------------- 4 files changed, 4 insertions(+), 26 deletions(-) diff --git a/design/src/main/java/com/example/design/top/search/SearchBarTopSheet.kt b/design/src/main/java/com/example/design/top/search/SearchBarTopSheet.kt index 0e06fad..da8cb6e 100644 --- a/design/src/main/java/com/example/design/top/search/SearchBarTopSheet.kt +++ b/design/src/main/java/com/example/design/top/search/SearchBarTopSheet.kt @@ -146,7 +146,7 @@ fun SearchBarTopSheet( * - 350ms 디바운스 * - 동일 값 중복 호출 방지 */ - LaunchedEffect(text) { + LaunchedEffect(Unit) { snapshotFlow { text } .map { it.trim() } .filter { it.length >= 2 } diff --git a/feature/curation/src/main/java/com/example/curation/ui/CurationScreen.kt b/feature/curation/src/main/java/com/example/curation/ui/CurationScreen.kt index 8fb8404..1fd4561 100644 --- a/feature/curation/src/main/java/com/example/curation/ui/CurationScreen.kt +++ b/feature/curation/src/main/java/com/example/curation/ui/CurationScreen.kt @@ -4,17 +4,14 @@ package com.example.curation.ui import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text -import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -27,9 +24,7 @@ import com.example.curation.R import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.width import androidx.compose.material3.Scaffold @@ -37,7 +32,6 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember -import androidx.compose.ui.draw.clip import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.sp @@ -46,8 +40,6 @@ import com.example.curation.CurationViewModel import com.example.curation.ui.list_card.LikedCurationCard import com.example.curation.Paperlogy import com.example.design.theme.LocalColorTheme -import com.example.design.theme.color.Basic -import com.example.design.R as Res import java.time.LocalDate import java.time.format.DateTimeFormatter import java.util.Locale @@ -306,7 +298,7 @@ fun CurationScreen( onQueryDelete = { viewModel.removeRecentQuery(it) }, onQueryClear = { viewModel.clearRecentQuery() }, fastSearchItems = viewModel.fastSearchItems.collectAsState().value, - recentQuerys = viewModel.recentQueryList.collectAsState().value.map{it.text} + recentQueries = viewModel.recentQueryList.collectAsState().value.map{it.text} ) } diff --git a/feature/file/src/main/java/com/example/file/FileScreen.kt b/feature/file/src/main/java/com/example/file/FileScreen.kt index 8c12897..117ab9d 100644 --- a/feature/file/src/main/java/com/example/file/FileScreen.kt +++ b/feature/file/src/main/java/com/example/file/FileScreen.kt @@ -284,7 +284,7 @@ fun FileScreen( onQueryDelete = { fileViewModel.removeRecentQuery(it) }, onQueryClear = { fileViewModel.clearRecentQuery() }, fastSearchItems = fileViewModel.fastSearchItems.collectAsState().value, - recentQuerys = fileViewModel.recentQueryList.collectAsState().value.map{it.text} + recentQueries = fileViewModel.recentQueryList.collectAsState().value.map{it.text} ) } diff --git a/feature/home/src/main/java/com/example/home/screen/HomeScreen.kt b/feature/home/src/main/java/com/example/home/screen/HomeScreen.kt index bc89c14..1b3bc03 100644 --- a/feature/home/src/main/java/com/example/home/screen/HomeScreen.kt +++ b/feature/home/src/main/java/com/example/home/screen/HomeScreen.kt @@ -1,8 +1,5 @@ package com.example.home.screen -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.expandVertically -import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image import androidx.compose.foundation.background @@ -16,8 +13,6 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.navigationBarsPadding -import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width @@ -25,7 +20,6 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -38,17 +32,13 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource -import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.withStyle import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.zIndex @@ -56,20 +46,16 @@ import coil3.compose.AsyncImage import com.example.core.model.LinkSimpleInfo import com.example.design.BrushText import com.example.design.top.search.SearchBarTopSheet -import com.example.design.modifier.noRippleClickable import com.example.design.theme.LocalColorTheme import com.example.design.theme.LocalFontTheme import com.example.design.theme.color.Basic import com.example.design.util.DesignSystemBars -import com.example.file.ui.theme.FileTopBarLinkUFont -import com.example.file.ui.theme.MainColor import com.example.home.HomeViewModel import com.example.home.R import com.example.home.component.ClipboardLinkPasteBanner import com.example.home.component.rememberClipboardUrl import com.example.home.ui.top.bar.HomeTopBar import kotlinx.coroutines.launch -import com.example.design.R as Res data class LinkItem( val imageResId: Int?, // 링크 대표 이미지 @@ -525,7 +511,7 @@ fun HomeScreen( onQueryDelete = { homeViewModel.removeRecentQuery(it) }, onQueryClear = { homeViewModel.clearRecentQuery() }, fastSearchItems = homeViewModel.fastSearchItems.collectAsState().value, - recentQuerys = homeViewModel.recentQueryList.collectAsState().value.map{it.text} + recentQueries = homeViewModel.recentQueryList.collectAsState().value.map{it.text} ) } From 1b6b50286ae091372ae13ae4c21f8cb250be515d Mon Sep 17 00:00:00 2001 From: gmin Date: Tue, 3 Feb 2026 20:37:46 +0900 Subject: [PATCH 09/13] =?UTF-8?q?=E2=9C=A8=20EmailLoginScreen=20UI=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=20=EA=B0=9C=EC=84=A0:=20=ED=95=98=EB=8B=A8?= =?UTF-8?q?=20=EB=A9=94=EB=89=B4=20=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20(Box=20->=20Row)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 비밀번호 재설정, 구분선, 회원가입 버튼의 배치를 `Box`의 오프셋 방식에서 `Row`를 사용한 정렬 방식으로 변경 - `Arrangement.spacedBy`를 사용하여 요소 간 간격 조정 및 중앙 정렬 적용 --- .../login/ui/screen/EmailLoginScreen.kt | 23 ++++++------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/feature/login/src/main/java/com/example/login/ui/screen/EmailLoginScreen.kt b/feature/login/src/main/java/com/example/login/ui/screen/EmailLoginScreen.kt index 16e41c8..9c36140 100644 --- a/feature/login/src/main/java/com/example/login/ui/screen/EmailLoginScreen.kt +++ b/feature/login/src/main/java/com/example/login/ui/screen/EmailLoginScreen.kt @@ -237,28 +237,26 @@ fun EmailLoginScreen( val dividerStartPos = (220.scaler) // | 시작점 val signUpStartPos = (247.scaler) // 회원가입 시작점 - Box( + Row( modifier = Modifier + .align(Alignment.CenterHorizontally) .fillMaxWidth() .height((30.scaler)), // 클릭 영역 확보를 위한 높이 - contentAlignment = Alignment.CenterStart //세로 중앙 정렬 - ) { - // 1. 비밀번호 재설정 + horizontalArrangement = Arrangement.spacedBy(25.dp, alignment = Alignment.CenterHorizontally), + verticalAlignment = Alignment.CenterVertically + ){ Text( text = "비밀번호 재설정", fontSize = 15.sp, fontFamily = Paperlogy.font, color = Color(0xFF87898F), modifier = Modifier - .offset(x = resetStartPos) // 항상 101/412 지점 .noRippleClickable { //if (loginState !is LoginState.Loading) { -> 혹시 나중에 로딩중이 길어지면 사용해주세요. navigator.navigate("resetPassword") //} } ) - - // 2. 구분선 (|) // TODO : 비밀번호 재설정, 회원가입 대비 내려가서 부득이하게 약간 위로 올림. 다현이랑 조절해보기. Text( text = "|", fontSize = 14.sp, @@ -266,30 +264,23 @@ fun EmailLoginScreen( color = Color(0xFF87898F), style = TextStyle( baselineShift = BaselineShift(0.3f) // 약간 위로 올림 - ), - //style = TextStyle(baselineShift = BaselineShift(0.15f)), - modifier = Modifier - .offset(x = dividerStartPos) // 항상 220/412 지점 + ) ) - - // 3. 회원가입 Text( text = "회원가입", fontSize = 15.sp, fontFamily = Paperlogy.font, color = Color(0xFF87898F), modifier = Modifier - .offset(x = signUpStartPos) // 항상 247/412 지점 .noRippleClickable { focusManager.clearFocus() onSignUpClick() } ) } - - } } } +} From 0e93751dcd10c6cef8aa345629e6927b17422819 Mon Sep 17 00:00:00 2001 From: gmin Date: Wed, 25 Feb 2026 11:28:17 +0900 Subject: [PATCH 10/13] =?UTF-8?q?=E2=9C=A8=20feature/file:=20BottomFolderL?= =?UTF-8?q?istMenu=20=EB=A6=AC=ED=8C=A9=ED=84=B0=EB=A7=81=20=EB=B0=8F=20?= =?UTF-8?q?=EC=8A=A4=ED=81=AC=EB=A1=A4=20=EB=B0=94=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `BottomFolderListMenu`를 UI 로직과 레이아웃(`BottomFolderListMenuLayout`)으로 분리 - `DropdownMenuItem` 대신 커스텀 로직이 포함된 `BottomFolderListMenuRow` 컴포넌트 사용 - 메뉴 내부에 `verticalScroll`을 적용하여 최대 높이(264dp) 초과 시 스크롤 가능하도록 수정 - 스크롤 바 구현을 위한 상태 관리 로직 추가 (추후 수정 대비 주석 포함) - 프리뷰 확인을 위한 테스트 데이터(`folderSimpleInfoList`) 및 `BottomFolderListMenuLayoutTest` 추가 --- .../top/bar/component/BottomFolderListMenu.kt | 471 +++++++++++++++--- 1 file changed, 397 insertions(+), 74 deletions(-) diff --git a/feature/file/src/main/java/com/example/file/ui/top/bar/component/BottomFolderListMenu.kt b/feature/file/src/main/java/com/example/file/ui/top/bar/component/BottomFolderListMenu.kt index 4d9b57d..98b2011 100644 --- a/feature/file/src/main/java/com/example/file/ui/top/bar/component/BottomFolderListMenu.kt +++ b/feature/file/src/main/java/com/example/file/ui/top/bar/component/BottomFolderListMenu.kt @@ -1,37 +1,58 @@ package com.example.file.ui.top.bar.component +import android.util.Log import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll import androidx.compose.material3.DropdownMenu -import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.viewmodel.compose.viewModel -import com.example.file.FileViewModel -import com.example.file.viewmodel.folder.state.FolderStateViewModel +import com.example.core.model.FolderSimpleInfo +import com.example.design.modifier.noRippleClickable +import com.example.design.theme.color.Basic import com.example.design.theme.color.CategoryColorStyle +import com.example.file.FileViewModel import com.example.file.ui.theme.DefaultFont import com.example.file.ui.theme.Gray800 import com.example.file.ui.theme.White import com.example.file.viewmodel.folder.state.FolderState +import com.example.file.viewmodel.folder.state.FolderStateViewModel @Composable fun BottomFolderListMenu( + modifier: Modifier = Modifier, fileViewModel: FileViewModel, folderStateViewModel: FolderStateViewModel, onChangeFolder: () -> Unit @@ -42,88 +63,390 @@ fun BottomFolderListMenu( val colorStyles = fileViewModel.categoryColorMap.collectAsStateWithLifecycle().value + BottomFolderListMenuLayout( + modifier = modifier, + isLinks = isLinks, + bottomMenuExpanded = folderStateViewModel.bottomMenuExpanded, + onDismissRequest = { folderStateViewModel.updateBottomMenuExpanded(false) }, + parentFolders = parentFolders, + parentFolderSelected = { it.folderId == folderStateViewModel.selectedTopFolder?.folderId }, + parentFolderName = { it.folderName }, + parentFolderColor = { folder -> colorStyles[folder.folderName]?: CategoryColorStyle.DEFAULT }, + onParentFolderClick = { folder -> + if(folder.folderId!=folderStateViewModel.selectedTopFolder?.folderId){ + fileViewModel.getFoldersAndNotCategorizationLinks(folder.folderId) + folderStateViewModel.updateSelectedTopFolder(folder) + folderStateViewModel.updateFolderState(FolderState.BOTTOM) + folderStateViewModel.updateBottomMenuExpanded(false) + onChangeFolder() + } + }, + subFolders = subFolders, + subFolderSelected = { it.folderId == folderStateViewModel.selectedBottomFolder?.folderId }, + subFolderName = { it.folderName }, + onSubFolderClick = { folder -> + if(folder.folderId!=folderStateViewModel.selectedBottomFolder?.folderId){ + fileViewModel.getLinks(folder.folderId) + folderStateViewModel.updateSelectedBottomFolder(folder) + folderStateViewModel.updateFolderState(FolderState.LINKS) + folderStateViewModel.updateBottomMenuExpanded(false) + onChangeFolder() + } + } + ) +} + +// 전체 메뉴 : 스크롤 바 = 205 : 4 +// private const val BOTTOM_FOLDER_LIST_MENU_WIDTH = 205f +// private const val BOTTOM_FOLDER_LIST_MENU_HEIGHT = 264F + +@Composable +private fun BottomFolderListMenuLayout( + modifier: Modifier = Modifier, + isLinks: Boolean, + bottomMenuExpanded: Boolean, + onDismissRequest: () -> Unit, + parentFolders: List, + parentFolderSelected: (FolderSimpleInfo) -> Boolean, + parentFolderName: (FolderSimpleInfo) -> String, + parentFolderColor: @Composable (FolderSimpleInfo) -> CategoryColorStyle, + onParentFolderClick: (FolderSimpleInfo) -> Unit, + subFolders: List, + subFolderSelected: (FolderSimpleInfo) -> Boolean, + subFolderName: (FolderSimpleInfo) -> String, + onSubFolderClick: (FolderSimpleInfo) -> Unit +){ + // ----------------------------스크롤 바 추후 수정---------------------------- + val menuHeight = 264f + val menuWidth = 205f + + // 스크롤 바 너비비 = 스크롤 바 너비 / 메뉴 너비 + val scrollBarWidthWeight = 4f / menuWidth + + // 스크롤 바 길이비 = 메뉴 길이 / 전체 아이템 총 길이 + val scrollBarHeightWeight = menuHeight / (subFolders.size * MENU_ITEM_HEIGHT) + + // 트랙(스크롤바가 움직이는 레일) 높이 + var trackHeight by remember { mutableIntStateOf(0) } + + + val menuScrollState = rememberScrollState() + + LaunchedEffect(bottomMenuExpanded) { + if(bottomMenuExpanded) + menuScrollState.scrollTo(0) + } + + val scrollable by remember { + derivedStateOf { menuScrollState.maxValue > 0 } + } + + val currentScrollValue = menuScrollState.value + val maxScrollValue = menuScrollState.maxValue + + // thumb(움직이는 막대) 높이/오프셋을 계산 + val thumbHeight by remember(trackHeight, maxScrollValue) { + derivedStateOf { + if (trackHeight <= 0) 0f + else if (maxScrollValue <= 0) 0f // 스크롤 불가면 전체(사실상 숨겨도 됨) + else { + val contentHeightPx = trackHeight + maxScrollValue + val ratio = trackHeight.toFloat() / contentHeightPx.toFloat() + // UX용 최소 높이(너무 작아지면 안 보임 방지) - 필요 없으면 삭제 가능 + /*val minThumbPx = (18.dp.value * (trackHeight / 264f)).roundToInt() + max((trackHeight * ratio).roundToInt(), minThumbPx)*/ + //(trackHeight * ratio).roundToInt() + //ratio + menuHeight * ratio + } + } + } + + val thumbTopPadding by remember(trackHeight, maxScrollValue, currentScrollValue, thumbHeight) { + derivedStateOf { + if (trackHeight <= 0) 0f + else if (maxScrollValue <= 0) 0f + else { + val movableRange = (trackHeight - thumbHeight).coerceAtLeast(0F) + val ratio = currentScrollValue.toFloat() / maxScrollValue.toFloat() + val contentHeightPx = trackHeight + maxScrollValue + val thumbRatio = trackHeight.toFloat() / contentHeightPx.toFloat() + val r = (1 - thumbRatio) * ratio + //(movableRange * ratio).roundToInt() + //(trackHeight * ratio).roundToInt() + r + } + } + } + + @Composable + fun ScrollBar(modifier: Modifier) { + Log.d("SB", "value=${menuScrollState.value}, max=${menuScrollState.maxValue}, track=$trackHeight, thumb=$thumbHeight, off=$thumbTopPadding") + Log.d("SB", "thumbTopPadding=$thumbTopPadding, thumbHeight=$thumbHeight") + Log.d("SB", "scrollable=$scrollable") + + // 스크롤이 가능한 경우에만 표시(원하면 항상 표시로 바꿀 수 있음) + if (/*maxScrollValue > 0 && trackHeight > 0 && */thumbHeight > 0f) { + Column( + modifier = modifier + .onSizeChanged { + Log.d("SB", "onSizeChanged") + + trackHeight = it.height + } + ) { + Spacer(modifier = Modifier + .fillMaxHeight(thumbTopPadding) + + .fillMaxWidth() + /*.background( + shape = RoundedCornerShape(18.dp), + color = Basic.blue[200].copy(alpha = 0.5f) + )*/ + ) + + // thumb + Box( + modifier = Modifier + .fillMaxWidth() + .height(thumbHeight.dp) + .clip(RoundedCornerShape(18.dp)) + .background(Basic.gray[500]) + ) + } + } else { + // 트랙 높이 측정은 해야 하니까(첫 프레임), 투명 트랙만 깔아둠 + Box( + modifier = modifier + .onSizeChanged { trackHeight = it.height } + ) + } + } + // ----------------------------스크롤 바 추후 수정---------------------------- + DropdownMenu( - modifier = Modifier - .heightIn(max = 264.dp) - .width(205.dp), + modifier = modifier + .width(menuWidth.dp) + .padding(vertical = 8.dp), shape = RoundedCornerShape(18.dp), offset = DpOffset(0.dp, 10.dp), - expanded = folderStateViewModel.bottomMenuExpanded, - onDismissRequest = { folderStateViewModel.updateBottomMenuExpanded(false) }, - containerColor = White + expanded = bottomMenuExpanded, + onDismissRequest = onDismissRequest, + containerColor = White, ) { - if(!isLinks){ - for ((i, folder) in parentFolders.withIndex()) { - val colorStyle = colorStyles[folder.folderName]?: CategoryColorStyle.DEFAULT - - DropdownMenuItem( - leadingIcon = { - Box( - modifier = Modifier - .size(25.dp) - .clip(CircleShape) - .background(color = colorStyle.color4) - ) - }, - text = { - Text( - text = folder.folderName, - fontSize = 15.sp, - lineHeight = 22.sp, - fontFamily = DefaultFont, - fontWeight = FontWeight(400), - color = Gray800, - maxLines = 1, // 한 줄만 보여주고 - overflow = TextOverflow.Ellipsis // 넘치면 ...으로 대체 + + // 링크 목록 위 스크롤 바를 띄우는 박스 + Box{ + if (!isLinks) { + Column( + modifier = Modifier + .heightIn(max = 264.dp) + .verticalScroll(menuScrollState), + verticalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterVertically), + ) { + parentFolders.forEachIndexed { index, folder -> + val selected = parentFolderSelected(folder) + val colorStyle = parentFolderColor(folder) + + BottomFolderListMenuRow( + leadingColor = colorStyle.color4, + text = parentFolderName(folder), + enabled = !selected, + onClick = { onParentFolderClick(folder) } ) - }, - onClick = { - if(folder.folderId!=folderStateViewModel.selectedTopFolder?.folderId){ - fileViewModel.getFoldersAndNotCategorizationLinks(folder.folderId) - folderStateViewModel.updateSelectedTopFolder(folder) - folderStateViewModel.updateFolderState(FolderState.BOTTOM) - folderStateViewModel.updateBottomMenuExpanded(false) - } } - ) - } - }else{ - for ((i, folder) in subFolders.withIndex()) { - DropdownMenuItem( - text = { - Text( - text = folder.folderName, - fontSize = 15.sp, - lineHeight = 22.sp, - fontFamily = DefaultFont, - fontWeight = FontWeight(400), - color = Gray800, - maxLines = 1, // 한 줄만 보여주고 - overflow = TextOverflow.Ellipsis // 넘치면 ...으로 대체 + } + } else { + Column( + modifier = Modifier + .heightIn(max = 264.dp) + .verticalScroll(menuScrollState), + verticalArrangement = Arrangement.spacedBy(18.dp, Alignment.CenterVertically), + ) { + subFolders.forEach { folder -> + val selected = subFolderSelected(folder) + + BottomFolderListMenuRow( + leadingColor = null, + text = subFolderName(folder), + enabled = !selected, + onClick = { onSubFolderClick(folder) } ) - }, - onClick = { - if(folder.folderId!=folderStateViewModel.selectedBottomFolder?.folderId){ - fileViewModel.getLinks(folder.folderId) - folderStateViewModel.updateSelectedBottomFolder(folder) - folderStateViewModel.updateFolderState(FolderState.LINKS) - folderStateViewModel.updateBottomMenuExpanded(false) - } } - ) + } } + /*if(scrollable){ + ScrollBar( + modifier = Modifier + //.fillMaxWidth(SCROLL_BAR_WIDTH_WEIGHT) + //.fillMaxHeight() + .align(Alignment.TopEnd) + .height(264.dp) + .width(4.dp) + ) + }*/ + } + } +} + +private const val MENU_ITEM_HEIGHT = 25 + +@Composable +private fun BottomFolderListMenuRow( + modifier: Modifier = Modifier, + leadingColor: Color?, + text: String, + enabled: Boolean, + onClick: () -> Unit +){ + Row( + modifier = modifier + .fillMaxWidth() + .noRippleClickable(enabled = enabled, onClick = onClick) + .padding(horizontal = 12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + if (leadingColor != null) { + Box( + modifier = Modifier + .size(MENU_ITEM_HEIGHT.dp) + .clip(CircleShape) + .background(color = leadingColor) + ) + Spacer(modifier = Modifier.width(8.dp)) } + + Text( + text = text, + fontSize = 15.sp, + lineHeight = 22.sp, + fontFamily = DefaultFont, + fontWeight = FontWeight(400), + color = Gray800, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) } } -@Preview() +private val folderSimpleInfoList = listOf( + FolderSimpleInfo( + folderId = 1L, + folderName = "Folder 1", + parentFolderId = 0L, + isBookmarked = false + ), + FolderSimpleInfo( + folderId = 2L, + folderName = "Folder 2", + parentFolderId = 0L, + isBookmarked = true + ), + FolderSimpleInfo( + folderId = 3L, + folderName = "Folder 3", + parentFolderId = 1L, + isBookmarked = false + ), + FolderSimpleInfo( + folderId = 4L, + folderName = "Folder 4", + parentFolderId = 1L, + isBookmarked = true + ), + FolderSimpleInfo( + folderId = 5L, + folderName = "Folder 5", + parentFolderId = 2L, + isBookmarked = false + ), + FolderSimpleInfo( + folderId = 6L, + folderName = "Folder 6", + parentFolderId = 2L, + isBookmarked = true + ), + FolderSimpleInfo( + folderId = 7L, + folderName = "Folder 7", + parentFolderId = 3L, + isBookmarked = false + ), + FolderSimpleInfo( + folderId = 8L, + folderName = "Folder 8", + parentFolderId = 3L, + isBookmarked = true + ), + FolderSimpleInfo( + folderId = 9L, + folderName = "Folder 9", + parentFolderId = 4L, + isBookmarked = false + ), + FolderSimpleInfo( + folderId = 10L, + folderName = "Folder 10", + parentFolderId = 4L, + isBookmarked = true + ), + FolderSimpleInfo( + folderId = 11L, + folderName = "Folder 11", + parentFolderId = 5L, + isBookmarked = false + ), + FolderSimpleInfo( + folderId = 12L, + folderName = "Folder 12", + parentFolderId = 5L, + isBookmarked = true + ), + FolderSimpleInfo( + folderId = 13L, + folderName = "Folder 13", + parentFolderId = 6L, + isBookmarked = false + ), + FolderSimpleInfo( + folderId = 14L, + folderName = "Folder 14", + parentFolderId = 6L, + isBookmarked = true + ), + FolderSimpleInfo( + folderId = 15L, + folderName = "Folder 15", + parentFolderId = 7L, + isBookmarked = false + ), + FolderSimpleInfo( + folderId = 16L, + folderName = "Folder 16", + parentFolderId = 7L, + isBookmarked = true + ) +) + +@Preview(heightDp = 1000) @Composable -fun BottomFolderListMenuTest(){ - val folderStateViewModel: FolderStateViewModel = viewModel() - BottomFolderListMenu( - fileViewModel = hiltViewModel(), - folderStateViewModel = folderStateViewModel, - onChangeFolder = {} +private fun BottomFolderListMenuLayoutTest(){ + + BottomFolderListMenuLayout( + isLinks = false, + bottomMenuExpanded = true, + onDismissRequest = {}, + parentFolders = folderSimpleInfoList, + parentFolderSelected = { false }, + parentFolderName = { "parentFolderName" }, + parentFolderColor = { + CategoryColorStyle.categoryStyleList[it.folderId.toInt() % CategoryColorStyle.categoryStyleList.size] + }, + onParentFolderClick = {}, + subFolders = folderSimpleInfoList, + subFolderSelected = { false }, + subFolderName = { "subFolderName" }, + onSubFolderClick = {} ) -} \ No newline at end of file +} From 51de66ccbcfd763b9e8396fb46294403eefb9e70 Mon Sep 17 00:00:00 2001 From: gmin Date: Wed, 25 Feb 2026 17:20:22 +0900 Subject: [PATCH 11/13] =?UTF-8?q?=E2=9C=A8=20design:=20=EC=9B=90=ED=98=95?= =?UTF-8?q?=20=EB=82=B4=EB=B6=80=20=EA=B7=B8=EB=A6=BC=EC=9E=90=20=ED=9A=A8?= =?UTF-8?q?=EA=B3=BC=EB=A5=BC=20=EC=9C=84=ED=95=9C=20innerRingShadow=20Mod?= =?UTF-8?q?ifier=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `Modifier.innerRingShadow` 확장 함수 구현 - `Brush.radialGradient`를 사용하여 컴포넌트 가장자리에 부드러운 안쪽 테두리 그림자 효과 적용 - 그림자 색상(`shadowColor`) 및 두께(`edgeThickness`) 커스텀 매개변수 제공 --- .../design/modifier/InnerRingShadow.kt | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 design/src/main/java/com/example/design/modifier/InnerRingShadow.kt diff --git a/design/src/main/java/com/example/design/modifier/InnerRingShadow.kt b/design/src/main/java/com/example/design/modifier/InnerRingShadow.kt new file mode 100644 index 0000000..1e2e635 --- /dev/null +++ b/design/src/main/java/com/example/design/modifier/InnerRingShadow.kt @@ -0,0 +1,119 @@ +package com.example.design.modifier + +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawWithCache +import androidx.compose.ui.geometry.center +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +/** + * 컴포넌트의 안쪽 테두리를 따라 부드러운 원형 그림자(Inner Ring Shadow) 효과를 적용합니다. + * + * 이 함수는 [Brush.radialGradient]를 사용하여 컴포넌트의 중심부터 가장자리까지 그레이디언트를 생성하며, + * 지정된 두께([edgeThickness]) 영역에만 [shadowColor]를 노출시켜 입체감을 부여합니다. + * + * @param shadowColor 가장자리 끝부분에 적용될 그림자의 색상 및 투명도입니다. 기본값은 10% 불투명도의 검정색입니다. + * @param edgeThickness 그림자가 그려질 가장자리의 두께입니다. 기본값은 2.dp입니다. + * @return 안쪽 그림자 효과가 적용된 [Modifier] 객체를 반환합니다. + * + * @see androidx.compose.ui.draw.drawWithCache + * @see androidx.compose.ui.graphics.Brush.Companion.radialGradient + * + * @sample + * Box( + * modifier = Modifier + * .size(100.dp) + * .clip(CircleShape) + * .innerRingShadow(shadowColor = Color.Black.copy(alpha = 0.2f), edgeThickness = 4.dp) + * ) + */ +fun Modifier.innerRingShadow( + shadowColor: Color = Color.Black.copy(alpha = 0.10f), + edgeThickness: Dp = 2.dp +): Modifier = this.drawWithCache { + + // ───────────────────────────────────────────────────────────── + // 1) 원형 반지름 계산 + // ───────────────────────────────────────────────────────────── + // size: 이 Modifier가 적용되는 컴포넌트의 실제 픽셀 크기 (Size(widthPx, heightPx)) + // minDimension: width/height 중 더 짧은 변 (원형 그림자 만들 때 "지름"으로 삼기 좋음) + // + // r: radialGradient의 반지름(px) + // - minDimension / 2: "짧은 변" 기준으로 원의 반지름을 잡음 + // - coerceAtLeast(1f): 너무 작은 값(0에 가까운 값)으로 인해 0으로 나눔/이상 계산을 피함 + val r = (size.minDimension / 2f).coerceAtLeast(1f) + + // ───────────────────────────────────────────────────────────── + // 2) 테두리 쪽에만 그림자를 남기기 위한 "내부 링 두께"를 px로 변환 + // ───────────────────────────────────────────────────────────── + // edgeThickness(Dp): 사용자 입력 두께(논리 단위) → toPx()로 "픽셀" 변환 + // + // t: 테두리 내부 그림자가 차지할 두께(px) + // - coerceIn(0f, r): 두께가 음수가 되거나 반지름보다 커지면 계산이 깨질 수 있어서 범위 제한 + // * 0f: 그림자 없음 + // * r: 반지름 전체(즉, 거의 전체가 그림자)까지 허용 + val t = edgeThickness.toPx().coerceIn(0f, r) + + // ───────────────────────────────────────────────────────────── + // 3) Gradient에서 "투명 → 그림자"로 바뀌는 경계 비율 계산 + // ───────────────────────────────────────────────────────────── + // radialGradient의 colorStops는 (0.0 ~ 1.0) 범위의 비율로 정의됨. + // + // (r - t) / r 의 의미: + // - r: 전체 반지름 + // - r - t: "그림자가 시작되기 전"까지의 반지름(= 내부는 투명하게 두고 싶은 영역) + // + // 예) r=50px, t=5px 라면 + // - (r - t) / r = 45/50 = 0.9 + // - 즉, 중심~0.9 구간은 투명, 0.9~1.0 구간에서만 그림자로 변화 + // + // coerceIn(0f, 1f): 비율이 범위를 벗어나면 gradient가 비정상 동작할 수 있어 제한 + val stop = ((r - t) / r).coerceIn(0f, 1f) + + // ───────────────────────────────────────────────────────────── + // 4) 중심은 투명, 가장자리로 갈수록 shadowColor가 되는 Radial Gradient 생성 + // ───────────────────────────────────────────────────────────── + // Brush.radialGradient: + // - center: 그라디언트 중심점 (여기서는 컴포넌트 중앙) + // - radius: 반지름(px) + // + // colorStops: + // - 0.0f : 중심점(반지름 0) 위치 + // - stop : "여기까지는 투명" 위치 (내부 영역) + // - 1.0f : 반지름 끝(가장자리) 위치 + // + // 0.0f to Transparent + stop to Transparent: + // - 내부 영역(0~stop)을 완전히 투명하게 고정해 "테두리만" 그림자가 남게 함 + // + // 1.0f to shadowColor: + // - 가장자리에서만 shadowColor가 적용되도록 하여, + // 두 번째 사진처럼 "희미한 내부 테두리 그림자" 느낌을 만들 수 있음 + val brush = Brush.radialGradient( + colorStops = arrayOf( + 0.0f to Color.Transparent, // 중심은 완전 투명 + stop to Color.Transparent, // stop 지점까지도 완전 투명(= 내부 깨끗) + 1.0f to shadowColor // 가장자리에만 그림자 색이 나타남 + ), + center = size.center, // 컴포넌트 중앙 기준으로 퍼짐 + radius = r // 위에서 계산한 반지름(px) + ) + + // ───────────────────────────────────────────────────────────── + // 5) 실제 그리기: 기존 컨텐츠를 그리고, 그 위에 그라디언트를 덮어씌움 + // ───────────────────────────────────────────────────────────── + onDrawWithContent { + + // (1) 원래 컴포넌트 내용(배경, 텍스트 등) 먼저 그림 + drawContent() + + // (2) 위에서 만든 brush를 사각형 전체에 칠함 + // - 원형으로만 보이게 하려면 반드시 바깥에서 clip(CircleShape) 같은 클립이 필요함! + // - clip이 없다면 "사각형 영역" 전체에 radialGradient가 적용됨. + // + // clip(CircleShape)가 이미 적용된 상태라면: + // - drawRect로 칠해도 원 밖은 잘려서 결국 "원 테두리 내부"에만 그림자가 남음 + drawRect(brush = brush) + } +} \ No newline at end of file From 6320dbd758344253e5e4fe3146ffddf1f4c2fd2b Mon Sep 17 00:00:00 2001 From: gmin Date: Wed, 25 Feb 2026 17:22:37 +0900 Subject: [PATCH 12/13] =?UTF-8?q?=E2=9C=A8=20`FileViewModel`=20=ED=8F=B4?= =?UTF-8?q?=EB=8D=94=20=EA=B3=B5=EA=B0=9C/=EB=B9=84=EA=B3=B5=EA=B0=9C=20?= =?UTF-8?q?=EC=A0=84=ED=99=98=20=EB=A1=9C=EC=A7=81=20=EB=B6=84=EB=A6=AC=20?= =?UTF-8?q?=EB=B0=8F=20=EB=A1=9C=EA=B7=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기존 `changeSharing` 함수를 `folderToShare`(공개 전환)와 `folderToPrivate`(비공개 전환)로 분리 - `getNotCategorizationLinks` 함수 내 `_subFolders` 상태 확인을 위한 디버그 로그 추가 - 각 전환 함수 내 조건문 로직 수정 및 상세 로그 추가 --- .../java/com/example/file/FileViewModel.kt | 64 ++++++++++++++----- .../file/ui/content/BottomFolderGrid.kt | 2 +- 2 files changed, 50 insertions(+), 16 deletions(-) diff --git a/feature/file/src/main/java/com/example/file/FileViewModel.kt b/feature/file/src/main/java/com/example/file/FileViewModel.kt index dd2c75f..ec807fc 100644 --- a/feature/file/src/main/java/com/example/file/FileViewModel.kt +++ b/feature/file/src/main/java/com/example/file/FileViewModel.kt @@ -511,6 +511,8 @@ class FileViewModel @Inject constructor( onGetLinks = { list -> _notCategorizationLinks.value = list.map { it.copy() } } ) + Log.d("FileViewModel", "getNotCategorizationLinks try result: ${_subFolders.value}") + Log.d("FileViewModel", "getNotCategorizationLinks try result: ${_notCategorizationLinks.value}") } catch (e: Exception) { @@ -1055,24 +1057,25 @@ class FileViewModel @Inject constructor( } Log.d("FileViewModel", "receiveSharedFolder return") } - - // 공개/비공개 전환 - fun changeSharing(folder: FolderSimpleInfo){ - Log.d("FileViewModel", "changeSharing") + // 공개 전환 + fun folderToShare(folder: FolderSimpleInfo){ + Log.d("FileViewModel", "folderToShare") viewModelScope.launch { - Log.d("FileViewModel", "changeSharing launch") + Log.d("FileViewModel", "folderToShare launch") startLoading() _errorMessage.value = null - try{ - Log.d("FileViewModel", "changeSharing try") + try { + Log.d("FileViewModel", "folderToShare try") - val isSharing = folder.isSharing == "share" + val isPrivate = folder.isSharing == "private" + + if (isPrivate) { + Log.d("FileViewModel", "folderToShare isPrivate true") - if(!isSharing){ - folderRepository.setFolderViewerPermission(folder.folderId) + //folderRepository.setFolderPublicPermission(folder.folderId) _subFolders.update { list -> list.map { @@ -1083,8 +1086,39 @@ class FileViewModel @Inject constructor( } } } + } + Log.d("FileViewModel", "folderToShare try result") + } catch (e: Exception) { + Log.d("FileViewModel", "folderToShare catch: $e.message") + + _errorMessage.value = e.message + } finally { + Log.d("FileViewModel", "folderToShare finally") + + stopLoading() + } + } + Log.d("FileViewModel", "folderToShare return") + + } + // 비공개 전환 + fun folderToPrivate(folder: FolderSimpleInfo){ + Log.d("FileViewModel", "folderToPrivate") + + viewModelScope.launch { + Log.d("FileViewModel", "folderToPrivate launch") + + startLoading() + _errorMessage.value = null + + try{ + Log.d("FileViewModel", "folderToPrivate try") + + val isSharing = folder.isSharing == "share" + + if(isSharing){ + Log.d("FileViewModel", "folderToPrivate isSharing true") - }else{ folderRepository.setFolderPrivatePermission(folder.folderId) _subFolders.update { list -> @@ -1098,18 +1132,18 @@ class FileViewModel @Inject constructor( } } - Log.d("FileViewModel", "changeSharing try result") + Log.d("FileViewModel", "folderToPrivate try result") }catch (e: Exception){ - Log.d("FileViewModel", "changeSharing catch: $e.message") + Log.d("FileViewModel", "folderToPrivate catch: $e.message") _errorMessage.value = e.message }finally { - Log.d("FileViewModel", "changeSharing finally") + Log.d("FileViewModel", "folderToPrivate finally") stopLoading() } } - Log.d("FileViewModel", "changeSharing return") + Log.d("FileViewModel", "folderToPrivate return") } // ---------- share method ---------- diff --git a/feature/file/src/main/java/com/example/file/ui/content/BottomFolderGrid.kt b/feature/file/src/main/java/com/example/file/ui/content/BottomFolderGrid.kt index 6481aa8..c17820e 100644 --- a/feature/file/src/main/java/com/example/file/ui/content/BottomFolderGrid.kt +++ b/feature/file/src/main/java/com/example/file/ui/content/BottomFolderGrid.kt @@ -144,7 +144,7 @@ fun BottomFolderGrid( folderStateViewModel.updateBottomFolderEditBottomSheetVisible(true) }, onChangeSharing = { - fileViewModel.changeSharing(folder) + fileViewModel.folderToPrivate(folder) } ) } From 99028c2edbe99115ce89cf9775c8100ba622b9b4 Mon Sep 17 00:00:00 2001 From: gmin Date: Wed, 25 Feb 2026 17:26:34 +0900 Subject: [PATCH 13/13] =?UTF-8?q?=E2=9C=A8=20feature/file=20:=20=ED=8F=B4?= =?UTF-8?q?=EB=8D=94=20=EC=95=84=EC=9D=B4=ED=85=9C=20=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EC=95=84=EC=9B=83=20UI=20=EA=B0=9C=EC=84=A0=20=EB=B0=8F=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `FolderItemLayout` 내 텍스트 배경 아이콘에 `innerRingShadow` 효과 적용 - 불필요한 주석 코드 및 미사용 import 제거 - `TopFolderItemLayout` 및 `CategoryFolderItemLayout`에 즐겨찾기 아이콘 노출 여부 제어를 위한 `visibleBookmarked` 파라미터 추가 - `CategoryFolderItemLayout`의 공유 상태 아이콘 로직 개선 및 색상 변경 (color1 -> color2) --- .../example/file/ui/item/FolderItemLayout.kt | 412 ++---------------- 1 file changed, 37 insertions(+), 375 deletions(-) diff --git a/feature/file/src/main/java/com/example/file/ui/item/FolderItemLayout.kt b/feature/file/src/main/java/com/example/file/ui/item/FolderItemLayout.kt index f7c2ca2..60d45d7 100644 --- a/feature/file/src/main/java/com/example/file/ui/item/FolderItemLayout.kt +++ b/feature/file/src/main/java/com/example/file/ui/item/FolderItemLayout.kt @@ -4,7 +4,6 @@ package com.example.file.ui.item import androidx.compose.foundation.Image 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.BoxWithConstraints @@ -17,11 +16,9 @@ import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width -import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Surface @@ -49,21 +46,22 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.lifecycle.viewmodel.compose.viewModel import com.example.core.model.FolderSimpleInfo -import com.example.file.R import com.example.design.modifier.noRippleClickable +import com.example.design.modifier.innerRingShadow +import com.example.design.theme.color.CategoryColorStyle +import com.example.file.R import com.example.file.ui.content.BookMarkStar import com.example.file.ui.content.LockFolderIcon import com.example.file.ui.content.PencilIcon import com.example.file.ui.content.ShareFolderIcon -import com.example.file.viewmodel.edit.state.EditStateViewModel import com.example.file.ui.theme.Black -import com.example.design.theme.color.CategoryColorStyle import com.example.file.ui.theme.DefaultFont import com.example.file.ui.theme.Gray100 import com.example.file.ui.theme.Gray200 import com.example.file.ui.theme.Gray300 import com.example.file.ui.theme.Gray500 import com.example.file.ui.theme.White +import com.example.file.viewmodel.edit.state.EditStateViewModel @Composable fun FolderItemLayout( @@ -94,6 +92,10 @@ fun FolderItemLayout( shadowElevation = 3.8.dp ) { BoxWithConstraints(Modifier.fillMaxSize()) { + + // 에러 경고 제거용 변수 + val tmp = this + // 현재 실제 너비/높이에 맞춰 스케일 계산 val scaleW = maxWidth / baseW val scaleH = maxHeight / baseH @@ -218,8 +220,12 @@ fun FolderItemLayout( Box( modifier = Modifier .size(s(29.1745.dp)) - .clip(CircleShape) - .background(color = textBackgroundColor), + .clip(shape = CircleShape) + .background(color = textBackgroundColor) + .innerRingShadow( + shadowColor = Color.Black.copy(alpha = 0.10f), + edgeThickness = 8.dp + ), contentAlignment = Alignment.Center ) { Text( @@ -250,354 +256,6 @@ fun FolderItemLayout( } } -//@Composable -//fun FolderItemLayout( -// modifier: Modifier = Modifier, -// backgroundColor: Color, -// color1: Color, -// color2: Color, -// color3: Color, -// folderMaskBrush: Brush, -// leftIcon: @Composable () -> Unit, -// rightIcon: @Composable () -> Unit, -// textBackgroundColor: Color, -// folderName: String = "", -//) { -// -// @Composable -// fun FolderLayerBox( -// color: Color, -// size: Dp, -// height: Dp = size, -// padding: PaddingValues = PaddingValues(0.dp), -// rotation: Float = 0f, -// ) { -// Surface( -// modifier = Modifier -// .padding(padding) -// .rotate(rotation) -// .width(size) -// .height(height) -// .shadow( -// elevation = 4.dp, -// ambientColor = Color.Black.copy(alpha = 0.5f), // 그림자 강도 조절! -// spotColor = Color.Black.copy(alpha = 0.5f), -// shape = RoundedCornerShape(18.dp) -// ), -// shape = RoundedCornerShape(18.dp), -// color = color, -// ) {} -// } -// -// Surface( -// modifier = Modifier -// .width(165.3.dp) -// .height(145.8535.dp), -// shape = RoundedCornerShape(28.5.dp), -// color = backgroundColor, -// shadowElevation = 3.8.dp -// ) { -// Box( -// Modifier.fillMaxSize(), -// contentAlignment = Alignment.Center -// ) { -// FolderLayerBox( -// color = color1, -// size = 105.45.dp, -// padding = PaddingValues(bottom = 5.7.dp), -// rotation = -7.39f -// ) -// FolderLayerBox( -// color = color2, -// size = 105.45.dp, -// padding = PaddingValues(bottom = 3.1825.dp), -// rotation = 4.86f -// ) -// FolderLayerBox( -// color = color3, -// size = 126.407.dp, -// height = 107.9605.dp, -// padding = PaddingValues(top = 7.6.dp), -// rotation = 0f -// ) -// -// Box( -// Modifier -// .fillMaxWidth() -// .align(Alignment.BottomCenter) -// ) { -// Image( -// painter = painterResource(R.drawable.folder_mask), -// contentDescription = null, -// modifier = Modifier -// .fillMaxWidth() -// .align(Alignment.BottomCenter) -// .graphicsLayer(alpha = 0.99f) -// .drawWithCache { -// onDrawWithContent { -// drawContent() -// drawRect(folderMaskBrush, blendMode = BlendMode.SrcAtop) -// } -// } -// .shadow( -// elevation = 9.5.dp, -// ambientColor = Color.Black.copy(alpha = 0.5f), -// spotColor = Color.Black.copy(alpha = 0.5f), -// ) -// ) -// -// Box( -// modifier = Modifier -// .align(Alignment.TopStart) -// .padding(top = 12.5495.dp, start = 19.95.dp) -// ) { -// leftIcon() -// } -// -// Box( -// modifier = Modifier -// .align(Alignment.TopEnd) -// .padding(top = 25.8685.dp, end = 19.dp) -// ) { -// rightIcon() -// } -// -// if(folderName.isNotEmpty()){ -// Row( -// modifier = Modifier -// .padding(end = 5.dp) -// .align(Alignment.BottomStart) -// .offset(x = 17.499.dp, y = (-17.499).dp), -// verticalAlignment = Alignment.CenterVertically, -// horizontalArrangement = Arrangement.spacedBy(7.7805.dp) -// ) { -// Box( -// modifier = Modifier -// .size(29.1745.dp) -// .clip(CircleShape) -// .background(color = textBackgroundColor), -// contentAlignment = Alignment.Center -// ) { -// Text( -// text = folderName.first().toString(), -// fontSize = 15.sp, -// fontFamily = DefaultFont, -// fontWeight = FontWeight.Bold, -// color = White, -// ) -// } -// -// Text( -// text = folderName, -// fontSize = 15.sp, -// fontFamily = DefaultFont, -// fontWeight = FontWeight.Medium, -// color = Black, -// maxLines = 1, // 최대 1줄 -// overflow = TextOverflow.Ellipsis // 잘리면 ... 표시 -// ) -// } -// } -// } -// } -// } -//} - -//@Composable -//fun FolderItemLayout( -// backgroundColor: Color, -// color1: Color, -// color2: Color, -// color3: Color, -// folderMaskBrush: Brush, -// leftIcon: @Composable () -> Unit, -// rightIcon: @Composable () -> Unit, -// textBackgroundColor: Color, -// folderName: String = "", -// modifier: Modifier = Modifier, // ⬅️ weight를 바깥에서 주입할 수 있게 열어둠 -//) { -// // 설계 기준(원본) 크기: 165.3 x 145.8535 (dp) -// val designW = 165.3.dp -// val designH = 145.8535.dp -// val aspect = 165.3f / 145.8535f // ≈ 1.13333 -// -// // 내부 박스/카드 조각 -// @Composable -// fun FolderLayerBox( -// color: Color, -// size: Dp, -// height: Dp = size, -// padding: PaddingValues = PaddingValues(0.dp), -// rotation: Float = 0f, -// corner: Dp, -// shadow: Dp, -// ) { -// Surface( -// modifier = Modifier -// .padding(padding) -// .rotate(rotation) -// .width(size) -// .height(height) -// .shadow( -// elevation = shadow, -// ambientColor = Color.Black.copy(alpha = 0.5f), -// spotColor = Color.Black.copy(alpha = 0.5f), -// shape = RoundedCornerShape(corner) -// ), -// shape = RoundedCornerShape(corner), -// color = color, -// ) {} -// } -// -// // 외곽 Surface는 고정 dp를 없애고, 비율만 고정 (가로 기준) -// Surface( -// modifier = modifier -// .fillMaxWidth() // 바깥(Row/Column)에서 weight로 폭을 결정하게 함 -// .aspectRatio(aspect), // 세로는 비율로 자동 결정 -// // 코너/섀도는 내부에서 스케일된 값으로 다시 지정 (아래 BoxWithConstraints 안에서) -// color = Color.Transparent, -// ) { -// // 부모가 준 실제 width로 스케일 팩터 계산 -// BoxWithConstraints(Modifier.fillMaxSize()) { -// val s = (maxWidth / designW) // 스케일(폭 기준). Dp/Dp -> Float -// -// // 자주 쓰는 헬퍼 -// fun Dp.scaled() = this * s -// -// // 라운드/섀도 값도 스케일 (룩 유지) -// val cornerOuter = 28.5.dp.scaled() -// val cornerInner = 18.dp.scaled() -// val shadowCard = 3.8.dp//.scaled() -// val shadowLayer = 4.dp.scaled() -// val imgShadow = 9.5.dp.scaled() -// -// // 겉 Surface에 최종 모양/섀도 적용 -// Surface( -// modifier = Modifier.fillMaxSize(), -// shape = RoundedCornerShape(cornerOuter), -// color = backgroundColor, -// shadowElevation = shadowCard -// ) { -// Box( -// Modifier.fillMaxSize(), -// contentAlignment = Alignment.Center -// ) { -// // 1층 -// FolderLayerBox( -// color = color1, -// size = 105.45.dp.scaled(), -// padding = PaddingValues(bottom = 5.7.dp.scaled()), -// rotation = -7.39f, -// corner = cornerInner, -// shadow = shadowLayer -// ) -// // 2층 -// FolderLayerBox( -// color = color2, -// size = 105.45.dp.scaled(), -// padding = PaddingValues(bottom = 3.1825.dp.scaled()), -// rotation = 4.86f, -// corner = cornerInner, -// shadow = shadowLayer -// ) -// // 3층 -// FolderLayerBox( -// color = color3, -// size = 126.407.dp.scaled(), -// height = 107.9605.dp.scaled(), -// padding = PaddingValues(top = 7.6.dp.scaled()), -// rotation = 0f, -// corner = cornerInner, -// shadow = shadowLayer -// ) -// -// Box( -// Modifier -// .fillMaxWidth() -// .align(Alignment.BottomCenter) -// ) { -// Image( -// painter = painterResource(R.drawable.folder_mask), -// contentDescription = null, -// contentScale = ContentScale.FillWidth, -// modifier = Modifier -// .fillMaxWidth() -// .align(Alignment.BottomCenter) -// .graphicsLayer(alpha = 0.99f) -// .drawWithCache { -// onDrawWithContent { -// drawContent() -// drawRect(folderMaskBrush, blendMode = BlendMode.SrcAtop) -// } -// } -// .shadow( -// elevation = imgShadow, -// ambientColor = Color.Black.copy(alpha = 0.5f), -// spotColor = Color.Black.copy(alpha = 0.5f), -// ) -// ) -// -// Box( -// modifier = Modifier -// .align(Alignment.TopStart) -// .padding(top = 12.5495.dp.scaled(), start = 19.95.dp.scaled()) -// ) { -// leftIcon() -// } -// -// Box( -// modifier = Modifier -// .align(Alignment.TopEnd) -// .padding(top = 25.8685.dp.scaled(), end = 19.dp.scaled()) -// ) { -// rightIcon() -// } -// -// if (folderName.isNotEmpty()) { -// Row( -// modifier = Modifier -// .align(Alignment.BottomStart) -// .offset( -// x = 17.499.dp.scaled(), -// y = (-17.499).dp.scaled() -// ), -// verticalAlignment = Alignment.CenterVertically, -// horizontalArrangement = Arrangement.spacedBy(7.7805.dp.scaled()) -// ) { -// Box( -// modifier = Modifier -// .size(29.1745.dp.scaled()) -// .clip(CircleShape) -// .background(color = textBackgroundColor), -// contentAlignment = Alignment.Center -// ) { -// Text( -// text = folderName.first().toString(), -// fontSize = 15.sp, // 폰트는 보통 px 고정 권장, 필요하면 스케일링도 가능 -// fontFamily = DefaultFont, -// fontWeight = FontWeight.Bold, -// color = White, -// ) -// } -// -// Text( -// text = folderName, -// fontSize = 15.sp, -// fontFamily = DefaultFont, -// fontWeight = FontWeight.Medium, -// color = Black, -// maxLines = 1, -// overflow = TextOverflow.Ellipsis -// ) -// } -// } -// } -// } -// } -// } -// } -//} - val topFolderMaskBrush = Brush.verticalGradient( colorStops = arrayOf( 1.0f to Gray100.copy(alpha = 0.7f), @@ -629,6 +287,7 @@ fun TopFolderItemLayout( modifier: Modifier = Modifier, colorStyle: CategoryColorStyle, folderName: String = "", + visibleBookmarked: Boolean = true, isBookmarked: Boolean = false, editStateViewModel: EditStateViewModel, onBookmark: () -> Unit @@ -641,20 +300,22 @@ fun TopFolderItemLayout( folderMaskBrush = topFolderMaskBrush, leftIcon = {}, rightIcon = { - if(editStateViewModel.isEditMode){ - Box( - modifier = Modifier - ) { - PencilIcon(colorStyle.color2) - } - }else { - Box( - modifier = Modifier - .noRippleClickable { - onBookmark() - } - ) { - BookMarkStar(isBookmarked) + if(visibleBookmarked){ + if (editStateViewModel.isEditMode) { + Box( + modifier = Modifier + ) { + PencilIcon(colorStyle.color2) + } + } else { + Box( + modifier = Modifier + .noRippleClickable { + onBookmark() + } + ) { + BookMarkStar(isBookmarked) + } } } }, @@ -669,6 +330,7 @@ fun BottomFolderItemLayout( modifier: Modifier = Modifier, colorStyle: CategoryColorStyle, folder: FolderSimpleInfo, + visibleBookmarked: Boolean = true, editStateViewModel: EditStateViewModel, onEdit: ()-> Unit = {}, onChangeSharing: () -> Unit = {} @@ -687,11 +349,11 @@ fun BottomFolderItemLayout( } } ){ - folder.isSharing?.let{ sharing -> - if(sharing=="share"){ - ShareFolderIcon(colorStyle.color1) - } else { - LockFolderIcon(colorStyle.color1) + folder.isSharing?.let{ + when(it){ + "share" -> ShareFolderIcon(colorStyle.color2) + "personal" -> LockFolderIcon(colorStyle.color2) + else -> {} } } }