mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 23:51:21 +02:00
Add some ui components
This commit is contained in:
parent
913334b43d
commit
d3eeb67877
12 changed files with 1169 additions and 16 deletions
|
|
@ -1,5 +1,7 @@
|
|||
package com.github.damontecres.dolphin.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.media.AudioManager
|
||||
import android.view.KeyEvent
|
||||
import androidx.compose.foundation.MarqueeAnimationMode
|
||||
import androidx.compose.foundation.basicMarquee
|
||||
|
|
@ -7,11 +9,14 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.onFocusChanged
|
||||
import androidx.compose.ui.input.key.onPreviewKeyEvent
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import timber.log.Timber
|
||||
|
|
@ -122,3 +127,28 @@ fun Modifier.enableMarquee(focused: Boolean) =
|
|||
} else {
|
||||
basicMarquee(animationMode = MarqueeAnimationMode.WhileFocused)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Modifier.playSoundOnFocus(enabled: Boolean): Modifier {
|
||||
if (!enabled) {
|
||||
return this
|
||||
}
|
||||
val context = LocalContext.current
|
||||
val audioManager =
|
||||
remember {
|
||||
context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||
}
|
||||
return onFocusChanged {
|
||||
if (it.isFocused) {
|
||||
audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_UP)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun playOnClickSound(
|
||||
context: Context,
|
||||
effectType: Int = AudioManager.FX_KEY_CLICK,
|
||||
) {
|
||||
val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||
audioManager.playSoundEffect(effectType)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
package com.github.damontecres.dolphin.ui.components
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.tv.material3.MaterialTheme
|
||||
import com.github.damontecres.dolphin.ui.ifElse
|
||||
|
||||
@Composable
|
||||
fun CircularProgress(
|
||||
modifier: Modifier = Modifier,
|
||||
fillMaxSize: Boolean = true,
|
||||
) {
|
||||
Box(modifier = modifier.ifElse(fillMaxSize, Modifier.fillMaxSize())) {
|
||||
CircularProgressIndicator(
|
||||
color = MaterialTheme.colorScheme.border,
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,298 @@
|
|||
package com.github.damontecres.dolphin.ui.components
|
||||
|
||||
import android.view.KeyEvent
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.focusable
|
||||
import androidx.compose.foundation.gestures.scrollBy
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawBehind
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.input.key.Key
|
||||
import androidx.compose.ui.input.key.KeyEventType
|
||||
import androidx.compose.ui.input.key.key
|
||||
import androidx.compose.ui.input.key.onKeyEvent
|
||||
import androidx.compose.ui.input.key.type
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.tv.material3.Icon
|
||||
import androidx.tv.material3.ListItem
|
||||
import androidx.tv.material3.MaterialTheme
|
||||
import androidx.tv.material3.Text
|
||||
import androidx.tv.material3.surfaceColorAtElevation
|
||||
import com.github.damontecres.dolphin.ui.FontAwesome
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
sealed interface DialogItemEntry
|
||||
|
||||
data object DialogItemDivider : DialogItemEntry
|
||||
|
||||
data class DialogItem(
|
||||
val headlineContent: @Composable () -> Unit,
|
||||
val onClick: () -> Unit,
|
||||
val overlineContent: @Composable (() -> Unit)? = null,
|
||||
val supportingContent: @Composable (() -> Unit)? = null,
|
||||
val leadingContent: @Composable (BoxScope.() -> Unit)? = null,
|
||||
val trailingContent: @Composable (() -> Unit)? = null,
|
||||
val enabled: Boolean = true,
|
||||
) : DialogItemEntry {
|
||||
constructor(
|
||||
text: String,
|
||||
@StringRes iconStringRes: Int,
|
||||
onClick: () -> Unit,
|
||||
) : this(
|
||||
headlineContent = {
|
||||
Text(
|
||||
text = text,
|
||||
// color = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||
)
|
||||
},
|
||||
leadingContent = {
|
||||
Text(
|
||||
text = stringResource(id = iconStringRes),
|
||||
// color = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||
fontFamily = FontAwesome,
|
||||
)
|
||||
},
|
||||
onClick = onClick,
|
||||
)
|
||||
|
||||
constructor(
|
||||
text: String,
|
||||
icon: ImageVector,
|
||||
onClick: () -> Unit,
|
||||
) : this(
|
||||
headlineContent = {
|
||||
Text(
|
||||
text = text,
|
||||
// color = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||
)
|
||||
},
|
||||
leadingContent = {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = text,
|
||||
// tint = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||
)
|
||||
},
|
||||
onClick = onClick,
|
||||
)
|
||||
|
||||
constructor(
|
||||
text: String,
|
||||
onClick: () -> Unit,
|
||||
) : this(
|
||||
headlineContent = {
|
||||
Text(
|
||||
text = text,
|
||||
// color = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||
)
|
||||
},
|
||||
onClick = onClick,
|
||||
)
|
||||
|
||||
companion object {
|
||||
fun divider(): DialogItemEntry = DialogItemDivider
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun DialogPopup(
|
||||
showDialog: Boolean,
|
||||
title: String,
|
||||
dialogItems: List<DialogItemEntry>,
|
||||
onDismissRequest: () -> Unit,
|
||||
dismissOnClick: Boolean = true,
|
||||
waitToLoad: Boolean = true,
|
||||
properties: DialogProperties = DialogProperties(),
|
||||
) {
|
||||
var waiting by remember { mutableStateOf(waitToLoad) }
|
||||
if (showDialog) {
|
||||
if (waitToLoad) {
|
||||
LaunchedEffect(Unit) {
|
||||
// This is a hack because a long click will propagate here and click the first list item
|
||||
// So this disables the list items assuming the user will stop pressing when the dialog appears
|
||||
// This is also bypassed in the code below if the user releases the enter/d-pad center button
|
||||
waiting = true
|
||||
delay(1000)
|
||||
waiting = false
|
||||
}
|
||||
} else {
|
||||
waiting = false
|
||||
}
|
||||
Dialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
properties = properties,
|
||||
) {
|
||||
val elevatedContainerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp)
|
||||
LazyColumn(
|
||||
modifier =
|
||||
Modifier
|
||||
// .widthIn(min = 520.dp, max = 300.dp)
|
||||
// .dialogFocusable()
|
||||
.graphicsLayer {
|
||||
this.clip = true
|
||||
this.shape = RoundedCornerShape(28.0.dp)
|
||||
}.drawBehind { drawRect(color = elevatedContainerColor) }
|
||||
.padding(PaddingValues(24.dp))
|
||||
.onKeyEvent { event ->
|
||||
val code = event.nativeKeyEvent.keyCode
|
||||
if (event.nativeKeyEvent.action == KeyEvent.ACTION_UP &&
|
||||
code in
|
||||
setOf(
|
||||
KeyEvent.KEYCODE_ENTER,
|
||||
KeyEvent.KEYCODE_DPAD_CENTER,
|
||||
KeyEvent.KEYCODE_NUMPAD_ENTER,
|
||||
)
|
||||
) {
|
||||
waiting = false
|
||||
}
|
||||
false
|
||||
},
|
||||
) {
|
||||
stickyHeader {
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
}
|
||||
items(dialogItems) {
|
||||
when (it) {
|
||||
is DialogItemDivider -> HorizontalDivider(Modifier.height(16.dp))
|
||||
|
||||
is DialogItem ->
|
||||
ListItem(
|
||||
selected = false,
|
||||
enabled = !waiting && it.enabled,
|
||||
onClick = {
|
||||
if (dismissOnClick) {
|
||||
onDismissRequest.invoke()
|
||||
}
|
||||
it.onClick.invoke()
|
||||
},
|
||||
headlineContent = it.headlineContent,
|
||||
overlineContent = it.overlineContent,
|
||||
supportingContent = it.supportingContent,
|
||||
leadingContent = it.leadingContent,
|
||||
trailingContent = it.trailingContent,
|
||||
modifier = Modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ScrollableDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
content: LazyListScope.() -> Unit,
|
||||
) {
|
||||
val scrollAmount = 100f
|
||||
val columnState = rememberLazyListState()
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
fun scroll(reverse: Boolean = false) {
|
||||
scope.launch {
|
||||
columnState.scrollBy(if (reverse) -scrollAmount else scrollAmount)
|
||||
}
|
||||
}
|
||||
Dialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
properties =
|
||||
DialogProperties(
|
||||
usePlatformDefaultWidth = false,
|
||||
),
|
||||
) {
|
||||
LazyColumn(
|
||||
state = columnState,
|
||||
contentPadding = PaddingValues(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
content = content,
|
||||
modifier =
|
||||
modifier
|
||||
.width(600.dp)
|
||||
.height(380.dp)
|
||||
.focusable()
|
||||
.background(
|
||||
MaterialTheme.colorScheme.surface,
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
).onKeyEvent {
|
||||
if (it.type == KeyEventType.KeyUp) {
|
||||
return@onKeyEvent false
|
||||
}
|
||||
if (it.key == Key.DirectionDown) {
|
||||
scroll(false)
|
||||
return@onKeyEvent true
|
||||
}
|
||||
if (it.key == Key.DirectionUp) {
|
||||
scroll(true)
|
||||
return@onKeyEvent true
|
||||
}
|
||||
return@onKeyEvent false
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MarkerDurationDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
onClick: (Long) -> Unit,
|
||||
) {
|
||||
val dialogItems =
|
||||
remember {
|
||||
listOf(
|
||||
15.seconds,
|
||||
20.seconds,
|
||||
30.seconds,
|
||||
60.seconds,
|
||||
5.minutes,
|
||||
10.minutes,
|
||||
20.minutes,
|
||||
).map {
|
||||
DialogItem(it.toString()) {
|
||||
onClick.invoke(it.inWholeMilliseconds)
|
||||
}
|
||||
}
|
||||
}
|
||||
DialogPopup(
|
||||
showDialog = true,
|
||||
title = "How long?",
|
||||
dialogItems = dialogItems,
|
||||
onDismissRequest = onDismissRequest,
|
||||
dismissOnClick = false,
|
||||
waitToLoad = false,
|
||||
properties = DialogProperties(),
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Copyright 2023 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.github.damontecres.dolphin.ui.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.tv.material3.MaterialTheme
|
||||
import androidx.tv.material3.Text
|
||||
|
||||
@Composable
|
||||
fun DotSeparatedRow(
|
||||
texts: List<String>,
|
||||
modifier: Modifier = Modifier,
|
||||
textStyle: TextStyle = MaterialTheme.typography.titleSmall,
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
texts.forEachIndexed { index, text ->
|
||||
Text(
|
||||
text = text,
|
||||
style = textStyle,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
if (index != texts.lastIndex) {
|
||||
Box(
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(horizontal = 8.dp)
|
||||
.clip(CircleShape)
|
||||
.background(MaterialTheme.colorScheme.onSurface.copy(alpha = 1f))
|
||||
.size(4.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,178 @@
|
|||
package com.github.damontecres.dolphin.ui.components
|
||||
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.text.selection.LocalTextSelectionColors
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.TextFieldDefaults
|
||||
import androidx.compose.material3.TextFieldDefaults.Container
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.tv.material3.Icon
|
||||
import androidx.tv.material3.MaterialTheme
|
||||
import com.github.damontecres.dolphin.R
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun EditTextBox(
|
||||
value: String,
|
||||
onValueChange: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
|
||||
keyboardActions: KeyboardActions = KeyboardActions.Default,
|
||||
leadingIcon: @Composable (() -> Unit)? = null,
|
||||
enabled: Boolean = true,
|
||||
readOnly: Boolean = false,
|
||||
height: Dp = 40.dp,
|
||||
isInputValid: (String) -> Boolean = { true },
|
||||
supportingText: @Composable (() -> Unit)? = null,
|
||||
placeholder: @Composable (() -> Unit)? = null,
|
||||
) {
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
// From ButtonDefaults
|
||||
val colors =
|
||||
TextFieldDefaults.colors(
|
||||
unfocusedTextColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.8f),
|
||||
focusedTextColor = MaterialTheme.colorScheme.inverseOnSurface,
|
||||
focusedContainerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.6f),
|
||||
unfocusedContainerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.8f),
|
||||
disabledContainerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.4f),
|
||||
errorContainerColor = MaterialTheme.colorScheme.errorContainer.copy(alpha = 0.8f),
|
||||
focusedIndicatorColor = MaterialTheme.colorScheme.border,
|
||||
unfocusedLabelColor = Color.Unspecified,
|
||||
)
|
||||
CompositionLocalProvider(LocalTextSelectionColors provides colors.textSelectionColors) {
|
||||
BasicTextField(
|
||||
value = value,
|
||||
modifier =
|
||||
modifier
|
||||
.defaultMinSize(
|
||||
minWidth = TextFieldDefaults.MinWidth,
|
||||
minHeight = height,
|
||||
).height(height),
|
||||
onValueChange = onValueChange,
|
||||
enabled = enabled,
|
||||
readOnly = readOnly,
|
||||
textStyle = MaterialTheme.typography.bodyLarge.merge(MaterialTheme.colorScheme.onPrimaryContainer),
|
||||
cursorBrush = SolidColor(colors.cursorColor),
|
||||
keyboardOptions = keyboardOptions,
|
||||
keyboardActions = keyboardActions,
|
||||
interactionSource = interactionSource,
|
||||
singleLine = true,
|
||||
maxLines = 1,
|
||||
minLines = 1,
|
||||
visualTransformation =
|
||||
if (keyboardOptions.keyboardType == KeyboardType.Password ||
|
||||
keyboardOptions.keyboardType == KeyboardType.NumberPassword
|
||||
) {
|
||||
PasswordVisualTransformation()
|
||||
} else {
|
||||
VisualTransformation.None
|
||||
},
|
||||
decorationBox =
|
||||
@Composable { innerTextField ->
|
||||
// places leading icon, text field with label and placeholder, trailing icon
|
||||
TextFieldDefaults.DecorationBox(
|
||||
value = value,
|
||||
visualTransformation =
|
||||
if (keyboardOptions.keyboardType == KeyboardType.Password ||
|
||||
keyboardOptions.keyboardType == KeyboardType.NumberPassword
|
||||
) {
|
||||
PasswordVisualTransformation()
|
||||
} else {
|
||||
VisualTransformation.None
|
||||
},
|
||||
innerTextField = innerTextField,
|
||||
placeholder = placeholder,
|
||||
label = null,
|
||||
leadingIcon = leadingIcon,
|
||||
trailingIcon = null,
|
||||
prefix = null,
|
||||
suffix = null,
|
||||
supportingText = supportingText,
|
||||
shape = CircleShape,
|
||||
singleLine = true,
|
||||
enabled = enabled,
|
||||
isError = false,
|
||||
interactionSource = interactionSource,
|
||||
colors = colors,
|
||||
contentPadding =
|
||||
PaddingValues(
|
||||
horizontal = 8.dp,
|
||||
vertical = 10.dp,
|
||||
),
|
||||
container = {
|
||||
Container(
|
||||
enabled = enabled,
|
||||
isError = !isInputValid.invoke(value),
|
||||
interactionSource = interactionSource,
|
||||
modifier = Modifier,
|
||||
colors = colors,
|
||||
shape = CircleShape,
|
||||
focusedIndicatorLineThickness = 4.dp,
|
||||
unfocusedIndicatorLineThickness = 0.dp,
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SearchEditTextBox(
|
||||
value: String,
|
||||
onValueChange: (String) -> Unit,
|
||||
onSearchClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
readOnly: Boolean = false,
|
||||
height: Dp = 40.dp,
|
||||
) {
|
||||
EditTextBox(
|
||||
value,
|
||||
onValueChange,
|
||||
modifier,
|
||||
keyboardOptions =
|
||||
KeyboardOptions(
|
||||
autoCorrectEnabled = false,
|
||||
imeAction = ImeAction.Search,
|
||||
),
|
||||
keyboardActions =
|
||||
KeyboardActions(
|
||||
onSearch = {
|
||||
onSearchClick.invoke()
|
||||
this.defaultKeyboardAction(ImeAction.Done)
|
||||
},
|
||||
),
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Search,
|
||||
contentDescription = stringResource(R.string.search),
|
||||
tint = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||
)
|
||||
},
|
||||
enabled,
|
||||
readOnly,
|
||||
height,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,210 @@
|
|||
package com.github.damontecres.dolphin.ui.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.focusGroup
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.selection.selectable
|
||||
import androidx.compose.foundation.selection.selectableGroup
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Star
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
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
|
||||
import androidx.compose.ui.draw.drawWithCache
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusProperties
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.focus.onFocusChanged
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.BlendMode
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.tv.material3.Icon
|
||||
import androidx.tv.material3.MaterialTheme
|
||||
import com.github.damontecres.dolphin.ui.AppColors
|
||||
import com.github.damontecres.dolphin.ui.playOnClickSound
|
||||
import com.github.damontecres.dolphin.ui.playSoundOnFocus
|
||||
import com.github.damontecres.dolphin.ui.tryRequestFocus
|
||||
|
||||
enum class StarRatingPrecision {
|
||||
FULL,
|
||||
HALF,
|
||||
QUARTER,
|
||||
;
|
||||
|
||||
companion object {
|
||||
fun fromFloat(value: Float): StarRatingPrecision =
|
||||
if (value <= .25f) {
|
||||
QUARTER
|
||||
} else if (value <= .5f) {
|
||||
HALF
|
||||
} else {
|
||||
FULL
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val FilledStarColor = Color(0xFFFFC700)
|
||||
val EmptyStarColor = Color(0x2AFFC700)
|
||||
|
||||
val ratingBarHeight: Dp = 32.dp
|
||||
|
||||
@Composable
|
||||
fun StarRating(
|
||||
rating100: Int,
|
||||
onRatingChange: (Int) -> Unit,
|
||||
precision: StarRatingPrecision,
|
||||
enabled: Boolean,
|
||||
playSoundOnFocus: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
bgColor: Color = AppColors.TransparentBlack75, // MaterialTheme.colorScheme.background,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
var tempRating by remember(rating100) { mutableIntStateOf(rating100) }
|
||||
val percentage = (if (enabled) tempRating else rating100) / 100f
|
||||
val focusRequesters = remember { List(5) { FocusRequester() } }
|
||||
Box(
|
||||
modifier =
|
||||
modifier
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.background(bgColor),
|
||||
) {
|
||||
LazyRow(
|
||||
modifier =
|
||||
Modifier
|
||||
.selectableGroup()
|
||||
.padding(4.dp)
|
||||
.drawWithCache {
|
||||
onDrawWithContent {
|
||||
drawContent()
|
||||
if (percentage in 0f..<1f) {
|
||||
drawRect(
|
||||
color = bgColor,
|
||||
topLeft = Offset(size.width * percentage, 0f),
|
||||
blendMode = BlendMode.SrcAtop,
|
||||
)
|
||||
}
|
||||
}
|
||||
}.focusGroup()
|
||||
.focusProperties {
|
||||
onEnter = {
|
||||
val index =
|
||||
if (rating100 <= 20) {
|
||||
0
|
||||
} else if (rating100 <= 40) {
|
||||
1
|
||||
} else if (rating100 <= 60) {
|
||||
2
|
||||
} else if (rating100 <= 80) {
|
||||
3
|
||||
} else {
|
||||
4
|
||||
}
|
||||
focusRequesters[index].tryRequestFocus()
|
||||
}
|
||||
},
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||
) {
|
||||
for (i in 1..5) {
|
||||
item {
|
||||
val isRated = (if (enabled) tempRating else rating100) >= (i * 20)
|
||||
val icon = Icons.Filled.Star
|
||||
var focused by remember { mutableStateOf(false) }
|
||||
|
||||
val focusedColor =
|
||||
if (focused) {
|
||||
MaterialTheme.colorScheme.border
|
||||
} else {
|
||||
Color.Unspecified
|
||||
}
|
||||
Box(
|
||||
modifier =
|
||||
Modifier
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.background(focusedColor),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
tint = FilledStarColor,
|
||||
contentDescription = null,
|
||||
modifier =
|
||||
if (enabled) {
|
||||
Modifier
|
||||
.onFocusChanged {
|
||||
focused = it.isFocused
|
||||
if (it.isFocused) {
|
||||
tempRating = i * 20
|
||||
} else {
|
||||
tempRating = rating100
|
||||
}
|
||||
}.playSoundOnFocus(playSoundOnFocus)
|
||||
.focusRequester(focusRequesters[i - 1])
|
||||
.focusProperties {
|
||||
left =
|
||||
if (i == 1) focusRequesters.last() else FocusRequester.Default
|
||||
right =
|
||||
if (i == 5) focusRequesters.first() else FocusRequester.Default
|
||||
}.selectable(
|
||||
selected = isRated,
|
||||
onClick = {
|
||||
if (playSoundOnFocus) playOnClickSound(context)
|
||||
val newRating100 =
|
||||
when (precision) {
|
||||
StarRatingPrecision.FULL ->
|
||||
if (i == 1 && rating100 > 0 && rating100 <= 20) 0 else i * 20
|
||||
|
||||
StarRatingPrecision.HALF -> {
|
||||
if (rating100 > i * 20) {
|
||||
i * 20
|
||||
} else if (rating100 == i * 20) {
|
||||
i * 20 - 10
|
||||
} else if (rating100 == i * 20 - 10) {
|
||||
(i - 1) * 20
|
||||
} else if (i == 1 && rating100 == 0) {
|
||||
20
|
||||
} else if (i == 1 && rating100 > 10) {
|
||||
10
|
||||
} else if (i == 1 && rating100 <= 10) {
|
||||
0
|
||||
} else {
|
||||
(i) * 20
|
||||
}
|
||||
}
|
||||
|
||||
StarRatingPrecision.QUARTER -> {
|
||||
// TODO
|
||||
null
|
||||
}
|
||||
}
|
||||
if (newRating100 != null) {
|
||||
tempRating = newRating100
|
||||
onRatingChange(newRating100)
|
||||
}
|
||||
},
|
||||
)
|
||||
} else {
|
||||
Modifier
|
||||
}.fillMaxHeight()
|
||||
.aspectRatio(1f),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
package com.github.damontecres.dolphin.ui.components
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.LocalIndication
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.interaction.collectIsFocusedAsState
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.tv.material3.MaterialTheme
|
||||
import androidx.tv.material3.Switch
|
||||
import androidx.tv.material3.Text
|
||||
|
||||
@Composable
|
||||
fun SwitchWithLabel(
|
||||
label: String,
|
||||
checked: Boolean,
|
||||
onStateChange: (Boolean) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||
enabled: Boolean = true,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val isFocused by interactionSource.collectIsFocusedAsState()
|
||||
Row(
|
||||
modifier =
|
||||
modifier
|
||||
.clip(shape = RoundedCornerShape(20.dp))
|
||||
.background(
|
||||
if (isFocused) {
|
||||
MaterialTheme.colorScheme.onSurface
|
||||
} else {
|
||||
MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.8f)
|
||||
},
|
||||
).clickable(
|
||||
interactionSource = interactionSource,
|
||||
indication = LocalIndication.current,
|
||||
role = Role.Switch,
|
||||
onClick = {
|
||||
if (enabled) {
|
||||
onStateChange(!checked)
|
||||
} else {
|
||||
// TODO there are other uses, so shouldn't hardcode this toast
|
||||
Toast
|
||||
.makeText(context, "Item has no children", Toast.LENGTH_SHORT)
|
||||
.show()
|
||||
}
|
||||
},
|
||||
).padding(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = label,
|
||||
color = if (isFocused) MaterialTheme.colorScheme.surfaceVariant else MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.padding(start = 8.dp))
|
||||
Switch(
|
||||
checked = checked,
|
||||
enabled = enabled,
|
||||
onCheckedChange = {
|
||||
onStateChange(!checked)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
package com.github.damontecres.dolphin.ui.components
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.LocalIndication
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.focusable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.interaction.collectIsFocusedAsState
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.tv.material3.MaterialTheme
|
||||
import androidx.tv.material3.ProvideTextStyle
|
||||
import androidx.tv.material3.Text
|
||||
import com.github.damontecres.dolphin.ui.ifElse
|
||||
import com.github.damontecres.dolphin.ui.isNotNullOrBlank
|
||||
|
||||
@Composable
|
||||
fun TableRowComposable(
|
||||
row: TableRow,
|
||||
modifier: Modifier = Modifier,
|
||||
keyWeight: Float = .3f,
|
||||
valueWeight: Float = .7f,
|
||||
focusable: Boolean = true,
|
||||
textStyle: TextStyle = MaterialTheme.typography.bodyLarge.copy(color = MaterialTheme.colorScheme.onBackground),
|
||||
) {
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
val isFocused by interactionSource.collectIsFocusedAsState()
|
||||
val background =
|
||||
if (isFocused && row.onClick != null) {
|
||||
MaterialTheme.colorScheme.border.copy(alpha = .75f)
|
||||
} else if (isFocused) {
|
||||
MaterialTheme.colorScheme.onBackground.copy(alpha = .25f)
|
||||
} else {
|
||||
Color.Unspecified
|
||||
}
|
||||
Row(
|
||||
modifier
|
||||
.background(background)
|
||||
.ifElse(
|
||||
row.onClick != null,
|
||||
ifTrueModifier =
|
||||
Modifier
|
||||
.clickable(
|
||||
enabled = true,
|
||||
interactionSource = interactionSource,
|
||||
indication = LocalIndication.current,
|
||||
onClick = { row.onClick?.invoke() },
|
||||
),
|
||||
ifFalseModifier =
|
||||
Modifier.focusable(
|
||||
enabled = focusable, // TODO, this allows scrolling
|
||||
interactionSource = interactionSource,
|
||||
),
|
||||
),
|
||||
) {
|
||||
val keyModifier =
|
||||
Modifier
|
||||
.weight(keyWeight)
|
||||
val valueModifier =
|
||||
Modifier
|
||||
.weight(valueWeight)
|
||||
ProvideTextStyle(textStyle) {
|
||||
Box(modifier = keyModifier) {
|
||||
row.key.invoke(this, Modifier.padding(4.dp))
|
||||
}
|
||||
Box(modifier = valueModifier) {
|
||||
row.value.invoke(this, Modifier.padding(4.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class TableRow(
|
||||
val key: @Composable BoxScope.(modifier: Modifier) -> Unit,
|
||||
val value: @Composable BoxScope.(modifier: Modifier) -> Unit,
|
||||
val onClick: (() -> Unit)? = null,
|
||||
) {
|
||||
constructor(key: String, value: String, onClick: (() -> Unit)? = null) : this(
|
||||
{ modifier: Modifier -> Text(text = "$key:", modifier = modifier) },
|
||||
{ modifier: Modifier -> Text(text = value, modifier = modifier) },
|
||||
onClick,
|
||||
)
|
||||
|
||||
companion object {
|
||||
@Composable
|
||||
fun from(
|
||||
@StringRes keyStringId: Int,
|
||||
value: String?,
|
||||
onClick: (() -> Unit)? = null,
|
||||
): TableRow? =
|
||||
if (value.isNotNullOrBlank()) {
|
||||
TableRow(stringResource(keyStringId), value, onClick)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
fun from(
|
||||
context: Context,
|
||||
@StringRes keyStringId: Int,
|
||||
value: String?,
|
||||
onClick: (() -> Unit)? = null,
|
||||
): TableRow? =
|
||||
if (value.isNotNullOrBlank()) {
|
||||
TableRow(context.getString(keyStringId), value, onClick)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
fun from(
|
||||
key: String,
|
||||
value: String?,
|
||||
onClick: (() -> Unit)? = null,
|
||||
): TableRow? =
|
||||
if (value.isNotNullOrBlank()) {
|
||||
TableRow(key, value, onClick)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* Copyright 2023 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.github.damontecres.dolphin.ui.components
|
||||
|
||||
import androidx.compose.foundation.LocalIndication
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.interaction.collectIsFocusedAsState
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.scale
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.tv.material3.MaterialTheme
|
||||
import androidx.tv.material3.Text
|
||||
import com.github.damontecres.dolphin.ui.ifElse
|
||||
import com.github.damontecres.dolphin.ui.playOnClickSound
|
||||
|
||||
@Composable
|
||||
fun TitleValueText(
|
||||
title: String,
|
||||
value: String,
|
||||
modifier: Modifier = Modifier,
|
||||
playSoundOnFocus: Boolean = false,
|
||||
onClick: (() -> Unit)? = null,
|
||||
onLongClick: (() -> Unit)? = null,
|
||||
) {
|
||||
MaybeClickColumn(
|
||||
onClick = onClick,
|
||||
onLongClick = onLongClick,
|
||||
playSoundOnFocus = playSoundOnFocus,
|
||||
modifier = modifier,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.alpha(0.8f),
|
||||
text = title,
|
||||
style = MaterialTheme.typography.labelMedium.copy(fontWeight = FontWeight.Normal),
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
Text(
|
||||
text = value,
|
||||
style = MaterialTheme.typography.labelLarge.copy(fontWeight = FontWeight.Normal),
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MaybeClickColumn(
|
||||
playSoundOnFocus: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
onClick: (() -> Unit)? = null,
|
||||
onLongClick: (() -> Unit)? = null,
|
||||
content: @Composable ColumnScope.() -> Unit,
|
||||
) {
|
||||
if (onClick != null || onLongClick != null) {
|
||||
val context = LocalContext.current
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
val isFocused by interactionSource.collectIsFocusedAsState()
|
||||
if (playSoundOnFocus) {
|
||||
LaunchedEffect(isFocused) {
|
||||
if (isFocused) playOnClickSound(context)
|
||||
}
|
||||
}
|
||||
val bgColor =
|
||||
if (isFocused) {
|
||||
MaterialTheme.colorScheme.onPrimary.copy(alpha = .75f)
|
||||
} else {
|
||||
Color.Unspecified
|
||||
}
|
||||
|
||||
Column(
|
||||
content = content,
|
||||
modifier =
|
||||
modifier
|
||||
.background(bgColor, shape = RoundedCornerShape(4.dp))
|
||||
.ifElse(isFocused, Modifier.scale(1.1f))
|
||||
.combinedClickable(
|
||||
enabled = true,
|
||||
interactionSource = interactionSource,
|
||||
indication = LocalIndication.current,
|
||||
onClick = {
|
||||
if (playSoundOnFocus) playOnClickSound(context)
|
||||
onClick?.invoke()
|
||||
},
|
||||
onLongClick = {
|
||||
if (playSoundOnFocus) playOnClickSound(context)
|
||||
onLongClick?.invoke()
|
||||
},
|
||||
),
|
||||
)
|
||||
} else {
|
||||
Column(content = content, modifier = modifier)
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import androidx.activity.compose.BackHandler
|
|||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.focusGroup
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
|
|
@ -12,12 +13,9 @@ import androidx.compose.foundation.lazy.items
|
|||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.AccountCircle
|
||||
import androidx.compose.material.icons.filled.DateRange
|
||||
import androidx.compose.material.icons.filled.Email
|
||||
import androidx.compose.material.icons.filled.Home
|
||||
import androidx.compose.material.icons.filled.Info
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.material.icons.filled.Settings
|
||||
import androidx.compose.material.icons.filled.ShoppingCart
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
|
|
@ -30,7 +28,10 @@ import androidx.compose.ui.focus.focusRequester
|
|||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
|
|
@ -45,9 +46,11 @@ import androidx.tv.material3.NavigationDrawerScope
|
|||
import androidx.tv.material3.ProvideTextStyle
|
||||
import androidx.tv.material3.Text
|
||||
import androidx.tv.material3.rememberDrawerState
|
||||
import com.github.damontecres.dolphin.R
|
||||
import com.github.damontecres.dolphin.data.ServerRepository
|
||||
import com.github.damontecres.dolphin.data.model.Library
|
||||
import com.github.damontecres.dolphin.preferences.UserPreferences
|
||||
import com.github.damontecres.dolphin.ui.FontAwesome
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jellyfin.sdk.api.client.ApiClient
|
||||
|
|
@ -121,7 +124,14 @@ fun NavDrawer(
|
|||
IconNavItem(
|
||||
text = user?.name ?: "",
|
||||
icon = Icons.Default.AccountCircle,
|
||||
onClick = {},
|
||||
onClick = { navigationManager.navigateTo(Destination.Setup) },
|
||||
)
|
||||
}
|
||||
item {
|
||||
IconNavItem(
|
||||
text = "Search",
|
||||
icon = Icons.Default.Search,
|
||||
onClick = { navigationManager.navigateTo(Destination.Search) },
|
||||
)
|
||||
}
|
||||
item {
|
||||
|
|
@ -148,7 +158,7 @@ fun NavDrawer(
|
|||
IconNavItem(
|
||||
text = "Settings",
|
||||
icon = Icons.Default.Settings,
|
||||
onClick = {},
|
||||
onClick = { navigationManager.navigateTo(Destination.Settings) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -202,20 +212,26 @@ fun NavigationDrawerScope.LibraryNavItem(
|
|||
// TODO
|
||||
val icon =
|
||||
when (library.collectionType) {
|
||||
CollectionType.MOVIES -> Icons.Default.Email
|
||||
CollectionType.TVSHOWS -> Icons.Default.DateRange
|
||||
CollectionType.HOMEVIDEOS -> Icons.Default.ShoppingCart
|
||||
else -> Icons.Default.Info
|
||||
CollectionType.MOVIES -> R.string.fa_film
|
||||
CollectionType.TVSHOWS -> R.string.fa_tv
|
||||
CollectionType.HOMEVIDEOS -> R.string.fa_video
|
||||
CollectionType.LIVETV -> R.string.fa_tv
|
||||
CollectionType.MUSIC -> R.string.fa_music
|
||||
else -> R.string.fa_film
|
||||
}
|
||||
NavigationDrawerItem(
|
||||
modifier = modifier,
|
||||
selected = false,
|
||||
onClick = onClick,
|
||||
leadingContent = {
|
||||
Icon(
|
||||
icon,
|
||||
contentDescription = null,
|
||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
Text(
|
||||
text = stringResource(icon),
|
||||
textAlign = TextAlign.Center,
|
||||
fontSize = 16.sp,
|
||||
fontFamily = FontAwesome,
|
||||
)
|
||||
}
|
||||
},
|
||||
interactionSource = null,
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -26,4 +26,6 @@
|
|||
<string name="fa_angles_down" translatable="false"></string>
|
||||
<string name="fa_house" translatable="false"></string>
|
||||
<string name="fa_closed_captioning" translatable="false"></string>
|
||||
<string name="fa_tv" translatable="false"></string>
|
||||
<string name="fa_music" translatable="false"></string>
|
||||
</resources>
|
||||
|
|
@ -11,4 +11,5 @@
|
|||
<string name="home_section_next_up">Next up</string>
|
||||
<string name="home_section_livetv">Live TV</string>
|
||||
<string name="home_section_none">None</string>
|
||||
<string name="search">Search</string>
|
||||
</resources>
|
||||
Loading…
Add table
Add a link
Reference in a new issue