Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Delete multiple videos at once #756

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ data class Video(
val duration: Long,
val uriString: String,
val displayName: String,
var isSelected: Boolean = false,
val nameWithExtension: String,
val width: Int,
val height: Int,
Expand All @@ -31,6 +32,7 @@ data class Video(
uriString = "",
nameWithExtension = "Avengers Endgame (2019) BluRay x264.mp4",
duration = 1000,
isSelected = false,
displayName = "Avengers Endgame (2019) BluRay x264",
width = 1920,
height = 1080,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import androidx.compose.material.icons.rounded.RadioButtonUnchecked
import androidx.compose.material.icons.rounded.Replay10
import androidx.compose.material.icons.rounded.ResetTv
import androidx.compose.material.icons.rounded.ScreenRotationAlt
import androidx.compose.material.icons.rounded.SelectAll
import androidx.compose.material.icons.rounded.Settings
import androidx.compose.material.icons.rounded.Share
import androidx.compose.material.icons.rounded.Speed
Expand Down Expand Up @@ -93,6 +94,7 @@ object NextIcons {
val Link = Icons.Rounded.Link
val Location = Icons.Rounded.LocationOn
val Movie = Icons.Rounded.LocalMovies
val MultiSelect = Icons.Rounded.SelectAll
val PhotoSize = Icons.Rounded.PhotoSizeSelectSmall
val Pinch = Icons.Rounded.Pinch
val Player = Icons.Rounded.PlayCircle
Expand Down
2 changes: 2 additions & 0 deletions core/ui/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,8 @@
<string name="frame_rate">Frame rate</string>
<string name="okay">Okay</string>
<string name="properties">Properties</string>
<string name="multi_select">Multi-select</string>
<string name="selected_tracks_count">%1$d/%2$d Selected</string>
<string name="volume_boost">Volume boost</string>
<string name="volume_boost_desc">Boost audio volume up to 200%</string>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ class MediaPickerScreenTest {
foldersState = FoldersState.Loading,
preferences = ApplicationPreferences(),
onDeleteVideoClick = {},
onDeleteFolderClick = {}
onDeleteFolderClick = {},
selectedTracks = emptyList(),
clearSelectedTracks = {}
)
}
}
Expand All @@ -59,7 +61,9 @@ class MediaPickerScreenTest {
foldersState = FoldersState.Loading,
preferences = ApplicationPreferences().copy(groupVideosByFolder = false),
onDeleteVideoClick = {},
onDeleteFolderClick = {}
onDeleteFolderClick = {},
selectedTracks = videoItemsTestData,
clearSelectedTracks = {}
)
}
}
Expand Down Expand Up @@ -98,7 +102,9 @@ class MediaPickerScreenTest {
),
preferences = ApplicationPreferences().copy(groupVideosByFolder = true),
onDeleteVideoClick = {},
onDeleteFolderClick = {}
onDeleteFolderClick = {},
selectedTracks = emptyList(),
clearSelectedTracks = {}
)
}
}
Expand Down Expand Up @@ -137,7 +143,9 @@ class MediaPickerScreenTest {
foldersState = FoldersState.Loading,
preferences = ApplicationPreferences().copy(groupVideosByFolder = false),
onDeleteVideoClick = {},
onDeleteFolderClick = {}
onDeleteFolderClick = {},
selectedTracks = emptyList(),
clearSelectedTracks = {}
)
}
}
Expand Down Expand Up @@ -166,7 +174,9 @@ class MediaPickerScreenTest {
),
preferences = ApplicationPreferences().copy(groupVideosByFolder = true),
onDeleteVideoClick = {},
onDeleteFolderClick = {}
onDeleteFolderClick = {},
selectedTracks = emptyList(),
clearSelectedTracks = {}
)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dev.anilbeesetti.nextplayer.feature.videopicker.composables

