Files
SleppyOwl/app/src/main/kotlin/ru/stenfredd/movies/ui/LoginScreen.kt

121 lines
5.2 KiB
Kotlin

package ru.stenfredd.movies.ui
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.runtime.remember
import androidx.compose.ui.focus.FocusDirection
import androidx.compose.ui.focus.onFocusChanged
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.onPreviewKeyEvent
import androidx.compose.ui.input.key.type
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.text.TextStyle
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.sp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.ViewModel
import androidx.tv.material3.Button
import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Text
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import ru.stenfredd.movies.core.jellyfin.JellyfinSession
import javax.inject.Inject
@HiltViewModel
class LoginViewModel @Inject constructor(val session: JellyfinSession) : ViewModel()
@Composable
fun LoginScreen(onLoggedIn: () -> Unit, vm: LoginViewModel = hiltViewModel()) {
var url by rememberSaveable { mutableStateOf("https://") }
var user by rememberSaveable { mutableStateOf("") }
var password by rememberSaveable { mutableStateOf("") }
var error by rememberSaveable { mutableStateOf<String?>(null) }
var busy by rememberSaveable { mutableStateOf(false) }
val scope = rememberCoroutineScope()
Column(
modifier = Modifier.fillMaxSize().padding(48.dp),
verticalArrangement = Arrangement.spacedBy(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text("Вход на сервер Jellyfin", style = MaterialTheme.typography.headlineMedium)
Field("Адрес сервера", url, KeyboardType.Uri) { url = it }
Field("Пользователь", user, KeyboardType.Text) { user = it }
Field("Пароль", password, KeyboardType.Password, PasswordVisualTransformation()) { password = it }
Button(enabled = !busy, onClick = {
busy = true
error = null
scope.launch {
runCatching { vm.session.login(url, user, password) }
.onSuccess { onLoggedIn() }
.onFailure { error = it.message ?: it.toString() }
busy = false
}
}) { Text(if (busy) "Вход…" else "Войти") }
error?.let { Text(it, color = MaterialTheme.colorScheme.error) }
}
}
@Composable
fun Field(
label: String,
value: String,
type: KeyboardType,
transformation: VisualTransformation = VisualTransformation.None,
onChange: (String) -> Unit,
) {
val focusManager = LocalFocusManager.current
var focused by remember { mutableStateOf(false) }
Column(Modifier.width(560.dp)) {
Text(label, style = MaterialTheme.typography.labelLarge)
BasicTextField(
value = value,
onValueChange = onChange,
singleLine = true,
keyboardOptions = KeyboardOptions(keyboardType = type, imeAction = ImeAction.Next),
keyboardActions = KeyboardActions(onNext = { focusManager.moveFocus(FocusDirection.Down) }),
visualTransformation = transformation,
textStyle = TextStyle(color = Color.White, fontSize = 20.sp),
cursorBrush = SolidColor(Color.White),
modifier = Modifier
.width(560.dp)
.onFocusChanged { focused = it.isFocused }
// A text field swallows D-pad keys; on a remote up/down must still move between fields.
.onPreviewKeyEvent { e ->
if (e.type != KeyEventType.KeyDown) return@onPreviewKeyEvent false
when (e.key) {
Key.DirectionDown -> focusManager.moveFocus(FocusDirection.Down)
Key.DirectionUp -> focusManager.moveFocus(FocusDirection.Up)
else -> false
}
}
.border(if (focused) 3.dp else 1.dp, if (focused) Color.White else Color.Gray)
.padding(12.dp),
)
}
}