Skip to content

Commit

Permalink
[feat]long press button to mark all images
Browse files Browse the repository at this point in the history
[feat]add fab to restore all images
  • Loading branch information
Steve-Mr committed Sep 25, 2024
1 parent 168ac9b commit ebb3722
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 9 deletions.
83 changes: 77 additions & 6 deletions app/src/main/java/top/maary/oblivionis/ui/ActionComponents.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,24 @@ import android.provider.MediaStore
import android.util.Log
import androidx.compose.animation.core.animate
import androidx.compose.animation.core.tween
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable
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.draggable
import androidx.compose.foundation.gestures.rememberDraggableState
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.PressInteraction
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
Expand All @@ -31,7 +41,9 @@ import androidx.compose.material3.Badge
import androidx.compose.material3.BadgeDefaults
import androidx.compose.material3.BadgedBox
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonColors
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ButtonElevation
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.ElevatedCard
Expand All @@ -42,6 +54,7 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.IconButtonColors
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBarDefaults
Expand All @@ -58,16 +71,27 @@ import androidx.compose.ui.Alignment
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.Shape
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.input.pointer.PointerEvent
import androidx.compose.ui.input.pointer.PointerInputChange
import androidx.compose.ui.input.pointer.PointerInputScope
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalViewConfiguration
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.semantics
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.dp
import androidx.compose.ui.util.lerp
import androidx.compose.ui.viewinterop.AndroidView
Expand All @@ -85,6 +109,8 @@ import io.sanghun.compose.video.VideoPlayer
import io.sanghun.compose.video.controller.VideoPlayerControllerConfig
import io.sanghun.compose.video.uri.VideoPlayerMediaItem
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest
import kotlin.math.absoluteValue

@OptIn(ExperimentalMaterial3Api::class)
Expand All @@ -111,6 +137,10 @@ fun ActionScreen(
}
.build()

val openDialog = remember {
mutableStateOf(false)
}

Scaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
containerColor = MaterialTheme.colorScheme.surfaceContainer,
Expand Down Expand Up @@ -142,7 +172,6 @@ fun ActionScreen(
} else {
BadgeDefaults.containerColor
}
// FilledTonalButton(onClick = { onNextButtonClicked() }) {
BadgedBox(
modifier = Modifier.padding(end = 8.dp),
badge = { Badge (containerColor = badgeColor) { Text(text = marked.value.size.toString())}}) {
Expand All @@ -151,7 +180,6 @@ fun ActionScreen(
painter = painterResource(id = R.drawable.ic_recycle),
contentDescription = stringResource(id = R.string.go_to_recycle_screen)
)
// }
}
}

