mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 23:51:21 +02:00
Support custom mpv.conf (#327)
Part of #235 Closes #305 Adds a preference for specifying a custom `mpv.conf` Also adds a preference to toggle between `vo=gpu` and `vo=gpu-next`, see https://github.com/mpv-player/mpv/wiki/GPU-Next-vs-GPU There are some caveats: * Multi-line text entry via a remote is rough * [Track selection options](https://mpv.io/manual/stable/#track-selection) may not work as expected because Wholphin manages this itself * Bad settings will crash the app hard Dev note: This rewrites the text field inputs to use the [state based implementations](https://developer.android.com/develop/ui/compose/text/migrate-state-based)
This commit is contained in:
parent
45cb385b9b
commit
a294661520
15 changed files with 408 additions and 269 deletions
|
|
@ -701,6 +701,24 @@ sealed interface AppPreference<Pref, T> {
|
||||||
summary = R.string.disable_if_crash,
|
summary = R.string.disable_if_crash,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val MpvGpuNext =
|
||||||
|
AppSwitchPreference<AppPreferences>(
|
||||||
|
title = R.string.mpv_use_gpu_next,
|
||||||
|
defaultValue = true,
|
||||||
|
getter = { it.playbackPreferences.mpvOptions.useGpuNext },
|
||||||
|
setter = { prefs, value ->
|
||||||
|
prefs.updateMpvOptions { useGpuNext = value }
|
||||||
|
},
|
||||||
|
summaryOn = R.string.enabled,
|
||||||
|
summaryOff = R.string.disabled,
|
||||||
|
)
|
||||||
|
|
||||||
|
val MpvConfFile =
|
||||||
|
AppClickablePreference<AppPreferences>(
|
||||||
|
title = R.string.mpv_conf,
|
||||||
|
summary = null,
|
||||||
|
)
|
||||||
|
|
||||||
val DebugLogging =
|
val DebugLogging =
|
||||||
AppSwitchPreference<AppPreferences>(
|
AppSwitchPreference<AppPreferences>(
|
||||||
title = R.string.verbose_logging,
|
title = R.string.verbose_logging,
|
||||||
|
|
@ -851,7 +869,11 @@ val advancedPreferences =
|
||||||
),
|
),
|
||||||
ConditionalPreferences(
|
ConditionalPreferences(
|
||||||
{ it.playbackPreferences.playerBackend == PlayerBackend.MPV },
|
{ it.playbackPreferences.playerBackend == PlayerBackend.MPV },
|
||||||
listOf(AppPreference.MpvHardwareDecoding),
|
listOf(
|
||||||
|
AppPreference.MpvHardwareDecoding,
|
||||||
|
AppPreference.MpvGpuNext,
|
||||||
|
AppPreference.MpvConfFile,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,7 @@ class AppPreferencesSerializer
|
||||||
.apply {
|
.apply {
|
||||||
enableHardwareDecoding =
|
enableHardwareDecoding =
|
||||||
AppPreference.MpvHardwareDecoding.defaultValue
|
AppPreference.MpvHardwareDecoding.defaultValue
|
||||||
|
useGpuNext = AppPreference.MpvGpuNext.defaultValue
|
||||||
}.build()
|
}.build()
|
||||||
}.build()
|
}.build()
|
||||||
homePagePreferences =
|
homePagePreferences =
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import com.github.damontecres.wholphin.preferences.AppPreference
|
||||||
import com.github.damontecres.wholphin.preferences.AppPreferences
|
import com.github.damontecres.wholphin.preferences.AppPreferences
|
||||||
import com.github.damontecres.wholphin.preferences.updateAdvancedPreferences
|
import com.github.damontecres.wholphin.preferences.updateAdvancedPreferences
|
||||||
import com.github.damontecres.wholphin.preferences.updateInterfacePreferences
|
import com.github.damontecres.wholphin.preferences.updateInterfacePreferences
|
||||||
|
import com.github.damontecres.wholphin.preferences.updateMpvOptions
|
||||||
import com.github.damontecres.wholphin.preferences.updatePlaybackOverrides
|
import com.github.damontecres.wholphin.preferences.updatePlaybackOverrides
|
||||||
import com.github.damontecres.wholphin.preferences.updateSubtitlePreferences
|
import com.github.damontecres.wholphin.preferences.updateSubtitlePreferences
|
||||||
import com.github.damontecres.wholphin.ui.preferences.PreferencesViewModel
|
import com.github.damontecres.wholphin.ui.preferences.PreferencesViewModel
|
||||||
|
|
@ -144,4 +145,11 @@ suspend fun upgradeApp(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (previous.isEqualOrBefore(Version.fromString("0.3.4-2-g0"))) {
|
||||||
|
appPreferences.updateData {
|
||||||
|
it.updateMpvOptions {
|
||||||
|
useGpuNext = AppPreference.MpvGpuNext.defaultValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,10 @@ class PlayerFactory
|
||||||
val enableHardwareDecoding =
|
val enableHardwareDecoding =
|
||||||
prefs?.mpvOptions?.enableHardwareDecoding
|
prefs?.mpvOptions?.enableHardwareDecoding
|
||||||
?: AppPreference.MpvHardwareDecoding.defaultValue
|
?: AppPreference.MpvHardwareDecoding.defaultValue
|
||||||
MpvPlayer(context, enableHardwareDecoding)
|
val useGpuNext =
|
||||||
|
prefs?.mpvOptions?.useGpuNext
|
||||||
|
?: AppPreference.MpvGpuNext.defaultValue
|
||||||
|
MpvPlayer(context, enableHardwareDecoding, useGpuNext)
|
||||||
.apply {
|
.apply {
|
||||||
playWhenReady = true
|
playWhenReady = true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,146 +1,156 @@
|
||||||
package com.github.damontecres.wholphin.ui.components
|
package com.github.damontecres.wholphin.ui.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
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.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.defaultMinSize
|
import androidx.compose.foundation.layout.defaultMinSize
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.text.BasicSecureTextField
|
||||||
import androidx.compose.foundation.text.BasicTextField
|
import androidx.compose.foundation.text.BasicTextField
|
||||||
import androidx.compose.foundation.text.KeyboardActions
|
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.foundation.text.selection.LocalTextSelectionColors
|
import androidx.compose.foundation.text.input.KeyboardActionHandler
|
||||||
|
import androidx.compose.foundation.text.input.TextFieldDecorator
|
||||||
|
import androidx.compose.foundation.text.input.TextFieldLineLimits
|
||||||
|
import androidx.compose.foundation.text.input.TextFieldState
|
||||||
|
import androidx.compose.foundation.text.input.rememberTextFieldState
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Search
|
import androidx.compose.material.icons.filled.Search
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.TextFieldDefaults
|
import androidx.compose.material3.TextFieldDefaults
|
||||||
import androidx.compose.material3.TextFieldDefaults.Container
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.graphics.SolidColor
|
import androidx.compose.ui.graphics.SolidColor
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.input.ImeAction
|
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.compose.ui.unit.dp
|
||||||
import androidx.tv.material3.Icon
|
import androidx.tv.material3.Icon
|
||||||
|
import androidx.tv.material3.LocalContentColor
|
||||||
import androidx.tv.material3.MaterialTheme
|
import androidx.tv.material3.MaterialTheme
|
||||||
import com.github.damontecres.wholphin.R
|
import com.github.damontecres.wholphin.R
|
||||||
import com.github.damontecres.wholphin.ui.PreviewTvSpec
|
import com.github.damontecres.wholphin.ui.PreviewTvSpec
|
||||||
import com.github.damontecres.wholphin.ui.theme.WholphinTheme
|
import com.github.damontecres.wholphin.ui.theme.WholphinTheme
|
||||||
import com.google.protobuf.value
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A modified [BasicTextField] that looks & fits better with TV material controls
|
|
||||||
*/
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun EditTextBox(
|
fun EditTextBox(
|
||||||
value: String,
|
state: TextFieldState,
|
||||||
onValueChange: (String) -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
|
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
|
||||||
keyboardActions: KeyboardActions = KeyboardActions.Default,
|
onKeyboardAction: KeyboardActionHandler? = null,
|
||||||
leadingIcon: @Composable (() -> Unit)? = null,
|
leadingIcon: @Composable (() -> Unit)? = null,
|
||||||
enabled: Boolean = true,
|
enabled: Boolean = true,
|
||||||
readOnly: Boolean = false,
|
readOnly: Boolean = false,
|
||||||
height: Dp = 40.dp,
|
maxLines: Int = 1,
|
||||||
isInputValid: (String) -> Boolean = { true },
|
isInputValid: (String) -> Boolean = { true },
|
||||||
supportingText: @Composable (() -> Unit)? = null,
|
isPassword: Boolean = false,
|
||||||
placeholder: @Composable (() -> Unit)? = null,
|
|
||||||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||||
) {
|
) {
|
||||||
// From ButtonDefaults
|
val focused by interactionSource.collectIsFocusedAsState()
|
||||||
val colors =
|
val colors =
|
||||||
TextFieldDefaults.colors(
|
TextFieldDefaults.colors(
|
||||||
unfocusedTextColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.8f),
|
unfocusedTextColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
focusedTextColor = MaterialTheme.colorScheme.inverseOnSurface,
|
unfocusedContainerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.6f),
|
||||||
focusedContainerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.6f),
|
focusedTextColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
unfocusedContainerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.8f),
|
focusedContainerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.8f),
|
||||||
disabledContainerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.4f),
|
disabledTextColor = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.25f),
|
||||||
errorContainerColor = MaterialTheme.colorScheme.errorContainer.copy(alpha = 0.8f),
|
disabledContainerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.25f),
|
||||||
focusedIndicatorColor = MaterialTheme.colorScheme.border,
|
errorContainerColor = MaterialTheme.colorScheme.errorContainer.copy(alpha = 0.75f),
|
||||||
unfocusedLabelColor = Color.Unspecified,
|
errorTextColor = MaterialTheme.colorScheme.onErrorContainer,
|
||||||
)
|
)
|
||||||
CompositionLocalProvider(LocalTextSelectionColors provides colors.textSelectionColors) {
|
val textColor =
|
||||||
BasicTextField(
|
when {
|
||||||
value = value,
|
!isInputValid(state.text.toString()) -> colors.errorTextColor
|
||||||
|
focused -> colors.focusedTextColor
|
||||||
|
!enabled -> colors.disabledTextColor
|
||||||
|
!focused -> colors.unfocusedTextColor
|
||||||
|
else -> colors.unfocusedTextColor
|
||||||
|
}
|
||||||
|
val lineLimits =
|
||||||
|
remember(maxLines) {
|
||||||
|
if (maxLines == 1) {
|
||||||
|
TextFieldLineLimits.SingleLine
|
||||||
|
} else {
|
||||||
|
TextFieldLineLimits.MultiLine(
|
||||||
|
maxLines,
|
||||||
|
maxLines,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val decorator =
|
||||||
|
remember {
|
||||||
|
TextFieldDecorator { innerTextField: @Composable () -> Unit ->
|
||||||
|
val containerColor =
|
||||||
|
when {
|
||||||
|
!isInputValid(state.text.toString()) -> colors.errorContainerColor
|
||||||
|
focused -> colors.focusedContainerColor
|
||||||
|
!enabled -> colors.disabledContainerColor
|
||||||
|
!focused -> colors.unfocusedContainerColor
|
||||||
|
else -> colors.unfocusedContainerColor
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.background(
|
||||||
|
containerColor,
|
||||||
|
shape = if (maxLines > 1) RoundedCornerShape(8.dp) else CircleShape,
|
||||||
|
).padding(8.dp),
|
||||||
|
) {
|
||||||
|
CompositionLocalProvider(
|
||||||
|
androidx.compose.material3.LocalContentColor provides MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
) {
|
||||||
|
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurfaceVariant) {
|
||||||
|
leadingIcon?.invoke()
|
||||||
|
innerTextField.invoke()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPassword) {
|
||||||
|
BasicSecureTextField(
|
||||||
|
state = state,
|
||||||
modifier =
|
modifier =
|
||||||
modifier
|
modifier.defaultMinSize(
|
||||||
.defaultMinSize(
|
minWidth = TextFieldDefaults.MinWidth,
|
||||||
minWidth = TextFieldDefaults.MinWidth,
|
),
|
||||||
minHeight = height,
|
|
||||||
).height(height),
|
|
||||||
onValueChange = onValueChange,
|
|
||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
readOnly = readOnly,
|
readOnly = readOnly,
|
||||||
textStyle = MaterialTheme.typography.bodyLarge.merge(MaterialTheme.colorScheme.onPrimaryContainer),
|
|
||||||
cursorBrush = SolidColor(colors.cursorColor),
|
|
||||||
keyboardOptions = keyboardOptions,
|
keyboardOptions = keyboardOptions,
|
||||||
keyboardActions = keyboardActions,
|
onKeyboardAction = onKeyboardAction,
|
||||||
|
textStyle = MaterialTheme.typography.bodyLarge.merge(textColor),
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
singleLine = true,
|
cursorBrush = SolidColor(colors.cursorColor),
|
||||||
maxLines = 1,
|
decorator = decorator,
|
||||||
minLines = 1,
|
)
|
||||||
visualTransformation =
|
} else {
|
||||||
if (keyboardOptions.keyboardType == KeyboardType.Password ||
|
BasicTextField(
|
||||||
keyboardOptions.keyboardType == KeyboardType.NumberPassword
|
state = state,
|
||||||
) {
|
modifier =
|
||||||
PasswordVisualTransformation()
|
modifier.defaultMinSize(
|
||||||
} else {
|
minWidth = TextFieldDefaults.MinWidth,
|
||||||
VisualTransformation.None
|
),
|
||||||
},
|
enabled = enabled,
|
||||||
decorationBox =
|
readOnly = readOnly,
|
||||||
@Composable { innerTextField ->
|
keyboardOptions = keyboardOptions,
|
||||||
// places leading icon, text field with label and placeholder, trailing icon
|
onKeyboardAction = onKeyboardAction,
|
||||||
TextFieldDefaults.DecorationBox(
|
textStyle = MaterialTheme.typography.bodyLarge.merge(textColor),
|
||||||
value = value,
|
interactionSource = interactionSource,
|
||||||
visualTransformation =
|
lineLimits = lineLimits,
|
||||||
if (keyboardOptions.keyboardType == KeyboardType.Password ||
|
cursorBrush = SolidColor(colors.cursorColor),
|
||||||
keyboardOptions.keyboardType == KeyboardType.NumberPassword
|
decorator = decorator,
|
||||||
) {
|
|
||||||
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,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -150,31 +160,24 @@ fun EditTextBox(
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun SearchEditTextBox(
|
fun SearchEditTextBox(
|
||||||
value: String,
|
state: TextFieldState,
|
||||||
onValueChange: (String) -> Unit,
|
|
||||||
onSearchClick: () -> Unit,
|
onSearchClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
enabled: Boolean = true,
|
enabled: Boolean = true,
|
||||||
readOnly: Boolean = false,
|
readOnly: Boolean = false,
|
||||||
height: Dp = 40.dp,
|
|
||||||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||||
) {
|
) {
|
||||||
EditTextBox(
|
EditTextBox(
|
||||||
value,
|
state = state,
|
||||||
onValueChange,
|
modifier = modifier,
|
||||||
modifier,
|
|
||||||
keyboardOptions =
|
keyboardOptions =
|
||||||
KeyboardOptions(
|
KeyboardOptions(
|
||||||
autoCorrectEnabled = false,
|
autoCorrectEnabled = false,
|
||||||
imeAction = ImeAction.Search,
|
imeAction = ImeAction.Search,
|
||||||
),
|
),
|
||||||
keyboardActions =
|
onKeyboardAction = {
|
||||||
KeyboardActions(
|
onSearchClick.invoke()
|
||||||
onSearch = {
|
},
|
||||||
onSearchClick.invoke()
|
|
||||||
this.defaultKeyboardAction(ImeAction.Search)
|
|
||||||
},
|
|
||||||
),
|
|
||||||
leadingIcon = {
|
leadingIcon = {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Search,
|
imageVector = Icons.Default.Search,
|
||||||
|
|
@ -184,7 +187,6 @@ fun SearchEditTextBox(
|
||||||
},
|
},
|
||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
readOnly = readOnly,
|
readOnly = readOnly,
|
||||||
height = height,
|
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -193,16 +195,35 @@ fun SearchEditTextBox(
|
||||||
@Composable
|
@Composable
|
||||||
private fun EditTextBoxPreview() {
|
private fun EditTextBoxPreview() {
|
||||||
WholphinTheme {
|
WholphinTheme {
|
||||||
Column {
|
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
EditTextBox(
|
EditTextBox(
|
||||||
value = "string",
|
state = rememberTextFieldState("string"),
|
||||||
onValueChange = {},
|
|
||||||
)
|
)
|
||||||
SearchEditTextBox(
|
SearchEditTextBox(
|
||||||
value = "search query",
|
state = rememberTextFieldState("search query"),
|
||||||
onValueChange = {},
|
|
||||||
onSearchClick = { },
|
onSearchClick = { },
|
||||||
)
|
)
|
||||||
|
EditTextBox(
|
||||||
|
state = rememberTextFieldState("string\nstring\nstring"),
|
||||||
|
maxLines = 5,
|
||||||
|
// height = 88.dp,
|
||||||
|
)
|
||||||
|
EditTextBox(
|
||||||
|
state = rememberTextFieldState("search"),
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Search,
|
||||||
|
contentDescription = stringResource(R.string.search),
|
||||||
|
tint = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
isInputValid = { true },
|
||||||
|
modifier = Modifier.width(280.dp),
|
||||||
|
)
|
||||||
|
EditTextBox(
|
||||||
|
state = rememberTextFieldState("password"),
|
||||||
|
isPassword = true,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,8 @@ import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.text.KeyboardActions
|
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.foundation.text.input.rememberTextFieldState
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Add
|
import androidx.compose.material.icons.filled.Add
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
|
@ -133,7 +133,7 @@ fun PlaylistList(
|
||||||
properties = DialogProperties(usePlatformDefaultWidth = false),
|
properties = DialogProperties(usePlatformDefaultWidth = false),
|
||||||
elevation = 10.dp,
|
elevation = 10.dp,
|
||||||
) {
|
) {
|
||||||
var playlistName by remember { mutableStateOf("") }
|
val playlistName = rememberTextFieldState()
|
||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
LaunchedEffect(Unit) { focusRequester.tryRequestFocus() }
|
LaunchedEffect(Unit) { focusRequester.tryRequestFocus() }
|
||||||
Column(
|
Column(
|
||||||
|
|
@ -148,10 +148,7 @@ fun PlaylistList(
|
||||||
text = stringResource(R.string.name),
|
text = stringResource(R.string.name),
|
||||||
)
|
)
|
||||||
EditTextBox(
|
EditTextBox(
|
||||||
value = playlistName,
|
state = playlistName,
|
||||||
onValueChange = {
|
|
||||||
playlistName = it
|
|
||||||
},
|
|
||||||
keyboardOptions =
|
keyboardOptions =
|
||||||
KeyboardOptions(
|
KeyboardOptions(
|
||||||
capitalization = KeyboardCapitalization.Words,
|
capitalization = KeyboardCapitalization.Words,
|
||||||
|
|
@ -159,13 +156,10 @@ fun PlaylistList(
|
||||||
keyboardType = KeyboardType.Text,
|
keyboardType = KeyboardType.Text,
|
||||||
imeAction = ImeAction.Done,
|
imeAction = ImeAction.Done,
|
||||||
),
|
),
|
||||||
keyboardActions =
|
onKeyboardAction = {
|
||||||
KeyboardActions(
|
showCreateDialog = false
|
||||||
onDone = {
|
onCreatePlaylist.invoke(playlistName.text.toString())
|
||||||
showCreateDialog = false
|
},
|
||||||
onCreatePlaylist.invoke(playlistName)
|
|
||||||
},
|
|
||||||
),
|
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.focusRequester(focusRequester)
|
.focusRequester(focusRequester)
|
||||||
|
|
@ -174,9 +168,9 @@ fun PlaylistList(
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
showCreateDialog = false
|
showCreateDialog = false
|
||||||
onCreatePlaylist.invoke(playlistName)
|
onCreatePlaylist.invoke(playlistName.text.toString())
|
||||||
},
|
},
|
||||||
enabled = playlistName.isNotNullOrBlank(),
|
enabled = playlistName.text.isNotNullOrBlank(),
|
||||||
modifier = Modifier,
|
modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
Text(text = stringResource(R.string.submit))
|
Text(text = stringResource(R.string.submit))
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ 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.LazyListScope
|
import androidx.compose.foundation.lazy.LazyListScope
|
||||||
|
import androidx.compose.foundation.text.input.rememberTextFieldState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
|
@ -171,7 +172,7 @@ fun SearchPage(
|
||||||
val series by viewModel.series.observeAsState(SearchResult.NoQuery)
|
val series by viewModel.series.observeAsState(SearchResult.NoQuery)
|
||||||
val episodes by viewModel.episodes.observeAsState(SearchResult.NoQuery)
|
val episodes by viewModel.episodes.observeAsState(SearchResult.NoQuery)
|
||||||
|
|
||||||
var query by rememberSaveable { mutableStateOf("") }
|
val query = rememberTextFieldState()
|
||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
|
|
||||||
var position by rememberPosition()
|
var position by rememberPosition()
|
||||||
|
|
@ -179,7 +180,7 @@ fun SearchPage(
|
||||||
|
|
||||||
LaunchedEffect(query) {
|
LaunchedEffect(query) {
|
||||||
delay(750L)
|
delay(750L)
|
||||||
viewModel.search(query)
|
viewModel.search(query.text.toString())
|
||||||
}
|
}
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
focusRequester.tryRequestFocus()
|
focusRequester.tryRequestFocus()
|
||||||
|
|
@ -213,12 +214,9 @@ fun SearchPage(
|
||||||
focusManager.moveFocus(FocusDirection.Next)
|
focusManager.moveFocus(FocusDirection.Next)
|
||||||
}
|
}
|
||||||
SearchEditTextBox(
|
SearchEditTextBox(
|
||||||
value = query,
|
state = query,
|
||||||
onValueChange = {
|
|
||||||
query = it
|
|
||||||
},
|
|
||||||
onSearchClick = {
|
onSearchClick = {
|
||||||
viewModel.search(query)
|
viewModel.search(query.text.toString())
|
||||||
searchClicked = true
|
searchClicked = true
|
||||||
},
|
},
|
||||||
modifier =
|
modifier =
|
||||||
|
|
|
||||||
|
|
@ -11,17 +11,14 @@ 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.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.text.KeyboardActions
|
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.foundation.text.input.rememberTextFieldState
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Star
|
import androidx.compose.material.icons.filled.Star
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
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.drawBehind
|
import androidx.compose.ui.draw.drawBehind
|
||||||
|
|
@ -103,7 +100,7 @@ fun DownloadSubtitlesContent(
|
||||||
style = MaterialTheme.typography.titleLarge,
|
style = MaterialTheme.typography.titleLarge,
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
)
|
)
|
||||||
var lang by remember { mutableStateOf(language) }
|
val lang = rememberTextFieldState(language)
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
|
@ -114,14 +111,10 @@ fun DownloadSubtitlesContent(
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
)
|
)
|
||||||
EditTextBox(
|
EditTextBox(
|
||||||
value = lang,
|
state = lang,
|
||||||
onValueChange = { lang = it },
|
onKeyboardAction = {
|
||||||
keyboardActions =
|
onSearch(lang.text.toString())
|
||||||
KeyboardActions(
|
},
|
||||||
onSearch = {
|
|
||||||
onSearch(lang)
|
|
||||||
},
|
|
||||||
),
|
|
||||||
keyboardOptions =
|
keyboardOptions =
|
||||||
KeyboardOptions(
|
KeyboardOptions(
|
||||||
imeAction = ImeAction.Search,
|
imeAction = ImeAction.Search,
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,7 @@
|
||||||
package com.github.damontecres.wholphin.ui.preferences
|
package com.github.damontecres.wholphin.ui.preferences
|
||||||
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.foundation.text.KeyboardActions
|
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Done
|
import androidx.compose.material.icons.filled.Done
|
||||||
|
|
@ -22,7 +13,6 @@ import androidx.compose.runtime.mutableStateSetOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
|
@ -30,13 +20,12 @@ import androidx.compose.ui.res.stringArrayResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.text.input.ImeAction
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
|
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||||
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.window.Dialog
|
|
||||||
import androidx.tv.material3.Button
|
|
||||||
import androidx.tv.material3.MaterialTheme
|
import androidx.tv.material3.MaterialTheme
|
||||||
import androidx.tv.material3.Text
|
import androidx.tv.material3.Text
|
||||||
import androidx.tv.material3.surfaceColorAtElevation
|
import androidx.tv.material3.surfaceColorAtElevation
|
||||||
import com.github.damontecres.wholphin.R
|
|
||||||
import com.github.damontecres.wholphin.preferences.AppChoicePreference
|
import com.github.damontecres.wholphin.preferences.AppChoicePreference
|
||||||
import com.github.damontecres.wholphin.preferences.AppClickablePreference
|
import com.github.damontecres.wholphin.preferences.AppClickablePreference
|
||||||
import com.github.damontecres.wholphin.preferences.AppDestinationPreference
|
import com.github.damontecres.wholphin.preferences.AppDestinationPreference
|
||||||
|
|
@ -48,10 +37,10 @@ import com.github.damontecres.wholphin.preferences.AppSwitchPreference
|
||||||
import com.github.damontecres.wholphin.ui.components.DialogItem
|
import com.github.damontecres.wholphin.ui.components.DialogItem
|
||||||
import com.github.damontecres.wholphin.ui.components.DialogParams
|
import com.github.damontecres.wholphin.ui.components.DialogParams
|
||||||
import com.github.damontecres.wholphin.ui.components.DialogPopup
|
import com.github.damontecres.wholphin.ui.components.DialogPopup
|
||||||
import com.github.damontecres.wholphin.ui.components.EditTextBox
|
|
||||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import java.io.File
|
||||||
import java.util.SortedSet
|
import java.util.SortedSet
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
|
@ -82,6 +71,37 @@ fun <T> ComposablePreference(
|
||||||
}
|
}
|
||||||
|
|
||||||
when (preference) {
|
when (preference) {
|
||||||
|
AppPreference.MpvConfFile -> {
|
||||||
|
ClickPreference(
|
||||||
|
title = title,
|
||||||
|
onClick = {
|
||||||
|
val confFile = File(context.filesDir, "mpv.conf")
|
||||||
|
val contents = if (confFile.exists()) confFile.readText() else ""
|
||||||
|
showStringDialog =
|
||||||
|
StringInput(
|
||||||
|
title = title,
|
||||||
|
value = contents,
|
||||||
|
keyboardOptions =
|
||||||
|
KeyboardOptions(
|
||||||
|
capitalization = KeyboardCapitalization.None,
|
||||||
|
autoCorrectEnabled = false,
|
||||||
|
keyboardType = KeyboardType.Text,
|
||||||
|
imeAction = ImeAction.None,
|
||||||
|
),
|
||||||
|
onSubmit = {
|
||||||
|
confFile.writeText(it)
|
||||||
|
showStringDialog = null
|
||||||
|
},
|
||||||
|
maxLines = 10,
|
||||||
|
confirmDiscard = true,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
summary = preference.summary(context, value),
|
||||||
|
interactionSource = interactionSource,
|
||||||
|
modifier = modifier,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
is AppDestinationPreference ->
|
is AppDestinationPreference ->
|
||||||
ClickPreference(
|
ClickPreference(
|
||||||
title = title,
|
title = title,
|
||||||
|
|
@ -244,73 +264,15 @@ fun <T> ComposablePreference(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AnimatedVisibility(showStringDialog != null) {
|
AnimatedVisibility(showStringDialog != null) {
|
||||||
showStringDialog?.let {
|
showStringDialog?.let { input ->
|
||||||
var mutableValue by remember { mutableStateOf(it.value ?: "") }
|
StringInputDialog(
|
||||||
val onDone = {
|
input = input,
|
||||||
it.onSubmit.invoke(mutableValue)
|
onSave = {
|
||||||
}
|
input.onSubmit.invoke(it)
|
||||||
Dialog(
|
},
|
||||||
onDismissRequest = { showStringDialog = null },
|
onDismissRequest = { showStringDialog = null },
|
||||||
) {
|
elevation = 3.dp,
|
||||||
Box(
|
)
|
||||||
contentAlignment = Alignment.Center,
|
|
||||||
modifier =
|
|
||||||
Modifier
|
|
||||||
.padding(16.dp)
|
|
||||||
.background(
|
|
||||||
color = dialogBackgroundColor,
|
|
||||||
shape = RoundedCornerShape(8.dp),
|
|
||||||
),
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
|
||||||
modifier =
|
|
||||||
Modifier
|
|
||||||
.padding(16.dp),
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = it.title,
|
|
||||||
style = MaterialTheme.typography.titleLarge,
|
|
||||||
color = MaterialTheme.colorScheme.onSecondaryContainer,
|
|
||||||
modifier = Modifier,
|
|
||||||
)
|
|
||||||
EditTextBox(
|
|
||||||
value = mutableValue,
|
|
||||||
onValueChange = { mutableValue = it },
|
|
||||||
keyboardOptions = it.keyboardOptions.copy(imeAction = ImeAction.Done),
|
|
||||||
keyboardActions =
|
|
||||||
KeyboardActions(
|
|
||||||
onDone = { onDone.invoke() },
|
|
||||||
),
|
|
||||||
leadingIcon = null,
|
|
||||||
isInputValid = { true },
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
)
|
|
||||||
|
|
||||||
Row(
|
|
||||||
horizontalArrangement = Arrangement.SpaceAround,
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
) {
|
|
||||||
Button(
|
|
||||||
onClick = { showStringDialog = null },
|
|
||||||
modifier = Modifier,
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.cancel),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Button(
|
|
||||||
onClick = onDone,
|
|
||||||
modifier = Modifier,
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.save),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -353,9 +315,11 @@ fun PreferenceSummary(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private data class StringInput(
|
data class StringInput(
|
||||||
val title: String,
|
val title: String,
|
||||||
val value: String?,
|
val value: String?,
|
||||||
val keyboardOptions: KeyboardOptions,
|
val keyboardOptions: KeyboardOptions,
|
||||||
val onSubmit: (String) -> Unit,
|
val onSubmit: (String) -> Unit,
|
||||||
|
val maxLines: Int = 1,
|
||||||
|
val confirmDiscard: Boolean = false,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,133 @@
|
||||||
|
package com.github.damontecres.wholphin.ui.preferences
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.text.input.rememberTextFieldState
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
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.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.window.Dialog
|
||||||
|
import androidx.compose.ui.window.DialogProperties
|
||||||
|
import androidx.tv.material3.Button
|
||||||
|
import androidx.tv.material3.MaterialTheme
|
||||||
|
import androidx.tv.material3.Text
|
||||||
|
import androidx.tv.material3.surfaceColorAtElevation
|
||||||
|
import com.github.damontecres.wholphin.R
|
||||||
|
import com.github.damontecres.wholphin.ui.components.ConfirmDialog
|
||||||
|
import com.github.damontecres.wholphin.ui.components.EditTextBox
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun StringInputDialog(
|
||||||
|
input: StringInput,
|
||||||
|
onSave: (String) -> Unit,
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
|
elevation: Dp = 3.dp,
|
||||||
|
) {
|
||||||
|
val mutableValue = rememberTextFieldState(input.value ?: "")
|
||||||
|
// var mutableValue by remember { mutableStateOf(input.value ?: "") }
|
||||||
|
val onDone = {
|
||||||
|
onSave.invoke(mutableValue.text.toString())
|
||||||
|
}
|
||||||
|
var showConfirm by remember { mutableStateOf(false) }
|
||||||
|
Dialog(
|
||||||
|
properties =
|
||||||
|
DialogProperties(
|
||||||
|
usePlatformDefaultWidth = false,
|
||||||
|
),
|
||||||
|
onDismissRequest = {
|
||||||
|
if (input.confirmDiscard && mutableValue.text.toString() != input.value) {
|
||||||
|
showConfirm = true
|
||||||
|
} else {
|
||||||
|
onDismissRequest.invoke()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
.width(512.dp)
|
||||||
|
.background(
|
||||||
|
color = MaterialTheme.colorScheme.surfaceColorAtElevation(elevation),
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.padding(16.dp),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = input.title,
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
color = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||||
|
modifier = Modifier,
|
||||||
|
)
|
||||||
|
EditTextBox(
|
||||||
|
state = mutableValue,
|
||||||
|
keyboardOptions = input.keyboardOptions,
|
||||||
|
onKeyboardAction = {
|
||||||
|
onDone.invoke()
|
||||||
|
},
|
||||||
|
leadingIcon = null,
|
||||||
|
isInputValid = { true },
|
||||||
|
maxLines = input.maxLines,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
)
|
||||||
|
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.SpaceAround,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
) {
|
||||||
|
Button(
|
||||||
|
onClick = onDismissRequest,
|
||||||
|
modifier = Modifier,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.cancel),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Button(
|
||||||
|
onClick = onDone,
|
||||||
|
modifier = Modifier,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.save),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showConfirm) {
|
||||||
|
ConfirmDialog(
|
||||||
|
title = stringResource(R.string.discard_change),
|
||||||
|
body = null,
|
||||||
|
onCancel = {
|
||||||
|
showConfirm = false
|
||||||
|
},
|
||||||
|
onConfirm = {
|
||||||
|
showConfirm = false
|
||||||
|
onDismissRequest.invoke()
|
||||||
|
},
|
||||||
|
elevation = elevation * 2,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,8 +9,8 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.text.KeyboardActions
|
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.foundation.text.input.rememberTextFieldState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
|
@ -23,6 +23,7 @@ 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.focusRequester
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
@ -170,9 +171,9 @@ fun SwitchServerContent(
|
||||||
viewModel.clearAddServerState()
|
viewModel.clearAddServerState()
|
||||||
}
|
}
|
||||||
val state by viewModel.addServerState.observeAsState(LoadingState.Pending)
|
val state by viewModel.addServerState.observeAsState(LoadingState.Pending)
|
||||||
var url by remember { mutableStateOf("") }
|
val url = rememberTextFieldState()
|
||||||
val submit = {
|
val submit = {
|
||||||
viewModel.addServer(url)
|
viewModel.addServer(url.text.toString())
|
||||||
}
|
}
|
||||||
BasicDialog(
|
BasicDialog(
|
||||||
onDismissRequest = {
|
onDismissRequest = {
|
||||||
|
|
@ -196,21 +197,15 @@ fun SwitchServerContent(
|
||||||
text = stringResource(R.string.enter_server_url),
|
text = stringResource(R.string.enter_server_url),
|
||||||
)
|
)
|
||||||
EditTextBox(
|
EditTextBox(
|
||||||
value = url,
|
state = url,
|
||||||
onValueChange = {
|
|
||||||
url = it
|
|
||||||
viewModel.clearAddServerState()
|
|
||||||
},
|
|
||||||
keyboardOptions =
|
keyboardOptions =
|
||||||
KeyboardOptions(
|
KeyboardOptions(
|
||||||
capitalization = KeyboardCapitalization.None,
|
capitalization = KeyboardCapitalization.None,
|
||||||
autoCorrectEnabled = false,
|
autoCorrectEnabled = false,
|
||||||
keyboardType = KeyboardType.Uri,
|
keyboardType = KeyboardType.Uri,
|
||||||
|
imeAction = ImeAction.Go,
|
||||||
),
|
),
|
||||||
keyboardActions =
|
onKeyboardAction = { submit.invoke() },
|
||||||
KeyboardActions(
|
|
||||||
onDone = { submit.invoke() },
|
|
||||||
),
|
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.focusRequester(focusRequester)
|
.focusRequester(focusRequester)
|
||||||
|
|
@ -230,7 +225,7 @@ fun SwitchServerContent(
|
||||||
}
|
}
|
||||||
Button(
|
Button(
|
||||||
onClick = { submit.invoke() },
|
onClick = { submit.invoke() },
|
||||||
enabled = url.isNotNullOrBlank() && state == LoadingState.Pending,
|
enabled = url.text.isNotNullOrBlank() && state == LoadingState.Pending,
|
||||||
modifier = Modifier,
|
modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
if (state == LoadingState.Loading) {
|
if (state == LoadingState.Loading) {
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,8 @@ import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.text.KeyboardActions
|
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.foundation.text.input.rememberTextFieldState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
|
@ -207,12 +207,17 @@ fun SwitchUserContent(
|
||||||
Text(text = "Use username/password")
|
Text(text = "Use username/password")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var username by remember { mutableStateOf("") }
|
val username = rememberTextFieldState()
|
||||||
var password by remember { mutableStateOf("") }
|
val password = rememberTextFieldState()
|
||||||
val onSubmit = {
|
val onSubmit = {
|
||||||
viewModel.login(server, username, password)
|
viewModel.login(
|
||||||
|
server,
|
||||||
|
username.text.toString(),
|
||||||
|
password.text.toString(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
|
val passwordFocusRequester = remember { FocusRequester() }
|
||||||
LaunchedEffect(Unit) { focusRequester.tryRequestFocus() }
|
LaunchedEffect(Unit) { focusRequester.tryRequestFocus() }
|
||||||
Text(
|
Text(
|
||||||
text = "Enter username/password to login to ${server.name ?: server.url}",
|
text = "Enter username/password to login to ${server.name ?: server.url}",
|
||||||
|
|
@ -229,11 +234,7 @@ fun SwitchUserContent(
|
||||||
modifier = Modifier.padding(end = 8.dp),
|
modifier = Modifier.padding(end = 8.dp),
|
||||||
)
|
)
|
||||||
EditTextBox(
|
EditTextBox(
|
||||||
value = username,
|
state = username,
|
||||||
onValueChange = {
|
|
||||||
username = it
|
|
||||||
viewModel.clearSwitchUserState()
|
|
||||||
},
|
|
||||||
keyboardOptions =
|
keyboardOptions =
|
||||||
KeyboardOptions(
|
KeyboardOptions(
|
||||||
capitalization = KeyboardCapitalization.None,
|
capitalization = KeyboardCapitalization.None,
|
||||||
|
|
@ -241,6 +242,9 @@ fun SwitchUserContent(
|
||||||
keyboardType = KeyboardType.Text,
|
keyboardType = KeyboardType.Text,
|
||||||
imeAction = ImeAction.Next,
|
imeAction = ImeAction.Next,
|
||||||
),
|
),
|
||||||
|
onKeyboardAction = {
|
||||||
|
passwordFocusRequester.tryRequestFocus()
|
||||||
|
},
|
||||||
isInputValid = { userState !is LoadingState.Error },
|
isInputValid = { userState !is LoadingState.Error },
|
||||||
modifier = Modifier.focusRequester(focusRequester),
|
modifier = Modifier.focusRequester(focusRequester),
|
||||||
)
|
)
|
||||||
|
|
@ -253,29 +257,27 @@ fun SwitchUserContent(
|
||||||
text = "Password",
|
text = "Password",
|
||||||
modifier = Modifier.padding(end = 8.dp),
|
modifier = Modifier.padding(end = 8.dp),
|
||||||
)
|
)
|
||||||
|
LaunchedEffect(password.text) {
|
||||||
|
viewModel.clearSwitchUserState()
|
||||||
|
}
|
||||||
EditTextBox(
|
EditTextBox(
|
||||||
value = password,
|
isPassword = true,
|
||||||
onValueChange = {
|
state = password,
|
||||||
password = it
|
|
||||||
viewModel.clearSwitchUserState()
|
|
||||||
},
|
|
||||||
keyboardOptions =
|
keyboardOptions =
|
||||||
KeyboardOptions(
|
KeyboardOptions(
|
||||||
capitalization = KeyboardCapitalization.None,
|
capitalization = KeyboardCapitalization.None,
|
||||||
autoCorrectEnabled = false,
|
autoCorrectEnabled = false,
|
||||||
keyboardType = KeyboardType.Password,
|
keyboardType = KeyboardType.Password,
|
||||||
|
imeAction = ImeAction.Go,
|
||||||
),
|
),
|
||||||
keyboardActions =
|
onKeyboardAction = { onSubmit.invoke() },
|
||||||
KeyboardActions(
|
|
||||||
onDone = { onSubmit.invoke() },
|
|
||||||
),
|
|
||||||
isInputValid = { userState !is LoadingState.Error },
|
isInputValid = { userState !is LoadingState.Error },
|
||||||
modifier = Modifier,
|
modifier = Modifier.focusRequester(passwordFocusRequester),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Button(
|
Button(
|
||||||
onClick = { onSubmit.invoke() },
|
onClick = { onSubmit.invoke() },
|
||||||
enabled = username.isNotNullOrBlank() && password.isNotNullOrBlank(),
|
enabled = username.text.isNotNullOrBlank() && password.text.isNotNullOrBlank(),
|
||||||
modifier = Modifier.align(Alignment.CenterHorizontally),
|
modifier = Modifier.align(Alignment.CenterHorizontally),
|
||||||
) {
|
) {
|
||||||
Text("Login")
|
Text("Login")
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,7 @@ import kotlin.time.Duration.Companion.seconds
|
||||||
class MpvPlayer(
|
class MpvPlayer(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
enableHardwareDecoding: Boolean,
|
enableHardwareDecoding: Boolean,
|
||||||
|
useGpuNext: Boolean,
|
||||||
) : BasePlayer(),
|
) : BasePlayer(),
|
||||||
MPVLib.EventObserver,
|
MPVLib.EventObserver,
|
||||||
TrackSelector.InvalidationListener {
|
TrackSelector.InvalidationListener {
|
||||||
|
|
@ -100,7 +101,7 @@ class MpvPlayer(
|
||||||
|
|
||||||
if (enableHardwareDecoding) {
|
if (enableHardwareDecoding) {
|
||||||
MPVLib.setOptionString("hwdec", "mediacodec,mediacodec-copy")
|
MPVLib.setOptionString("hwdec", "mediacodec,mediacodec-copy")
|
||||||
MPVLib.setOptionString("vo", "gpu")
|
MPVLib.setOptionString("vo", if (useGpuNext) "gpu-next" else "gpu")
|
||||||
} else {
|
} else {
|
||||||
MPVLib.setOptionString("hwdec", "no")
|
MPVLib.setOptionString("hwdec", "no")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ enum PlayerBackend{
|
||||||
|
|
||||||
message MpvOptions{
|
message MpvOptions{
|
||||||
bool enable_hardware_decoding = 1;
|
bool enable_hardware_decoding = 1;
|
||||||
|
bool use_gpu_next = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message PlaybackOverrides{
|
message PlaybackOverrides{
|
||||||
|
|
|
||||||
|
|
@ -326,6 +326,9 @@
|
||||||
<string name="remove">Remove</string>
|
<string name="remove">Remove</string>
|
||||||
<string name="dolby_vision">Dolby Vision</string>
|
<string name="dolby_vision">Dolby Vision</string>
|
||||||
<string name="dolby_atmos">Dolby Atmos</string>
|
<string name="dolby_atmos">Dolby Atmos</string>
|
||||||
|
<string name="mpv_use_gpu_next">MPV: Use gpu-next</string>
|
||||||
|
<string name="mpv_conf">Edit mpv.conf</string>
|
||||||
|
<string name="discard_change">Discard changes?</string>
|
||||||
<string name="subtitle_margin">Margin</string>
|
<string name="subtitle_margin">Margin</string>
|
||||||
<string name="verbose_logging">Verbose logging</string>
|
<string name="verbose_logging">Verbose logging</string>
|
||||||
<string name="image_cache_size">Image disk cache size (MB)</string>
|
<string name="image_cache_size">Image disk cache size (MB)</string>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue