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 05a1fcab..27286c74 100644 --- a/app/src/main/kotlin/dev/meloda/fast/presentation/MainScreen.kt +++ b/app/src/main/kotlin/dev/meloda/fast/presentation/MainScreen.kt @@ -43,7 +43,6 @@ import dev.meloda.fast.model.BaseError import dev.meloda.fast.model.BottomNavigationItem import dev.meloda.fast.navigation.MainGraph import dev.meloda.fast.profile.navigation.profileScreen -import dev.meloda.fast.ui.theme.LocalBottomPadding import dev.meloda.fast.ui.theme.LocalHazeState import dev.meloda.fast.ui.theme.LocalThemeConfig import dev.meloda.fast.ui.util.ImmutableList @@ -146,11 +145,11 @@ fun MainScreen( Box( modifier = Modifier .fillMaxSize() - .padding(bottom = if (currentTheme.enableBlur) 0.dp else padding.calculateBottomPadding()) + .padding(bottom = padding.calculateBottomPadding()) ) { CompositionLocalProvider( LocalHazeState provides hazeState, - LocalBottomPadding provides if (currentTheme.enableBlur) padding.calculateBottomPadding() else 0.dp +// LocalBottomPadding provides if (currentTheme.enableBlur) padding.calculateBottomPadding() else 0.dp ) { NavHost( navController = navController, diff --git a/app/src/main/kotlin/dev/meloda/fast/service/longpolling/LongPollingService.kt b/app/src/main/kotlin/dev/meloda/fast/service/longpolling/LongPollingService.kt index 4a9089ab..354c1f14 100644 --- a/app/src/main/kotlin/dev/meloda/fast/service/longpolling/LongPollingService.kt +++ b/app/src/main/kotlin/dev/meloda/fast/service/longpolling/LongPollingService.kt @@ -49,6 +49,11 @@ class LongPollingService : Service() { throwable.printStackTrace() } + if (throwable is LongPollException) { + // TODO: 23-Mar-25, Danil Nikolaev: restart LongPoll + return@CoroutineExceptionHandler + } + longPollController.updateCurrentState(LongPollState.Exception) longPollController.setStateToApply(LongPollState.Exception) } @@ -142,7 +147,7 @@ class LongPollingService : Service() { Log.d(STATE_TAG, "job started") - return coroutineScope.launch { + return coroutineScope.launch(coroutineContext) { if (UserConfig.accessToken.isEmpty()) { throw NoAccessTokenException } diff --git a/build-logic/convention/build.gradle.kts b/build-logic/convention/build.gradle.kts index 262012c5..b63df73d 100644 --- a/build-logic/convention/build.gradle.kts +++ b/build-logic/convention/build.gradle.kts @@ -7,13 +7,13 @@ plugins { group = "dev.meloda.fast.buildlogic" java { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 } kotlin { compilerOptions { - jvmTarget = JvmTarget.JVM_17 + jvmTarget = JvmTarget.JVM_21 } } diff --git a/build-logic/convention/src/main/kotlin/dev/meloda/fast/KotlinAndroid.kt b/build-logic/convention/src/main/kotlin/dev/meloda/fast/KotlinAndroid.kt index c0ce7a51..d1238e06 100644 --- a/build-logic/convention/src/main/kotlin/dev/meloda/fast/KotlinAndroid.kt +++ b/build-logic/convention/src/main/kotlin/dev/meloda/fast/KotlinAndroid.kt @@ -23,8 +23,8 @@ internal fun Project.configureKotlinAndroid( } compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 } } @@ -33,8 +33,8 @@ internal fun Project.configureKotlinAndroid( internal fun Project.configureKotlinJvm() { extensions.configure { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 } configureKotlin() @@ -49,7 +49,7 @@ private inline fun Project.configureKotlin() = is KotlinJvmProjectExtension -> compilerOptions else -> TODO("Unsupported project extension $this ${T::class}") }.apply { - jvmTarget = JvmTarget.JVM_17 + jvmTarget = JvmTarget.JVM_21 allWarningsAsErrors = warningsAsErrors.toBoolean() freeCompilerArgs.addAll( "-opt-in=kotlin.RequiresOptIn", diff --git a/feature/friends/src/main/kotlin/dev/meloda/fast/friends/presentation/FriendsList.kt b/feature/friends/src/main/kotlin/dev/meloda/fast/friends/presentation/FriendsList.kt index c63075ee..bcd350c4 100644 --- a/feature/friends/src/main/kotlin/dev/meloda/fast/friends/presentation/FriendsList.kt +++ b/feature/friends/src/main/kotlin/dev/meloda/fast/friends/presentation/FriendsList.kt @@ -46,8 +46,6 @@ fun FriendsList( val coroutineScope = rememberCoroutineScope() - val friends = uiFriends.toList() - LazyColumn( modifier = modifier, state = listState @@ -58,7 +56,7 @@ fun FriendsList( } items( - items = friends, + items = uiFriends.toList(), key = UiFriend::userId, ) { friend -> FriendItem( 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 a52702c2..b599ce3c 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 @@ -1,6 +1,5 @@ package dev.meloda.fast.messageshistory.presentation -import android.content.SharedPreferences import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.animateContentSize import androidx.compose.animation.core.Animatable @@ -78,7 +77,7 @@ import androidx.core.view.HapticFeedbackConstantsCompat import androidx.lifecycle.compose.collectAsStateWithLifecycle import coil.compose.AsyncImage import dev.chrisbanes.haze.HazeState -import dev.chrisbanes.haze.hazeChild +import dev.chrisbanes.haze.hazeEffect import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import dev.chrisbanes.haze.materials.HazeMaterials import dev.meloda.fast.datastore.AppSettings @@ -90,6 +89,7 @@ import dev.meloda.fast.messageshistory.model.MessagesHistoryScreenState import dev.meloda.fast.messageshistory.util.firstMessage import dev.meloda.fast.messageshistory.util.indexOfMessageByCmId import dev.meloda.fast.model.BaseError +import dev.meloda.fast.ui.components.ErrorView import dev.meloda.fast.ui.components.IconButton import dev.meloda.fast.ui.theme.LocalThemeConfig import dev.meloda.fast.ui.util.ImmutableList @@ -119,8 +119,9 @@ fun MessagesHistoryRoute( canPaginate = canPaginate, showEmojiButton = showEmojiButton, onBack = onBack, + onSessionExpiredLogOutButtonClicked = { onError(BaseError.SessionExpired) }, onChatMaterialsDropdownItemClicked = onChatMaterialsDropdownItemClicked, - onRefreshDropdownItemClicked = viewModel::onRefresh, + onRefresh = viewModel::onRefresh, onPaginationConditionsMet = viewModel::onPaginationConditionsMet, onMessageInputChanged = viewModel::onMessageInputChanged, onAttachmentButtonClicked = viewModel::onAttachmentButtonClicked, @@ -141,9 +142,9 @@ fun MessagesHistoryScreen( canPaginate: Boolean = false, showEmojiButton: Boolean = false, onBack: () -> Unit = {}, + onSessionExpiredLogOutButtonClicked: () -> Unit = {}, onChatMaterialsDropdownItemClicked: (peerId: Int, conversationMessageId: Int) -> Unit = { _, _ -> }, - onRefreshDropdownItemClicked: () -> Unit = {}, - onToggleAnimationsDropdownItemClicked: (Boolean) -> Unit = {}, + onRefresh: () -> Unit = {}, onPaginationConditionsMet: () -> Unit = {}, onMessageInputChanged: (TextFieldValue) -> Unit = {}, onAttachmentButtonClicked: () -> Unit = {}, @@ -154,7 +155,6 @@ fun MessagesHistoryScreen( val coroutineScope = rememberCoroutineScope() - val preferences: SharedPreferences = koinInject() val currentTheme = LocalThemeConfig.current val listState = rememberLazyListState() @@ -200,7 +200,7 @@ fun MessagesHistoryScreen( modifier = Modifier .then( if (currentTheme.enableBlur) { - Modifier.hazeChild( + Modifier.hazeEffect( state = hazeState, style = HazeMaterials.thick() ) @@ -237,8 +237,8 @@ fun MessagesHistoryScreen( Text( text = - if (screenState.isLoading) stringResource(id = UiR.string.title_loading) - else screenState.title, + if (screenState.isLoading) stringResource(id = UiR.string.title_loading) + else screenState.title, maxLines = 1, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.headlineSmall @@ -282,7 +282,7 @@ fun MessagesHistoryScreen( // TODO: 11/07/2024, Danil Nikolaev: to VM - // TODO: 23-Mar-25, Danil Nikolaev: crash if not messages (ex. new chat) + // TODO: 23-Mar-25, Danil Nikolaev: crash if no messages (ex. new chat) onChatMaterialsDropdownItemClicked( screenState.conversationId, screenState.messages.firstMessage().conversationMessageId @@ -294,7 +294,7 @@ fun MessagesHistoryScreen( ) DropdownMenuItem( onClick = { - onRefreshDropdownItemClicked() + onRefresh() dropDownMenuExpanded = false }, text = { @@ -323,6 +323,7 @@ fun MessagesHistoryScreen( } } ) { padding -> + Box( modifier = Modifier .fillMaxSize() @@ -386,7 +387,9 @@ fun MessagesHistoryScreen( IconButton( onClick = { if (AppSettings.General.enableHaptic) { - view.performHapticFeedback(HapticFeedbackConstantsCompat.REJECT) + view.performHapticFeedback( + HapticFeedbackConstantsCompat.REJECT + ) } scope.launch { for (i in 20 downTo 0 step 4) { @@ -405,7 +408,9 @@ fun MessagesHistoryScreen( }, onLongClick = { if (AppSettings.General.enableHaptic) { - view.performHapticFeedback(HapticFeedbackConstantsCompat.LONG_PRESS) + view.performHapticFeedback( + HapticFeedbackConstantsCompat.LONG_PRESS + ) } onEmojiButtonLongClicked() }, @@ -447,8 +452,11 @@ fun MessagesHistoryScreen( Column(verticalArrangement = Arrangement.Bottom) { IconButton( onClick = { + onAttachmentButtonClicked() if (AppSettings.General.enableHaptic) { - view.performHapticFeedback(HapticFeedbackConstantsCompat.REJECT) + view.performHapticFeedback( + HapticFeedbackConstantsCompat.REJECT + ) } scope.launch { for (i in 20 downTo 0 step 4) { @@ -484,7 +492,9 @@ fun MessagesHistoryScreen( onClick = { if (screenState.actionMode == ActionMode.Record) { if (AppSettings.General.enableHaptic) { - view.performHapticFeedback(HapticFeedbackConstantsCompat.REJECT) + view.performHapticFeedback( + HapticFeedbackConstantsCompat.REJECT + ) } scope.launch { for (i in 20 downTo 0 step 4) { @@ -535,9 +545,32 @@ fun MessagesHistoryScreen( } } - if (screenState.isLoading && screenState.messages.isEmpty()) { - CircularProgressIndicator(modifier = Modifier.align(Alignment.Center)) + when { + screenState.isLoading && screenState.messages.isEmpty() -> { + CircularProgressIndicator(modifier = Modifier.align(Alignment.Center)) + } + + baseError != null -> { + when (baseError) { + is BaseError.SessionExpired -> { + ErrorView( + text = stringResource(UiR.string.session_expired), + buttonText = stringResource(UiR.string.action_log_out), + onButtonClick = onSessionExpiredLogOutButtonClicked + ) + } + + is BaseError.SimpleError -> { + ErrorView( + text = baseError.message, + buttonText = stringResource(UiR.string.try_again), + onButtonClick = onRefresh + ) + } + } + } } } } } + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cbaa0436..2029a09a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,8 +9,8 @@ agp = "8.9.0" converterMoshi = "2.11.0" eithernet = "2.0.0" haze = "1.5.1" -kotlin = "2.1.10" -ksp = "2.1.10-1.0.31" +kotlin = "2.1.20" +ksp = "2.1.20-1.0.31" compose-bom = "2025.03.00" koin = "4.0.2"