diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 4407c9b..e9dbc94 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -153,6 +153,6 @@ dependencies { implementation(libs.androidx.datastore.preferences) - + implementation("androidx.compose.material:material-icons-extended:1.7.2") } \ No newline at end of file diff --git a/app/src/main/java/top/maary/oblivionis/ui/ActionComponents.kt b/app/src/main/java/top/maary/oblivionis/ui/ActionComponents.kt index 5f2deaf..3737bd7 100644 --- a/app/src/main/java/top/maary/oblivionis/ui/ActionComponents.kt +++ b/app/src/main/java/top/maary/oblivionis/ui/ActionComponents.kt @@ -14,6 +14,7 @@ import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.animateScrollBy import androidx.compose.foundation.gestures.awaitEachGesture import androidx.compose.foundation.gestures.awaitFirstDown +import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.gestures.draggable import androidx.compose.foundation.gestures.rememberDraggableState import androidx.compose.foundation.interaction.MutableInteractionSource @@ -37,6 +38,8 @@ import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.CheckCircle +import androidx.compose.material.icons.filled.RadioButtonUnchecked import androidx.compose.material3.AlertDialog import androidx.compose.material3.Badge import androidx.compose.material3.BadgeDefaults @@ -73,6 +76,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.shadow import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.nestedscroll.nestedScroll @@ -418,30 +422,101 @@ fun ActionScreen( } } +//@Composable +//fun MediaPlayer(modifier: Modifier, uri: Uri, imageLoader: ImageLoader, +// onImageClick: () -> Unit = {}, onVideoClick: () -> Unit, onLongPress: () -> Unit = {}) { +// Box(modifier = Modifier.wrapContentSize()) { +// when { +// uri.toString().startsWith(MediaStore.Images.Media.EXTERNAL_CONTENT_URI.toString()) -> { +// // 处理图片变化 +// AsyncImage( +// model = uri, +// contentDescription = "", +// modifier = modifier +// .clickable { onImageClick() } +// .clip(RoundedCornerShape(8.dp)) +// ) +// } +// +// uri.toString().startsWith(MediaStore.Video.Media.EXTERNAL_CONTENT_URI.toString()) -> { +// // 处理视频变化 +//// VideoView(modifier = modifier, uri = uri) +// VideoViewAlt(modifier = modifier, uri = uri, imageLoader = imageLoader, onClick = { +// onVideoClick() +// }) +// } +// } +// } +//} + @Composable -fun MediaPlayer(modifier: Modifier, uri: Uri, imageLoader: ImageLoader, - onImageClick: () -> Unit = {}, onVideoClick: () -> Unit) { - when { - uri.toString().startsWith(MediaStore.Images.Media.EXTERNAL_CONTENT_URI.toString()) -> { - // 处理图片变化 - AsyncImage( - model = uri, - contentDescription = "", - modifier = modifier - .clickable { onImageClick() } - .clip(RoundedCornerShape(8.dp)) - ) +fun MediaPlayer( + modifier: Modifier, + uri: Uri, + imageLoader: ImageLoader, + isMultiSelectionState: Boolean = false, + isSelected: Boolean = false, // 用于表示当前项是否被选中 + onImageClick: () -> Unit = {}, + onVideoClick: () -> Unit = {}, + onLongPress: () -> Unit = {} // 长按事件 +) { + Box( + modifier = Modifier + .wrapContentSize() + .pointerInput(Unit) { + detectTapGestures( + onLongPress = { + Log.v("OBLIVIONIS", "LOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONG PRESS") + onLongPress() // 处理长按事件 + }, + onTap = { + if (uri.toString().startsWith(MediaStore.Images.Media.EXTERNAL_CONTENT_URI.toString())) { + onImageClick() + } else if (uri.toString().startsWith(MediaStore.Video.Media.EXTERNAL_CONTENT_URI.toString())) { + onVideoClick() + } + } + ) + } + ) { + when { + // 处理图片显示 + uri.toString().startsWith(MediaStore.Images.Media.EXTERNAL_CONTENT_URI.toString()) -> { + AsyncImage( + model = uri, + contentDescription = "", + modifier = modifier + .clip(RoundedCornerShape(8.dp)) + ) + } + + // 处理视频显示 + uri.toString().startsWith(MediaStore.Video.Media.EXTERNAL_CONTENT_URI.toString()) -> { + VideoViewAlt( + modifier = modifier, + uri = uri, + imageLoader = imageLoader + ) + } } - uri.toString().startsWith(MediaStore.Video.Media.EXTERNAL_CONTENT_URI.toString()) -> { - // 处理视频变化 -// VideoView(modifier = modifier, uri = uri) - VideoViewAlt(modifier = modifier, uri = uri, imageLoader = imageLoader,onClick = { - onVideoClick() - }) + + if (isMultiSelectionState) { + // 显示选择框的图标 + Icon( + imageVector = if (isSelected) Icons.Default.CheckCircle else Icons.Default.RadioButtonUnchecked, + contentDescription = if (isSelected) "Selected" else "Not Selected", + modifier = Modifier + .align(Alignment.TopEnd) // 选择框显示在右上角 + .size(48.dp) + .padding(8.dp), + tint = if (isSelected) Color.Blue else Color.Gray // 设置选中的颜色 + ) } + } } + @Composable fun ActionRow( modifier: Modifier, diff --git a/app/src/main/java/top/maary/oblivionis/ui/RecycleCompoments.kt b/app/src/main/java/top/maary/oblivionis/ui/RecycleCompoments.kt index fdbadcf..40b3166 100644 --- a/app/src/main/java/top/maary/oblivionis/ui/RecycleCompoments.kt +++ b/app/src/main/java/top/maary/oblivionis/ui/RecycleCompoments.kt @@ -1,12 +1,14 @@ package top.maary.oblivionis.ui import android.app.Activity +import android.util.Log import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.IntentSenderRequest import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.calculateTargetValue import androidx.compose.animation.splineBasedDecay +import androidx.compose.foundation.background import androidx.compose.foundation.gestures.awaitFirstDown import androidx.compose.foundation.gestures.verticalDrag import androidx.compose.foundation.layout.Spacer @@ -32,12 +34,15 @@ import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.composed +import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.positionChange import androidx.compose.ui.input.pointer.util.VelocityTracker @@ -101,6 +106,7 @@ fun RecycleScreen( } } + val selectedItems = remember { mutableStateOf(mutableSetOf()) } Scaffold( containerColor = MaterialTheme.colorScheme.surfaceContainer, @@ -153,9 +159,26 @@ fun RecycleScreen( dialogText = stringResource(id = R.string.deleteAllConfirmation) ) } - FilledTonalButton(onClick = { openDialog.value = true }, - modifier = Modifier.padding(end = 8.dp)) { - Text(stringResource(id = R.string.deleteAll)) + if (selectedItems.value.isNotEmpty()) { + FilledTonalButton( + onClick = { + if (selectedItems.value.size != images.value.size) { + selectedItems.value = images.value.indices.toMutableSet() + }else{ + selectedItems.value = mutableSetOf() + } }, + modifier = Modifier.padding(end = 8.dp) + ) { + Text(stringResource(id = R.string.select_all)) + } + } else { + FilledTonalButton( + enabled = images.value.isNotEmpty(), + onClick = { openDialog.value = true }, + modifier = Modifier.padding(end = 8.dp) + ) { + Text(stringResource(id = R.string.deleteAll)) + } } }, title = {} @@ -165,19 +188,28 @@ fun RecycleScreen( val openDialog = remember { mutableStateOf(false) } - FloatingActionButton(onClick = { openDialog.value = true }) { - if (openDialog.value) { - Dialog( - onDismissRequest = { openDialog.value = false }, - onConfirmation = { - actionViewModel.unMarkAll() - openDialog.value = false }, - dialogText = stringResource(id = R.string.restoreAllConfirmation) + if (selectedItems.value.isNotEmpty()) { + FloatingActionButton(onClick = { openDialog.value = true }) { + if (openDialog.value) { + Dialog( + onDismissRequest = { openDialog.value = false }, + onConfirmation = { + selectedItems.value.forEach { index -> + actionViewModel.unMarkImage(images.value[index]) + } + selectedItems.value = mutableSetOf() + openDialog.value = false + }, + dialogText = stringResource(id = R.string.restore_selected_confirmation) + ) + } + Icon( + painter = painterResource(id = R.drawable.ic_restore_all), + contentDescription = stringResource( + id = R.string.restoreAllConfirmation + ) ) } - Icon(painter = painterResource(id = R.drawable.ic_restore_all), contentDescription = stringResource( - id = R.string.restoreAllConfirmation - )) } } ) { innerPadding -> @@ -201,6 +233,7 @@ fun RecycleScreen( ) } items(images.value.size) { index -> + val isSelected by remember { derivedStateOf { selectedItems.value.contains(index) }} if (openDialog.value) { Dialog( @@ -216,16 +249,43 @@ fun RecycleScreen( MediaPlayer( modifier = Modifier - .padding(2.dp), + .padding(2.dp) + .background(if (isSelected) Color.Gray else Color.Transparent), uri = images.value[index].contentUri, + isMultiSelectionState = selectedItems.value.isNotEmpty(), + isSelected = selectedItems.value.contains(index), imageLoader = imageLoader, onImageClick = { + if (selectedItems.value.isNotEmpty()) { + val newSet = selectedItems.value.toMutableSet() // 创建一个新集合 + if (!newSet.add(index)) { + newSet.remove(index) // 如果元素已存在,则移除 + } + selectedItems.value = newSet // 更新 `selectedItems.value`,触发重组 + return@MediaPlayer + } clickedIndex.intValue = index openDialog.value = true }, onVideoClick = { + if (selectedItems.value.isNotEmpty()) { + val newSet = selectedItems.value.toMutableSet() // 创建一个新集合 + if (!newSet.add(index)) { + newSet.remove(index) // 如果元素已存在,则移除 + } + selectedItems.value = newSet // 更新 `selectedItems.value`,触发重组 + return@MediaPlayer + } clickedIndex.intValue = index openDialog.value = true + }, + onLongPress = { + val newSet = selectedItems.value.toMutableSet() // 创建一个新集合 + if (!newSet.add(index)) { + newSet.remove(index) // 如果元素已存在,则移除 + } + selectedItems.value = newSet // 更新 `selectedItems.value`,触发重组 + }) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2479615..f658483 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -46,4 +46,6 @@ This media will be excluded when holding delete button to delete This media has been marked to exclude from deletion, are you sure to delete it? Share this media + Select All + Selected media will be restored. \ No newline at end of file