Skip to content

Commit

Permalink
Mostly complete multiselect for home and NP queue
Browse files Browse the repository at this point in the history
Implement multiselection for the now playing queue #47
- Clear or shuffle using the existing buttons
- Pin
- Download
  • Loading branch information
toasterofbread committed May 15, 2023
1 parent 1c763d5 commit 4ea7e4d
Show file tree
Hide file tree
Showing 12 changed files with 382 additions and 194 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ private fun convertState(exo_state: Int): MediaPlayerState {

actual open class MediaPlayerService: PlatformService() {

actual interface UndoRedoAction {
actual fun undo()
actual fun redo()
}

actual open class Listener {
actual open fun onSongTransition(song: Song?) {}
actual open fun onStateChanged(state: MediaPlayerState) {}
Expand Down Expand Up @@ -114,8 +119,8 @@ actual open class MediaPlayerService: PlatformService() {
private var notification_manager: PlayerNotificationManager? = null

// Undo
private var current_action: MutableList<Action>? = null
private val action_list: MutableList<List<Action>> = mutableListOf()
private var current_action: MutableList<UndoRedoAction>? = null
private val action_list: MutableList<List<UndoRedoAction>> = mutableListOf()
private var action_head: Int = 0

private val audio_processor = FFTAudioProcessor()
Expand Down Expand Up @@ -502,10 +507,21 @@ actual open class MediaPlayerService: PlatformService() {
}

actual fun undoableAction(action: MediaPlayerService.() -> Unit) {
undoableActionWithCustom {
action()
null
}
}

actual fun undoableActionWithCustom(action: MediaPlayerService.() -> UndoRedoAction?) {
synchronized(action_list) {
assert(current_action == null)
current_action = mutableListOf()
action(this)

val customAction = action(this)
if (customAction != null) {
performAction(customAction)
}

for (i in 0 until redo_count) {
action_list.removeLast()
Expand All @@ -518,7 +534,7 @@ actual open class MediaPlayerService: PlatformService() {
}
}

private fun performAction(action: Action) {
private fun performAction(action: UndoRedoAction) {
action.redo()
current_action?.add(action)
}
Expand Down Expand Up @@ -565,11 +581,8 @@ actual open class MediaPlayerService: PlatformService() {
}
}

private abstract inner class Action() {
private abstract inner class Action: UndoRedoAction {
protected val is_undoable: Boolean get() = current_action != null

abstract fun redo()
abstract fun undo()
}
private inner class AddAction(val item: ExoMediaItem, val index: Int): Action() {
override fun redo() {
Expand Down
31 changes: 21 additions & 10 deletions shared/src/commonMain/kotlin/com/spectre7/spmp/PlayerService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -130,23 +130,34 @@ class PlayerService : MediaPlayerService() {
updateActiveQueueIndex()
}

fun shuffleQueue(start: Int = -1) {
fun shuffleQueue(start: Int = -1, end: Int = song_count - 1) {
val range: IntRange =
if (start < 0) {
current_song_index + 1 until song_count
}
else if (song_count - start <= 1) {
return
}
else {
start until song_count
if (start < 0) {
current_song_index + 1 .. end
}
else if (song_count - start <= 1) {
return
}
else {
start .. end
}
shuffleQueue(range)
}

fun shuffleQueueAndIndices(indices: List<Int>) {
for (i in indices.withIndex()) {
val swap_index = Random.nextInt(indices.size)
swapQueuePositions(i.value, indices[swap_index], false)
// indices.swap(i.index, swap_index)
}
savePersistentQueue()
}

fun shuffleQueue(range: IntRange) {
for (i in range) {
val swap = Random.nextInt(range)
swapQueuePositions(i, swap, false)
}

savePersistentQueue()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package com.spectre7.spmp.model

import androidx.compose.foundation.Indication
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalViewConfiguration
import com.spectre7.spmp.platform.vibrateShort
import com.spectre7.spmp.ui.component.LongPressMenuData
import com.spectre7.spmp.ui.layout.mainpage.PlayerViewContext
import com.spectre7.spmp.platform.Platform
import com.spectre7.spmp.platform.composable.platformClickable
import com.spectre7.utils.composable.OnChangedEffect
import kotlinx.coroutines.delay

private enum class PressStage {
INSTANT, BRIEF, LONG_1, LONG_2;

fun execute(item: MediaItem, playerProvider: () -> PlayerViewContext, long_press_menu_data: LongPressMenuData) {
when (this) {
BRIEF -> {}
INSTANT -> {
if (long_press_menu_data.multiselect_context?.is_active == true) {
long_press_menu_data.multiselect_context.toggleItem(item, long_press_menu_data.multiselect_key)
}
else {
playerProvider().onMediaItemClicked(item)
}
}
LONG_1 -> playerProvider().showLongPressMenu(long_press_menu_data)
LONG_2 -> long_press_menu_data.multiselect_context?.apply {
setActive(true)
toggleItem(item, long_press_menu_data.multiselect_key)
}
}
}

fun isAvailable(long_press_menu_data: LongPressMenuData): Boolean =
when (this) {
LONG_2 -> long_press_menu_data.multiselect_context != null
else -> true
}
}

private fun getIndication(): Indication? = null

@Composable
fun Modifier.mediaItemPreviewInteraction(
item: MediaItem,
playerProvider: () -> PlayerViewContext,
long_press_menu_data: LongPressMenuData
): Modifier {
if (Platform.is_desktop) {
return platformClickable(
onClick = { PressStage.INSTANT.execute(item, playerProvider, long_press_menu_data) },
onAltClick = { PressStage.LONG_1.execute(item, playerProvider, long_press_menu_data) },
indication = getIndication()
)
}

var current_press_stage: PressStage by remember { mutableStateOf(PressStage.INSTANT) }
val long_press_timeout = LocalViewConfiguration.current.longPressTimeoutMillis

val interaction_source = remember { MutableInteractionSource() }
val pressed by interaction_source.collectIsPressedAsState()

OnChangedEffect(pressed) {
if (pressed) {
var delays = 0
for (stage in PressStage.values()) {
if (stage.ordinal == 0 || !stage.isAvailable(long_press_menu_data)) {
continue
}

if (stage.ordinal == 1) {
current_press_stage = stage
}
else {
delay(long_press_timeout * (++delays))
current_press_stage = stage
SpMp.context.vibrateShort()

if (stage == PressStage.values().last { it.isAvailable(long_press_menu_data) }) {
current_press_stage.execute(item, playerProvider, long_press_menu_data)
break
}
}
}
}
else {
if (current_press_stage != PressStage.values().last { it.isAvailable(long_press_menu_data) }) {
current_press_stage.execute(item, playerProvider, long_press_menu_data)
}
current_press_stage = PressStage.INSTANT
}
}

return clickable(interaction_source, getIndication(), onClick = {
current_press_stage.execute(item, playerProvider, long_press_menu_data)
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ enum class Settings {
KEY_FEED_SHOW_CHARTS_ROW,

// Now playing queue
KEY_NP_QUEUE_RADIO_INFO_POSITION,
KEY_NP_QUEUE_RADIO_INFO_POSITION, // TODO prefs item

// Server
KEY_SPMS_PORT,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ enum class MediaPlayerRepeatMode {
}

expect open class MediaPlayerService(): PlatformService {
interface UndoRedoAction {
fun undo()
fun redo()
}

open class Listener() {
open fun onSongTransition(song: Song?)
open fun onStateChanged(state: MediaPlayerState)
Expand Down Expand Up @@ -61,6 +66,8 @@ expect open class MediaPlayerService(): PlatformService {
fun Visualiser(colour: Color, modifier: Modifier = Modifier, opacity: Float = 1f)

fun undoableAction(action: MediaPlayerService.() -> Unit)
fun undoableActionWithCustom(action: MediaPlayerService.() -> UndoRedoAction?)

fun redo()
fun redoAll()
fun undo()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import androidx.compose.ui.unit.sp
import com.spectre7.spmp.PlayerServiceHost
import com.spectre7.spmp.model.Artist
import com.spectre7.spmp.model.MediaItem
import com.spectre7.spmp.model.mediaItemPreviewInteraction
import com.spectre7.spmp.platform.composable.platformClickable
import com.spectre7.spmp.ui.component.multiselect.MediaItemMultiSelectContext
import com.spectre7.spmp.ui.layout.ArtistSubscribeButton
Expand All @@ -38,24 +39,11 @@ fun ArtistPreviewSquare(
params: MediaItem.PreviewParams
) {
val long_press_menu_data = remember(artist) {
getArtistLongPressMenuData(artist)
getArtistLongPressMenuData(artist, multiselect_context = params.multiselect_context)
}

Column(
params.modifier
.platformClickable(
onClick = {
if (params.multiselect_context?.is_active == true) {
params.multiselect_context.toggleItem(artist)
}
else {
params.playerProvider().onMediaItemClicked(artist)
}
},
onAltClick = {
params.playerProvider().showLongPressMenu(long_press_menu_data)
}
),
params.modifier.mediaItemPreviewInteraction(artist, params.playerProvider, long_press_menu_data),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(5.dp)
) {
Expand Down Expand Up @@ -93,22 +81,7 @@ fun ArtistPreviewLong(

Row(
verticalAlignment = Alignment.CenterVertically,
modifier = params.modifier
.combinedClickable(
interactionSource = remember { MutableInteractionSource() },
indication = null,
onClick = {
if (params.multiselect_context?.is_active == true) {
params.multiselect_context.toggleItem(artist)
}
else {
params.playerProvider().onMediaItemClicked(artist)
}
},
onLongClick = {
params.playerProvider().showLongPressMenu(long_press_menu_data)
}
)
modifier = params.modifier.mediaItemPreviewInteraction(artist, params.playerProvider, long_press_menu_data)
) {
Box(Modifier.width(IntrinsicSize.Min).height(IntrinsicSize.Min), contentAlignment = Alignment.Center) {
artist.Thumbnail(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import androidx.compose.ui.unit.sp
import com.spectre7.spmp.model.MediaItem
import com.spectre7.spmp.model.Playlist
import com.spectre7.spmp.model.getReadable
import com.spectre7.spmp.model.mediaItemPreviewInteraction
import com.spectre7.spmp.platform.composable.platformClickable
import com.spectre7.utils.setAlpha

Expand All @@ -27,25 +28,13 @@ fun PlaylistPreviewSquare(
val long_press_menu_data = remember(playlist) {
LongPressMenuData(
playlist,
RoundedCornerShape(10.dp)
RoundedCornerShape(10.dp),
multiselect_context = params.multiselect_context
) { } // TODO
}

Column(
params.modifier
.platformClickable(
onClick = {
if (params.multiselect_context?.is_active == true) {
params.multiselect_context.toggleItem(playlist)
}
else {
params.playerProvider().onMediaItemClicked(playlist)
}
},
onAltClick = {
params.playerProvider().showLongPressMenu(long_press_menu_data)
}
),
params.modifier.mediaItemPreviewInteraction(playlist, params.playerProvider, long_press_menu_data),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(5.dp)
) {
Expand Down Expand Up @@ -81,28 +70,14 @@ fun PlaylistPreviewLong(
val long_press_menu_data = remember(playlist) {
LongPressMenuData(
playlist,
RoundedCornerShape(10.dp)
RoundedCornerShape(10.dp),
multiselect_context = params.multiselect_context
) { } // TODO
}

Row(
verticalAlignment = Alignment.CenterVertically,
modifier = params.modifier
.combinedClickable(
interactionSource = remember { MutableInteractionSource() },
indication = null,
onClick = {
if (params.multiselect_context?.is_active == true) {
params.multiselect_context.toggleItem(playlist)
}
else {
params.playerProvider().onMediaItemClicked(playlist)
}
},
onLongClick = {
params.playerProvider().showLongPressMenu(long_press_menu_data)
}
)
modifier = params.modifier.mediaItemPreviewInteraction(playlist, params.playerProvider, long_press_menu_data)
) {
Box(Modifier.width(IntrinsicSize.Min).height(IntrinsicSize.Min), contentAlignment = Alignment.Center) {
playlist.Thumbnail(
Expand Down

0 comments on commit 4ea7e4d

Please sign in to comment.