Группы: восстановление ключей по инвайту и Apple Emoji

- Добавлено восстановление локального ключа группы из инвайта при повторном нажатии, даже если на сервере статус уже JOINED.
- В карточке приглашения сначала восстанавливается ключ, затем открывается группа.
- Включено отображение Apple Emoji для названия/описания группы в GroupInfo и в заголовке группы в чате.
- Обновлён превью-заголовок в GroupSetup на Apple Emoji рендер.
This commit is contained in:
2026-03-06 17:23:11 +05:00
parent 1fb891df53
commit 59d71c9717
5 changed files with 94 additions and 22 deletions

View File

@@ -284,6 +284,47 @@ class GroupRepository private constructor(context: Context) {
) )
} }
/**
* Desktop parity fix:
* if user is already joined on server, repeated invite click should still restore local group key.
*/
suspend fun ensureLocalGroupFromInvite(
accountPublicKey: String,
accountPrivateKey: String,
inviteString: String
): GroupJoinResult {
val parsed = parseInviteString(inviteString)
?: return GroupJoinResult(
success = false,
status = GroupStatus.INVALID,
error = "Invalid invite string"
)
val existingGroupKey = getGroupKey(accountPublicKey, accountPrivateKey, parsed.groupId)
if (!existingGroupKey.isNullOrBlank()) {
return GroupJoinResult(
success = true,
status = GroupStatus.JOINED,
dialogPublicKey = toGroupDialogPublicKey(parsed.groupId),
title = parsed.title
)
}
persistJoinedGroup(
accountPublicKey = accountPublicKey,
accountPrivateKey = accountPrivateKey,
parsedInvite = parsed,
emitSystemJoinMessage = false
)
return GroupJoinResult(
success = true,
status = GroupStatus.JOINED,
dialogPublicKey = toGroupDialogPublicKey(parsed.groupId),
title = parsed.title
)
}
suspend fun synchronizeJoinedGroup( suspend fun synchronizeJoinedGroup(
accountPublicKey: String, accountPublicKey: String,
accountPrivateKey: String, accountPrivateKey: String,

View File

@@ -90,6 +90,7 @@ import com.rosetta.messenger.ui.chats.components.MultiImageEditorScreen
import com.rosetta.messenger.ui.chats.input.* import com.rosetta.messenger.ui.chats.input.*
import com.rosetta.messenger.ui.chats.models.* import com.rosetta.messenger.ui.chats.models.*
import com.rosetta.messenger.ui.chats.utils.* import com.rosetta.messenger.ui.chats.utils.*
import com.rosetta.messenger.ui.components.AppleEmojiText
import com.rosetta.messenger.ui.components.AvatarImage import com.rosetta.messenger.ui.components.AvatarImage
import com.rosetta.messenger.ui.components.VerifiedBadge import com.rosetta.messenger.ui.components.VerifiedBadge
import com.rosetta.messenger.ui.onboarding.PrimaryBlue import com.rosetta.messenger.ui.onboarding.PrimaryBlue
@@ -1133,21 +1134,14 @@ fun ChatDetailScreen(
Alignment Alignment
.CenterVertically .CenterVertically
) { ) {
Text( AppleEmojiText(
text = text = chatTitle,
chatTitle, fontSize = 16.sp,
fontSize = fontWeight = FontWeight.SemiBold,
16.sp, color = Color.White,
fontWeight = maxLines = 1,
FontWeight overflow = android.text.TextUtils.TruncateAt.END,
.SemiBold, enableLinks = false
color =
Color.White,
maxLines =
1,
overflow =
TextOverflow
.Ellipsis
) )
if (!isSavedMessages && if (!isSavedMessages &&
!isGroupChat && !isGroupChat &&

View File

@@ -122,6 +122,7 @@ import com.rosetta.messenger.ui.chats.components.ImageAttachment
import com.rosetta.messenger.ui.chats.components.ImageSourceBounds import com.rosetta.messenger.ui.chats.components.ImageSourceBounds
import com.rosetta.messenger.ui.chats.components.ImageViewerScreen import com.rosetta.messenger.ui.chats.components.ImageViewerScreen
import com.rosetta.messenger.ui.chats.components.ViewableImage import com.rosetta.messenger.ui.chats.components.ViewableImage
import com.rosetta.messenger.ui.components.AppleEmojiText
import com.rosetta.messenger.ui.components.AvatarImage import com.rosetta.messenger.ui.components.AvatarImage
import com.rosetta.messenger.ui.components.VerifiedBadge import com.rosetta.messenger.ui.components.VerifiedBadge
import com.rosetta.messenger.ui.icons.TelegramIcons import com.rosetta.messenger.ui.icons.TelegramIcons
@@ -854,13 +855,14 @@ fun GroupInfoScreen(
Spacer(modifier = Modifier.height(14.dp)) Spacer(modifier = Modifier.height(14.dp))
Text( AppleEmojiText(
text = groupTitle, text = groupTitle,
color = Color.White, color = Color.White,
fontSize = 24.sp, fontSize = 24.sp,
fontWeight = FontWeight.SemiBold, fontWeight = FontWeight.SemiBold,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis overflow = android.text.TextUtils.TruncateAt.END,
enableLinks = false
) )
Text( Text(
@@ -917,12 +919,13 @@ fun GroupInfoScreen(
if (groupDescription.isNotBlank()) { if (groupDescription.isNotBlank()) {
Spacer(modifier = Modifier.height(10.dp)) Spacer(modifier = Modifier.height(10.dp))
Text( AppleEmojiText(
text = groupDescription, text = groupDescription,
color = Color.White.copy(alpha = 0.7f), color = Color.White.copy(alpha = 0.7f),
fontSize = 12.sp, fontSize = 12.sp,
maxLines = 2, maxLines = 2,
overflow = TextOverflow.Ellipsis overflow = android.text.TextUtils.TruncateAt.END,
enableLinks = false
) )
} }
} }

View File

@@ -81,6 +81,7 @@ import com.rosetta.messenger.network.SearchUser
import com.rosetta.messenger.repository.AvatarRepository import com.rosetta.messenger.repository.AvatarRepository
import com.rosetta.messenger.ui.components.KeyboardHeightProvider import com.rosetta.messenger.ui.components.KeyboardHeightProvider
import com.rosetta.messenger.ui.components.OptimizedEmojiPicker import com.rosetta.messenger.ui.components.OptimizedEmojiPicker
import com.rosetta.messenger.ui.components.AppleEmojiText
import com.rosetta.messenger.ui.components.AvatarImage import com.rosetta.messenger.ui.components.AvatarImage
import com.rosetta.messenger.ui.icons.TelegramIcons import com.rosetta.messenger.ui.icons.TelegramIcons
import com.rosetta.messenger.utils.AvatarFileManager import com.rosetta.messenger.utils.AvatarFileManager
@@ -622,13 +623,14 @@ fun GroupSetupScreen(
Spacer(modifier = Modifier.size(12.dp)) Spacer(modifier = Modifier.size(12.dp))
Column(modifier = Modifier.weight(1f)) { Column(modifier = Modifier.weight(1f)) {
Text( AppleEmojiText(
text = title.trim(), text = title.trim(),
color = primaryTextColor, color = primaryTextColor,
fontSize = 16.sp, fontSize = 16.sp,
fontWeight = FontWeight.Medium, fontWeight = FontWeight.Medium,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis overflow = android.text.TextUtils.TruncateAt.END,
enableLinks = false
) )
Spacer(modifier = Modifier.height(2.dp)) Spacer(modifier = Modifier.height(2.dp))
Text( Text(

View File

@@ -1452,7 +1452,39 @@ private fun GroupInviteInlineCard(
if (parsedInvite == null) return if (parsedInvite == null) return
if (status == GroupStatus.JOINED) { if (status == GroupStatus.JOINED) {
openParsedGroup() if (accountPublicKey.isBlank() || accountPrivateKey.isBlank()) {
openParsedGroup()
return
}
scope.launch {
actionLoading = true
val restoreResult =
withContext(Dispatchers.IO) {
groupRepository.ensureLocalGroupFromInvite(
accountPublicKey = accountPublicKey,
accountPrivateKey = accountPrivateKey,
inviteString = normalizedInvite
)
}
actionLoading = false
if (restoreResult.success) {
status = GroupStatus.JOINED
groupRepository.cacheInviteInfo(
parsedInvite.groupId,
GroupStatus.JOINED,
membersCount
)
openParsedGroup()
} else {
Toast.makeText(
context,
restoreResult.error ?: "Failed to restore group access",
Toast.LENGTH_SHORT
).show()
}
}
return return
} }