Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ fun SearchBarTopSheet(
* - 350ms 디바운스
* - 동일 값 중복 호출 방지
*/
LaunchedEffect(text) {
LaunchedEffect(Unit) {
snapshotFlow { text }
.map { it.trim() }
.filter { it.length >= 2 }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

검색했을 때, 한 글자만 검색한 사용자 입장에서 왜 검색이 안되지? 싶을 수 있어서, 디자이너와 상의 후 관련 ui 추가하는 건 어떠실까요?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

검색했을 때, 한 글자만 검색한 사용자 입장에서 왜 검색이 안되지? 싶을 수 있어서, 디자이너와 상의 후 관련 ui 추가하는 건 어떠실까요?

윤다인과 이다현의 TODO. 한글자일 때 비활성화+상태메시지 ui

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -27,17 +24,14 @@ 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
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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion feature/file/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -73,6 +72,9 @@ fun FileBottomSheet(
color = Basic.gray[300]
)
},

// 딤 효과 수치
scrimColor = Basic.black.copy(alpha = 0.5f),
sheetState = sheetState,
onDismissRequest = onDismiss,
tonalElevation = 8.dp,
Expand All @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -62,9 +64,9 @@ fun LinkCategorizationBottomSheet(
) {
val links by fileViewModel.notCategorizationLinks.collectAsStateWithLifecycle()

var link by remember { mutableStateOf<LinkItemInfo?>(null) }
//var link by remember { mutableStateOf<LinkItemInfo?>(null) }

var selectedLinks = mutableListOf<LinkItemInfo>()
val selectedLinks = remember { mutableStateListOf<LinkItemInfo>() }

val scope = rememberCoroutineScope()

Expand All @@ -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()
Comment on lines +80 to +90
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

find . -name "LinkCategorizationBottomSheet.kt" -type f

Repository: LinkYou-2025/LinkU_Android

Length of output: 164


🏁 Script executed:

wc -l ./feature/file/src/main/java/com/example/file/ui/bottom/sheet/LinkCategorizationBottomSheet.kt

Repository: LinkYou-2025/LinkU_Android

Length of output: 168


🏁 Script executed:

cat -n ./feature/file/src/main/java/com/example/file/ui/bottom/sheet/LinkCategorizationBottomSheet.kt

Repository: LinkYou-2025/LinkU_Android

Length of output: 11453


🏁 Script executed:

find . -name "FolderStateViewModel.kt" -type f

Repository: LinkYou-2025/LinkU_Android

Length of output: 162


🏁 Script executed:

wc -l ./feature/file/src/main/java/com/example/file/viewmodel/folder/state/FolderStateViewModel.kt

Repository: LinkYou-2025/LinkU_Android

Length of output: 166


🏁 Script executed:

cat -n ./feature/file/src/main/java/com/example/file/viewmodel/folder/state/FolderStateViewModel.kt

Repository: LinkYou-2025/LinkU_Android

Length of output: 6378


selectedBottomFolder가 null이면 앱이 크래시됩니다.

selectedBottomFolder는 nullable 타입(FolderSimpleInfo?)으로 선언되어 있고, isReady는 선택된 링크 여부만 확인하므로 폴더 유효성을 검증하지 않습니다. 사용자가 폴더를 선택하지 않은 상태에서 버튼을 클릭하면 requireNotNull(folderStateViewModel.selectedBottomFolder?.folderId)에서 NPE가 발생하여 앱이 강제 종료됩니다. 폴더 존재 여부를 isReady에 추가하거나 onOkay에서 안전한 early-return 처리를 하세요.

🛡️ 제안 수정안
-        isReady = selectedLinks.isNotEmpty(),
+        isReady = selectedLinks.isNotEmpty() &&
+            folderStateViewModel.selectedBottomFolder?.folderId != null,
         visible = folderStateViewModel.linkCategorizationBottomSheetVisible,
         onOkay = {
             scope.launch{
-                val folderId = requireNotNull(folderStateViewModel.selectedBottomFolder?.folderId)
+                val folderId =
+                    folderStateViewModel.selectedBottomFolder?.folderId ?: return@launch
 
                 selectedLinks.forEach {
                     fileViewModel.updateLinkFolder(it, folderId)
                 }
 
                 selectedLinks.clear()
             }
         },
🤖 Prompt for AI Agents
In
`@feature/file/src/main/java/com/example/file/ui/bottom/sheet/LinkCategorizationBottomSheet.kt`
around lines 80 - 90, 사용자가 폴더를 선택하지 않았을 때
requireNotNull(folderStateViewModel.selectedBottomFolder?.folderId)로 인해 앱이 크래시
발생하므로, selectedBottomFolder의 유효성을 검사하도록 수정하세요: UI 상태 검사 지점인 isReady에
folderStateViewModel.selectedBottomFolder != null 조건을 추가하거나, 더 안전하게 onOkay
블록(함수명/속성: onOkay, scope.launch) 시작부에서 val folderId =
folderStateViewModel.selectedBottomFolder?.folderId ?: return@launch 식의
early-return을 추가해 folderId가 없으면 작업을 중단하고 selectedLinks 순회나 updateLinkFolder 호출을
하지 않도록 하세요; 관련 식별자: selectedBottomFolder, isReady, onOkay, folderStateViewModel,
selectedLinks, updateLinkFolder, requireNotNull.

}
},
onDismiss = { folderStateViewModel.updateLinkCategorizationBottomSheetVisible(false) }
Expand All @@ -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)
Expand All @@ -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,
Expand Down Expand Up @@ -194,15 +205,17 @@ fun LinkCategorizationBottomSheet(
Text(
text = url,
fontSize = 12.sp,
lineHeight = 14.sp,
fontFamily = DefaultFont,
fontWeight = FontWeight(400),
color = Gray800,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
// 링크 하단 패딩
Spacer(modifier = Modifier.height(7.dp))
}

}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import com.example.file.ui.theme.White

// 공유 폴더 사람 아이콘
@Composable
fun ShareFolderIcon(
internal fun ShareFolderIcon(
tint: Color
) {
Icon(
Expand All @@ -31,7 +31,7 @@ fun ShareFolderIcon(

// 잠금 폴더 자물쇠 아이콘
@Composable
fun LockFolderIcon(
internal fun LockFolderIcon(
tint: Color
) {
Icon(
Expand All @@ -43,7 +43,7 @@ fun LockFolderIcon(

// 수정 연필 아이콘
@Composable
fun PencilIcon(
internal fun PencilIcon(
tint: Color
) {
Icon(
Expand All @@ -55,7 +55,7 @@ fun PencilIcon(

// 북마크 별 아이콘
@Composable
fun BookMarkStar(
internal fun BookMarkStar(
isBookmarked: Boolean
) {
val modifier = if(isBookmarked) Modifier
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand All @@ -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(
Expand Down Expand Up @@ -164,8 +161,8 @@ fun LinksGrid(
},
onDismiss = { deleteModalWindowVisible = false },
title = "해당 링크를 삭제하시겠습니까?",
positiveText = "삭제",
negativeText = "취소"
positiveText = "삭제하기",
negativeText = "취소하기"
) {
Text(
text = "삭제 시 해당 링크가 영구적으로 제거되며\n복구가 불가능합니다.",
Expand Down
Loading