mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-09 16:11:20 +02:00
UI improvements & bug fixes for queuing
This commit is contained in:
parent
49adae9365
commit
82f8a63c86
7 changed files with 267 additions and 60 deletions
|
|
@ -87,7 +87,6 @@ import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||||
import org.jellyfin.sdk.model.api.CollectionType
|
|
||||||
import org.jellyfin.sdk.model.serializer.toUUID
|
import org.jellyfin.sdk.model.serializer.toUUID
|
||||||
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
|
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
@ -326,9 +325,8 @@ class MainActivity : AppCompatActivity() {
|
||||||
// ?: Destination.Home(),
|
// ?: Destination.Home(),
|
||||||
// TODO undo
|
// TODO undo
|
||||||
?: Destination.MediaItem(
|
?: Destination.MediaItem(
|
||||||
itemId = "7e64e319-657a-9516-ec78-490da03edccb".toUUID(),
|
itemId = "011ef0c7-ca45-684f-2cd9-dd3b020ca5f6".toUUID(),
|
||||||
type = BaseItemKind.COLLECTION_FOLDER,
|
type = BaseItemKind.MUSIC_ALBUM,
|
||||||
collectionType = CollectionType.MUSIC,
|
|
||||||
),
|
),
|
||||||
navigationManager = navigationManager,
|
navigationManager = navigationManager,
|
||||||
preferences = preferences,
|
preferences = preferences,
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,7 @@ class MusicService
|
||||||
player.shuffleModeEnabled = shuffled
|
player.shuffleModeEnabled = shuffled
|
||||||
player.play()
|
player.play()
|
||||||
}
|
}
|
||||||
addAllToQueue(items)
|
addAllToQueue(items, startIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun setQueue(
|
suspend fun setQueue(
|
||||||
|
|
@ -140,16 +140,26 @@ class MusicService
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun addAllToQueue(list: BlockingList<BaseItem?>) {
|
suspend fun addAllToQueue(
|
||||||
|
list: BlockingList<BaseItem?>,
|
||||||
|
startIndex: Int,
|
||||||
|
) {
|
||||||
|
var remaining = startIndex
|
||||||
list.indices
|
list.indices
|
||||||
.chunked(25)
|
.chunked(25)
|
||||||
.forEach {
|
.forEach {
|
||||||
val mediaItems =
|
val mediaItems =
|
||||||
it.mapNotNull {
|
it.mapNotNull {
|
||||||
|
if (remaining == 0) {
|
||||||
list
|
list
|
||||||
.getBlocking(it)
|
.getBlocking(it)
|
||||||
?.takeIf { it.type == BaseItemKind.AUDIO }
|
?.takeIf { it.type == BaseItemKind.AUDIO }
|
||||||
?.let(::convert)
|
?.let(::convert)
|
||||||
|
} else {
|
||||||
|
Timber.v("Skipping $remaining")
|
||||||
|
remaining--
|
||||||
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
onMain { player.addMediaItems(mediaItems) }
|
onMain { player.addMediaItems(mediaItems) }
|
||||||
}
|
}
|
||||||
|
|
@ -243,18 +253,24 @@ private class MusicPlayerListener(
|
||||||
timeline: Timeline,
|
timeline: Timeline,
|
||||||
reason: Int,
|
reason: Int,
|
||||||
) {
|
) {
|
||||||
Timber.v("MusicPlayerListener onTimelineChanged")
|
// Timber.v("MusicPlayerListener onTimelineChanged")
|
||||||
if (reason == Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED) {
|
if (reason == Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED) {
|
||||||
updateCurrentIndex()
|
updateCurrentIndex()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateCurrentIndex() {
|
private fun updateCurrentIndex() {
|
||||||
state.update {
|
state.update { state ->
|
||||||
it.copy(
|
player.currentMediaItemIndex.takeIf { it >= 0 }?.let { currentMediaItemIndex ->
|
||||||
currentIndex = player.currentMediaItemIndex,
|
if (currentMediaItemIndex in (0..<player.mediaItemCount)) {
|
||||||
currentItemId = player.getMediaItemAt(player.currentMediaItemIndex).mediaId.toUUIDOrNull(),
|
state.copy(
|
||||||
|
currentIndex = currentMediaItemIndex,
|
||||||
|
currentItemId = player.getMediaItemAt(currentMediaItemIndex).mediaId.toUUIDOrNull(),
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
state
|
||||||
|
}
|
||||||
|
} ?: state
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,35 +1,59 @@
|
||||||
package com.github.damontecres.wholphin.ui.detail.music
|
package com.github.damontecres.wholphin.ui.detail.music
|
||||||
|
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.annotation.OptIn
|
import androidx.annotation.OptIn
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.animation.expandVertically
|
||||||
|
import androidx.compose.animation.shrinkVertically
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
|
import androidx.compose.ui.focus.focusRequester
|
||||||
|
import androidx.compose.ui.focus.onFocusChanged
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.media3.common.Player
|
import androidx.media3.common.Player
|
||||||
import androidx.media3.common.util.UnstableApi
|
import androidx.media3.common.util.UnstableApi
|
||||||
import androidx.media3.ui.compose.state.rememberNextButtonState
|
import androidx.media3.ui.compose.state.rememberNextButtonState
|
||||||
import androidx.media3.ui.compose.state.rememberPlayPauseButtonState
|
import androidx.media3.ui.compose.state.rememberPlayPauseButtonState
|
||||||
import androidx.media3.ui.compose.state.rememberPreviousButtonState
|
import androidx.media3.ui.compose.state.rememberPreviousButtonState
|
||||||
|
import androidx.media3.ui.compose.state.rememberRepeatButtonState
|
||||||
|
import androidx.media3.ui.compose.state.rememberShuffleButtonState
|
||||||
|
import androidx.tv.material3.MaterialTheme
|
||||||
import androidx.tv.material3.Text
|
import androidx.tv.material3.Text
|
||||||
|
import com.github.damontecres.wholphin.R
|
||||||
import com.github.damontecres.wholphin.data.model.AudioItem
|
import com.github.damontecres.wholphin.data.model.AudioItem
|
||||||
|
import com.github.damontecres.wholphin.ui.ifElse
|
||||||
import com.github.damontecres.wholphin.ui.playback.ControllerViewState
|
import com.github.damontecres.wholphin.ui.playback.ControllerViewState
|
||||||
import com.github.damontecres.wholphin.ui.playback.PlaybackAction
|
import com.github.damontecres.wholphin.ui.playback.PlaybackAction
|
||||||
import com.github.damontecres.wholphin.ui.playback.PlaybackButtons
|
import com.github.damontecres.wholphin.ui.playback.PlaybackButtons
|
||||||
|
import com.github.damontecres.wholphin.ui.playback.PlaybackFaButton
|
||||||
import com.github.damontecres.wholphin.ui.playback.SeekBar
|
import com.github.damontecres.wholphin.ui.playback.SeekBar
|
||||||
import com.github.damontecres.wholphin.ui.roundSeconds
|
import com.github.damontecres.wholphin.ui.roundSeconds
|
||||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import kotlin.time.Duration
|
import kotlin.time.Duration
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
|
|
@ -43,20 +67,62 @@ fun NowPlayingOverlay(
|
||||||
controllerViewState: ControllerViewState,
|
controllerViewState: ControllerViewState,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
LaunchedEffect(Unit) { focusRequester.tryRequestFocus() }
|
LaunchedEffect(Unit) { focusRequester.tryRequestFocus() }
|
||||||
val playPauseState = rememberPlayPauseButtonState(player)
|
val playPauseState = rememberPlayPauseButtonState(player)
|
||||||
val previousState = rememberPreviousButtonState(player)
|
val previousState = rememberPreviousButtonState(player)
|
||||||
val nextState = rememberNextButtonState(player)
|
val nextState = rememberNextButtonState(player)
|
||||||
Column(modifier = modifier.padding(16.dp)) {
|
val shuffleState = rememberShuffleButtonState(player)
|
||||||
|
val repeatState = rememberRepeatButtonState(player)
|
||||||
|
|
||||||
|
var queueHasFocus by remember { mutableStateOf(false) }
|
||||||
|
val height by animateFloatAsState(
|
||||||
|
if (queueHasFocus) {
|
||||||
|
1f
|
||||||
|
} else {
|
||||||
|
.5f
|
||||||
|
},
|
||||||
|
animationSpec = tween(durationMillis = 500),
|
||||||
|
)
|
||||||
|
val listState = rememberLazyListState()
|
||||||
|
val hideButtons by remember {
|
||||||
|
derivedStateOf {
|
||||||
|
listState.firstVisibleItemIndex > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val firstFocusRequester = remember { FocusRequester() }
|
||||||
|
BackHandler(hideButtons) {
|
||||||
|
scope.launch {
|
||||||
|
listState.animateScrollToItem(0)
|
||||||
|
firstFocusRequester.tryRequestFocus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier =
|
||||||
|
modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
.fillMaxHeight(height),
|
||||||
|
) {
|
||||||
current?.title?.let {
|
current?.title?.let {
|
||||||
Text(it)
|
Text(
|
||||||
|
text = it,
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
current?.albumTitle?.let {
|
current?.albumTitle?.let {
|
||||||
Text(it)
|
Text(
|
||||||
|
text = it,
|
||||||
|
style = MaterialTheme.typography.titleSmall,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
current?.artistNames?.let {
|
current?.artistNames?.let {
|
||||||
Text(it)
|
Text(
|
||||||
|
text = it,
|
||||||
|
style = MaterialTheme.typography.titleSmall,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
SeekBar(
|
SeekBar(
|
||||||
player = player,
|
player = player,
|
||||||
|
|
@ -69,11 +135,21 @@ fun NowPlayingOverlay(
|
||||||
seekForward = Duration.ZERO,
|
seekForward = Duration.ZERO,
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.padding(vertical = 8.dp)
|
.padding(top = 8.dp)
|
||||||
.fillMaxWidth(.95f),
|
.fillMaxWidth(.95f),
|
||||||
)
|
)
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = !hideButtons,
|
||||||
|
enter = expandVertically(),
|
||||||
|
exit = shrinkVertically(),
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.align(Alignment.CenterHorizontally),
|
||||||
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.align(Alignment.CenterHorizontally),
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.align(Alignment.CenterHorizontally),
|
||||||
) {
|
) {
|
||||||
PlaybackButtons(
|
PlaybackButtons(
|
||||||
player = player,
|
player = player,
|
||||||
|
|
@ -101,15 +177,55 @@ fun NowPlayingOverlay(
|
||||||
nextEnabled = nextState.isEnabled,
|
nextEnabled = nextState.isEnabled,
|
||||||
seekBack = 10.seconds,
|
seekBack = 10.seconds,
|
||||||
skipBackOnResume = null,
|
skipBackOnResume = null,
|
||||||
seekForward = 30.seconds,
|
seekForward = 30.seconds, // TODO
|
||||||
)
|
)
|
||||||
|
PlaybackFaButton(
|
||||||
|
iconRes = R.string.fa_shuffle,
|
||||||
|
onClick = {
|
||||||
|
shuffleState.onClick()
|
||||||
|
},
|
||||||
|
onControllerInteraction = { controllerViewState.pulseControls() },
|
||||||
|
textColor =
|
||||||
|
if (shuffleState.shuffleOn) {
|
||||||
|
MaterialTheme.colorScheme.secondary
|
||||||
|
} else {
|
||||||
|
Color.Unspecified
|
||||||
|
},
|
||||||
|
)
|
||||||
|
PlaybackFaButton(
|
||||||
|
iconRes = R.string.fa_repeat,
|
||||||
|
onClick = {
|
||||||
|
repeatState.onClick()
|
||||||
|
},
|
||||||
|
onControllerInteraction = { controllerViewState.pulseControls() },
|
||||||
|
textColor =
|
||||||
|
when (repeatState.repeatModeState) {
|
||||||
|
Player.REPEAT_MODE_ALL -> MaterialTheme.colorScheme.secondary
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
Player.REPEAT_MODE_ONE -> MaterialTheme.colorScheme.tertiary
|
||||||
|
|
||||||
|
else -> Color.Unspecified
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (queue.isEmpty()) {
|
if (queue.isEmpty()) {
|
||||||
Text("No items")
|
Text("No items")
|
||||||
} else {
|
} else {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.queue),
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
)
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
contentPadding = PaddingValues(16.dp),
|
state = listState,
|
||||||
modifier = Modifier.fillMaxSize(),
|
contentPadding = PaddingValues(bottom = 16.dp, start = 16.dp, end = 16.dp),
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.onFocusChanged {
|
||||||
|
queueHasFocus = it.hasFocus
|
||||||
|
},
|
||||||
) {
|
) {
|
||||||
itemsIndexed(queue) { index, song ->
|
itemsIndexed(queue) { index, song ->
|
||||||
SongListItem(
|
SongListItem(
|
||||||
|
|
@ -123,7 +239,11 @@ fun NowPlayingOverlay(
|
||||||
player.seekTo(index, 0L)
|
player.seekTo(index, 0L)
|
||||||
},
|
},
|
||||||
onLongClick = {},
|
onLongClick = {},
|
||||||
modifier = Modifier,
|
modifier =
|
||||||
|
Modifier.ifElse(
|
||||||
|
index == 0,
|
||||||
|
Modifier.focusRequester(firstFocusRequester),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import androidx.annotation.OptIn
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.expandHorizontally
|
import androidx.compose.animation.expandHorizontally
|
||||||
import androidx.compose.animation.shrinkHorizontally
|
import androidx.compose.animation.shrinkHorizontally
|
||||||
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.focusable
|
import androidx.compose.foundation.focusable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
|
|
@ -31,6 +32,7 @@ import com.github.damontecres.wholphin.preferences.AppPreferences
|
||||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||||
import com.github.damontecres.wholphin.preferences.skipBackOnResume
|
import com.github.damontecres.wholphin.preferences.skipBackOnResume
|
||||||
import com.github.damontecres.wholphin.services.rememberQueue
|
import com.github.damontecres.wholphin.services.rememberQueue
|
||||||
|
import com.github.damontecres.wholphin.ui.AppColors
|
||||||
import com.github.damontecres.wholphin.ui.playback.PlaybackKeyHandler
|
import com.github.damontecres.wholphin.ui.playback.PlaybackKeyHandler
|
||||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||||
import kotlin.time.Duration.Companion.milliseconds
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
|
@ -127,14 +129,22 @@ fun NowPlayingPage(
|
||||||
BackHandler(controllerViewState.controlsVisible) {
|
BackHandler(controllerViewState.controlsVisible) {
|
||||||
controllerViewState.hideControls()
|
controllerViewState.hideControls()
|
||||||
}
|
}
|
||||||
AnimatedVisibility(controllerViewState.controlsVisible) {
|
AnimatedVisibility(
|
||||||
|
controllerViewState.controlsVisible,
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.align(Alignment.BottomCenter),
|
||||||
|
) {
|
||||||
NowPlayingOverlay(
|
NowPlayingOverlay(
|
||||||
state = state,
|
state = state,
|
||||||
player = player,
|
player = player,
|
||||||
current = current,
|
current = current,
|
||||||
queue = queue,
|
queue = queue,
|
||||||
controllerViewState = controllerViewState,
|
controllerViewState = controllerViewState,
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.background(AppColors.TransparentBlack50)
|
||||||
|
.align(Alignment.BottomCenter),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -87,8 +87,12 @@ class NowPlayingViewModel
|
||||||
|
|
||||||
private suspend fun getCurrent(): AudioItem? {
|
private suspend fun getCurrent(): AudioItem? {
|
||||||
val mediaItem =
|
val mediaItem =
|
||||||
onMain { player.currentMediaItemIndex.let { player.getMediaItemAt(it) } }
|
onMain {
|
||||||
return mediaItem.localConfiguration?.tag as? AudioItem
|
player.currentMediaItemIndex
|
||||||
|
.takeIf { it >= 0 }
|
||||||
|
?.let { player.getMediaItemAt(it) }
|
||||||
|
}
|
||||||
|
return mediaItem?.localConfiguration?.tag as? AudioItem
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun playbackLoop() {
|
private fun playbackLoop() {
|
||||||
|
|
@ -125,7 +129,7 @@ class NowPlayingViewModel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
delay(250)
|
delay(150)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,10 @@ package com.github.damontecres.wholphin.ui.playback
|
||||||
import android.view.Gravity
|
import android.view.Gravity
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import androidx.annotation.OptIn
|
import androidx.annotation.OptIn
|
||||||
|
import androidx.annotation.StringRes
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.focusGroup
|
import androidx.compose.foundation.focusGroup
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.interaction.collectIsFocusedAsState
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
|
@ -24,7 +24,6 @@ import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.relocation.BringIntoViewRequester
|
import androidx.compose.foundation.relocation.BringIntoViewRequester
|
||||||
import androidx.compose.foundation.relocation.bringIntoViewRequester
|
import androidx.compose.foundation.relocation.bringIntoViewRequester
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
|
@ -37,14 +36,18 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.focus.onFocusChanged
|
import androidx.compose.ui.focus.onFocusChanged
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.isSpecified
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.LocalView
|
import androidx.compose.ui.platform.LocalView
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.compose.ui.window.Dialog
|
import androidx.compose.ui.window.Dialog
|
||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
import androidx.compose.ui.window.DialogWindowProvider
|
import androidx.compose.ui.window.DialogWindowProvider
|
||||||
|
|
@ -60,6 +63,7 @@ import androidx.tv.material3.surfaceColorAtElevation
|
||||||
import com.github.damontecres.wholphin.R
|
import com.github.damontecres.wholphin.R
|
||||||
import com.github.damontecres.wholphin.preferences.AppThemeColors
|
import com.github.damontecres.wholphin.preferences.AppThemeColors
|
||||||
import com.github.damontecres.wholphin.ui.AppColors
|
import com.github.damontecres.wholphin.ui.AppColors
|
||||||
|
import com.github.damontecres.wholphin.ui.FontAwesome
|
||||||
import com.github.damontecres.wholphin.ui.PreviewTvSpec
|
import com.github.damontecres.wholphin.ui.PreviewTvSpec
|
||||||
import com.github.damontecres.wholphin.ui.components.Button
|
import com.github.damontecres.wholphin.ui.components.Button
|
||||||
import com.github.damontecres.wholphin.ui.components.SelectedLeadingContent
|
import com.github.damontecres.wholphin.ui.components.SelectedLeadingContent
|
||||||
|
|
@ -462,6 +466,54 @@ fun PlaybackButton(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun PlaybackFaButton(
|
||||||
|
@StringRes iconRes: Int,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
onControllerInteraction: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
enabled: Boolean = true,
|
||||||
|
interactionSource: MutableInteractionSource? = null,
|
||||||
|
textColor: Color = Color.Unspecified,
|
||||||
|
) {
|
||||||
|
val selectedColor = MaterialTheme.colorScheme.border
|
||||||
|
Button(
|
||||||
|
enabled = enabled,
|
||||||
|
onClick = onClick,
|
||||||
|
// shape = ButtonDefaults.shape(CircleShape),
|
||||||
|
colors =
|
||||||
|
ClickableSurfaceDefaults.colors(
|
||||||
|
containerColor = AppColors.TransparentBlack25,
|
||||||
|
focusedContainerColor = selectedColor,
|
||||||
|
),
|
||||||
|
contentPadding = PaddingValues(4.dp),
|
||||||
|
interactionSource = interactionSource,
|
||||||
|
modifier =
|
||||||
|
modifier
|
||||||
|
.size(36.dp, 36.dp)
|
||||||
|
.onFocusChanged { onControllerInteraction.invoke() },
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(iconRes),
|
||||||
|
fontSize = 18.sp,
|
||||||
|
fontFamily = FontAwesome,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
color =
|
||||||
|
if (textColor.isSpecified) {
|
||||||
|
textColor
|
||||||
|
} else if (LocalTheme.current == AppThemeColors.OLED_BLACK) {
|
||||||
|
LocalContentColor.current
|
||||||
|
} else {
|
||||||
|
MaterialTheme.colorScheme.onSurface
|
||||||
|
},
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.align(Alignment.CenterVertically),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun <T> BottomDialog(
|
fun <T> BottomDialog(
|
||||||
choices: List<BottomDialogItem<T>>,
|
choices: List<BottomDialogItem<T>>,
|
||||||
|
|
@ -545,12 +597,18 @@ data class BottomDialogItem<T>(
|
||||||
@Composable
|
@Composable
|
||||||
private fun ButtonPreview() {
|
private fun ButtonPreview() {
|
||||||
WholphinTheme {
|
WholphinTheme {
|
||||||
Row {
|
Row(Modifier.background(Color.Red)) {
|
||||||
PlaybackButton(
|
PlaybackButton(
|
||||||
iconRes = R.drawable.baseline_play_arrow_24,
|
iconRes = R.drawable.baseline_play_arrow_24,
|
||||||
onClick = {},
|
onClick = {},
|
||||||
onControllerInteraction = {},
|
onControllerInteraction = {},
|
||||||
)
|
)
|
||||||
|
PlaybackFaButton(
|
||||||
|
iconRes = R.string.fa_shuffle,
|
||||||
|
onClick = {},
|
||||||
|
onControllerInteraction = {},
|
||||||
|
textColor = Color.Green,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,4 +54,5 @@
|
||||||
<string name="fa_cloud_arrow_up" translatable="false"></string>
|
<string name="fa_cloud_arrow_up" translatable="false"></string>
|
||||||
<string name="fa_cloud_arrow_down" translatable="false"></string>
|
<string name="fa_cloud_arrow_down" translatable="false"></string>
|
||||||
<string name="fa_compass" translatable="false"></string>
|
<string name="fa_compass" translatable="false"></string>
|
||||||
|
<string name="fa_repeat" translatable="false"></string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue