Skip to content

Commit

Permalink
Add multiselect to search and radiobuilder, add visual preview long p…
Browse files Browse the repository at this point in the history
…ress hint

Closes #47
  • Loading branch information
toasterofbread committed May 24, 2023
1 parent c5abab0 commit 77a377f
Show file tree
Hide file tree
Showing 9 changed files with 70 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import com.spectre7.spmp.ui.layout.mainpage.PlayerState
import com.spectre7.utils.composable.OnChangedEffect
import kotlinx.coroutines.delay

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

fun execute(item: MediaItem, long_press_menu_data: LongPressMenuData, player: PlayerState) {
Expand Down Expand Up @@ -57,13 +57,13 @@ fun Modifier.mediaItemPreviewInteraction(

if (Platform.is_desktop) {
return platformClickable(
onClick = { PressStage.INSTANT.execute(item, long_press_menu_data, player) },
onAltClick = { PressStage.LONG_1.execute(item, long_press_menu_data, player) },
onClick = { MediaItemPreviewInteractionPressStage.INSTANT.execute(item, long_press_menu_data, player) },
onAltClick = { MediaItemPreviewInteractionPressStage.LONG_1.execute(item, long_press_menu_data, player) },
indication = getIndication()
)
}

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

val interaction_source = remember { MutableInteractionSource() }
Expand All @@ -72,31 +72,34 @@ fun Modifier.mediaItemPreviewInteraction(
OnChangedEffect(pressed) {
if (pressed) {
var delays = 0
for (stage in PressStage.values()) {
for (stage in MediaItemPreviewInteractionPressStage.values()) {
if (stage.ordinal == 0 || !stage.isAvailable(long_press_menu_data)) {
continue
}

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

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,24 @@ data class LongPressMenuData(
val multiselect_key: Int? = null,
val sideButton: (@Composable (Modifier, background: Color, accent: Color) -> Unit)? = null,
val actions: (@Composable LongPressMenuActionProvider.(MediaItem) -> Unit)? = null
)
) {
var current_interaction_stage: MediaItemPreviewInteractionPressStage? by remember { mutableStateOf(null) }
private val HINT_MIN_STAGE = MediaItemPreviewInteractionPressStage.LONG_1

fun Modifier.longPressMenuIcon(data: LongPressMenuData, enabled: Boolean = true): Modifier {
if (!enabled) {
return this.clip(data.thumb_shape ?: RoundedCornerShape(DEFAULT_THUMBNAIL_ROUNDING))
fun getInteractionHintScale(): Int {
if (current_interaction_stage == null || current_interaction_stage < HINT_MIN_STAGE) {
return 0
}
return current_interaction_stage.ordinal - HINT_MIN_STAGE.ordinal + 1
}
return this.clip(data.thumb_shape ?: RoundedCornerShape(DEFAULT_THUMBNAIL_ROUNDING))
}

@Composable
fun Modifier.longPressMenuIcon(data: LongPressMenuData, enabled: Boolean = true): Modifier {
val scale by animateFloatAsState(1f + (if (!enabled) 0f else data.getInteractionHintScale() * 1.2f))
return this
.clip(data.thumb_shape ?: RoundedCornerShape(DEFAULT_THUMBNAIL_ROUNDING))
.scale(scale)
}

@Composable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ data class MediaItemLayout(
}

@Composable
fun Layout(modifier: Modifier = Modifier) {
type!!.Layout(this, modifier)
fun Layout(modifier: Modifier = Modifier, multiselect_context: MediaItemMultiSelectContext? = null) {
type!!.Layout(this, modifier, multiselect_context)
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,11 @@ private fun TitleBar(item: MediaItem, modifier: Modifier = Modifier) {
val horizontal_padding = 20.dp
var editing_title by remember { mutableStateOf(false) }
Crossfade(editing_title) { editing ->
Column(modifier.padding(start = horizontal_padding), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(10.dp, Alignment.Bottom)) {
Column(
modifier.padding(start = horizontal_padding),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(10.dp, Alignment.Bottom)
) {
if (editing) {
var edited_title by remember(item) { mutableStateOf(item.title!!) }

Expand All @@ -322,6 +326,7 @@ private fun TitleBar(item: MediaItem, modifier: Modifier = Modifier) {
item.editRegistry {
it.title = edited_title
}
editing_title = false
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,9 +230,17 @@ fun RadioBuilderPage(
}
else if (playlist?.feed_layouts?.isNotEmpty() == true) {
val layout = playlist.feed_layouts!!.first()
val multiselect_context = remember { MediaItemMultiSelectContext() {} }

LazyColumn(Modifier.fillMaxSize(), contentPadding = PaddingValues(bottom = bottom_padding)) {
item {
AnimatedVisibility(multiselect_context.is_active) {
multiselect_context.InfoDisplay(background_modifier)
}
}

items(layout.items) { item ->
item.PreviewLong(MediaItem.PreviewParams())
item.PreviewLong(MediaItem.PreviewParams(multiselect_context = multiselect_context))
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import com.spectre7.spmp.resources.getString
import com.spectre7.spmp.ui.component.MediaItemLayout
import com.spectre7.spmp.ui.component.PillMenu
import com.spectre7.spmp.ui.theme.Theme
import com.spectre7.spmp.ui.component.multiselect.MediaItemMultiSelectContext
import com.spectre7.utils.*
import com.spectre7.utils.composable.ShapedIconButton
import com.spectre7.utils.composable.SubtleLoadingIndicator
Expand All @@ -55,6 +56,7 @@ fun SearchPage(
val focus_manager = LocalFocusManager.current
val keyboard_controller = LocalSoftwareKeyboardController.current
val player = LocalPlayerState.current
val multiselect_context = remember { MediaItemMultiSelectContext() {} }

var search_in_progress: Boolean by remember { mutableStateOf(false) }
val search_lock = remember { Object() }
Expand Down Expand Up @@ -161,7 +163,7 @@ fun SearchPage(
}

@Composable
private fun Results(results: SearchResults, bottom_padding: Dp) {
private fun Results(results: SearchResults, bottom_padding: Dp, multiselect_context: MediaItemMultiSelectContext) {
val horizontal_padding = 10.dp
LazyColumn(
Modifier.fillMaxSize(),
Expand All @@ -173,6 +175,12 @@ private fun Results(results: SearchResults, bottom_padding: Dp) {
),
verticalArrangement = Arrangement.spacedBy(30.dp)
) {
item {
AnimatedVisibility(multiselect_context.is_active) {
multiselect_context.InfoDisplay(background_modifier)
}
}

if (results.suggested_correction != null) {
item {
Text(results.suggested_correction)
Expand All @@ -182,7 +190,7 @@ private fun Results(results: SearchResults, bottom_padding: Dp) {
for (category in results.categories) {
val layout = category.first
item {
(layout.type ?: MediaItemLayout.Type.LIST).Layout(layout)
(layout.type ?: MediaItemLayout.Type.LIST).Layout(layout, multiselect_context = multiselect_context)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ open class PlayerState(
private val upstream: PlayerState? = null
) {
open val np_theme_mode: ThemeMode get() = upstream!!.np_theme_mode
open val overlay_page: Triple<OverlayPage, MediaItem?, MediaItem?>? get() = upstream!!.overlay_page
open val overlay_page: Triple<OverlayPage, Any?, MediaItem?>? get() = upstream!!.overlay_page
open val bottom_padding: Dp get() = upstream!!.bottom_padding
open val pill_menu: PillMenu get() = upstream!!.pill_menu
open val main_multiselect_context: MediaItemMultiSelectContext get() = upstream!!.main_multiselect_context
Expand Down Expand Up @@ -62,7 +62,7 @@ open class PlayerState(

open fun getNowPlayingTopOffset(screen_height: Dp, density: Density): Int = upstream!!.getNowPlayingTopOffset(screen_height, density)

open fun setOverlayPage(page: OverlayPage?, media_item: MediaItem? = null, from_current: Boolean = false) { upstream!!.setOverlayPage(page, media_item, from_current) }
open fun setOverlayPage(page: OverlayPage?, data: Any? = null, from_current: Boolean = false) { upstream!!.setOverlayPage(page, data, from_current) }

open fun navigateBack() { upstream!!.navigateBack() }

Expand All @@ -86,6 +86,8 @@ open class PlayerState(
open fun openMediaItem(item: MediaItem, from_current: Boolean = false) { upstream!!.openMediaItem(item, from_current) }
open fun playMediaItem(item: MediaItem, shuffle: Boolean = false) { upstream!!.playMediaItem(item, shuffle) }

open fun openViewMoreURL(url: String) { upstream!!.openViewMoreURL(url) }

open fun onMediaItemPinnedChanged(item: MediaItem, pinned: Boolean) { upstream!!.onMediaItemPinnedChanged(item, pinned) }

open fun showLongPressMenu(data: LongPressMenuData) { upstream!!.showLongPressMenu(data) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ fun getMainPageItemSize(): DpSize {
@OptIn(ExperimentalMaterialApi::class)
class PlayerStateImpl: PlayerState(null, null, null) {
private var now_playing_switch_page: Int by mutableStateOf(-1)
private val overlay_page_undo_stack: MutableList<Triple<OverlayPage, MediaItem?, MediaItem?>?> = mutableListOf()
private val overlay_page_undo_stack: MutableList<Triple<OverlayPage, Data?, MediaItem?>?> = mutableListOf()
private val bottom_padding_anim = Animatable(PlayerServiceHost.session_started.toFloat() * MINIMISED_NOW_PLAYING_HEIGHT)
private var main_page_showing: Boolean by mutableStateOf(false)

Expand All @@ -83,7 +83,7 @@ class PlayerStateImpl: PlayerState(null, null, null) {
private val pinned_items: MutableList<MediaItem> = mutableStateListOf()

override var np_theme_mode: ThemeMode by mutableStateOf(Settings.getEnum(Settings.KEY_NOWPLAYING_THEME_MODE))
override var overlay_page: Triple<OverlayPage, MediaItem?, MediaItem?>? by mutableStateOf(null)
override var overlay_page: Triple<OverlayPage, Any?, MediaItem?>? by mutableStateOf(null)
private set
override val bottom_padding: Dp get() = bottom_padding_anim.value.dp
override val pill_menu = PillMenu(
Expand Down Expand Up @@ -140,10 +140,10 @@ class PlayerStateImpl: PlayerState(null, null, null) {
return with (density) { (-np_swipe_state.value.offset.value.dp - (screen_height * 0.5f)).toPx().toInt() }
}

override fun setOverlayPage(page: OverlayPage?, media_item: MediaItem?, from_current: Boolean) {
val current = if (from_current) overlay_page!!.second!! else null
override fun setOverlayPage(page: OverlayPage?, data: Any?, from_current: Boolean) {
val current = if (from_current) (overlay_page!!.second as MediaItem) else null

val new_page = page?.let { Triple(page, media_item, current) }
val new_page = page?.let { Triple(page, data, current) }
if (new_page != overlay_page) {
overlay_page_undo_stack.add(overlay_page)
overlay_page = new_page
Expand Down Expand Up @@ -183,6 +183,10 @@ class PlayerStateImpl: PlayerState(null, null, null) {
hideLongPressMenu()
}

override fun openViewMoreURL(url: String) {

}

override fun playMediaItem(item: MediaItem, shuffle: Boolean) {
if (shuffle) {
TODO()
Expand Down Expand Up @@ -406,13 +410,14 @@ class PlayerStateImpl: PlayerState(null, null, null) {
)
OverlayPage.SETTINGS -> PrefsPage(pill_menu, close)
OverlayPage.MEDIAITEM -> Crossfade(page) { p ->
when (val item = p.second) {
when (val item = (p.second as MediaItem?)) {
null -> {}
is Artist -> ArtistPage(pill_menu, item, p.third, close)
is Playlist -> PlaylistPage(pill_menu, item, p.third, close)
else -> throw NotImplementedError()
}
}
OverlayPage.VIEW_MORE_URL -> TODO(page.second as String)
OverlayPage.LIBRARY -> LibraryPage(pill_menu, close = close)
OverlayPage.RADIO_BUILDER -> RadioBuilderPage(
pill_menu,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const val MINIMISED_NOW_PLAYING_V_PADDING: Int = 10
const val MEDIAITEM_PREVIEW_SQUARE_SIZE_SMALL: Float = 100f
const val MEDIAITEM_PREVIEW_SQUARE_SIZE_LARGE: Float = 200f

enum class OverlayPage { SEARCH, SETTINGS, MEDIAITEM, LIBRARY, RADIO_BUILDER, YTM_LOGIN, YTM_MANUAL_LOGIN }
enum class OverlayPage { SEARCH, SETTINGS, MEDIAITEM, VIEW_MORE_URL, LIBRARY, RADIO_BUILDER, YTM_LOGIN, YTM_MANUAL_LOGIN }

@Composable
fun PlayerView(player: PlayerStateImpl) {
Expand Down

0 comments on commit 77a377f

Please sign in to comment.