QR-коды: экран профиля в стиле Telegram (5 тем, цветной QR, логотип, аватар), сканер (CameraX + ML Kit), deep links (rosetta:// + rosetta.im), Scan QR в drawer, Share/Copy. Фикс base64 prefix в аватарках. Call: кнопка на чужом профиле, анимированный градиентный фон (iOS parity), мгновенный rejected call. Статус-бар: чёрные иконки на белом фоне + restore при уходе. Удалены dev-логи.

This commit is contained in:
2026-04-08 19:10:53 +05:00
parent 8bfbba3159
commit 325073fc09
22 changed files with 1036 additions and 251 deletions

View File

@@ -671,6 +671,8 @@ sealed class Screen {
data object CrashLogs : Screen()
data object Biometric : Screen()
data object Appearance : Screen()
data object QrScanner : Screen()
data object MyQr : Screen()
}
@Composable
@@ -1028,6 +1030,8 @@ fun MainScreen(
val isAppearanceVisible by remember {
derivedStateOf { navStack.any { it is Screen.Appearance } }
}
val isQrScannerVisible by remember { derivedStateOf { navStack.any { it is Screen.QrScanner } } }
val isMyQrVisible by remember { derivedStateOf { navStack.any { it is Screen.MyQr } } }
var profileHasUnsavedChanges by remember(accountPublicKey) { mutableStateOf(false) }
var showDiscardProfileChangesDialog by remember { mutableStateOf(false) }
var discardProfileChangesFromChat by remember { mutableStateOf(false) }
@@ -1240,7 +1244,9 @@ fun MainScreen(
onAddAccount()
},
onSwitchAccount = onSwitchAccount,
onDeleteAccountFromSidebar = onDeleteAccountFromSidebar
onDeleteAccountFromSidebar = onDeleteAccountFromSidebar,
onQrScanClick = { pushScreen(Screen.QrScanner) },
onMyQrClick = { pushScreen(Screen.MyQr) }
)
}
@@ -1304,6 +1310,7 @@ fun MainScreen(
onNavigateToSafety = { pushScreen(Screen.Safety) },
onNavigateToLogs = { pushScreen(Screen.Logs) },
onNavigateToBiometric = { pushScreen(Screen.Biometric) },
onNavigateToMyQr = { pushScreen(Screen.MyQr) },
viewModel = profileViewModel,
avatarRepository = avatarRepository,
dialogDao = RosettaDatabase.getDatabase(context).dialogDao(),
@@ -1542,6 +1549,7 @@ fun MainScreen(
onNavigateToSafety = { pushScreen(Screen.Safety) },
onNavigateToLogs = { pushScreen(Screen.Logs) },
onNavigateToBiometric = { pushScreen(Screen.Biometric) },
onNavigateToMyQr = { pushScreen(Screen.MyQr) },
viewModel = profileViewModel,
avatarRepository = avatarRepository,
dialogDao = RosettaDatabase.getDatabase(context).dialogDao(),
@@ -1710,6 +1718,9 @@ fun MainScreen(
navStack = navStack.filterNot {
it is Screen.OtherProfile || it is Screen.ChatDetail
} + Screen.ChatDetail(chatUser)
},
onCall = { callableUser ->
startCallWithPermission(callableUser)
}
)
}
@@ -1788,6 +1799,74 @@ fun MainScreen(
)
}
// QR Scanner
SwipeBackContainer(
isVisible = isQrScannerVisible,
onBack = { navStack = navStack.filterNot { it is Screen.QrScanner } },
isDarkTheme = isDarkTheme,
layer = 3
) {
com.rosetta.messenger.ui.qr.QrScannerScreen(
onBack = { navStack = navStack.filterNot { it is Screen.QrScanner } },
onResult = { result ->
navStack = navStack.filterNot { it is Screen.QrScanner }
when (result.type) {
com.rosetta.messenger.ui.qr.QrResultType.PROFILE -> {
mainScreenScope.launch {
val users = com.rosetta.messenger.network.ProtocolManager.searchUsers(result.payload, 5000)
val user = users.firstOrNull()
if (user != null) {
pushScreen(Screen.OtherProfile(user))
} else {
val searchUser = com.rosetta.messenger.network.SearchUser(
publicKey = result.payload,
title = "", username = "", verified = 0, online = 0
)
pushScreen(Screen.ChatDetail(searchUser))
}
}
}
com.rosetta.messenger.ui.qr.QrResultType.GROUP -> {
mainScreenScope.launch {
val groupRepo = com.rosetta.messenger.data.GroupRepository.getInstance(context)
val joinResult = groupRepo.joinGroup(accountPublicKey, accountPrivateKey, result.payload)
if (joinResult.success && !joinResult.dialogPublicKey.isNullOrBlank()) {
val groupUser = com.rosetta.messenger.network.SearchUser(
publicKey = joinResult.dialogPublicKey,
title = joinResult.title.ifBlank { "Group" },
username = "", verified = 0, online = 0
)
pushScreen(Screen.ChatDetail(groupUser))
}
}
}
else -> {}
}
}
)
}
// My QR Code
SwipeBackContainer(
isVisible = isMyQrVisible,
onBack = { navStack = navStack.filterNot { it is Screen.MyQr } },
isDarkTheme = isDarkTheme,
layer = 2
) {
com.rosetta.messenger.ui.qr.MyQrCodeScreen(
isDarkTheme = isDarkTheme,
publicKey = accountPublicKey,
displayName = accountName,
username = accountUsername,
avatarRepository = avatarRepository,
onBack = { navStack = navStack.filterNot { it is Screen.MyQr } },
onScanQr = {
navStack = navStack.filterNot { it is Screen.MyQr }
pushScreen(Screen.QrScanner)
}
)
}
if (isCallScreenVisible) {
// Блокируем любой ввод по нижележащим экранам, пока открыт полноэкранный CallOverlay.
// Иначе тапы могут "пробивать" в чат (иконка звонка, kebab, input и т.д.).