From 57672de34dc667687636e51cfd57260136054376 Mon Sep 17 00:00:00 2001 From: Yashraj254 Date: Tue, 14 Nov 2023 14:29:53 +0530 Subject: [PATCH 1/6] select multiple videos functionality added --- .../nextplayer/core/model/Video.kt | 2 + .../core/ui/designsystem/NextIcons.kt | 2 + core/ui/src/main/res/values/strings.xml | 1 + .../videopicker/composables/VideoItem.kt | 25 +++++++- .../videopicker/composables/VideosView.kt | 60 +++++++++++++++++-- .../navigation/MediaPickerFolderNavigation.kt | 6 +- .../screens/media/MediaPickerScreen.kt | 43 ++++++++++++- .../mediaFolder/MediaPickerFolderScreen.kt | 50 +++++++++++++++- 8 files changed, 176 insertions(+), 13 deletions(-) diff --git a/core/model/src/main/java/dev/anilbeesetti/nextplayer/core/model/Video.kt b/core/model/src/main/java/dev/anilbeesetti/nextplayer/core/model/Video.kt index dd3aeac3d..b258cab97 100644 --- a/core/model/src/main/java/dev/anilbeesetti/nextplayer/core/model/Video.kt +++ b/core/model/src/main/java/dev/anilbeesetti/nextplayer/core/model/Video.kt @@ -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, @@ -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, diff --git a/core/ui/src/main/java/dev/anilbeesetti/nextplayer/core/ui/designsystem/NextIcons.kt b/core/ui/src/main/java/dev/anilbeesetti/nextplayer/core/ui/designsystem/NextIcons.kt index 7f168dbcc..a463042a9 100644 --- a/core/ui/src/main/java/dev/anilbeesetti/nextplayer/core/ui/designsystem/NextIcons.kt +++ b/core/ui/src/main/java/dev/anilbeesetti/nextplayer/core/ui/designsystem/NextIcons.kt @@ -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 @@ -92,6 +93,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 diff --git a/core/ui/src/main/res/values/strings.xml b/core/ui/src/main/res/values/strings.xml index 8629aa1cf..96e621c10 100644 --- a/core/ui/src/main/res/values/strings.xml +++ b/core/ui/src/main/res/values/strings.xml @@ -182,4 +182,5 @@ Frame rate Okay Properties + Multi-select \ No newline at end of file diff --git a/feature/videopicker/src/main/java/dev/anilbeesetti/nextplayer/feature/videopicker/composables/VideoItem.kt b/feature/videopicker/src/main/java/dev/anilbeesetti/nextplayer/feature/videopicker/composables/VideoItem.kt index 982bfe0bb..c14b4926a 100644 --- a/feature/videopicker/src/main/java/dev/anilbeesetti/nextplayer/feature/videopicker/composables/VideoItem.kt +++ b/feature/videopicker/src/main/java/dev/anilbeesetti/nextplayer/feature/videopicker/composables/VideoItem.kt @@ -1,5 +1,6 @@ package dev.anilbeesetti.nextplayer.feature.videopicker.composables +import android.util.Log import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -9,7 +10,9 @@ import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width +import androidx.compose.material3.Checkbox import androidx.compose.material3.Icon import androidx.compose.material3.ListItem import androidx.compose.material3.MaterialTheme @@ -17,6 +20,10 @@ import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.surfaceColorAtElevation 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.clip @@ -33,7 +40,6 @@ import dev.anilbeesetti.nextplayer.core.model.ApplicationPreferences import dev.anilbeesetti.nextplayer.core.model.Video import dev.anilbeesetti.nextplayer.core.ui.designsystem.NextIcons import dev.anilbeesetti.nextplayer.core.ui.preview.DayNightPreview -import dev.anilbeesetti.nextplayer.core.ui.preview.DevicePreviews import dev.anilbeesetti.nextplayer.core.ui.theme.NextPlayerTheme @OptIn(ExperimentalLayoutApi::class) @@ -41,6 +47,8 @@ import dev.anilbeesetti.nextplayer.core.ui.theme.NextPlayerTheme fun VideoItem( video: Video, preferences: ApplicationPreferences, + multiSelectEnabled: Boolean, + isSelected: (Boolean) -> Unit, modifier: Modifier = Modifier ) { val context = LocalContext.current @@ -84,6 +92,17 @@ fun VideoItem( shape = MaterialTheme.shapes.extraSmall ) } + if (multiSelectEnabled) { + Checkbox( + checked = video.isSelected, + onCheckedChange = { + isSelected(it) + }, + modifier = Modifier + .size(30.dp) + .align(Alignment.BottomStart) + ) + } } }, headlineContent = { @@ -124,12 +143,12 @@ fun VideoItem( } @DayNightPreview -@DevicePreviews +// @DevicePreviews @Composable fun VideoItemPreview() { NextPlayerTheme { Surface { - VideoItem(video = Video.sample, preferences = ApplicationPreferences()) + VideoItem(video = Video.sample, multiSelectEnabled = true, isSelected = {}, preferences = ApplicationPreferences()) } } } diff --git a/feature/videopicker/src/main/java/dev/anilbeesetti/nextplayer/feature/videopicker/composables/VideosView.kt b/feature/videopicker/src/main/java/dev/anilbeesetti/nextplayer/feature/videopicker/composables/VideosView.kt index 3bc67d61b..4fe16a970 100644 --- a/feature/videopicker/src/main/java/dev/anilbeesetti/nextplayer/feature/videopicker/composables/VideosView.kt +++ b/feature/videopicker/src/main/java/dev/anilbeesetti/nextplayer/feature/videopicker/composables/VideosView.kt @@ -2,6 +2,7 @@ package dev.anilbeesetti.nextplayer.feature.videopicker.composables import android.content.Intent import android.net.Uri +import android.util.Log import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Arrangement @@ -21,7 +22,9 @@ import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect 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.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue @@ -48,12 +51,25 @@ fun VideosView( preferences: ApplicationPreferences, onVideoClick: (Uri) -> Unit, onDeleteVideoClick: (String) -> Unit, + showDeleteIcon: (Boolean) -> Unit, + selectedItemsCount: (Int) -> Unit, + disableMultiSelect:Boolean, + totalVideos: (Int) -> Unit, onVideoLoaded: (Uri) -> Unit = {} ) { 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 enableMultiSelect by rememberSaveable { mutableStateOf(false) } + var itemsSelected by rememberSaveable { + mutableIntStateOf(0) + } + if(disableMultiSelect){ + enableMultiSelect = false + itemsSelected = 0 + selectedItemsCount(0) + } val scope = rememberCoroutineScope() val context = LocalContext.current val bottomSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) @@ -63,19 +79,44 @@ fun VideosView( is VideosState.Success -> if (videosState.data.isEmpty()) { NoVideosFound() } else { + totalVideos(videosState.data.size) MediaLazyList { items(videosState.data, key = { it.path }) { video -> LaunchedEffect(Unit) { onVideoLoaded(Uri.parse(video.uriString)) } + var videoItem by remember { mutableStateOf(video) } + if(disableMultiSelect){ + videoItem.isSelected = false + } + VideoItem( - video = video, + video = videoItem, preferences = preferences, + multiSelectEnabled = enableMultiSelect, + isSelected = {isSelected -> + videoItem = videoItem.copy(isSelected = isSelected) + video.isSelected = isSelected + itemsSelected += if (isSelected) 1 else -1 + selectedItemsCount(itemsSelected) + }, modifier = Modifier.combinedClickable( - onClick = { onVideoClick(Uri.parse(video.uriString)) }, + onClick = { + if (enableMultiSelect) { + val newSelected = !videoItem.isSelected + videoItem = videoItem.copy(isSelected = newSelected) + video.isSelected = newSelected + itemsSelected += if (newSelected) 1 else -1 + selectedItemsCount(itemsSelected) + } else { + onVideoClick(Uri.parse(video.uriString)) + } + }, onLongClick = { - haptic.performHapticFeedback(HapticFeedbackType.LongPress) - showMediaActionsFor = video + if (!enableMultiSelect) { + haptic.performHapticFeedback(HapticFeedbackType.LongPress) + showMediaActionsFor = video + } } ) ) @@ -128,6 +169,17 @@ fun VideosView( } } ) + BottomSheetItem( + text = stringResource(R.string.multi_select), + icon = NextIcons.MultiSelect, + onClick = { + enableMultiSelect = true + showDeleteIcon(true) + scope.launch { bottomSheetState.hide() }.invokeOnCompletion { + if (!bottomSheetState.isVisible) showMediaActionsFor = null + } + } + ) } } diff --git a/feature/videopicker/src/main/java/dev/anilbeesetti/nextplayer/feature/videopicker/navigation/MediaPickerFolderNavigation.kt b/feature/videopicker/src/main/java/dev/anilbeesetti/nextplayer/feature/videopicker/navigation/MediaPickerFolderNavigation.kt index 85a496696..9faf5d49f 100644 --- a/feature/videopicker/src/main/java/dev/anilbeesetti/nextplayer/feature/videopicker/navigation/MediaPickerFolderNavigation.kt +++ b/feature/videopicker/src/main/java/dev/anilbeesetti/nextplayer/feature/videopicker/navigation/MediaPickerFolderNavigation.kt @@ -29,8 +29,8 @@ fun NavController.navigateToMediaPickerFolderScreen( fun NavGraphBuilder.mediaPickerFolderScreen( onNavigateUp: () -> Unit, - onVideoClick: (uri: Uri) -> Unit -) { + onVideoClick: (uri: Uri) -> Unit, + ) { composable( route = "$mediaPickerFolderNavigationRoute/{$folderIdArg}", arguments = listOf( @@ -41,7 +41,7 @@ fun NavGraphBuilder.mediaPickerFolderScreen( ) { MediaPickerFolderRoute( onVideoClick = onVideoClick, - onNavigateUp = onNavigateUp + onNavigateUp = onNavigateUp, ) } } diff --git a/feature/videopicker/src/main/java/dev/anilbeesetti/nextplayer/feature/videopicker/screens/media/MediaPickerScreen.kt b/feature/videopicker/src/main/java/dev/anilbeesetti/nextplayer/feature/videopicker/screens/media/MediaPickerScreen.kt index d97a2d110..a959394ae 100644 --- a/feature/videopicker/src/main/java/dev/anilbeesetti/nextplayer/feature/videopicker/screens/media/MediaPickerScreen.kt +++ b/feature/videopicker/src/main/java/dev/anilbeesetti/nextplayer/feature/videopicker/screens/media/MediaPickerScreen.kt @@ -13,6 +13,7 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue @@ -27,6 +28,7 @@ import dev.anilbeesetti.nextplayer.core.model.ApplicationPreferences import dev.anilbeesetti.nextplayer.core.model.Video import dev.anilbeesetti.nextplayer.core.ui.R import dev.anilbeesetti.nextplayer.core.ui.components.NextCenterAlignedTopAppBar +import dev.anilbeesetti.nextplayer.core.ui.components.NextTopAppBar import dev.anilbeesetti.nextplayer.core.ui.designsystem.NextIcons import dev.anilbeesetti.nextplayer.core.ui.preview.DayNightPreview import dev.anilbeesetti.nextplayer.core.ui.preview.DevicePreviews @@ -86,8 +88,13 @@ internal fun MediaPickerScreen( onAddToSync: (Uri) -> Unit = {} ) { var showMenu by rememberSaveable { mutableStateOf(false) } + var isDeleteIconVisible by rememberSaveable { mutableStateOf(false) } + var selectedItems by rememberSaveable { mutableIntStateOf(0) } + var totalVideosCount by rememberSaveable { mutableIntStateOf(0) } + var disableMultiSelect by rememberSaveable { mutableStateOf(false) } Column { + if(!isDeleteIconVisible) NextCenterAlignedTopAppBar( title = stringResource(id = R.string.app_name), navigationIcon = { @@ -107,6 +114,29 @@ internal fun MediaPickerScreen( } } ) + else + NextTopAppBar( + title = "$selectedItems/${totalVideosCount} Selected", + navigationIcon = { + IconButton(onClick = { + disableMultiSelect = true + isDeleteIconVisible = false + }) { + Icon( + imageVector = NextIcons.ArrowBack, + contentDescription = stringResource(id = R.string.navigate_up) + ) + } + }, + actions = { + IconButton(onClick = { }) { + Icon( + imageVector = NextIcons.Delete, + contentDescription = stringResource(id = R.string.delete) + ) + } + } + ) Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center @@ -124,7 +154,18 @@ internal fun MediaPickerScreen( onVideoClick = onPlayVideo, preferences = preferences, onDeleteVideoClick = onDeleteVideoClick, - onVideoLoaded = onAddToSync + onVideoLoaded = onAddToSync, + showDeleteIcon = { + isDeleteIconVisible = it + }, + selectedItemsCount = { + selectedItems = it + disableMultiSelect = false + }, + disableMultiSelect = disableMultiSelect, + totalVideos = { + totalVideosCount = it + } ) } } diff --git a/feature/videopicker/src/main/java/dev/anilbeesetti/nextplayer/feature/videopicker/screens/mediaFolder/MediaPickerFolderScreen.kt b/feature/videopicker/src/main/java/dev/anilbeesetti/nextplayer/feature/videopicker/screens/mediaFolder/MediaPickerFolderScreen.kt index f08032b3e..2646cf8ed 100644 --- a/feature/videopicker/src/main/java/dev/anilbeesetti/nextplayer/feature/videopicker/screens/mediaFolder/MediaPickerFolderScreen.kt +++ b/feature/videopicker/src/main/java/dev/anilbeesetti/nextplayer/feature/videopicker/screens/mediaFolder/MediaPickerFolderScreen.kt @@ -1,6 +1,7 @@ package dev.anilbeesetti.nextplayer.feature.videopicker.screens.mediaFolder import android.net.Uri +import android.util.Log import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.layout.Box @@ -11,6 +12,10 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource @@ -49,7 +54,7 @@ fun MediaPickerFolderRoute( onVideoClick = onVideoClick, onNavigateUp = onNavigateUp, onDeleteVideoClick = { viewModel.deleteVideos(listOf(it), deleteIntentSenderLauncher) }, - onAddToSync = viewModel::addToMediaInfoSynchronizer + onAddToSync = viewModel::addToMediaInfoSynchronizer ) } @@ -64,7 +69,12 @@ internal fun MediaPickerFolderScreen( onDeleteVideoClick: (String) -> Unit, onAddToSync: (Uri) -> Unit ) { + var selectedItems by rememberSaveable { mutableIntStateOf(0) } + var isDeleteIconVisible by rememberSaveable { mutableStateOf(false) } + var disableMultiSelect by rememberSaveable { mutableStateOf(false) } + var totalVideosCount by rememberSaveable { mutableIntStateOf(0) } Column { + if(!isDeleteIconVisible) NextTopAppBar( title = File(folderPath).prettyName, navigationIcon = { @@ -76,6 +86,31 @@ internal fun MediaPickerFolderScreen( } } ) + else + NextTopAppBar( + title = "$selectedItems/${totalVideosCount} Selected", + navigationIcon = { + IconButton(onClick = { + disableMultiSelect = true + isDeleteIconVisible = false + }) { + Icon( + imageVector = NextIcons.ArrowBack, + contentDescription = stringResource(id = R.string.navigate_up) + ) + } + }, + actions = { + IconButton(onClick = { + + }) { + Icon( + imageVector = NextIcons.Delete, + contentDescription = stringResource(id = R.string.delete) + ) + } + } + ) Box( modifier = Modifier .fillMaxSize(), @@ -86,7 +121,18 @@ internal fun MediaPickerFolderScreen( preferences = preferences, onVideoClick = onVideoClick, onDeleteVideoClick = onDeleteVideoClick, - onVideoLoaded = onAddToSync + showDeleteIcon = { + isDeleteIconVisible = it + disableMultiSelect = false + }, + selectedItemsCount = { + selectedItems = it + }, + disableMultiSelect = disableMultiSelect, + totalVideos = { + totalVideosCount = it + }, + onVideoLoaded = onAddToSync ) } } From 776be68a0b80a82fdd9bbf930c986d501499834c Mon Sep 17 00:00:00 2001 From: Yashraj Singh Jadon Date: Mon, 20 Nov 2023 02:31:10 +0530 Subject: [PATCH 2/6] fixed a bug in multiple tracks selection --- .../videopicker/composables/VideoItem.kt | 10 +- .../videopicker/composables/VideosView.kt | 72 ++++++----- .../navigation/MediaPickerFolderNavigation.kt | 6 +- .../screens/media/MediaPickerScreen.kt | 97 +++++++++------ .../screens/media/MediaPickerViewModel.kt | 19 +++ .../mediaFolder/MediaPickerFolderScreen.kt | 115 ++++++++++-------- 6 files changed, 185 insertions(+), 134 deletions(-) diff --git a/feature/videopicker/src/main/java/dev/anilbeesetti/nextplayer/feature/videopicker/composables/VideoItem.kt b/feature/videopicker/src/main/java/dev/anilbeesetti/nextplayer/feature/videopicker/composables/VideoItem.kt index c14b4926a..5b705ee98 100644 --- a/feature/videopicker/src/main/java/dev/anilbeesetti/nextplayer/feature/videopicker/composables/VideoItem.kt +++ b/feature/videopicker/src/main/java/dev/anilbeesetti/nextplayer/feature/videopicker/composables/VideoItem.kt @@ -1,6 +1,5 @@ package dev.anilbeesetti.nextplayer.feature.videopicker.composables -import android.util.Log import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -20,10 +19,6 @@ import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.surfaceColorAtElevation 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.clip @@ -40,6 +35,7 @@ import dev.anilbeesetti.nextplayer.core.model.ApplicationPreferences import dev.anilbeesetti.nextplayer.core.model.Video import dev.anilbeesetti.nextplayer.core.ui.designsystem.NextIcons import dev.anilbeesetti.nextplayer.core.ui.preview.DayNightPreview +import dev.anilbeesetti.nextplayer.core.ui.preview.DevicePreviews import dev.anilbeesetti.nextplayer.core.ui.theme.NextPlayerTheme @OptIn(ExperimentalLayoutApi::class) @@ -96,7 +92,7 @@ fun VideoItem( Checkbox( checked = video.isSelected, onCheckedChange = { - isSelected(it) + isSelected(it) }, modifier = Modifier .size(30.dp) @@ -143,7 +139,7 @@ fun VideoItem( } @DayNightPreview -// @DevicePreviews +@DevicePreviews @Composable fun VideoItemPreview() { NextPlayerTheme { diff --git a/feature/videopicker/src/main/java/dev/anilbeesetti/nextplayer/feature/videopicker/composables/VideosView.kt b/feature/videopicker/src/main/java/dev/anilbeesetti/nextplayer/feature/videopicker/composables/VideosView.kt index 4fe16a970..d0fe8fae9 100644 --- a/feature/videopicker/src/main/java/dev/anilbeesetti/nextplayer/feature/videopicker/composables/VideosView.kt +++ b/feature/videopicker/src/main/java/dev/anilbeesetti/nextplayer/feature/videopicker/composables/VideosView.kt @@ -2,7 +2,6 @@ package dev.anilbeesetti.nextplayer.feature.videopicker.composables import android.content.Intent import android.net.Uri -import android.util.Log import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Arrangement @@ -22,9 +21,7 @@ import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect 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.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue @@ -35,6 +32,7 @@ import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel import dev.anilbeesetti.nextplayer.core.common.Utils import dev.anilbeesetti.nextplayer.core.model.ApplicationPreferences import dev.anilbeesetti.nextplayer.core.model.Video @@ -42,6 +40,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) @@ -50,25 +49,21 @@ fun VideosView( videosState: VideosState, preferences: ApplicationPreferences, onVideoClick: (Uri) -> Unit, - onDeleteVideoClick: (String) -> Unit, - showDeleteIcon: (Boolean) -> Unit, - selectedItemsCount: (Int) -> Unit, - disableMultiSelect:Boolean, + onDeleteVideoClick: (List) -> Unit, + toggleMultiSelect: () -> Unit, + disableMultiSelect: Boolean, totalVideos: (Int) -> Unit, - onVideoLoaded: (Uri) -> Unit = {} + onVideoLoaded: (Uri) -> Unit = {}, + viewModel: MediaPickerViewModel = hiltViewModel() ) { 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 enableMultiSelect by rememberSaveable { mutableStateOf(false) } - var itemsSelected by rememberSaveable { - mutableIntStateOf(0) - } - if(disableMultiSelect){ - enableMultiSelect = false - itemsSelected = 0 - selectedItemsCount(0) + var multiSelect by rememberSaveable { mutableStateOf(false) } + + if (disableMultiSelect) { + multiSelect = false } val scope = rememberCoroutineScope() val context = LocalContext.current @@ -81,39 +76,34 @@ fun VideosView( } 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, key = { it.path }) { video -> LaunchedEffect(Unit) { onVideoLoaded(Uri.parse(video.uriString)) } - var videoItem by remember { mutableStateOf(video) } - if(disableMultiSelect){ - videoItem.isSelected = false - } VideoItem( - video = videoItem, + video = video, preferences = preferences, - multiSelectEnabled = enableMultiSelect, - isSelected = {isSelected -> - videoItem = videoItem.copy(isSelected = isSelected) + multiSelectEnabled = multiSelect, + isSelected = { isSelected -> video.isSelected = isSelected - itemsSelected += if (isSelected) 1 else -1 - selectedItemsCount(itemsSelected) + toggleSelection(video, viewModel) }, modifier = Modifier.combinedClickable( onClick = { - if (enableMultiSelect) { - val newSelected = !videoItem.isSelected - videoItem = videoItem.copy(isSelected = newSelected) - video.isSelected = newSelected - itemsSelected += if (newSelected) 1 else -1 - selectedItemsCount(itemsSelected) + if (multiSelect) { + video.isSelected = !video.isSelected + toggleSelection(video, viewModel) } else { onVideoClick(Uri.parse(video.uriString)) } }, onLongClick = { - if (!enableMultiSelect) { + if (!multiSelect) { haptic.performHapticFeedback(HapticFeedbackType.LongPress) showMediaActionsFor = video } @@ -173,8 +163,8 @@ fun VideosView( text = stringResource(R.string.multi_select), icon = NextIcons.MultiSelect, onClick = { - enableMultiSelect = true - showDeleteIcon(true) + multiSelect = true + toggleMultiSelect() scope.launch { bottomSheetState.hide() }.invokeOnCompletion { if (!bottomSheetState.isVisible) showMediaActionsFor = null } @@ -188,7 +178,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) @@ -203,6 +193,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, diff --git a/feature/videopicker/src/main/java/dev/anilbeesetti/nextplayer/feature/videopicker/navigation/MediaPickerFolderNavigation.kt b/feature/videopicker/src/main/java/dev/anilbeesetti/nextplayer/feature/videopicker/navigation/MediaPickerFolderNavigation.kt index 9faf5d49f..85a496696 100644 --- a/feature/videopicker/src/main/java/dev/anilbeesetti/nextplayer/feature/videopicker/navigation/MediaPickerFolderNavigation.kt +++ b/feature/videopicker/src/main/java/dev/anilbeesetti/nextplayer/feature/videopicker/navigation/MediaPickerFolderNavigation.kt @@ -29,8 +29,8 @@ fun NavController.navigateToMediaPickerFolderScreen( fun NavGraphBuilder.mediaPickerFolderScreen( onNavigateUp: () -> Unit, - onVideoClick: (uri: Uri) -> Unit, - ) { + onVideoClick: (uri: Uri) -> Unit +) { composable( route = "$mediaPickerFolderNavigationRoute/{$folderIdArg}", arguments = listOf( @@ -41,7 +41,7 @@ fun NavGraphBuilder.mediaPickerFolderScreen( ) { MediaPickerFolderRoute( onVideoClick = onVideoClick, - onNavigateUp = onNavigateUp, + onNavigateUp = onNavigateUp ) } } diff --git a/feature/videopicker/src/main/java/dev/anilbeesetti/nextplayer/feature/videopicker/screens/media/MediaPickerScreen.kt b/feature/videopicker/src/main/java/dev/anilbeesetti/nextplayer/feature/videopicker/screens/media/MediaPickerScreen.kt index a959394ae..1274dec9b 100644 --- a/feature/videopicker/src/main/java/dev/anilbeesetti/nextplayer/feature/videopicker/screens/media/MediaPickerScreen.kt +++ b/feature/videopicker/src/main/java/dev/anilbeesetti/nextplayer/feature/videopicker/screens/media/MediaPickerScreen.kt @@ -34,6 +34,7 @@ import dev.anilbeesetti.nextplayer.core.ui.preview.DayNightPreview import dev.anilbeesetti.nextplayer.core.ui.preview.DevicePreviews import dev.anilbeesetti.nextplayer.core.ui.preview.VideoPickerPreviewParameterProvider import dev.anilbeesetti.nextplayer.core.ui.theme.NextPlayerTheme +import dev.anilbeesetti.nextplayer.feature.videopicker.composables.DeleteConfirmationDialog import dev.anilbeesetti.nextplayer.feature.videopicker.composables.FoldersView import dev.anilbeesetti.nextplayer.feature.videopicker.composables.QuickSettingsDialog import dev.anilbeesetti.nextplayer.feature.videopicker.composables.TextIconToggleButton @@ -67,9 +68,11 @@ fun MediaPickerRoute( onFolderClick = onFolderClick, onSettingsClick = onSettingsClick, updatePreferences = viewModel::updateMenu, - onDeleteVideoClick = { viewModel.deleteVideos(listOf(it), deleteIntentSenderLauncher) }, + onDeleteVideoClick = { viewModel.deleteVideos(it, deleteIntentSenderLauncher) }, onDeleteFolderClick = { viewModel.deleteFolders(listOf(it), deleteIntentSenderLauncher) }, - onAddToSync = viewModel::addToMediaInfoSynchronizer + onAddToSync = viewModel::addToMediaInfoSynchronizer, + selectedTracks = viewModel.selectedTracks, + clearSelectedTracks = { viewModel.clearSelectedTracks() } ) } @@ -83,44 +86,44 @@ internal fun MediaPickerScreen( onFolderClick: (folderPath: String) -> Unit = {}, onSettingsClick: () -> Unit = {}, updatePreferences: (ApplicationPreferences) -> Unit = {}, - onDeleteVideoClick: (String) -> Unit, + onDeleteVideoClick: (List) -> Unit, onDeleteFolderClick: (String) -> Unit, + selectedTracks: List