Update API version (#147)

* Bump VK Api version to 5.238
* Implemented new authorization flow (at the moment, without auto re-requesting token)
* Add support for sticker pack preview attachments
* Bump LongPoll to version 19
* Improved messages handling
* Fixed coloring issues
* Cache improvements
* Archive screen with full functionality
* Recomposition fixes
* Markdown support for messages bubbles
* Adjust app name font size based on screen width
* Navigation related improvements
* Add logout functionality
This commit is contained in:
2025-04-04 20:43:59 +03:00
committed by GitHub
parent add67b6f8d
commit 89748b72ed
237 changed files with 4896 additions and 3289 deletions
@@ -0,0 +1,70 @@
package dev.meloda.fast.ui.components
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import dev.meloda.fast.model.BaseError
import dev.meloda.fast.ui.R
@Composable
fun VkErrorView(
modifier: Modifier = Modifier,
baseError: BaseError,
onButtonClick: () -> Unit = {}
) {
when (baseError) {
is BaseError.SessionExpired -> {
ErrorView(
modifier = modifier,
text = stringResource(R.string.session_expired),
buttonText = stringResource(R.string.action_log_out),
onButtonClick = onButtonClick
)
}
is BaseError.SimpleError -> {
ErrorView(
modifier = modifier,
text = baseError.message,
buttonText = stringResource(R.string.try_again),
onButtonClick = onButtonClick
)
}
BaseError.AccountBlocked -> {
ErrorView(
modifier = modifier,
text = "Account blocked",
buttonText = stringResource(R.string.action_log_out),
onButtonClick = onButtonClick
)
}
BaseError.ConnectionError -> {
ErrorView(
modifier = modifier,
text = "Connection error",
buttonText = stringResource(R.string.try_again),
onButtonClick = onButtonClick
)
}
BaseError.InternalError -> {
ErrorView(
modifier = modifier,
text = "Internal error",
buttonText = stringResource(R.string.try_again),
onButtonClick = onButtonClick
)
}
BaseError.UnknownError -> {
ErrorView(
modifier = modifier,
text = "Unknown error",
buttonText = stringResource(R.string.try_again),
onButtonClick = onButtonClick
)
}
}
}
@@ -6,13 +6,27 @@ import androidx.lifecycle.ViewModel
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavController
import org.koin.androidx.compose.koinViewModel
import org.koin.androidx.compose.navigation.koinNavViewModel
import org.koin.core.parameter.ParametersDefinition
import org.koin.core.qualifier.Qualifier
@Composable
inline fun <reified T : ViewModel> NavBackStackEntry.sharedViewModel(navController: NavController): T {
val navGraphRoute = destination.parent?.route ?: return koinViewModel()
inline fun <reified T : ViewModel> NavBackStackEntry.sharedViewModel(
navController: NavController,
route: String? = null,
qualifier: Qualifier? = null,
noinline parameters: ParametersDefinition? = null,
): T {
val navGraphRoute = route ?: destination.parent?.route ?: return koinViewModel(
qualifier = qualifier,
parameters = parameters
)
val parentEntry = remember(this) {
navController.getBackStackEntry(navGraphRoute)
}
return koinNavViewModel(viewModelStoreOwner = parentEntry)
return koinViewModel(
viewModelStoreOwner = parentEntry,
qualifier = qualifier,
parameters = parameters
)
}
@@ -28,4 +28,14 @@ sealed class ConversationOption(
title = UiText.Resource(R.string.action_delete),
icon = UiImage.Resource(R.drawable.round_delete_outline_24)
)
data object Archive : ConversationOption(
title = UiText.Resource(R.string.conversation_context_action_archive),
icon = UiImage.Resource(R.drawable.outline_archive_24)
)
data object Unarchive : ConversationOption(
title = UiText.Resource(R.string.conversation_context_action_unarchive),
icon = UiImage.Resource(R.drawable.outline_unarchive_24)
)
}
@@ -1,14 +0,0 @@
package dev.meloda.fast.ui.model.api
data class ConversationsShowOptions(
val showDeleteDialog: Int?,
val showPinDialog: UiConversation?
) {
companion object {
val EMPTY: ConversationsShowOptions = ConversationsShowOptions(
showDeleteDialog = null,
showPinDialog = null
)
}
}
@@ -9,8 +9,8 @@ import dev.meloda.fast.ui.util.ImmutableList
@Immutable
data class UiConversation(
val id: Int,
val lastMessageId: Int?,
val id: Long,
val lastMessageId: Long?,
val avatar: UiImage?,
val title: String,
val unreadCount: String?,
@@ -27,5 +27,6 @@ data class UiConversation(
val peerType: PeerType,
val interactionText: String?,
val isExpanded: Boolean,
val isArchived: Boolean,
val options: ImmutableList<ConversationOption>,
)
@@ -6,7 +6,7 @@ import dev.meloda.fast.model.api.domain.OnlineStatus
@Immutable
data class UiFriend(
val userId: Int,
val userId: Long,
val avatar: UiImage?,
val firstName: String,
val lastName: String,
@@ -2,6 +2,7 @@ package dev.meloda.fast.ui.theme
import android.app.Activity
import android.os.Build
import androidx.compose.animation.animateColorAsState
import androidx.compose.material3.ColorScheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
@@ -9,8 +10,10 @@ import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ProvidableCompositionLocal
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.getValue
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
@@ -20,6 +23,7 @@ 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 androidx.navigation.NavController
import dev.chrisbanes.haze.HazeState
import dev.meloda.fast.model.api.domain.VkUser
import dev.meloda.fast.ui.R
@@ -129,7 +133,14 @@ val LocalSizeConfig = compositionLocalOf {
val LocalHazeState = compositionLocalOf { HazeState() }
val LocalBottomPadding = compositionLocalOf { 0.dp }
val LocalUser = compositionLocalOf<VkUser?> { null }
val LocalScrollToTop = compositionLocalOf { mapOf<Any, Boolean>() }
val LocalReselectedTab = compositionLocalOf { mapOf<Any, Boolean>() }
val LocalNavRootController = compositionLocalOf<NavController?> { null }
val LocalNavController = compositionLocalOf<NavController?> { null }
@Composable
fun <T: NavController> ProvidableCompositionLocal<T?>.getOrThrow(): T {
return requireNotNull(current)
}
@Composable
fun AppTheme(
@@ -141,9 +152,10 @@ fun AppTheme(
selectedColorScheme: Int = 0,
content: @Composable () -> Unit
) {
val context = LocalContext.current
val colorScheme: ColorScheme = when {
useDynamicColors && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (useDarkTheme) dynamicDarkColorScheme(context)
else dynamicLightColorScheme(context)
}
@@ -166,6 +178,10 @@ fun AppTheme(
}
}
val colorPrimary by animateColorAsState(colorScheme.primary)
val colorSurface by animateColorAsState(colorScheme.surface)
val colorBackground by animateColorAsState(colorScheme.background)
val typography = if (useSystemFont) {
MaterialTheme.typography
} else {
@@ -198,7 +214,12 @@ fun AppTheme(
}
MaterialTheme(
colorScheme = predefinedColorScheme ?: colorScheme,
colorScheme = (predefinedColorScheme ?: colorScheme)
.copy(
primary = colorPrimary,
background = colorBackground,
surface = colorSurface
),
typography = typography,
content = content
)
@@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M20.54,5.23l-1.39,-1.68C18.88,3.21 18.47,3 18,3L6,3c-0.47,0 -0.88,0.21 -1.16,0.55L3.46,5.23C3.17,5.57 3,6.02 3,6.5L3,19c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,6.5c0,-0.48 -0.17,-0.93 -0.46,-1.27zM6.24,5h11.52l0.81,0.97L5.44,5.97l0.8,-0.97zM5,19L5,8h14v11L5,19zM13.45,10h-2.9v3L8,13l4,4 4,-4h-2.55z" />
</vector>
@@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M20.54,5.23l-1.39,-1.68C18.88,3.21 18.47,3 18,3L6,3c-0.47,0 -0.88,0.21 -1.16,0.55L3.46,5.23C3.17,5.57 3,6.02 3,6.5L3,19c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,6.5c0,-0.48 -0.17,-0.93 -0.46,-1.27zM6.24,5h11.52l0.83,1L5.42,6l0.82,-1zM5,19L5,8h14v11L5,19zM8,14h2.55v3h2.9v-3L16,14l-4,-4z" />
</vector>
+11 -1
View File
@@ -24,6 +24,7 @@
<string name="message_mark_as_spam">Пометить как спам</string>
<string name="action_mark_as_read">Прочитать</string>
<string name="action_delete">Удалить</string>
<string name="conversation_context_action_unarchive">Из архива</string>
<string name="conversation_context_action_delete">Удалить</string>
<string name="confirm_delete_conversation">Удалить чат?</string>
<string name="action_sign_out">Выйти</string>
@@ -32,10 +33,12 @@
<string name="conversation_context_action_pin">Закрепить</string>
<string name="confirm_unpin_conversation">Открепить чат?</string>
<string name="confirm_pin_conversation">Закрепить чат?</string>
<string name="confirm_unarchive_conversation">Разархивировать чат?</string>
<string name="action_pin">Закрепить</string>
<string name="action_unpin">Открепить</string>
<string name="action_mark">Пометить</string>
<string name="action_unmark">Убрать пометку</string>
<string name="action_unarchive">Из архива</string>
<string name="message_call_type_outgoing">Исходящий вызов</string>
<string name="message_call_type_incoming">Входящий вызов</string>
<string name="message_call_state_ended">Закончился</string>
@@ -125,10 +128,13 @@
<string name="message_attachments_podcast">Подкаст</string>
<string name="message_attachments_narrative">Момент</string>
<string name="message_attachments_article">Статья</string>
<string name="message_attachments_video_message">Видеосообщение</string>
<string name="message_attachments_group_sticker">Стикер группы</string>
<string name="message_attachments_sticker_pack_preview">Превью стикерпака</string>
<string name="chat_interaction_uploading_file">Загрузка файла</string>
<string name="chat_interaction_uploading_photo">Загрузка фото</string>
<string name="chat_interaction_uploading_video">Загрузка видео</string>
<string name="chat_interaction_typing">Печатает</string>
<string name="chat_interaction_typing">печатает</string>
<string name="chat_interaction_recording_audio_message">Записывает</string>
<string name="chat_interaction_chat_typing">%1$s печатают</string>
<string name="chat_interaction_chat_single_typing">%1$s печатает</string>
@@ -175,6 +181,7 @@
<string name="members_count">Участники: %1$d</string>
<string name="title_loading">Загрузка&#8230;</string>
<string name="title_conversations">Чаты</string>
<string name="title_archive">Архив</string>
<string name="title_friends">Друзья</string>
<string name="title_profile">Профиль</string>
<string name="title_friends_all">Все</string>
@@ -253,4 +260,7 @@
<string name="unspam_message_text">Вы уверены, что хотите убрать пометку спама у этого сообщения?</string>
<string name="pin_message_title">Закрепить сообщение</string>
<string name="copied_to_clipboard">Скопировано в буфер обмена</string>
<string name="conversation_context_action_archive">В архив</string>
<string name="confirm_archive_conversation">Архивировать чат?</string>
<string name="action_archive">В архив</string>
</resources>
+11 -1
View File
@@ -104,11 +104,14 @@
<string name="message_attachments_podcast">Podcast</string>
<string name="message_attachments_narrative">Narrative</string>
<string name="message_attachments_article">Article</string>
<string name="message_attachments_video_message">Video message</string>
<string name="message_attachments_group_sticker">Group sticker</string>
<string name="message_attachments_sticker_pack_preview">Sticker pack preview</string>
<string name="chat_interaction_uploading_file">Uploading file</string>
<string name="chat_interaction_uploading_photo">Uploading photo</string>
<string name="chat_interaction_uploading_video">Uploading video</string>
<string name="chat_interaction_typing">Typing</string>
<string name="chat_interaction_typing">typing</string>
<string name="chat_interaction_recording_audio_message">Recording</string>
<string name="chat_interaction_chat_typing">%1$s are typing</string>
@@ -149,6 +152,8 @@
<string name="action_mark_as_read">Read</string>
<string name="action_delete">Delete</string>
<string name="conversation_context_action_archive">Archive</string>
<string name="conversation_context_action_unarchive">Unarchive</string>
<string name="conversation_context_action_delete">Delete</string>
<string name="confirm_delete_conversation">Delete the conversation?</string>
<string name="action_sign_out">Sign out</string>
@@ -157,10 +162,14 @@
<string name="conversation_context_action_pin">Pin</string>
<string name="confirm_unpin_conversation">Unpin the conversation?</string>
<string name="confirm_pin_conversation">Pin the conversation?</string>
<string name="confirm_archive_conversation">Archive the conversation?</string>
<string name="confirm_unarchive_conversation">Unarchive the conversation?</string>
<string name="action_pin">Pin</string>
<string name="action_unpin">Unpin</string>
<string name="action_mark">Mark</string>
<string name="action_unmark">Unmark</string>
<string name="action_archive">Archive</string>
<string name="action_unarchive">Unarchive</string>
<string name="message_call_type_outgoing">Outgoing call</string>
<string name="message_call_type_incoming">Incoming call</string>
<string name="message_call_state_ended">Ended</string>
@@ -236,6 +245,7 @@
<string name="members_count">Members: %1$d</string>
<string name="title_loading">Loading&#8230;</string>
<string name="title_conversations">Conversations</string>
<string name="title_archive">Archive</string>
<string name="title_friends">Friends</string>
<string name="title_profile">Profile</string>
<string name="title_friends_all">All</string>