import androidx.compose.foundation.background
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.ExperimentalLayoutApi
Expand All @@ -12,6 +13,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem
import androidx.compose.material3.ListItemDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
Expand Down Expand Up @@ -41,10 +43,19 @@ import dev.anilbeesetti.nextplayer.core.ui.theme.NextPlayerTheme
fun VideoItem(
video: Video,
preferences: ApplicationPreferences,
isSelected: Boolean,
modifier: Modifier = Modifier
) {
val context = LocalContext.current
val selectedItemColor = if (!isSystemInDarkTheme()) {
MaterialTheme.colorScheme.primary.copy(alpha = 0.3f)
} else {
MaterialTheme.colorScheme.secondary.copy(alpha = 0.3f)
}
ListItem(
colors = ListItemDefaults.colors(
containerColor = if (isSelected) selectedItemColor else ListItemDefaults.containerColor
),
leadingContent = {
Box(
modifier = Modifier
Expand All @@ -53,14 +64,6 @@ fun VideoItem(
.width(min(150.dp, LocalConfiguration.current.screenWidthDp.dp * 0.35f))
.aspectRatio(16f / 10f)
) {
Icon(
imageVector = NextIcons.Video,
contentDescription = null,
tint = MaterialTheme.colorScheme.surfaceColorAtElevation(100.dp),
modifier = Modifier
.align(Alignment.Center)
.fillMaxSize(0.5f)
)
if (preferences.showThumbnailField) {
AsyncImage(
model = ImageRequest.Builder(context)
Expand All @@ -70,7 +73,17 @@ fun VideoItem(
contentDescription = null,
alignment = Alignment.Center,
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize()
modifier = Modifier.fillMaxSize(),
alpha = if (isSelected) 0.2f else 1f
)
} else {
Icon(
imageVector = NextIcons.Video,
contentDescription = null,
tint = MaterialTheme.colorScheme.surfaceColorAtElevation(100.dp),
modifier = Modifier
.align(Alignment.Center)
.fillMaxSize(0.5f)
)
}
if (preferences.showDurationField) {
Expand All @@ -84,6 +97,14 @@ fun VideoItem(
shape = MaterialTheme.shapes.extraSmall
)
}
if (isSelected) {
Icon(
imageVector = NextIcons.CheckBox,
contentDescription = "Selected",
modifier = Modifier
.align(Alignment.BottomStart)
)
}
}
},
headlineContent = {
Expand Down Expand Up @@ -129,7 +150,7 @@ fun VideoItem(
fun VideoItemPreview() {
NextPlayerTheme {
Surface {
VideoItem(video = Video.sample, preferences = ApplicationPreferences())
VideoItem(video = Video.sample, isSelected = true, preferences = ApplicationPreferences())
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import dev.anilbeesetti.nextplayer.core.ui.R
import dev.anilbeesetti.nextplayer.core.ui.components.NextDialog
import dev.anilbeesetti.nextplayer.core.ui.designsystem.NextIcons
import dev.anilbeesetti.nextplayer.feature.videopicker.screens.VideosState
import dev.anilbeesetti.nextplayer.feature.videopicker.screens.media.MediaPickerViewModel
import kotlinx.coroutines.launch

@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
Expand All @@ -47,13 +48,22 @@ fun VideosView(
videosState: VideosState,
preferences: ApplicationPreferences,
onVideoClick: (Uri) -> Unit,
onDeleteVideoClick: (String) -> Unit,
onVideoLoaded: (Uri) -> Unit = {}
onDeleteVideoClick: (List<String>) -> Unit,
toggleMultiSelect: () -> Unit,
disableMultiSelect: Boolean,
totalVideos: (Int) -> Unit,
onVideoLoaded: (Uri) -> Unit = {},
viewModel: MediaPickerViewModel?
) {
val haptic = LocalHapticFeedback.current
var showMediaActionsFor: Video? by rememberSaveable { mutableStateOf(null) }
var deleteAction: Video? by rememberSaveable { mutableStateOf(null) }
var showInfoAction: Video? by rememberSaveable { mutableStateOf(null) }
var multiSelect by rememberSaveable { mutableStateOf(false) }

if (disableMultiSelect) {
multiSelect = false
}
val scope = rememberCoroutineScope()
val context = LocalContext.current
val bottomSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
Expand All @@ -63,19 +73,36 @@ fun VideosView(
is VideosState.Success -> if (videosState.data.isEmpty()) {
NoVideosFound()
} else {
totalVideos(videosState.data.size)
MediaLazyList {
items(videosState.data, key = { it.path }) { video ->
if (disableMultiSelect) {
viewModel?.videoTracks = videosState.data.map { it.copy() }
}

items(viewModel?.videoTracks ?: videosState.data, key = { it.path }) { video ->
LaunchedEffect(Unit) {
onVideoLoaded(Uri.parse(video.uriString))
}
VideoItem(
video = video,
preferences = preferences,
isSelected = video.isSelected,
modifier = Modifier.combinedClickable(
onClick = { onVideoClick(Uri.parse(video.uriString)) },
onClick = {
if (multiSelect) {
video.isSelected = !video.isSelected
viewModel?.let {
toggleSelection(video, it)
}
} else {
onVideoClick(Uri.parse(video.uriString))
}
},
onLongClick = {
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
showMediaActionsFor = video
if (!multiSelect) {
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
showMediaActionsFor = video
}
}
)
)
Expand Down Expand Up @@ -128,6 +155,17 @@ fun VideosView(
}
}
)
BottomSheetItem(
text = stringResource(R.string.multi_select),
icon = NextIcons.MultiSelect,
onClick = {
multiSelect = true
toggleMultiSelect()
scope.launch { bottomSheetState.hide() }.invokeOnCompletion {
if (!bottomSheetState.isVisible) showMediaActionsFor = null
}
}
)
}
}

Expand All @@ -136,7 +174,7 @@ fun VideosView(
subText = stringResource(id = R.string.delete_file),
onCancel = { deleteAction = null },
onConfirm = {
onDeleteVideoClick(it.uriString)
onDeleteVideoClick(listOf(it.uriString))
deleteAction = null
},
fileNames = listOf(it.nameWithExtension)
Expand All @@ -151,6 +189,14 @@ fun VideosView(
}
}

private fun toggleSelection(video: Video, viewModel: MediaPickerViewModel) {
if (video.isSelected) {
viewModel.addToSelectedTracks(video)
} else {
viewModel.removeFromSelectedTracks(video)
}
}

@Composable
fun ShowVideoInfoDialog(
video: Video,
Expand Down
Loading