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

Fix #431 Added shuffle feature #476

Closed
Closed
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 @@ -9,5 +9,6 @@ data class ApplicationPreferences(
val groupVideosByFolder: Boolean = true,
val themeConfig: ThemeConfig = ThemeConfig.SYSTEM,
val useDynamicColors: Boolean = true,
val excludeFolders: List<String> = emptyList()
val excludeFolders: List<String> = emptyList(),
val isShuffleOn: Boolean = false
)
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ import androidx.compose.material.icons.rounded.ResetTv
import androidx.compose.material.icons.rounded.ScreenRotationAlt
import androidx.compose.material.icons.rounded.Settings
import androidx.compose.material.icons.rounded.Share
import androidx.compose.material.icons.rounded.Shuffle
import androidx.compose.material.icons.rounded.ShuffleOn
import androidx.compose.material.icons.rounded.Speed
import androidx.compose.material.icons.rounded.Straighten
import androidx.compose.material.icons.rounded.Style
Expand Down Expand Up @@ -91,6 +93,8 @@ object NextIcons {
val Style = Icons.Rounded.Style
val Subtitle = Icons.Rounded.Subtitles
val Size = Icons.Rounded.CompareArrows
val Shuffle = Icons.Rounded.Shuffle
val ShuffleOn = Icons.Rounded.ShuffleOn
val Speed = Icons.Rounded.Speed
val SwipeHorizontal = Icons.Rounded.Swipe
val SwipeVertical = Icons.Rounded.SwipeVertical
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 @@ -140,4 +140,6 @@
<string name="delete">Delete</string>
<string name="delete_file">Delete the following file</string>
<string name="subtitle_text_encoding">Subtitle text encoding</string>
<string name="shuffle_enabled">Shuffle enabled</string>
<string name="shuffle_disabled">Shuffle disabled</string>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ import dev.anilbeesetti.nextplayer.feature.player.utils.PlaylistManager
import dev.anilbeesetti.nextplayer.feature.player.utils.toMillis
import java.nio.charset.Charset
import java.util.Arrays
import java.util.Collections
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
Expand Down Expand Up @@ -372,7 +373,11 @@ class PlayerActivity : AppCompatActivity() {

if (mediaUri != null) {
launch(Dispatchers.IO) {
val playlist = viewModel.getPlaylistFromUri(mediaUri)
var playlist = viewModel.getPlaylistFromUri(mediaUri)
if (applicationPreferences.isShuffleOn) {
playlist = viewModel.getShuffledPlaylist(mediaUri)
Collections.swap(playlist, 0, playlist.indexOf(mediaUri))
}
playlistManager.setPlaylist(playlist)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ class PlayerViewModel @Inject constructor(
return getSortedPlaylistUseCase.invoke(uri)
}

suspend fun getShuffledPlaylist(uri: Uri): List<Uri> {
return getPlaylistFromUri(uri).shuffled()
}

fun saveState(
path: String?,
position: Long,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dev.anilbeesetti.nextplayer.feature.videopicker.screens.mediaFolder

import android.net.Uri
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Box
Expand All @@ -9,10 +10,12 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.Lifecycle
Expand Down Expand Up @@ -45,7 +48,8 @@ fun MediaPickerFolderRoute(
videosState = videosState,
onVideoClick = onVideoClick,
onNavigateUp = onNavigateUp,
onDeleteVideoClick = { viewModel.deleteVideos(listOf(it), deleteIntentSenderLauncher) }
onDeleteVideoClick = { viewModel.deleteVideos(listOf(it), deleteIntentSenderLauncher) },
viewModel = viewModel
)
}

Expand All @@ -56,23 +60,60 @@ internal fun MediaPickerFolderScreen(
videosState: VideosState,
onNavigateUp: () -> Unit,
onVideoClick: (Uri) -> Unit,
onDeleteVideoClick: (String) -> Unit
onDeleteVideoClick: (String) -> Unit,
viewModel: MediaPickerFolderViewModel
) {
val prefs = viewModel.appPrefs.collectAsStateWithLifecycle()
val context = LocalContext.current
Column {
NextTopAppBar(
title = File(folderPath).prettyName,
navigationIcon = {
IconButton(onClick = onNavigateUp) {
Icon(
imageVector = NextIcons.ArrowBack,
contentDescription = stringResource(id = R.string.navigate_up)
)
}
NextTopAppBar(title = File(folderPath).prettyName, navigationIcon = {
IconButton(onClick = onNavigateUp) {
Icon(
imageVector = NextIcons.ArrowBack,
contentDescription = stringResource(id = R.string.navigate_up)
)
}
)
}, actions = {
IconButton(onClick = {
if (prefs.value.isShuffleOn) {
Toast.makeText(
context,
R.string.shuffle_disabled,
Toast.LENGTH_SHORT
).show()
} else {
Toast.makeText(context, R.string.shuffle_enabled, Toast.LENGTH_SHORT).show()
when (videosState) {
is VideosState.Success -> {
onVideoClick(
Uri.parse(
videosState.data.shuffled().first().uriString
)
)
}

else -> {}
}
}
viewModel.toggleShuffle()
}) {
if (prefs.value.isShuffleOn) {
Icon(
imageVector = NextIcons.ShuffleOn,
contentDescription = stringResource(id = R.string.shuffle_enabled),
tint = MaterialTheme.colorScheme.primary
)
} else {
Icon(
imageVector = NextIcons.Shuffle,
contentDescription = stringResource(id = R.string.shuffle_enabled),
tint = MaterialTheme.colorScheme.secondary
)
}
}
})
Box(
modifier = Modifier
.fillMaxSize(),
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
VideosListFromState(videosState = videosState, onVideoClick = onVideoClick, onDeleteVideoClick = onDeleteVideoClick)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import dev.anilbeesetti.nextplayer.core.data.repository.MediaRepository
import dev.anilbeesetti.nextplayer.core.data.repository.PreferencesRepository
import dev.anilbeesetti.nextplayer.core.domain.GetSortedVideosUseCase
import dev.anilbeesetti.nextplayer.core.model.ApplicationPreferences
import dev.anilbeesetti.nextplayer.feature.videopicker.navigation.FolderArgs
import dev.anilbeesetti.nextplayer.feature.videopicker.screens.VideosState
import javax.inject.Inject
Expand All @@ -20,13 +22,20 @@ import kotlinx.coroutines.launch
class MediaPickerFolderViewModel @Inject constructor(
getSortedVideosUseCase: GetSortedVideosUseCase,
savedStateHandle: SavedStateHandle,
private val mediaRepository: MediaRepository
private val mediaRepository: MediaRepository,
private val preferencesRepository: PreferencesRepository
) : ViewModel() {

private val folderArgs = FolderArgs(savedStateHandle)

val folderPath = folderArgs.folderId

val appPrefs = preferencesRepository.applicationPreferences.stateIn(
scope = viewModelScope,
started = SharingStarted.Eagerly,
initialValue = ApplicationPreferences()
)

val videos = getSortedVideosUseCase.invoke(folderPath)
.map { VideosState.Success(it) }
.stateIn(
Expand All @@ -40,4 +49,14 @@ class MediaPickerFolderViewModel @Inject constructor(
mediaRepository.deleteVideos(uris, intentSenderLauncher)
}
}

fun toggleShuffle() {
viewModelScope.launch {
preferencesRepository.updateApplicationPreferences {
it.copy(
isShuffleOn = !it.isShuffleOn
)
}
}
}
}