forked from melod1n/fast-messenger
improve visuals for Logo & Login screens for compact devices
This commit is contained in:
+182
-162
@@ -6,11 +6,13 @@ import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.ime
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.union
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
@@ -20,6 +22,7 @@ import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.ScaffoldDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -48,6 +51,7 @@ import androidx.compose.ui.text.input.VisualTransformation
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.meloda.app.fast.common.UiText
|
||||
import com.meloda.app.fast.designsystem.LocalTheme
|
||||
import com.meloda.app.fast.designsystem.MaterialDialog
|
||||
import com.meloda.app.fast.designsystem.TextFieldErrorText
|
||||
import com.meloda.app.fast.designsystem.autoFillRequestHandler
|
||||
@@ -60,8 +64,8 @@ import com.meloda.fast.auth.login.LoginViewModelImpl
|
||||
import com.meloda.fast.auth.login.model.CaptchaArguments
|
||||
import com.meloda.fast.auth.login.model.LoginError
|
||||
import com.meloda.fast.auth.login.model.LoginScreenState
|
||||
import com.meloda.fast.auth.login.model.LoginValidationArguments
|
||||
import com.meloda.fast.auth.login.model.LoginUserBannedArguments
|
||||
import com.meloda.fast.auth.login.model.LoginValidationArguments
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
import com.meloda.app.fast.designsystem.R as UiR
|
||||
|
||||
@@ -153,6 +157,7 @@ fun LoginScreen(
|
||||
onPasswordFieldGoAction: () -> Unit = {},
|
||||
onSignInButtonClicked: () -> Unit = {}
|
||||
) {
|
||||
val currentTheme = LocalTheme.current
|
||||
val focusManager = LocalFocusManager.current
|
||||
val (loginFocusable, passwordFocusable) = FocusRequester.createRefs()
|
||||
|
||||
@@ -181,183 +186,198 @@ fun LoginScreen(
|
||||
}
|
||||
)
|
||||
|
||||
Scaffold { padding ->
|
||||
val titleStyle = if (currentTheme.isDeviceCompact) {
|
||||
MaterialTheme.typography.displaySmall
|
||||
} else {
|
||||
MaterialTheme.typography.displayMedium
|
||||
}
|
||||
|
||||
val titleSpacerSize = if (currentTheme.isDeviceCompact) {
|
||||
24.dp
|
||||
} else {
|
||||
58.dp
|
||||
}
|
||||
|
||||
val bottomPadding = if (currentTheme.isDeviceCompact) {
|
||||
10.dp
|
||||
} else {
|
||||
30.dp
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
contentWindowInsets = ScaffoldDefaults.contentWindowInsets.union(WindowInsets.ime)
|
||||
) { padding ->
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(padding)
|
||||
.padding(top = 30.dp)
|
||||
.padding(horizontal = 30.dp)
|
||||
.padding(bottom = bottomPadding)
|
||||
) {
|
||||
Box(
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(30.dp)
|
||||
.imePadding()
|
||||
.fillMaxWidth()
|
||||
.align(Alignment.Center)
|
||||
) {
|
||||
Column(
|
||||
Text(
|
||||
text = stringResource(id = UiR.string.sign_in_to_vk),
|
||||
color = MaterialTheme.colorScheme.onBackground,
|
||||
style = titleStyle
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(titleSpacerSize))
|
||||
|
||||
TextField(
|
||||
modifier = Modifier
|
||||
.height(58.dp)
|
||||
.fillMaxWidth()
|
||||
.align(Alignment.Center)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = UiR.string.sign_in_to_vk),
|
||||
color = MaterialTheme.colorScheme.onBackground,
|
||||
style = MaterialTheme.typography.displayMedium
|
||||
)
|
||||
.clip(RoundedCornerShape(10.dp))
|
||||
.handleEnterKey {
|
||||
passwordFocusable.requestFocus()
|
||||
true
|
||||
}
|
||||
.handleTabKey {
|
||||
passwordFocusable.requestFocus()
|
||||
true
|
||||
}
|
||||
.focusRequester(loginFocusable)
|
||||
.connectNode(handler = autoFillEmailHandler)
|
||||
.defaultFocusChangeAutoFill(handler = autoFillEmailHandler),
|
||||
value = loginText,
|
||||
onValueChange = { newText ->
|
||||
val text = newText.text
|
||||
if (text.isEmpty()) {
|
||||
autoFillEmailHandler.requestVerifyManual()
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(58.dp))
|
||||
|
||||
TextField(
|
||||
modifier = Modifier
|
||||
.height(58.dp)
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(10.dp))
|
||||
.handleEnterKey {
|
||||
passwordFocusable.requestFocus()
|
||||
true
|
||||
loginText = newText
|
||||
onLoginInputChanged(text)
|
||||
},
|
||||
label = { Text(text = stringResource(id = UiR.string.login_hint)) },
|
||||
placeholder = { Text(text = stringResource(id = UiR.string.login_hint)) },
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
painter = painterResource(id = UiR.drawable.ic_round_person_24),
|
||||
contentDescription = "Login icon",
|
||||
tint = if (showLoginError) {
|
||||
MaterialTheme.colorScheme.error
|
||||
} else {
|
||||
MaterialTheme.colorScheme.primary
|
||||
}
|
||||
.handleTabKey {
|
||||
passwordFocusable.requestFocus()
|
||||
true
|
||||
}
|
||||
.focusRequester(loginFocusable)
|
||||
.connectNode(handler = autoFillEmailHandler)
|
||||
.defaultFocusChangeAutoFill(handler = autoFillEmailHandler),
|
||||
value = loginText,
|
||||
onValueChange = { newText ->
|
||||
val text = newText.text
|
||||
if (text.isEmpty()) {
|
||||
autoFillEmailHandler.requestVerifyManual()
|
||||
}
|
||||
|
||||
loginText = newText
|
||||
onLoginInputChanged(text)
|
||||
},
|
||||
label = { Text(text = stringResource(id = UiR.string.login_hint)) },
|
||||
placeholder = { Text(text = stringResource(id = UiR.string.login_hint)) },
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
painter = painterResource(id = UiR.drawable.ic_round_person_24),
|
||||
contentDescription = "Login icon",
|
||||
tint = if (showLoginError) {
|
||||
MaterialTheme.colorScheme.error
|
||||
} else {
|
||||
MaterialTheme.colorScheme.primary
|
||||
}
|
||||
)
|
||||
},
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
imeAction = ImeAction.Next,
|
||||
keyboardType = KeyboardType.Email
|
||||
),
|
||||
keyboardActions = KeyboardActions(onNext = { passwordFocusable.requestFocus() }),
|
||||
isError = showLoginError,
|
||||
singleLine = true
|
||||
)
|
||||
AnimatedVisibility(visible = showLoginError) {
|
||||
TextFieldErrorText(text = stringResource(id = UiR.string.error_empty_field))
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
TextField(
|
||||
modifier = Modifier
|
||||
.height(58.dp)
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(10.dp))
|
||||
.handleEnterKey {
|
||||
focusManager.clearFocus()
|
||||
onPasswordFieldEnterKeyClicked()
|
||||
true
|
||||
}
|
||||
.focusRequester(passwordFocusable)
|
||||
.connectNode(handler = autoFillPasswordHandler)
|
||||
.defaultFocusChangeAutoFill(handler = autoFillPasswordHandler),
|
||||
value = passwordText,
|
||||
onValueChange = { newText ->
|
||||
val text = newText.text
|
||||
if (text.isEmpty()) {
|
||||
autoFillPasswordHandler.requestVerifyManual()
|
||||
}
|
||||
|
||||
passwordText = newText
|
||||
onPasswordInputChanged(text)
|
||||
},
|
||||
label = { Text(text = stringResource(id = UiR.string.password_login_hint)) },
|
||||
placeholder = { Text(text = stringResource(id = UiR.string.password_login_hint)) },
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
painter = painterResource(id = UiR.drawable.round_vpn_key_24),
|
||||
contentDescription = "Password icon",
|
||||
tint = if (showPasswordError) {
|
||||
MaterialTheme.colorScheme.error
|
||||
} else {
|
||||
MaterialTheme.colorScheme.primary
|
||||
}
|
||||
)
|
||||
},
|
||||
trailingIcon = {
|
||||
val imagePainter = painterResource(
|
||||
id = if (screenState.passwordVisible) UiR.drawable.round_visibility_off_24
|
||||
else UiR.drawable.round_visibility_24
|
||||
)
|
||||
|
||||
IconButton(onClick = onPasswordVisibilityButtonClicked) {
|
||||
Icon(
|
||||
painter = imagePainter,
|
||||
contentDescription = if (screenState.passwordVisible) "Password visible icon"
|
||||
else "Password invisible icon"
|
||||
)
|
||||
}
|
||||
},
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
imeAction = ImeAction.Go,
|
||||
keyboardType = KeyboardType.Password
|
||||
),
|
||||
keyboardActions = KeyboardActions(
|
||||
onGo = {
|
||||
focusManager.clearFocus()
|
||||
onPasswordFieldGoAction()
|
||||
}
|
||||
),
|
||||
isError = showPasswordError,
|
||||
visualTransformation = if (screenState.passwordVisible) {
|
||||
VisualTransformation.None
|
||||
} else {
|
||||
PasswordVisualTransformation()
|
||||
},
|
||||
singleLine = true
|
||||
)
|
||||
AnimatedVisibility(visible = showPasswordError) {
|
||||
TextFieldErrorText(text = stringResource(id = UiR.string.error_empty_field))
|
||||
}
|
||||
)
|
||||
},
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
imeAction = ImeAction.Next,
|
||||
keyboardType = KeyboardType.Email
|
||||
),
|
||||
keyboardActions = KeyboardActions(onNext = { passwordFocusable.requestFocus() }),
|
||||
isError = showLoginError,
|
||||
singleLine = true
|
||||
)
|
||||
AnimatedVisibility(visible = showLoginError) {
|
||||
TextFieldErrorText(text = stringResource(id = UiR.string.error_empty_field))
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier.align(Alignment.BottomCenter),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
FloatingActionButton(
|
||||
onClick = {
|
||||
TextField(
|
||||
modifier = Modifier
|
||||
.height(58.dp)
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(10.dp))
|
||||
.handleEnterKey {
|
||||
focusManager.clearFocus()
|
||||
onSignInButtonClicked()
|
||||
},
|
||||
containerColor = MaterialTheme.colorScheme.secondaryContainer,
|
||||
modifier = Modifier.testTag("sing_in_fab")
|
||||
) {
|
||||
onPasswordFieldEnterKeyClicked()
|
||||
true
|
||||
}
|
||||
.focusRequester(passwordFocusable)
|
||||
.connectNode(handler = autoFillPasswordHandler)
|
||||
.defaultFocusChangeAutoFill(handler = autoFillPasswordHandler),
|
||||
value = passwordText,
|
||||
onValueChange = { newText ->
|
||||
val text = newText.text
|
||||
if (text.isEmpty()) {
|
||||
autoFillPasswordHandler.requestVerifyManual()
|
||||
}
|
||||
|
||||
passwordText = newText
|
||||
onPasswordInputChanged(text)
|
||||
},
|
||||
label = { Text(text = stringResource(id = UiR.string.password_login_hint)) },
|
||||
placeholder = { Text(text = stringResource(id = UiR.string.password_login_hint)) },
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
painter = painterResource(id = UiR.drawable.ic_arrow_end),
|
||||
contentDescription = "Sign in icon",
|
||||
tint = MaterialTheme.colorScheme.onSecondaryContainer
|
||||
painter = painterResource(id = UiR.drawable.round_vpn_key_24),
|
||||
contentDescription = "Password icon",
|
||||
tint = if (showPasswordError) {
|
||||
MaterialTheme.colorScheme.error
|
||||
} else {
|
||||
MaterialTheme.colorScheme.primary
|
||||
}
|
||||
)
|
||||
}
|
||||
AnimatedVisibility(
|
||||
visible = screenState.isLoading,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut()
|
||||
) {
|
||||
CircularProgressIndicator()
|
||||
}
|
||||
},
|
||||
trailingIcon = {
|
||||
val imagePainter = painterResource(
|
||||
id = if (screenState.passwordVisible) UiR.drawable.round_visibility_off_24
|
||||
else UiR.drawable.round_visibility_24
|
||||
)
|
||||
|
||||
IconButton(onClick = onPasswordVisibilityButtonClicked) {
|
||||
Icon(
|
||||
painter = imagePainter,
|
||||
contentDescription = if (screenState.passwordVisible) "Password visible icon"
|
||||
else "Password invisible icon"
|
||||
)
|
||||
}
|
||||
},
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
imeAction = ImeAction.Go,
|
||||
keyboardType = KeyboardType.Password
|
||||
),
|
||||
keyboardActions = KeyboardActions(
|
||||
onGo = {
|
||||
focusManager.clearFocus()
|
||||
onPasswordFieldGoAction()
|
||||
}
|
||||
),
|
||||
isError = showPasswordError,
|
||||
visualTransformation = if (screenState.passwordVisible) {
|
||||
VisualTransformation.None
|
||||
} else {
|
||||
PasswordVisualTransformation()
|
||||
},
|
||||
singleLine = true
|
||||
)
|
||||
AnimatedVisibility(visible = showPasswordError) {
|
||||
TextFieldErrorText(text = stringResource(id = UiR.string.error_empty_field))
|
||||
}
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier.align(Alignment.BottomCenter),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
FloatingActionButton(
|
||||
onClick = {
|
||||
focusManager.clearFocus()
|
||||
onSignInButtonClicked()
|
||||
},
|
||||
containerColor = MaterialTheme.colorScheme.secondaryContainer,
|
||||
modifier = Modifier.testTag("sing_in_fab")
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = UiR.drawable.ic_arrow_end),
|
||||
contentDescription = "Sign in icon",
|
||||
tint = MaterialTheme.colorScheme.onSecondaryContainer
|
||||
)
|
||||
}
|
||||
AnimatedVisibility(
|
||||
visible = screenState.isLoading,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut()
|
||||
) {
|
||||
CircularProgressIndicator()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+34
-8
@@ -13,6 +13,7 @@ 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.width
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
@@ -30,6 +31,7 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.meloda.app.fast.designsystem.LocalTheme
|
||||
import com.meloda.fast.auth.login.LoginViewModel
|
||||
import com.meloda.fast.auth.login.LoginViewModelImpl
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
@@ -64,6 +66,8 @@ fun LogoScreen(
|
||||
onLogoLongClicked: () -> Unit = {},
|
||||
onGoNextButtonClicked: () -> Unit = {}
|
||||
) {
|
||||
val currentTheme = LocalTheme.current
|
||||
|
||||
Scaffold { padding ->
|
||||
val topPadding by animateDpAsState(
|
||||
targetValue = padding.calculateTopPadding(),
|
||||
@@ -83,6 +87,24 @@ fun LogoScreen(
|
||||
label = "startPaddingAnimation"
|
||||
)
|
||||
|
||||
val iconWidth = if (currentTheme.isDeviceCompact) {
|
||||
100.dp
|
||||
} else {
|
||||
134.dp
|
||||
}
|
||||
|
||||
val appNameTextStyle = if (currentTheme.isDeviceCompact) {
|
||||
MaterialTheme.typography.displaySmall
|
||||
} else {
|
||||
MaterialTheme.typography.displayMedium
|
||||
}
|
||||
|
||||
val bottomAdditionalPadding = if (currentTheme.isDeviceCompact) {
|
||||
10.dp
|
||||
} else {
|
||||
30.dp
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
@@ -92,7 +114,9 @@ fun LogoScreen(
|
||||
end = endPadding,
|
||||
bottom = bottomPadding
|
||||
)
|
||||
.padding(30.dp)
|
||||
.padding(top = 30.dp)
|
||||
.padding(horizontal = 30.dp)
|
||||
.padding(bottom = bottomAdditionalPadding)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
@@ -104,17 +128,19 @@ fun LogoScreen(
|
||||
painter = painterResource(id = UiR.drawable.ic_logo_big),
|
||||
contentDescription = "Application Logo",
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.combinedClickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = null,
|
||||
onLongClick = onLogoLongClicked,
|
||||
onClick = {}
|
||||
)
|
||||
modifier = Modifier
|
||||
.width(iconWidth)
|
||||
.combinedClickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = null,
|
||||
onLongClick = onLogoLongClicked,
|
||||
onClick = {}
|
||||
)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(46.dp))
|
||||
Text(
|
||||
text = stringResource(id = UiR.string.fast_messenger),
|
||||
style = MaterialTheme.typography.displayMedium,
|
||||
style = appNameTextStyle,
|
||||
color = MaterialTheme.colorScheme.onBackground
|
||||
)
|
||||
}
|
||||
|
||||
+14
-7
@@ -60,6 +60,7 @@ import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.DpOffset
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -163,16 +164,16 @@ fun MessagesHistoryScreen(
|
||||
)
|
||||
}
|
||||
|
||||
val toolbarColorAlpha by animateFloatAsState(
|
||||
targetValue = if (!listState.canScrollForward) 1f else 0f,
|
||||
label = "toolbarColorAlpha",
|
||||
animationSpec = tween(durationMillis = 50)
|
||||
)
|
||||
|
||||
Scaffold(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentWindowInsets = WindowInsets.statusBars,
|
||||
topBar = {
|
||||
val toolbarColorAlpha by animateFloatAsState(
|
||||
targetValue = if (!listState.canScrollForward) 1f else 0f,
|
||||
label = "toolbarColorAlpha",
|
||||
animationSpec = tween(durationMillis = 50)
|
||||
)
|
||||
|
||||
Column(modifier = Modifier.fillMaxWidth()) {
|
||||
TopAppBar(
|
||||
modifier = Modifier
|
||||
@@ -370,7 +371,13 @@ fun MessagesHistoryScreen(
|
||||
unfocusedIndicatorColor = Color.Transparent,
|
||||
focusedIndicatorColor = Color.Transparent,
|
||||
),
|
||||
placeholder = { Text(text = stringResource(id = UiR.string.message_input_hint)) }
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(id = UiR.string.message_input_hint),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
IconButton(onClick = onAttachmentButtonClicked) {
|
||||
|
||||
Reference in New Issue
Block a user