From e69a3a6d4a5dd96150530c6ad860c97002d29c53 Mon Sep 17 00:00:00 2001 From: Barnabas Balogh Date: Wed, 13 May 2026 21:16:48 +0200 Subject: [PATCH] feat: redesign login screen on both app and app-tv --- .../purefin/ui/screen/login/LoginScreen.kt | 2 +- .../purefin/ui/screen/login/TvLoginContent.kt | 354 ++++++++++++++++++ .../purefin/ui/screen/login/LoginContent.kt | 149 ++++++++ .../purefin/ui/common/image/PurefinLogo.kt | 2 +- .../purefin/ui/screen/login/LoginContent.kt | 201 ---------- .../purefin/ui/screen/login/LoginModels.kt | 18 + .../res/drawable/purefin_logo_tintable.xml | 0 7 files changed, 523 insertions(+), 203 deletions(-) create mode 100644 app-tv/src/main/java/hu/bbara/purefin/ui/screen/login/TvLoginContent.kt create mode 100644 app/src/main/java/hu/bbara/purefin/ui/screen/login/LoginContent.kt rename {app => core-ui}/src/main/java/hu/bbara/purefin/ui/common/image/PurefinLogo.kt (94%) delete mode 100644 core-ui/src/main/java/hu/bbara/purefin/ui/screen/login/LoginContent.kt create mode 100644 core-ui/src/main/java/hu/bbara/purefin/ui/screen/login/LoginModels.kt rename {app => core-ui}/src/main/res/drawable/purefin_logo_tintable.xml (100%) diff --git a/app-tv/src/main/java/hu/bbara/purefin/ui/screen/login/LoginScreen.kt b/app-tv/src/main/java/hu/bbara/purefin/ui/screen/login/LoginScreen.kt index 0bea9088..b13ec193 100644 --- a/app-tv/src/main/java/hu/bbara/purefin/ui/screen/login/LoginScreen.kt +++ b/app-tv/src/main/java/hu/bbara/purefin/ui/screen/login/LoginScreen.kt @@ -59,7 +59,7 @@ fun LoginScreen( ) } - LoginContent( + TvLoginContent( state = state, callbacks = callbacks, isLoggingIn = isLoggingIn, diff --git a/app-tv/src/main/java/hu/bbara/purefin/ui/screen/login/TvLoginContent.kt b/app-tv/src/main/java/hu/bbara/purefin/ui/screen/login/TvLoginContent.kt new file mode 100644 index 00000000..966314e2 --- /dev/null +++ b/app-tv/src/main/java/hu/bbara/purefin/ui/screen/login/TvLoginContent.kt @@ -0,0 +1,354 @@ +package hu.bbara.purefin.ui.screen.login + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Lock +import androidx.compose.material.icons.filled.Person +import androidx.compose.material.icons.filled.Storage +import androidx.compose.material.icons.filled.Visibility +import androidx.compose.material.icons.filled.VisibilityOff +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.material3.TextFieldDefaults +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.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +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.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.tv.material3.Button +import androidx.tv.material3.MaterialTheme as TvMaterialTheme +import androidx.tv.material3.Text as TvText +import androidx.tv.material3.darkColorScheme +import hu.bbara.purefin.ui.common.image.PurefinLogo +import hu.bbara.purefin.ui.screen.waiting.PurefinWaitingScreen + +@Composable +fun TvLoginContent( + state: LoginContentState, + callbacks: LoginContentCallbacks, + isLoggingIn: Boolean, + modifier: Modifier = Modifier +) { + if (isLoggingIn) { + PurefinWaitingScreen(modifier = modifier) + return + } + + ProvideTvLoginTheme { + TvLoginForm( + state = state, + callbacks = callbacks, + modifier = modifier + ) + } +} + +@Composable +private fun ProvideTvLoginTheme(content: @Composable () -> Unit) { + val scheme = MaterialTheme.colorScheme + + TvMaterialTheme( + colorScheme = darkColorScheme( + primary = scheme.primary, + onPrimary = scheme.onPrimary, + primaryContainer = scheme.primaryContainer, + onPrimaryContainer = scheme.onPrimaryContainer, + secondary = scheme.secondary, + onSecondary = scheme.onSecondary, + secondaryContainer = scheme.secondaryContainer, + onSecondaryContainer = scheme.onSecondaryContainer, + tertiary = scheme.tertiary, + onTertiary = scheme.onTertiary, + tertiaryContainer = scheme.tertiaryContainer, + onTertiaryContainer = scheme.onTertiaryContainer, + background = scheme.background, + onBackground = scheme.onBackground, + surface = scheme.surface, + onSurface = scheme.onSurface, + surfaceVariant = scheme.surfaceVariant, + onSurfaceVariant = scheme.onSurfaceVariant, + surfaceTint = scheme.surfaceTint, + inverseSurface = scheme.inverseSurface, + inverseOnSurface = scheme.inverseOnSurface, + error = scheme.error, + onError = scheme.onError, + errorContainer = scheme.errorContainer, + onErrorContainer = scheme.onErrorContainer, + border = scheme.outline, + borderVariant = scheme.outlineVariant, + scrim = scheme.scrim + ), + content = content + ) +} + +@Composable +private fun TvLoginForm( + state: LoginContentState, + callbacks: LoginContentCallbacks, + modifier: Modifier = Modifier +) { + val scheme = MaterialTheme.colorScheme + val serverFocusRequester = remember { FocusRequester() } + val usernameFocusRequester = remember { FocusRequester() } + val passwordFocusRequester = remember { FocusRequester() } + val connectFocusRequester = remember { FocusRequester() } + + LaunchedEffect(Unit) { + serverFocusRequester.requestFocus() + } + + Box( + modifier = modifier + .fillMaxSize() + .background(scheme.background) + .padding(horizontal = 64.dp, vertical = 32.dp), + contentAlignment = Alignment.Center + ) { + Column( + modifier = Modifier + .widthIn(max = 480.dp) + .fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + PurefinLogo( + color = scheme.primary, + modifier = Modifier.size(64.dp) + ) + + Text( + text = "Purefin", + color = scheme.onBackground, + style = MaterialTheme.typography.headlineMedium.copy(fontWeight = FontWeight.Bold), + modifier = Modifier.padding(top = 8.dp) + ) + Text( + text = "Connect to your media server", + color = scheme.onSurfaceVariant, + style = MaterialTheme.typography.bodyLarge + ) + + Spacer(modifier = Modifier.height(20.dp)) + + state.errorMessage?.let { errorMessage -> + Text( + text = errorMessage, + color = scheme.onErrorContainer, + style = MaterialTheme.typography.bodyMedium, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + modifier = Modifier + .fillMaxWidth() + .background( + color = scheme.errorContainer, + shape = RoundedCornerShape(14.dp) + ) + .padding(horizontal = 14.dp, vertical = 10.dp) + ) + Spacer(modifier = Modifier.height(10.dp)) + } + + TvLoginTextField( + label = "Server URL", + value = state.serverUrl, + onValueChange = callbacks.onServerUrlChange, + placeholder = "http://192.168.1.100:8096", + leadingIcon = Icons.Default.Storage, + modifier = Modifier + .focusRequester(serverFocusRequester) + .focusProperties { + down = usernameFocusRequester + } + ) + + Spacer(modifier = Modifier.height(12.dp)) + + TvLoginTextField( + label = "Username", + value = state.username, + onValueChange = callbacks.onUsernameChange, + placeholder = "Enter your username", + leadingIcon = Icons.Default.Person, + modifier = Modifier + .focusRequester(usernameFocusRequester) + .focusProperties { + up = serverFocusRequester + down = passwordFocusRequester + } + ) + + Spacer(modifier = Modifier.height(12.dp)) + + TvLoginPasswordField( + label = "Password", + value = state.password, + onValueChange = callbacks.onPasswordChange, + placeholder = "Enter your password", + leadingIcon = Icons.Default.Lock, + modifier = Modifier + .focusRequester(passwordFocusRequester) + .focusProperties { + up = usernameFocusRequester + down = connectFocusRequester + } + ) + + Spacer(modifier = Modifier.height(22.dp)) + + Button( + onClick = callbacks.onConnect, + modifier = Modifier + .focusRequester(connectFocusRequester) + .focusProperties { + up = passwordFocusRequester + } + .fillMaxWidth() + .height(48.dp) + ) { + TvText( + text = "Connect", + fontSize = 16.sp, + fontWeight = FontWeight.Bold + ) + } + } + } +} + +@Composable +private fun TvLoginTextField( + label: String, + value: String, + onValueChange: (String) -> Unit, + placeholder: String, + leadingIcon: ImageVector, + modifier: Modifier = Modifier, + trailingIcon: (@Composable () -> Unit)? = null, + visualTransformation: VisualTransformation = VisualTransformation.None +) { + val scheme = MaterialTheme.colorScheme + val shape = RoundedCornerShape(14.dp) + var isFocused by remember { mutableStateOf(false) } + + Column(modifier = Modifier.fillMaxWidth()) { + Text( + text = label, + color = scheme.onBackground, + style = MaterialTheme.typography.titleSmall.copy(fontWeight = FontWeight.Bold), + modifier = Modifier.padding(bottom = 6.dp) + ) + TextField( + value = value, + onValueChange = onValueChange, + modifier = modifier + .fillMaxWidth() + .height(52.dp) + .onFocusChanged { isFocused = it.isFocused || it.hasFocus } + .border( + width = if (isFocused) 2.dp else 1.dp, + color = if (isFocused) scheme.primary else scheme.outlineVariant.copy(alpha = 0.4f), + shape = shape + ) + .clip(shape), + singleLine = true, + textStyle = MaterialTheme.typography.bodyLarge, + placeholder = { + Text( + text = placeholder, + color = scheme.onSurfaceVariant, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + }, + leadingIcon = { + Icon( + imageVector = leadingIcon, + contentDescription = null, + tint = scheme.onSurfaceVariant + ) + }, + trailingIcon = trailingIcon, + visualTransformation = visualTransformation, + colors = TextFieldDefaults.colors( + focusedContainerColor = scheme.surfaceContainer, + unfocusedContainerColor = scheme.surface, + focusedIndicatorColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent, + disabledIndicatorColor = Color.Transparent, + cursorColor = scheme.primary, + focusedTextColor = scheme.onSurface, + unfocusedTextColor = scheme.onSurface + ) + ) + } +} + +@Composable +private fun TvLoginPasswordField( + label: String, + value: String, + onValueChange: (String) -> Unit, + placeholder: String, + leadingIcon: ImageVector, + modifier: Modifier = Modifier +) { + val scheme = MaterialTheme.colorScheme + var isPasswordVisible by remember { mutableStateOf(false) } + + TvLoginTextField( + label = label, + value = value, + onValueChange = onValueChange, + placeholder = placeholder, + leadingIcon = leadingIcon, + modifier = modifier, + trailingIcon = { + IconButton(onClick = { isPasswordVisible = !isPasswordVisible }) { + Icon( + imageVector = if (isPasswordVisible) { + Icons.Default.VisibilityOff + } else { + Icons.Default.Visibility + }, + contentDescription = if (isPasswordVisible) "Hide password" else "Show password", + tint = scheme.onSurfaceVariant + ) + } + }, + visualTransformation = if (isPasswordVisible) { + VisualTransformation.None + } else { + PasswordVisualTransformation() + } + ) +} diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/login/LoginContent.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/login/LoginContent.kt new file mode 100644 index 00000000..45dfa5af --- /dev/null +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/login/LoginContent.kt @@ -0,0 +1,149 @@ +package hu.bbara.purefin.ui.screen.login + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Lock +import androidx.compose.material.icons.filled.Person +import androidx.compose.material.icons.filled.Storage +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import hu.bbara.purefin.ui.common.button.PurefinTextButton +import hu.bbara.purefin.ui.common.image.PurefinLogo +import hu.bbara.purefin.ui.common.textfield.PurefinComplexTextField +import hu.bbara.purefin.ui.common.textfield.PurefinPasswordField +import hu.bbara.purefin.ui.screen.waiting.PurefinWaitingScreen + +@Composable +fun LoginContent( + state: LoginContentState, + callbacks: LoginContentCallbacks, + isLoggingIn: Boolean, + modifier: Modifier = Modifier +) { + val scheme = MaterialTheme.colorScheme + + if (isLoggingIn) { + PurefinWaitingScreen(modifier = modifier) + return + } + + Column( + modifier = modifier + .fillMaxSize() + .background(scheme.background) + .verticalScroll(rememberScrollState()) + .padding(horizontal = 24.dp, vertical = 20.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Column( + modifier = Modifier + .widthIn(max = 420.dp) + .fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + PurefinLogo( + color = scheme.primary, + modifier = Modifier.size(64.dp) + ) + + Text( + text = "Purefin", + color = scheme.onBackground, + style = MaterialTheme.typography.headlineSmall.copy(fontWeight = FontWeight.Bold), + modifier = Modifier.padding(top = 8.dp) + ) + Text( + text = "Personal media, your way", + color = scheme.onSurfaceVariant, + style = MaterialTheme.typography.bodySmall + ) + + Spacer(modifier = Modifier.height(28.dp)) + + Text( + text = "Connect to server", + color = scheme.onBackground, + style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold), + modifier = Modifier.align(Alignment.Start) + ) + Text( + text = "Enter your server and account details.", + color = scheme.onSurfaceVariant, + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier + .align(Alignment.Start) + .padding(top = 2.dp, bottom = 16.dp) + ) + + state.errorMessage?.let { errorMessage -> + Text( + text = errorMessage, + color = scheme.onErrorContainer, + style = MaterialTheme.typography.bodySmall, + modifier = Modifier + .fillMaxWidth() + .background( + scheme.errorContainer, + RoundedCornerShape(12.dp) + ) + .padding(12.dp) + ) + Spacer(modifier = Modifier.height(12.dp)) + } + + PurefinComplexTextField( + label = "Server URL", + value = state.serverUrl, + onValueChange = callbacks.onServerUrlChange, + placeholder = "http://192.168.1.100:8096", + leadingIcon = Icons.Default.Storage + ) + + Spacer(modifier = Modifier.height(12.dp)) + + PurefinComplexTextField( + label = "Username", + value = state.username, + onValueChange = callbacks.onUsernameChange, + placeholder = "Enter your username", + leadingIcon = Icons.Default.Person + ) + + Spacer(modifier = Modifier.height(12.dp)) + + PurefinPasswordField( + label = "Password", + value = state.password, + onValueChange = callbacks.onPasswordChange, + placeholder = "Enter your password", + leadingIcon = Icons.Default.Lock, + ) + + Spacer(modifier = Modifier.height(24.dp)) + + PurefinTextButton( + content = { Text("Connect") }, + onClick = callbacks.onConnect, + modifier = Modifier + .fillMaxWidth() + .height(48.dp) + ) + } + } +} diff --git a/app/src/main/java/hu/bbara/purefin/ui/common/image/PurefinLogo.kt b/core-ui/src/main/java/hu/bbara/purefin/ui/common/image/PurefinLogo.kt similarity index 94% rename from app/src/main/java/hu/bbara/purefin/ui/common/image/PurefinLogo.kt rename to core-ui/src/main/java/hu/bbara/purefin/ui/common/image/PurefinLogo.kt index 70957137..ed3dcc35 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/common/image/PurefinLogo.kt +++ b/core-ui/src/main/java/hu/bbara/purefin/ui/common/image/PurefinLogo.kt @@ -5,7 +5,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource -import hu.bbara.purefin.R +import hu.bbara.purefin.core.ui.R @Composable fun PurefinLogo( diff --git a/core-ui/src/main/java/hu/bbara/purefin/ui/screen/login/LoginContent.kt b/core-ui/src/main/java/hu/bbara/purefin/ui/screen/login/LoginContent.kt deleted file mode 100644 index b4909c7c..00000000 --- a/core-ui/src/main/java/hu/bbara/purefin/ui/screen/login/LoginContent.kt +++ /dev/null @@ -1,201 +0,0 @@ -package hu.bbara.purefin.ui.screen.login - -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.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Lock -import androidx.compose.material.icons.filled.Movie -import androidx.compose.material.icons.filled.Person -import androidx.compose.material.icons.filled.Search -import androidx.compose.material.icons.filled.Storage -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.material3.TextButton -import androidx.compose.runtime.Composable -import androidx.compose.runtime.Immutable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import hu.bbara.purefin.ui.common.button.PurefinTextButton -import hu.bbara.purefin.ui.common.textfield.PurefinComplexTextField -import hu.bbara.purefin.ui.common.textfield.PurefinPasswordField -import hu.bbara.purefin.ui.screen.waiting.PurefinWaitingScreen - -@Immutable -data class LoginContentState( - val serverUrl: String, - val username: String, - val password: String, - val errorMessage: String? = null -) - -class LoginContentCallbacks( - val onServerUrlChange: (String) -> Unit, - val onUsernameChange: (String) -> Unit, - val onPasswordChange: (String) -> Unit, - val onConnect: () -> Unit, - val onDiscoverServers: () -> Unit = {}, - val onNeedHelp: () -> Unit = {} -) - -@Composable -fun LoginContent( - state: LoginContentState, - callbacks: LoginContentCallbacks, - isLoggingIn: Boolean, - modifier: Modifier = Modifier -) { - val scheme = MaterialTheme.colorScheme - - if (isLoggingIn) { - PurefinWaitingScreen(modifier = modifier) - return - } - - Column( - modifier = modifier - .fillMaxSize() - .background(scheme.background) - .padding(24.dp) - .verticalScroll(rememberScrollState()), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Spacer(modifier = Modifier.weight(0.5f)) - - Box( - modifier = Modifier - .size(100.dp) - .background(scheme.primary, RoundedCornerShape(24.dp)), - contentAlignment = Alignment.Center - ) { - Icon( - imageVector = Icons.Default.Movie, - contentDescription = "Logo", - tint = scheme.onPrimary, - modifier = Modifier.size(60.dp) - ) - } - - Text( - text = "Jellyfin", - color = scheme.onBackground, - fontSize = 32.sp, - fontWeight = FontWeight.Bold, - modifier = Modifier.padding(top = 16.dp) - ) - Text( - text = "PERSONAL MEDIA SYSTEM", - color = scheme.onSurfaceVariant, - fontSize = 12.sp, - letterSpacing = 2.sp - ) - - Spacer(modifier = Modifier.height(48.dp)) - - Text( - text = "Connect to Server", - color = scheme.onBackground, - fontSize = 22.sp, - fontWeight = FontWeight.Bold, - modifier = Modifier.align(Alignment.Start) - ) - Text( - text = "Enter your details to access your library", - color = scheme.onSurfaceVariant, - fontSize = 14.sp, - modifier = Modifier - .align(Alignment.Start) - .padding(bottom = 24.dp) - ) - - state.errorMessage?.let { errorMessage -> - Text( - text = errorMessage, - color = scheme.error, - fontSize = 14.sp, - modifier = Modifier - .fillMaxWidth() - .background( - scheme.errorContainer, - RoundedCornerShape(8.dp) - ) - .padding(12.dp) - ) - Spacer(modifier = Modifier.height(16.dp)) - } - - PurefinComplexTextField( - label = "Server URL", - value = state.serverUrl, - onValueChange = callbacks.onServerUrlChange, - placeholder = "http://192.168.1.100:8096", - leadingIcon = Icons.Default.Storage - ) - - Spacer(modifier = Modifier.height(16.dp)) - - PurefinComplexTextField( - label = "Username", - value = state.username, - onValueChange = callbacks.onUsernameChange, - placeholder = "Enter your username", - leadingIcon = Icons.Default.Person - ) - - Spacer(modifier = Modifier.height(16.dp)) - - PurefinPasswordField( - label = "Password", - value = state.password, - onValueChange = callbacks.onPasswordChange, - placeholder = "••••••••", - leadingIcon = Icons.Default.Lock, - ) - - Spacer(modifier = Modifier.height(32.dp)) - - PurefinTextButton( - content = { Text("Connect") }, - onClick = callbacks.onConnect - ) - - Spacer(modifier = Modifier.weight(0.5f)) - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween - ) { - TextButton(onClick = callbacks.onDiscoverServers) { - Row(verticalAlignment = Alignment.CenterVertically) { - Icon( - imageVector = Icons.Default.Search, - contentDescription = null, - tint = scheme.onSurfaceVariant, - modifier = Modifier.size(18.dp) - ) - Text(" Discover Servers", color = scheme.onSurfaceVariant) - } - } - TextButton(onClick = callbacks.onNeedHelp) { - Text("Need Help?", color = scheme.onSurfaceVariant) - } - } - - Spacer(modifier = Modifier.height(16.dp)) - } -} diff --git a/core-ui/src/main/java/hu/bbara/purefin/ui/screen/login/LoginModels.kt b/core-ui/src/main/java/hu/bbara/purefin/ui/screen/login/LoginModels.kt new file mode 100644 index 00000000..833f8e1c --- /dev/null +++ b/core-ui/src/main/java/hu/bbara/purefin/ui/screen/login/LoginModels.kt @@ -0,0 +1,18 @@ +package hu.bbara.purefin.ui.screen.login + +import androidx.compose.runtime.Immutable + +@Immutable +data class LoginContentState( + val serverUrl: String, + val username: String, + val password: String, + val errorMessage: String? = null +) + +class LoginContentCallbacks( + val onServerUrlChange: (String) -> Unit, + val onUsernameChange: (String) -> Unit, + val onPasswordChange: (String) -> Unit, + val onConnect: () -> Unit +) diff --git a/app/src/main/res/drawable/purefin_logo_tintable.xml b/core-ui/src/main/res/drawable/purefin_logo_tintable.xml similarity index 100% rename from app/src/main/res/drawable/purefin_logo_tintable.xml rename to core-ui/src/main/res/drawable/purefin_logo_tintable.xml