Expand All @@ -160,6 +188,15 @@ fun ActionScreen(
)
},
bottomBar = {
if (openDialog.value) {
Dialog(
onDismissRequest = { openDialog.value = false },
onConfirmation = {
viewModel.markAllImages()
openDialog.value = false },
dialogText = stringResource(id = R.string.deleteAllConfirmation)
)
}
ActionRow(
modifier = Modifier.navigationBarsPadding(),
delButtonClickable = images.value.isNotEmpty(),
Expand All @@ -169,6 +206,9 @@ fun ActionScreen(
viewModel.markImage(pagerState.currentPage)
}
},
onDelButtonLongClicked = {
openDialog.value = true
},
onRollBackButtonClicked = {
viewModel.unMarkLastImage()
},
Expand Down Expand Up @@ -254,7 +294,8 @@ fun ActionScreen(
if (dragOffset < -300f || velocity < -1000f) { // Threshold for dismissal
// Call the function to remove the item
coroutineScope {
val screenHeight = with(density) { configuration.screenHeightDp.dp.value * density.absoluteValue }
val screenHeight =
with(density) { configuration.screenHeightDp.dp.value * density.absoluteValue }
val targetValue = -screenHeight // 将目标值设置为屏幕上边缘
animate(
initialValue = dragOffset,
Expand Down Expand Up @@ -337,6 +378,7 @@ fun MediaPlayer(modifier: Modifier, uri: Uri, imageLoader: ImageLoader,
fun ActionRow(
modifier: Modifier,
delButtonClickable: Boolean = true,
onDelButtonLongClicked: () -> Unit,
onDelButtonClicked: () -> Unit,
onRollBackButtonClicked: () -> Unit,
showRestore: Boolean
Expand All @@ -352,7 +394,8 @@ fun ActionRow(
onClick = onRollBackButtonClicked,
shape = CircleShape,
contentPadding = PaddingValues(0.dp),
modifier = Modifier.size(48.dp),
modifier = Modifier
.size(48.dp),
colors = ButtonDefaults.filledTonalButtonColors(),
elevation = ButtonDefaults.buttonElevation(10.dp)
) {
Expand All @@ -365,17 +408,45 @@ fun ActionRow(
}
}


val interactionSource = remember { MutableInteractionSource() }

val viewConfiguration = LocalViewConfiguration.current


LaunchedEffect(interactionSource) {
var isLongClick = false

interactionSource.interactions.collectLatest { interaction ->
when (interaction) {
is PressInteraction.Press -> {
isLongClick = false
delay(viewConfiguration.longPressTimeoutMillis)
isLongClick = true
onDelButtonLongClicked()
}

is PressInteraction.Release -> {
if (isLongClick.not()) {
onDelButtonClicked()
}
}
}
}
}

Box(modifier = modifier
.fillMaxWidth()
.padding(16.dp), contentAlignment = Alignment.Center) {
Button(
onClick = onDelButtonClicked,
onClick = { },
enabled = delButtonClickable,
shape = CircleShape,
contentPadding = PaddingValues(0.dp),
modifier = Modifier.size(48.dp),
colors = ButtonDefaults.buttonColors(containerColor = BadgeDefaults.containerColor),
elevation = ButtonDefaults.buttonElevation(10.dp)
elevation = ButtonDefaults.buttonElevation(10.dp),
interactionSource = interactionSource
) {
Icon(
painter = painterResource(id = R.drawable.ic_close),
Expand Down
21 changes: 21 additions & 0 deletions app/src/main/java/top/maary/oblivionis/ui/RecycleCompoments.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridItemSpan
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
Expand Down Expand Up @@ -89,6 +90,7 @@ fun RecycleScreen(
}
}


Scaffold(
containerColor = MaterialTheme.colorScheme.surfaceContainer,

Expand Down Expand Up @@ -132,6 +134,25 @@ fun RecycleScreen(
},
title = {}
)
},
floatingActionButton = {
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)
)
}
Icon(painter = painterResource(id = R.drawable.ic_restore_all), contentDescription = stringResource(
id = R.string.restoreAllConfirmation
))
}
}
) { innerPadding ->
val openDialog = remember {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ class ActionViewModel(
viewModelScope.launch {
performDeleteImageList(images)
clearImages()
loadAlbums()
}
}

Expand Down Expand Up @@ -404,7 +405,6 @@ class ActionViewModel(

init {
loadAlbums()
/* TODO 获取权限 */
}

fun databaseMark(image: MediaStoreImage) = viewModelScope.launch {
Expand All @@ -415,14 +415,39 @@ class ActionViewModel(
imageRepository.unmark(image.copy(isMarked = false))
}

fun databaseRemoveAll() = viewModelScope.launch {
imageRepository.removeAll()
fun databaseMarkAll(images: List<MediaStoreImage>) = viewModelScope.launch {
images.forEach {
imageRepository.mark(it.copy(isMarked = false))
}
}

fun databaseRemoveAll() = databaseUnmarkAll(_images.value)

fun removeId(id: Long) = viewModelScope.launch {
imageRepository.removeId(id)
}

fun markAllImages() {
databaseMarkAll(images = _images.value)
_images.update { currentList ->
currentList.map { it.copy(isMarked = true) }
}
}

fun unMarkAll() {
databaseUnmarkAll(_images.value)
_images.update { currentList ->
currentList.map { it.copy(isMarked = false) }
}
_lastMarked.value = null
}

private fun databaseUnmarkAll(images: List<MediaStoreImage>) = viewModelScope.launch {
images.forEach {
imageRepository.unmark(it.copy(isMarked = false))
}
}

@Suppress("UNCHECKED_CAST")
companion object {
val Factory : ViewModelProvider.Factory = object : ViewModelProvider.Factory {
Expand Down
7 changes: 7 additions & 0 deletions app/src/main/res/drawable/ic_restore_all.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">

<path android:fillColor="@android:color/white" android:pathData="M12,5V2L8,6l4,4V7c3.31,0 6,2.69 6,6c0,2.97 -2.17,5.43 -5,5.91v2.02c3.95,-0.49 7,-3.85 7,-7.93C20,8.58 16.42,5 12,5z"/>

<path android:fillColor="@android:color/white" android:pathData="M6,13c0,-1.65 0.67,-3.15 1.76,-4.24L6.34,7.34C4.9,8.79 4,10.79 4,13c0,4.08 3.05,7.44 7,7.93v-2.02C8.17,18.43 6,15.97 6,13z"/>

</vector>
1 change: 1 addition & 0 deletions app/src/main/res/values-zh-rCN/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@
<string name="congratulations">恭喜!清理干净了。</string>
<string name="nothing_to_do">无事可做。</string>
<string name="choose_app">选择应用打开</string>
<string name="restoreAllConfirmation">此操作将恢复此页面上的所有媒体文件</string>
</resources>
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@
<string name="congratulations">Congratulations!\nThere is nothing to delete.</string>
<string name="nothing_to_do">There is nothing to do.</string>
<string name="choose_app">Choose an app to open this media</string>
<string name="restoreAllConfirmation">This action will restore all the media here.</string>
</resources>

0 comments on commit ebb3722

Please sign in to comment.