diff --git a/app/src/main/kotlin/dev/meloda/fast/presentation/MainScreen.kt b/app/src/main/kotlin/dev/meloda/fast/presentation/MainScreen.kt index 77eabeae..12abaf00 100644 --- a/app/src/main/kotlin/dev/meloda/fast/presentation/MainScreen.kt +++ b/app/src/main/kotlin/dev/meloda/fast/presentation/MainScreen.kt @@ -68,7 +68,7 @@ fun MainScreen( ) { val activity = LocalActivity.current as? AppCompatActivity ?: return val theme = LocalThemeConfig.current - val hazeState = remember { HazeState() } + val hazeState = remember { HazeState(true) } val navController = rememberNavController() var selectedItemIndex by rememberSaveable { diff --git a/core/ui/src/main/kotlin/dev/meloda/fast/ui/theme/AppTheme.kt b/core/ui/src/main/kotlin/dev/meloda/fast/ui/theme/AppTheme.kt index 17134119..0a0bf276 100644 --- a/core/ui/src/main/kotlin/dev/meloda/fast/ui/theme/AppTheme.kt +++ b/core/ui/src/main/kotlin/dev/meloda/fast/ui/theme/AppTheme.kt @@ -130,7 +130,7 @@ val LocalSizeConfig = compositionLocalOf { ) } -val LocalHazeState = compositionLocalOf { HazeState() } +val LocalHazeState = compositionLocalOf { HazeState(true) } val LocalBottomPadding = compositionLocalOf { 0.dp } val LocalUser = compositionLocalOf { null } val LocalReselectedTab = compositionLocalOf { mapOf() } diff --git a/core/ui/src/main/res/values-ru/strings.xml b/core/ui/src/main/res/values-ru/strings.xml index 6bea13ed..71ec819b 100644 --- a/core/ui/src/main/res/values-ru/strings.xml +++ b/core/ui/src/main/res/values-ru/strings.xml @@ -222,7 +222,7 @@ Показывать кнопку эмоджи на панели чата Показывать время в сервисных сообщениях Использовать размытие - Добавлять размытие везде, где возможно.\\nРаботает только с 12 версии Android + Добавлять размытие везде, где возможно Больше анимаций Подтверждение Вы уверены? Процесс ввода капчи будет отменён @@ -269,4 +269,6 @@ Курсив Подчёркнутый Ссылка + Регистрация + Забыли пароль? diff --git a/core/ui/src/main/res/values/strings.xml b/core/ui/src/main/res/values/strings.xml index a6f052a3..02c6ed12 100644 --- a/core/ui/src/main/res/values/strings.xml +++ b/core/ui/src/main/res/values/strings.xml @@ -270,7 +270,7 @@ Your messages will be updating even when app is not on the screen Show time in action messages Use blur - Adds blur wherever possible.\nWorks on android 12 and newer + Adds blur wherever possible More animations Use animations wherever possible @@ -340,9 +340,11 @@ Are you sure you want to unmark this message as spam? Copied to clipboard - Autofill + Autofill Bold Italic Underline Link + Sign up + Forgot password? diff --git a/feature/auth/src/main/kotlin/dev/meloda/fast/auth/login/LoginViewModel.kt b/feature/auth/src/main/kotlin/dev/meloda/fast/auth/login/LoginViewModel.kt index e91b2a45..1277bc59 100644 --- a/feature/auth/src/main/kotlin/dev/meloda/fast/auth/login/LoginViewModel.kt +++ b/feature/auth/src/main/kotlin/dev/meloda/fast/auth/login/LoginViewModel.kt @@ -1,5 +1,6 @@ package dev.meloda.fast.auth.login +import android.os.Build import android.os.Bundle import android.util.Log import androidx.lifecycle.ViewModel @@ -24,6 +25,7 @@ import dev.meloda.fast.data.db.AccountsRepository import dev.meloda.fast.data.processState import dev.meloda.fast.data.success import dev.meloda.fast.datastore.AppSettings +import dev.meloda.fast.datastore.UserSettings import dev.meloda.fast.domain.LoadUserByIdUseCase import dev.meloda.fast.domain.OAuthUseCase import dev.meloda.fast.model.database.AccountEntity @@ -62,6 +64,8 @@ interface LoginViewModel { fun onSignInButtonClicked() + fun onLogoClicked() + fun onNavigatedToMain() fun onNavigatedToUserBanned() fun onNavigatedToCaptcha() @@ -79,7 +83,8 @@ class LoginViewModelImpl( private val loadUserByIdUseCase: LoadUserByIdUseCase, private val accountsRepository: AccountsRepository, private val loginValidator: LoginValidator, - private val longPollController: LongPollController + private val longPollController: LongPollController, + private val userSettings: UserSettings ) : ViewModel(), LoginViewModel { override val screenState = MutableStateFlow(LoginScreenState.EMPTY) @@ -164,6 +169,14 @@ class LoginViewModelImpl( login() } + override fun onLogoClicked() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + userSettings.onEnableDynamicColorsChanged( + !userSettings.enableDynamicColors.value + ) + } + } + override fun onNavigatedToMain() { isNeedToOpenMain.update { false } } @@ -210,7 +223,7 @@ class LoginViewModelImpl( processValidation() if (!validationState.value.contains(LoginValidationResult.Valid)) return - screenState.updateValue { copy(isLoading = false) } + screenState.updateValue { copy(isLoading = true) } val currentValidationSid = validationSid.value val currentValidationCode = validationCode.value?.takeIf { currentValidationSid != null } diff --git a/feature/auth/src/main/kotlin/dev/meloda/fast/auth/login/presentation/LoginScreen.kt b/feature/auth/src/main/kotlin/dev/meloda/fast/auth/login/presentation/LoginScreen.kt index a3daa3a8..501e1e3e 100644 --- a/feature/auth/src/main/kotlin/dev/meloda/fast/auth/login/presentation/LoginScreen.kt +++ b/feature/auth/src/main/kotlin/dev/meloda/fast/auth/login/presentation/LoginScreen.kt @@ -1,5 +1,6 @@ package dev.meloda.fast.auth.login.presentation +import android.content.Intent import android.os.Bundle import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedVisibility @@ -8,6 +9,7 @@ import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut 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.WindowInsets import androidx.compose.foundation.layout.fillMaxSize @@ -27,6 +29,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.ScaffoldDefaults import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.material3.TextField import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -37,18 +40,20 @@ import androidx.compose.ui.autofill.ContentType import androidx.compose.ui.draw.clip import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.contentType -import androidx.compose.ui.semantics.password import androidx.compose.ui.semantics.semantics 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.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.core.net.toUri import androidx.lifecycle.compose.collectAsStateWithLifecycle import dev.meloda.fast.auth.login.LoginViewModel import dev.meloda.fast.auth.login.LoginViewModelImpl @@ -126,7 +131,8 @@ fun LoginRoute( onPasswordFieldEnterKeyClicked = viewModel::onSignInButtonClicked, onPasswordVisibilityButtonClicked = viewModel::onPasswordVisibilityButtonClicked, onPasswordFieldGoAction = viewModel::onSignInButtonClicked, - onSignInButtonClicked = viewModel::onSignInButtonClicked + onSignInButtonClicked = viewModel::onSignInButtonClicked, + onLogoClicked = viewModel::onLogoClicked ) HandleDialogs( @@ -144,13 +150,21 @@ fun LoginScreen( onPasswordFieldEnterKeyClicked: () -> Unit = {}, onPasswordVisibilityButtonClicked: () -> Unit = {}, onPasswordFieldGoAction: () -> Unit = {}, - onSignInButtonClicked: () -> Unit = {} + onSignInButtonClicked: () -> Unit = {}, + onLogoClicked: () -> Unit = {} ) { + val context = LocalContext.current val size = LocalSizeConfig.current val focusManager = LocalFocusManager.current - val titleSpacerSize by animateDpAsState(if (size.isHeightSmall) 24.dp else 58.dp) - val bottomPadding by animateDpAsState(if (size.isHeightSmall) 10.dp else 30.dp) + val titleSpacerSize by animateDpAsState( + targetValue = if (size.isHeightSmall) 24.dp else 58.dp, + label = "title spacer size" + ) + val bottomPadding by animateDpAsState( + targetValue = if (size.isHeightSmall) 10.dp else 30.dp, + label = "bottom padding" + ) val (loginFocusable, passwordFocusable) = FocusRequester.createRefs() @@ -163,15 +177,15 @@ fun LoginScreen( .padding(padding) .padding(top = 30.dp) .padding(horizontal = 30.dp) - .padding(bottom = bottomPadding) .fillMaxSize() ) { AnimatedVisibility( visible = screenState.showLogo, enter = fadeIn(), - exit = fadeOut() + exit = fadeOut(), + label = "Logo visibility" ) { - Logo() + Logo(onLogoClicked = onLogoClicked) } AnimatedVisibility( @@ -180,7 +194,8 @@ fun LoginScreen( .align(Alignment.Center), visible = !screenState.showLogo, enter = fadeIn(), - exit = fadeOut() + exit = fadeOut(), + label = "Login visibility" ) { Column( modifier = Modifier @@ -235,7 +250,10 @@ fun LoginScreen( isError = screenState.loginError, singleLine = true ) - AnimatedVisibility(visible = screenState.loginError) { + AnimatedVisibility( + visible = screenState.loginError, + label = "Login error visibility" + ) { TextFieldErrorText(text = stringResource(id = UiR.string.error_empty_field)) } @@ -300,16 +318,18 @@ fun LoginScreen( }, singleLine = true ) - AnimatedVisibility(visible = screenState.passwordError) { + AnimatedVisibility( + visible = screenState.passwordError, + label = "Password error visibility" + ) { TextFieldErrorText(text = stringResource(id = UiR.string.error_empty_field)) } } } - - Box( + Column( modifier = Modifier.align(Alignment.BottomCenter), - contentAlignment = Alignment.Center + horizontalAlignment = Alignment.CenterHorizontally ) { FloatingActionButton( onClick = { @@ -324,7 +344,8 @@ fun LoginScreen( AnimatedVisibility( visible = screenState.isLoading, enter = fadeIn(), - exit = fadeOut() + exit = fadeOut(), + label = "Progress indicator visibility" ) { CircularProgressIndicator() } @@ -332,7 +353,8 @@ fun LoginScreen( AnimatedVisibility( visible = !screenState.isLoading, enter = fadeIn(), - exit = fadeOut() + exit = fadeOut(), + label = "Sign in icon visibility" ) { Icon( painter = painterResource(id = UiR.drawable.ic_arrow_end), @@ -341,12 +363,57 @@ fun LoginScreen( ) } } + + AnimatedVisibility( + visible = screenState.showLogo, + label = "Bottom padding visibility" + ) { + Spacer(Modifier.height(bottomPadding)) + } + + AnimatedVisibility( + visible = !screenState.showLogo, + label = "Spacer between fab and bottom text buttons visibility" + ) { + Spacer(Modifier.height(4.dp)) + } + + AnimatedVisibility( + visible = !screenState.showLogo, + label = "Text button row visibility" + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + TextButton( + onClick = { + context.startActivity( + Intent(Intent.ACTION_VIEW, "https://vk.com/join".toUri()) + ) + } + ) { + Text(stringResource(UiR.string.login_sign_up)) + } + + Text( + text = "•", + color = MaterialTheme.colorScheme.primary + ) + + TextButton( + onClick = { + context.startActivity( + Intent(Intent.ACTION_VIEW, "https://vk.com/restore".toUri()) + ) + } + ) { + Text(stringResource(UiR.string.login_forgot_password)) + } + } + } } } } } - @Composable fun HandleDialogs( loginDialog: LoginDialog?, @@ -368,3 +435,13 @@ fun HandleDialogs( } } } + +@Preview +@Composable +private fun LoginScreenPreview() { + LoginScreen( + screenState = LoginScreenState.EMPTY.copy( + showLogo = false + ) + ) +} diff --git a/feature/auth/src/main/kotlin/dev/meloda/fast/auth/login/presentation/Logo.kt b/feature/auth/src/main/kotlin/dev/meloda/fast/auth/login/presentation/Logo.kt index fe304aa1..85f3f498 100644 --- a/feature/auth/src/main/kotlin/dev/meloda/fast/auth/login/presentation/Logo.kt +++ b/feature/auth/src/main/kotlin/dev/meloda/fast/auth/login/presentation/Logo.kt @@ -1,9 +1,7 @@ package dev.meloda.fast.auth.login.presentation -import android.os.Build import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateIntAsState -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -26,22 +24,20 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import dev.meloda.fast.datastore.UserSettings import dev.meloda.fast.ui.R import dev.meloda.fast.ui.theme.LocalSizeConfig -import org.koin.compose.koinInject -@OptIn(ExperimentalFoundationApi::class) @Composable -fun Logo(modifier: Modifier = Modifier) { +fun Logo( + modifier: Modifier = Modifier, + onLogoClicked: () -> Unit = {} +) { val size = LocalSizeConfig.current val iconWidth by animateDpAsState(if (size.isWidthSmall) 110.dp else 134.dp) val appNameFontSize by animateIntAsState(if (size.isWidthSmall) 32 else 38) val bottomAdditionalPadding by animateDpAsState(if (size.isHeightSmall) 10.dp else 30.dp) - val userSettings: UserSettings = koinInject() - Box( modifier = modifier .fillMaxSize() @@ -65,13 +61,7 @@ fun Logo(modifier: Modifier = Modifier) { interactionSource = null, indication = null, onLongClick = null, - onClick = { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - userSettings.onEnableDynamicColorsChanged( - !userSettings.enableDynamicColors.value - ) - } - } + onClick = onLogoClicked ) ) diff --git a/feature/auth/src/main/kotlin/dev/meloda/fast/auth/login/validation/LoginValidator.kt b/feature/auth/src/main/kotlin/dev/meloda/fast/auth/login/validation/LoginValidator.kt index b1c00f79..ed4aa711 100644 --- a/feature/auth/src/main/kotlin/dev/meloda/fast/auth/login/validation/LoginValidator.kt +++ b/feature/auth/src/main/kotlin/dev/meloda/fast/auth/login/validation/LoginValidator.kt @@ -23,5 +23,4 @@ class LoginValidator { return resultList } - } diff --git a/feature/chatmaterials/src/main/kotlin/dev/meloda/fast/chatmaterials/presentation/ChatMaterialsScreen.kt b/feature/chatmaterials/src/main/kotlin/dev/meloda/fast/chatmaterials/presentation/ChatMaterialsScreen.kt index 0e830fa3..0f988d7f 100644 --- a/feature/chatmaterials/src/main/kotlin/dev/meloda/fast/chatmaterials/presentation/ChatMaterialsScreen.kt +++ b/feature/chatmaterials/src/main/kotlin/dev/meloda/fast/chatmaterials/presentation/ChatMaterialsScreen.kt @@ -84,7 +84,7 @@ fun ChatMaterialsScreen( ) { val scope = rememberCoroutineScope() val currentTheme = LocalThemeConfig.current - val hazeState = remember { HazeState() } + val hazeState = remember { HazeState(true) } val titles = remember { listOf( diff --git a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/MessagesHistoryScreen.kt b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/MessagesHistoryScreen.kt index 5c1b4df8..5ec33e7f 100644 --- a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/MessagesHistoryScreen.kt +++ b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/MessagesHistoryScreen.kt @@ -153,7 +153,7 @@ fun MessagesHistoryScreen( val coroutineScope = rememberCoroutineScope() val theme = LocalThemeConfig.current val listState = rememberLazyListState() - val hazeState = remember { HazeState() } + val hazeState = remember { HazeState(true) } LaunchedEffect(scrollIndex) { if (scrollIndex != null) { diff --git a/feature/settings/src/main/kotlin/dev/meloda/fast/settings/SettingsViewModel.kt b/feature/settings/src/main/kotlin/dev/meloda/fast/settings/SettingsViewModel.kt index 216737e7..f8932c03 100644 --- a/feature/settings/src/main/kotlin/dev/meloda/fast/settings/SettingsViewModel.kt +++ b/feature/settings/src/main/kotlin/dev/meloda/fast/settings/SettingsViewModel.kt @@ -416,8 +416,7 @@ class SettingsViewModelImpl( key = SettingsKeys.KEY_USE_BLUR, defaultValue = SettingsKeys.DEFAULT_USE_BLUR, title = UiText.Resource(UiR.string.settings_experimental_use_blur_title), - text = UiText.Resource(UiR.string.settings_experimental_use_blur_summary), - isVisible = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S + text = UiText.Resource(UiR.string.settings_experimental_use_blur_summary) ) val enableAnimations = SettingsItem.Switch( key = SettingsKeys.KEY_MORE_ANIMATIONS, diff --git a/feature/settings/src/main/kotlin/dev/meloda/fast/settings/presentation/SettingsScreen.kt b/feature/settings/src/main/kotlin/dev/meloda/fast/settings/presentation/SettingsScreen.kt index e83a7af2..7ae4665e 100644 --- a/feature/settings/src/main/kotlin/dev/meloda/fast/settings/presentation/SettingsScreen.kt +++ b/feature/settings/src/main/kotlin/dev/meloda/fast/settings/presentation/SettingsScreen.kt @@ -128,7 +128,7 @@ fun SettingsScreen( val themeConfig = LocalThemeConfig.current - val hazeState = remember { HazeState() } + val hazeState = remember { HazeState(true) } Scaffold( modifier = Modifier.fillMaxSize(), diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index bf9e6f3e..8b1f1991 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,7 +8,7 @@ versionName = "0.2.2" agp = "8.10.0" converterMoshi = "2.11.0" eithernet = "2.0.0" -haze = "1.5.4" +haze = "1.6.0" kotlin = "2.1.20" ksp = "2.1.20-2.0.1"