improvements in ui
This commit is contained in:
@@ -42,8 +42,11 @@ import dev.meloda.fast.datastore.AppSettings
|
||||
import dev.meloda.fast.datastore.UserSettings
|
||||
import dev.meloda.fast.service.OnlineService
|
||||
import dev.meloda.fast.service.longpolling.LongPollingService
|
||||
import dev.meloda.fast.ui.model.DeviceSize
|
||||
import dev.meloda.fast.ui.model.SizeConfig
|
||||
import dev.meloda.fast.ui.model.ThemeConfig
|
||||
import dev.meloda.fast.ui.theme.AppTheme
|
||||
import dev.meloda.fast.ui.theme.LocalSizeConfig
|
||||
import dev.meloda.fast.ui.theme.LocalThemeConfig
|
||||
import dev.meloda.fast.ui.util.isNeedToEnableDarkMode
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
@@ -152,10 +155,42 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
val isDeviceCompact by remember(true) {
|
||||
derivedStateOf {
|
||||
context.resources.displayMetrics.widthPixels.pxToDp() <= 360
|
||||
val deviceWidthDp = remember(true) {
|
||||
context.resources.displayMetrics.widthPixels.pxToDp()
|
||||
}
|
||||
val deviceHeightDp = remember(true) {
|
||||
context.resources.displayMetrics.heightPixels.pxToDp()
|
||||
}
|
||||
|
||||
val deviceWidthSize by remember(deviceWidthDp) {
|
||||
derivedStateOf {
|
||||
when {
|
||||
deviceWidthDp <= 360 -> DeviceSize.Small
|
||||
deviceWidthDp <= 600 -> DeviceSize.Compact
|
||||
deviceWidthDp <= 840 -> DeviceSize.Medium
|
||||
else -> DeviceSize.Expanded
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val deviceHeightSize by remember(deviceHeightDp) {
|
||||
derivedStateOf {
|
||||
when {
|
||||
deviceHeightDp <= 480 -> DeviceSize.Small
|
||||
deviceHeightDp <= 700 -> DeviceSize.Compact
|
||||
deviceHeightDp <= 900 -> DeviceSize.Medium
|
||||
else -> DeviceSize.Expanded
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val sizeConfig by remember(deviceWidthSize, deviceHeightSize) {
|
||||
mutableStateOf(
|
||||
SizeConfig(
|
||||
widthSize = deviceWidthSize,
|
||||
heightSize = deviceHeightSize
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val darkMode by userSettings.darkMode.collectAsStateWithLifecycle()
|
||||
@@ -181,13 +216,15 @@ class MainActivity : AppCompatActivity() {
|
||||
selectedColorScheme = 0,
|
||||
amoledDark = amoledDark,
|
||||
enableBlur = enableBlur,
|
||||
enableMultiline = enableMultiline,
|
||||
isDeviceCompact = isDeviceCompact
|
||||
enableMultiline = enableMultiline
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
CompositionLocalProvider(LocalThemeConfig provides themeConfig) {
|
||||
CompositionLocalProvider(
|
||||
LocalThemeConfig provides themeConfig,
|
||||
LocalSizeConfig provides sizeConfig
|
||||
) {
|
||||
AppTheme(
|
||||
useDarkTheme = themeConfig.darkMode,
|
||||
useDynamicColors = themeConfig.dynamicColors,
|
||||
|
||||
@@ -3,7 +3,8 @@ package dev.meloda.fast.network
|
||||
enum class VkOAuthErrorType(val value: String) {
|
||||
WRONG_OTP_FORMAT("otp_format_is_incorrect"),
|
||||
WRONG_OTP("wrong_otp"),
|
||||
PASSWORD_BRUTEFORCE_ATTEMPT("password_bruteforce_attempt");
|
||||
PASSWORD_BRUTEFORCE_ATTEMPT("password_bruteforce_attempt"),
|
||||
USERNAME_OR_PASSWORD_IS_INCORRECT("username_or_password_is_incorrect");
|
||||
|
||||
companion object {
|
||||
fun parse(value: String): VkOAuthErrorType = entries.firstOrNull { it.value == value }
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package dev.meloda.fast.ui.model
|
||||
|
||||
sealed class DeviceSize {
|
||||
data object Small : DeviceSize()
|
||||
data object Compact : DeviceSize()
|
||||
data object Medium : DeviceSize()
|
||||
data object Expanded : DeviceSize()
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package dev.meloda.fast.ui.model
|
||||
|
||||
data class SizeConfig(
|
||||
val widthSize: DeviceSize,
|
||||
val heightSize: DeviceSize
|
||||
) {
|
||||
|
||||
val isHeightSmall: Boolean get() = heightSize is DeviceSize.Small
|
||||
val isWidthSmall: Boolean get() = widthSize is DeviceSize.Small
|
||||
}
|
||||
@@ -6,6 +6,5 @@ data class ThemeConfig(
|
||||
val selectedColorScheme: Int,
|
||||
val amoledDark: Boolean,
|
||||
val enableBlur: Boolean,
|
||||
val enableMultiline: Boolean,
|
||||
val isDeviceCompact: Boolean
|
||||
val enableMultiline: Boolean
|
||||
)
|
||||
|
||||
@@ -20,9 +20,11 @@ import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.view.WindowCompat
|
||||
import dev.meloda.fast.ui.R
|
||||
import dev.meloda.fast.ui.model.ThemeConfig
|
||||
import dev.chrisbanes.haze.HazeState
|
||||
import dev.meloda.fast.ui.R
|
||||
import dev.meloda.fast.ui.model.DeviceSize
|
||||
import dev.meloda.fast.ui.model.SizeConfig
|
||||
import dev.meloda.fast.ui.model.ThemeConfig
|
||||
|
||||
private val googleSansFonts = FontFamily(
|
||||
Font(resId = R.font.google_sans_regular),
|
||||
@@ -110,8 +112,14 @@ val LocalThemeConfig = compositionLocalOf {
|
||||
selectedColorScheme = 0,
|
||||
amoledDark = false,
|
||||
enableBlur = false,
|
||||
enableMultiline = false,
|
||||
isDeviceCompact = false
|
||||
enableMultiline = false
|
||||
)
|
||||
}
|
||||
|
||||
val LocalSizeConfig = compositionLocalOf {
|
||||
SizeConfig(
|
||||
widthSize = DeviceSize.Compact,
|
||||
heightSize = DeviceSize.Compact
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package dev.meloda.fast.auth.login
|
||||
|
||||
import dev.meloda.fast.auth.login.model.AuthInfo
|
||||
import dev.meloda.fast.data.State
|
||||
import dev.meloda.fast.data.api.oauth.OAuthRepository
|
||||
import dev.meloda.fast.network.OAuthErrorDomain
|
||||
import dev.meloda.fast.network.ValidationType
|
||||
import dev.meloda.fast.network.VkOAuthError
|
||||
import dev.meloda.fast.network.VkOAuthErrorType
|
||||
import dev.meloda.fast.auth.login.model.AuthInfo
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
|
||||
@@ -94,6 +94,8 @@ class OAuthUseCaseImpl(
|
||||
|
||||
VkOAuthError.INVALID_REQUEST -> {
|
||||
when (errorType) {
|
||||
null -> State.Error.OAuthError(OAuthErrorDomain.UnknownError)
|
||||
|
||||
VkOAuthErrorType.WRONG_OTP -> {
|
||||
State.Error.OAuthError(OAuthErrorDomain.WrongValidationCode)
|
||||
}
|
||||
@@ -106,7 +108,9 @@ class OAuthUseCaseImpl(
|
||||
State.Error.OAuthError(OAuthErrorDomain.TooManyTriesError)
|
||||
}
|
||||
|
||||
null -> State.Error.OAuthError(OAuthErrorDomain.UnknownError)
|
||||
VkOAuthErrorType.USERNAME_OR_PASSWORD_IS_INCORRECT -> {
|
||||
State.Error.OAuthError(OAuthErrorDomain.InvalidCredentialsError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+21
-13
@@ -50,14 +50,6 @@ import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import dev.meloda.fast.ui.basic.autoFillRequestHandler
|
||||
import dev.meloda.fast.ui.basic.connectNode
|
||||
import dev.meloda.fast.ui.basic.defaultFocusChangeAutoFill
|
||||
import dev.meloda.fast.ui.components.MaterialDialog
|
||||
import dev.meloda.fast.ui.components.TextFieldErrorText
|
||||
import dev.meloda.fast.ui.theme.LocalThemeConfig
|
||||
import dev.meloda.fast.ui.util.handleEnterKey
|
||||
import dev.meloda.fast.ui.util.handleTabKey
|
||||
import dev.meloda.fast.auth.login.LoginViewModel
|
||||
import dev.meloda.fast.auth.login.LoginViewModelImpl
|
||||
import dev.meloda.fast.auth.login.model.CaptchaArguments
|
||||
@@ -65,6 +57,14 @@ import dev.meloda.fast.auth.login.model.LoginError
|
||||
import dev.meloda.fast.auth.login.model.LoginScreenState
|
||||
import dev.meloda.fast.auth.login.model.LoginUserBannedArguments
|
||||
import dev.meloda.fast.auth.login.model.LoginValidationArguments
|
||||
import dev.meloda.fast.ui.basic.autoFillRequestHandler
|
||||
import dev.meloda.fast.ui.basic.connectNode
|
||||
import dev.meloda.fast.ui.basic.defaultFocusChangeAutoFill
|
||||
import dev.meloda.fast.ui.components.MaterialDialog
|
||||
import dev.meloda.fast.ui.components.TextFieldErrorText
|
||||
import dev.meloda.fast.ui.theme.LocalSizeConfig
|
||||
import dev.meloda.fast.ui.util.handleEnterKey
|
||||
import dev.meloda.fast.ui.util.handleTabKey
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
import dev.meloda.fast.ui.R as UiR
|
||||
|
||||
@@ -156,7 +156,7 @@ fun LoginScreen(
|
||||
onPasswordFieldGoAction: () -> Unit = {},
|
||||
onSignInButtonClicked: () -> Unit = {}
|
||||
) {
|
||||
val currentTheme = LocalThemeConfig.current
|
||||
val currentSize = LocalSizeConfig.current
|
||||
val focusManager = LocalFocusManager.current
|
||||
val (loginFocusable, passwordFocusable) = FocusRequester.createRefs()
|
||||
|
||||
@@ -182,19 +182,19 @@ fun LoginScreen(
|
||||
}
|
||||
)
|
||||
|
||||
val titleStyle = if (currentTheme.isDeviceCompact) {
|
||||
MaterialTheme.typography.displaySmall
|
||||
val titleStyle = if (currentSize.isWidthSmall) {
|
||||
MaterialTheme.typography.displayMedium
|
||||
} else {
|
||||
MaterialTheme.typography.displayMedium
|
||||
}
|
||||
|
||||
val titleSpacerSize = if (currentTheme.isDeviceCompact) {
|
||||
val titleSpacerSize = if (currentSize.isHeightSmall) {
|
||||
24.dp
|
||||
} else {
|
||||
58.dp
|
||||
}
|
||||
|
||||
val bottomPadding = if (currentTheme.isDeviceCompact) {
|
||||
val bottomPadding = if (currentSize.isHeightSmall) {
|
||||
10.dp
|
||||
} else {
|
||||
30.dp
|
||||
@@ -353,11 +353,18 @@ fun LoginScreen(
|
||||
Box(
|
||||
modifier = Modifier.align(Alignment.BottomCenter),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = !screenState.isLoading,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut()
|
||||
) {
|
||||
FloatingActionButton(
|
||||
onClick = {
|
||||
if (!screenState.isLoading) {
|
||||
focusManager.clearFocus()
|
||||
onSignInButtonClicked()
|
||||
}
|
||||
},
|
||||
containerColor = MaterialTheme.colorScheme.secondaryContainer,
|
||||
modifier = Modifier.testTag("sing_in_fab")
|
||||
@@ -368,6 +375,7 @@ fun LoginScreen(
|
||||
tint = MaterialTheme.colorScheme.onSecondaryContainer
|
||||
)
|
||||
}
|
||||
}
|
||||
AnimatedVisibility(
|
||||
visible = screenState.isLoading,
|
||||
enter = fadeIn(),
|
||||
|
||||
+35
-11
@@ -1,6 +1,9 @@
|
||||
package dev.meloda.fast.auth.login.presentation
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
@@ -14,6 +17,7 @@ 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.CircularProgressIndicator
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
@@ -34,13 +38,14 @@ import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import dev.meloda.fast.auth.login.BuildConfig
|
||||
import dev.meloda.fast.auth.login.LoginViewModel
|
||||
import dev.meloda.fast.auth.login.LoginViewModelImpl
|
||||
import dev.meloda.fast.ui.components.ActionInvokeDismiss
|
||||
import dev.meloda.fast.ui.components.MaterialDialog
|
||||
import dev.meloda.fast.ui.theme.LocalThemeConfig
|
||||
import dev.meloda.fast.ui.theme.LocalSizeConfig
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
import dev.meloda.fast.ui.R as UiR
|
||||
|
||||
@@ -50,6 +55,7 @@ fun LogoRoute(
|
||||
onGoNextButtonClicked: () -> Unit,
|
||||
viewModel: LoginViewModel = koinViewModel<LoginViewModelImpl>()
|
||||
) {
|
||||
val screenState by viewModel.screenState.collectAsStateWithLifecycle()
|
||||
val isNeedToOpenMain by viewModel.isNeedToOpenMain.collectAsStateWithLifecycle()
|
||||
val isNeedToShowSignInAlert by viewModel.isNeedToShowFastSignInAlert.collectAsStateWithLifecycle()
|
||||
|
||||
@@ -61,6 +67,7 @@ fun LogoRoute(
|
||||
}
|
||||
|
||||
LogoScreen(
|
||||
isLoading = screenState.isLoading,
|
||||
onLogoLongClicked = viewModel::onLogoLongClicked,
|
||||
onGoNextButtonClicked = onGoNextButtonClicked
|
||||
)
|
||||
@@ -76,10 +83,11 @@ fun LogoRoute(
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun LogoScreen(
|
||||
isLoading: Boolean,
|
||||
onLogoLongClicked: () -> Unit = {},
|
||||
onGoNextButtonClicked: () -> Unit = {}
|
||||
) {
|
||||
val currentTheme = LocalThemeConfig.current
|
||||
val currentSize = LocalSizeConfig.current
|
||||
|
||||
Scaffold { padding ->
|
||||
val topPadding by animateDpAsState(
|
||||
@@ -100,19 +108,19 @@ fun LogoScreen(
|
||||
label = "startPaddingAnimation"
|
||||
)
|
||||
|
||||
val iconWidth = if (currentTheme.isDeviceCompact) {
|
||||
100.dp
|
||||
val iconWidth = if (currentSize.isWidthSmall) {
|
||||
110.dp
|
||||
} else {
|
||||
134.dp
|
||||
}
|
||||
|
||||
val appNameTextStyle = if (currentTheme.isDeviceCompact) {
|
||||
MaterialTheme.typography.displaySmall
|
||||
val appNameTextStyle = if (currentSize.isWidthSmall) {
|
||||
MaterialTheme.typography.displayMedium.copy(fontSize = 40.sp)
|
||||
} else {
|
||||
MaterialTheme.typography.displayMedium
|
||||
}
|
||||
|
||||
val bottomAdditionalPadding = if (currentTheme.isDeviceCompact) {
|
||||
val bottomAdditionalPadding = if (currentSize.isHeightSmall) {
|
||||
10.dp
|
||||
} else {
|
||||
30.dp
|
||||
@@ -158,12 +166,20 @@ fun LogoScreen(
|
||||
)
|
||||
}
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = !isLoading,
|
||||
modifier = Modifier.align(Alignment.BottomCenter),
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut()
|
||||
) {
|
||||
FloatingActionButton(
|
||||
onClick = onGoNextButtonClicked,
|
||||
onClick = {
|
||||
if (!isLoading) {
|
||||
onGoNextButtonClicked()
|
||||
}
|
||||
},
|
||||
containerColor = MaterialTheme.colorScheme.secondaryContainer,
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.testTag("go_next_fab")
|
||||
modifier = Modifier.testTag("go_next_fab")
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = UiR.drawable.ic_arrow_end),
|
||||
@@ -172,6 +188,14 @@ fun LogoScreen(
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = isLoading,
|
||||
modifier = Modifier.align(Alignment.BottomCenter)
|
||||
) {
|
||||
CircularProgressIndicator()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user