Refactor code structure for improved readability and maintainability
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
package com.rosetta.messenger
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
@@ -11,110 +10,115 @@ import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||
import androidx.compose.animation.core.FastOutLinearInEasing
|
||||
import androidx.compose.animation.core.LinearOutSlowInEasing
|
||||
import androidx.compose.animation.core.LinearEasing
|
||||
import androidx.compose.animation.core.Spring
|
||||
import androidx.compose.animation.core.spring
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.firebase.FirebaseApp
|
||||
import com.google.firebase.messaging.FirebaseMessaging
|
||||
import com.rosetta.messenger.crypto.CryptoManager
|
||||
import com.rosetta.messenger.data.AccountManager
|
||||
import com.rosetta.messenger.data.DecryptedAccount
|
||||
import com.rosetta.messenger.data.PreferencesManager
|
||||
import com.rosetta.messenger.data.RecentSearchesManager
|
||||
import com.rosetta.messenger.network.PacketPushNotification
|
||||
import com.rosetta.messenger.network.PushNotificationAction
|
||||
import com.rosetta.messenger.network.ProtocolManager
|
||||
import com.rosetta.messenger.network.ProtocolState
|
||||
import com.rosetta.messenger.network.PushNotificationAction
|
||||
import com.rosetta.messenger.network.SearchUser
|
||||
import com.rosetta.messenger.ui.auth.AccountInfo
|
||||
import com.rosetta.messenger.ui.auth.AuthFlow
|
||||
import com.rosetta.messenger.ui.chats.ChatsListScreen
|
||||
import com.rosetta.messenger.ui.chats.ChatDetailScreen
|
||||
import com.rosetta.messenger.ui.chats.ChatsListScreen
|
||||
import com.rosetta.messenger.ui.chats.SearchScreen
|
||||
import com.rosetta.messenger.network.SearchUser
|
||||
import com.rosetta.messenger.ui.components.EmojiCache
|
||||
import com.rosetta.messenger.ui.components.OptimizedEmojiCache
|
||||
import com.rosetta.messenger.ui.onboarding.OnboardingScreen
|
||||
import com.rosetta.messenger.ui.splash.SplashScreen
|
||||
import com.rosetta.messenger.ui.theme.RosettaAndroidTheme
|
||||
import kotlinx.coroutines.launch
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
private lateinit var preferencesManager: PreferencesManager
|
||||
private lateinit var accountManager: AccountManager
|
||||
|
||||
|
||||
companion object {
|
||||
private const val TAG = "MainActivity"
|
||||
|
||||
// 🔔 FCM Логи для отображения в UI
|
||||
private val _fcmLogs = mutableStateListOf<String>()
|
||||
val fcmLogs: List<String>
|
||||
get() = _fcmLogs
|
||||
|
||||
fun addFcmLog(message: String) {
|
||||
val timestamp = SimpleDateFormat("HH:mm:ss", Locale.getDefault()).format(Date())
|
||||
_fcmLogs.add(0, "[$timestamp] $message") // Добавляем в начало списка
|
||||
// Ограничиваем количество логов
|
||||
if (_fcmLogs.size > 20) {
|
||||
_fcmLogs.removeAt(_fcmLogs.size - 1)
|
||||
}
|
||||
Log.d(TAG, "FCM: $message")
|
||||
}
|
||||
|
||||
fun clearFcmLogs() {
|
||||
_fcmLogs.clear()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
enableEdgeToEdge()
|
||||
|
||||
|
||||
preferencesManager = PreferencesManager(this)
|
||||
accountManager = AccountManager(this)
|
||||
RecentSearchesManager.init(this)
|
||||
|
||||
|
||||
// 🔥 Инициализируем ProtocolManager для обработки онлайн статусов
|
||||
ProtocolManager.initialize(this)
|
||||
|
||||
|
||||
// 🔔 Инициализируем Firebase для push-уведомлений
|
||||
initializeFirebase()
|
||||
|
||||
|
||||
// 🔥 Помечаем что приложение в foreground
|
||||
com.rosetta.messenger.push.RosettaFirebaseMessagingService.isAppInForeground = true
|
||||
|
||||
|
||||
// 📱 Предзагружаем эмодзи в фоне для мгновенного открытия пикера
|
||||
// Используем новый оптимизированный кэш
|
||||
OptimizedEmojiCache.preload(this)
|
||||
|
||||
setContent { // 🔔 Запрос разрешения на уведомления для Android 13+
|
||||
val notificationPermissionLauncher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.RequestPermission(),
|
||||
onResult = { isGranted ->
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
setContent { // 🔔 Запрос разрешения на уведомления для Android 13+
|
||||
val notificationPermissionLauncher =
|
||||
rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.RequestPermission(),
|
||||
onResult = { isGranted -> }
|
||||
)
|
||||
|
||||
// Запрашиваем разрешение при первом запуске (Android 13+)
|
||||
LaunchedEffect(Unit) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
val hasPermission = ContextCompat.checkSelfPermission(
|
||||
this@MainActivity,
|
||||
Manifest.permission.POST_NOTIFICATIONS
|
||||
) == PackageManager.PERMISSION_GRANTED
|
||||
|
||||
val hasPermission =
|
||||
ContextCompat.checkSelfPermission(
|
||||
this@MainActivity,
|
||||
Manifest.permission.POST_NOTIFICATIONS
|
||||
) == PackageManager.PERMISSION_GRANTED
|
||||
|
||||
if (!hasPermission) {
|
||||
notificationPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
|
||||
notificationPermissionLauncher.launch(
|
||||
Manifest.permission.POST_NOTIFICATIONS
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
val isDarkTheme by preferencesManager.isDarkTheme.collectAsState(initial = true)
|
||||
val isLoggedIn by accountManager.isLoggedIn.collectAsState(initial = null)
|
||||
@@ -123,152 +127,179 @@ class MainActivity : ComponentActivity() {
|
||||
var hasExistingAccount by remember { mutableStateOf<Boolean?>(null) }
|
||||
var currentAccount by remember { mutableStateOf<DecryptedAccount?>(null) }
|
||||
var accountInfoList by remember { mutableStateOf<List<AccountInfo>>(emptyList()) }
|
||||
|
||||
|
||||
// Check for existing accounts and build AccountInfo list
|
||||
// Also force logout so user always sees unlock screen on app restart
|
||||
LaunchedEffect(Unit) {
|
||||
accountManager.logout() // Always start logged out
|
||||
val accounts = accountManager.getAllAccounts()
|
||||
hasExistingAccount = accounts.isNotEmpty()
|
||||
accountInfoList = accounts.map { account ->
|
||||
val shortKey = account.publicKey.take(7)
|
||||
val displayName = account.name ?: shortKey
|
||||
val initials = displayName.trim().split(Regex("\\s+"))
|
||||
.filter { it.isNotEmpty() }
|
||||
.let { words ->
|
||||
when {
|
||||
words.isEmpty() -> "??"
|
||||
words.size == 1 -> words[0].take(2).uppercase()
|
||||
else -> "${words[0].first()}${words[1].first()}".uppercase()
|
||||
}
|
||||
accountInfoList =
|
||||
accounts.map { account ->
|
||||
val shortKey = account.publicKey.take(7)
|
||||
val displayName = account.name ?: shortKey
|
||||
val initials =
|
||||
displayName
|
||||
.trim()
|
||||
.split(Regex("\\s+"))
|
||||
.filter { it.isNotEmpty() }
|
||||
.let { words ->
|
||||
when {
|
||||
words.isEmpty() -> "??"
|
||||
words.size == 1 -> words[0].take(2).uppercase()
|
||||
else ->
|
||||
"${words[0].first()}${words[1].first()}".uppercase()
|
||||
}
|
||||
}
|
||||
AccountInfo(
|
||||
id = account.publicKey,
|
||||
name = displayName,
|
||||
initials = initials,
|
||||
publicKey = account.publicKey
|
||||
)
|
||||
}
|
||||
AccountInfo(
|
||||
id = account.publicKey,
|
||||
name = displayName,
|
||||
initials = initials,
|
||||
publicKey = account.publicKey
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Wait for initial load
|
||||
if (hasExistingAccount == null) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(if (isDarkTheme) Color(0xFF1B1B1B) else Color.White)
|
||||
modifier =
|
||||
Modifier.fillMaxSize()
|
||||
.background(
|
||||
if (isDarkTheme) Color(0xFF1B1B1B) else Color.White
|
||||
)
|
||||
)
|
||||
return@setContent
|
||||
}
|
||||
|
||||
RosettaAndroidTheme(
|
||||
darkTheme = isDarkTheme,
|
||||
animated = true
|
||||
) {
|
||||
|
||||
RosettaAndroidTheme(darkTheme = isDarkTheme, animated = true) {
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
color = if (isDarkTheme) Color(0xFF1B1B1B) else Color.White
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
color = if (isDarkTheme) Color(0xFF1B1B1B) else Color.White
|
||||
) {
|
||||
AnimatedContent(
|
||||
targetState = when {
|
||||
showSplash -> "splash"
|
||||
showOnboarding && hasExistingAccount == false -> "onboarding"
|
||||
isLoggedIn != true && hasExistingAccount == false -> "auth_new"
|
||||
isLoggedIn != true && hasExistingAccount == true -> "auth_unlock"
|
||||
else -> "main"
|
||||
},
|
||||
transitionSpec = {
|
||||
fadeIn(animationSpec = tween(600)) togetherWith
|
||||
fadeOut(animationSpec = tween(600))
|
||||
},
|
||||
label = "screenTransition"
|
||||
targetState =
|
||||
when {
|
||||
showSplash -> "splash"
|
||||
showOnboarding && hasExistingAccount == false ->
|
||||
"onboarding"
|
||||
isLoggedIn != true && hasExistingAccount == false ->
|
||||
"auth_new"
|
||||
isLoggedIn != true && hasExistingAccount == true ->
|
||||
"auth_unlock"
|
||||
else -> "main"
|
||||
},
|
||||
transitionSpec = {
|
||||
fadeIn(animationSpec = tween(600)) togetherWith
|
||||
fadeOut(animationSpec = tween(600))
|
||||
},
|
||||
label = "screenTransition"
|
||||
) { screen ->
|
||||
when (screen) {
|
||||
"splash" -> {
|
||||
SplashScreen(
|
||||
isDarkTheme = isDarkTheme,
|
||||
onSplashComplete = { showSplash = false }
|
||||
isDarkTheme = isDarkTheme,
|
||||
onSplashComplete = { showSplash = false }
|
||||
)
|
||||
}
|
||||
"onboarding" -> {
|
||||
OnboardingScreen(
|
||||
isDarkTheme = isDarkTheme,
|
||||
onThemeToggle = {
|
||||
scope.launch {
|
||||
preferencesManager.setDarkTheme(!isDarkTheme)
|
||||
}
|
||||
},
|
||||
onStartMessaging = {
|
||||
showOnboarding = false
|
||||
}
|
||||
isDarkTheme = isDarkTheme,
|
||||
onThemeToggle = {
|
||||
scope.launch {
|
||||
preferencesManager.setDarkTheme(!isDarkTheme)
|
||||
}
|
||||
},
|
||||
onStartMessaging = { showOnboarding = false }
|
||||
)
|
||||
}
|
||||
"auth_new", "auth_unlock" -> {
|
||||
AuthFlow(
|
||||
isDarkTheme = isDarkTheme,
|
||||
hasExistingAccount = screen == "auth_unlock",
|
||||
accounts = accountInfoList,
|
||||
accountManager = accountManager,
|
||||
onAuthComplete = { account ->
|
||||
currentAccount = account
|
||||
hasExistingAccount = true
|
||||
// Save as last logged account
|
||||
account?.let { accountManager.setLastLoggedPublicKey(it.publicKey) }
|
||||
|
||||
// 📤 Отправляем FCM токен на сервер после успешной аутентификации
|
||||
account?.let { sendFcmTokenToServer(it) }
|
||||
|
||||
// Reload accounts list
|
||||
scope.launch {
|
||||
val accounts = accountManager.getAllAccounts()
|
||||
accountInfoList = accounts.map { acc ->
|
||||
val shortKey = acc.publicKey.take(7)
|
||||
val displayName = acc.name ?: shortKey
|
||||
val initials = displayName.trim().split(Regex("\\s+"))
|
||||
.filter { it.isNotEmpty() }
|
||||
.let { words ->
|
||||
when {
|
||||
words.isEmpty() -> "??"
|
||||
words.size == 1 -> words[0].take(2).uppercase()
|
||||
else -> "${words[0].first()}${words[1].first()}".uppercase()
|
||||
isDarkTheme = isDarkTheme,
|
||||
hasExistingAccount = screen == "auth_unlock",
|
||||
accounts = accountInfoList,
|
||||
accountManager = accountManager,
|
||||
onAuthComplete = { account ->
|
||||
currentAccount = account
|
||||
hasExistingAccount = true
|
||||
// Save as last logged account
|
||||
account?.let {
|
||||
accountManager.setLastLoggedPublicKey(it.publicKey)
|
||||
}
|
||||
|
||||
// 📤 Отправляем FCM токен на сервер после успешной
|
||||
// аутентификации
|
||||
account?.let { sendFcmTokenToServer(it) }
|
||||
|
||||
// Reload accounts list
|
||||
scope.launch {
|
||||
val accounts = accountManager.getAllAccounts()
|
||||
accountInfoList =
|
||||
accounts.map { acc ->
|
||||
val shortKey = acc.publicKey.take(7)
|
||||
val displayName = acc.name ?: shortKey
|
||||
val initials =
|
||||
displayName
|
||||
.trim()
|
||||
.split(Regex("\\s+"))
|
||||
.filter {
|
||||
it.isNotEmpty()
|
||||
}
|
||||
.let { words ->
|
||||
when {
|
||||
words.isEmpty() ->
|
||||
"??"
|
||||
words.size ==
|
||||
1 ->
|
||||
words[0]
|
||||
.take(
|
||||
2
|
||||
)
|
||||
.uppercase()
|
||||
else ->
|
||||
"${words[0].first()}${words[1].first()}".uppercase()
|
||||
}
|
||||
}
|
||||
AccountInfo(
|
||||
id = acc.publicKey,
|
||||
name = displayName,
|
||||
initials = initials,
|
||||
publicKey = acc.publicKey
|
||||
)
|
||||
}
|
||||
}
|
||||
AccountInfo(
|
||||
id = acc.publicKey,
|
||||
name = displayName,
|
||||
initials = initials,
|
||||
publicKey = acc.publicKey
|
||||
)
|
||||
}
|
||||
},
|
||||
onLogout = {
|
||||
// Set currentAccount to null immediately to prevent UI
|
||||
// lag
|
||||
currentAccount = null
|
||||
scope.launch {
|
||||
com.rosetta.messenger.network.ProtocolManager
|
||||
.disconnect()
|
||||
accountManager.logout()
|
||||
}
|
||||
}
|
||||
},
|
||||
onLogout = {
|
||||
// Set currentAccount to null immediately to prevent UI lag
|
||||
currentAccount = null
|
||||
scope.launch {
|
||||
com.rosetta.messenger.network.ProtocolManager.disconnect()
|
||||
accountManager.logout()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
"main" -> {
|
||||
MainScreen(
|
||||
account = currentAccount,
|
||||
isDarkTheme = isDarkTheme,
|
||||
onToggleTheme = {
|
||||
scope.launch {
|
||||
preferencesManager.setDarkTheme(!isDarkTheme)
|
||||
account = currentAccount,
|
||||
isDarkTheme = isDarkTheme,
|
||||
onToggleTheme = {
|
||||
scope.launch {
|
||||
preferencesManager.setDarkTheme(!isDarkTheme)
|
||||
}
|
||||
},
|
||||
onLogout = {
|
||||
// Set currentAccount to null immediately to prevent UI
|
||||
// lag
|
||||
currentAccount = null
|
||||
scope.launch {
|
||||
com.rosetta.messenger.network.ProtocolManager
|
||||
.disconnect()
|
||||
accountManager.logout()
|
||||
}
|
||||
}
|
||||
},
|
||||
onLogout = {
|
||||
// Set currentAccount to null immediately to prevent UI lag
|
||||
currentAccount = null
|
||||
scope.launch {
|
||||
com.rosetta.messenger.network.ProtocolManager.disconnect()
|
||||
accountManager.logout()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -277,86 +308,107 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
// 🔥 Приложение стало видимым - отключаем уведомления
|
||||
com.rosetta.messenger.push.RosettaFirebaseMessagingService.isAppInForeground = true
|
||||
}
|
||||
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
// 🔥 Приложение ушло в background - включаем уведомления
|
||||
com.rosetta.messenger.push.RosettaFirebaseMessagingService.isAppInForeground = false
|
||||
}
|
||||
|
||||
/**
|
||||
* 🔔 Инициализация Firebase Cloud Messaging
|
||||
*/
|
||||
|
||||
/** 🔔 Инициализация Firebase Cloud Messaging */
|
||||
private fun initializeFirebase() {
|
||||
try {
|
||||
addFcmLog("🔔 Инициализация Firebase...")
|
||||
// Инициализируем Firebase
|
||||
FirebaseApp.initializeApp(this)
|
||||
|
||||
addFcmLog("✅ Firebase инициализирован")
|
||||
|
||||
// Получаем FCM токен
|
||||
addFcmLog("📲 Запрос FCM токена...")
|
||||
FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->
|
||||
if (!task.isSuccessful) {
|
||||
addFcmLog("❌ Ошибка получения токена: ${task.exception?.message}")
|
||||
return@addOnCompleteListener
|
||||
}
|
||||
|
||||
|
||||
val token = task.result
|
||||
|
||||
// Сохраняем токен локально
|
||||
token?.let { saveFcmToken(it) }
|
||||
|
||||
|
||||
if (token != null) {
|
||||
val shortToken = "${token.take(12)}...${token.takeLast(8)}"
|
||||
addFcmLog("✅ FCM токен получен: $shortToken")
|
||||
// Сохраняем токен локально
|
||||
saveFcmToken(token)
|
||||
addFcmLog("💾 Токен сохранен локально")
|
||||
} else {
|
||||
addFcmLog("⚠️ Токен пустой")
|
||||
}
|
||||
|
||||
// Токен будет отправлен на сервер после успешной аутентификации
|
||||
// (см. вызов sendFcmTokenToServer в onAccountLogin)
|
||||
}
|
||||
|
||||
} catch (e: Exception) {
|
||||
addFcmLog("❌ Ошибка Firebase: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Сохранить FCM токен в SharedPreferences
|
||||
*/
|
||||
|
||||
/** Сохранить FCM токен в SharedPreferences */
|
||||
private fun saveFcmToken(token: String) {
|
||||
val prefs = getSharedPreferences("rosetta_prefs", MODE_PRIVATE)
|
||||
prefs.edit().putString("fcm_token", token).apply()
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Отправить FCM токен на сервер
|
||||
* Вызывается после успешной аутентификации, когда аккаунт уже расшифрован
|
||||
* Отправить FCM токен на сервер Вызывается после успешной аутентификации, когда аккаунт уже
|
||||
* расшифрован
|
||||
*/
|
||||
private fun sendFcmTokenToServer(account: DecryptedAccount) {
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
val prefs = getSharedPreferences("rosetta_prefs", MODE_PRIVATE)
|
||||
val token = prefs.getString("fcm_token", null)
|
||||
|
||||
|
||||
if (token == null) {
|
||||
addFcmLog("⚠️ Нет сохраненного токена для отправки")
|
||||
return@launch
|
||||
}
|
||||
|
||||
|
||||
val shortToken = "${token.take(12)}...${token.takeLast(8)}"
|
||||
addFcmLog("📤 Подготовка к отправке токена на сервер")
|
||||
addFcmLog("⏳ Ожидание аутентификации...")
|
||||
|
||||
// 🔥 КРИТИЧНО: Ждем пока протокол станет AUTHENTICATED
|
||||
var waitAttempts = 0
|
||||
while (ProtocolManager.state.value != ProtocolState.AUTHENTICATED && waitAttempts < 50) {
|
||||
while (ProtocolManager.state.value != ProtocolState.AUTHENTICATED &&
|
||||
waitAttempts < 50) {
|
||||
delay(100) // Ждем 100ms
|
||||
waitAttempts++
|
||||
}
|
||||
|
||||
|
||||
if (ProtocolManager.state.value != ProtocolState.AUTHENTICATED) {
|
||||
addFcmLog("❌ Таймаут аутентификации (${waitAttempts * 100}ms)")
|
||||
return@launch
|
||||
}
|
||||
|
||||
val packet = PacketPushNotification().apply {
|
||||
this.notificationsToken = token
|
||||
this.action = PushNotificationAction.SUBSCRIBE
|
||||
}
|
||||
|
||||
|
||||
addFcmLog("✅ Аутентификация успешна")
|
||||
addFcmLog("📨 Отправка токена: $shortToken")
|
||||
|
||||
val packet =
|
||||
PacketPushNotification().apply {
|
||||
this.notificationsToken = token
|
||||
this.action = PushNotificationAction.SUBSCRIBE
|
||||
}
|
||||
|
||||
ProtocolManager.send(packet)
|
||||
addFcmLog("✅ Пакет отправлен на сервер (ID: 0x10)")
|
||||
addFcmLog("🎉 FCM токен успешно зарегистрирован!")
|
||||
} catch (e: Exception) {
|
||||
addFcmLog("❌ Ошибка отправки: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -364,177 +416,162 @@ class MainActivity : ComponentActivity() {
|
||||
|
||||
@Composable
|
||||
fun MainScreen(
|
||||
account: DecryptedAccount? = null,
|
||||
isDarkTheme: Boolean = true,
|
||||
onToggleTheme: () -> Unit = {},
|
||||
onLogout: () -> Unit = {}
|
||||
account: DecryptedAccount? = null,
|
||||
isDarkTheme: Boolean = true,
|
||||
onToggleTheme: () -> Unit = {},
|
||||
onLogout: () -> Unit = {}
|
||||
) {
|
||||
val accountName = account?.name ?: "Account"
|
||||
val accountPhone = account?.publicKey?.take(16)?.let {
|
||||
"+${it.take(1)} ${it.substring(1, 4)} ${it.substring(4, 7)}${it.substring(7)}"
|
||||
} ?: "+7 775 9932587"
|
||||
val accountPhone =
|
||||
account?.publicKey?.take(16)?.let {
|
||||
"+${it.take(1)} ${it.substring(1, 4)} ${it.substring(4, 7)}${it.substring(7)}"
|
||||
}
|
||||
?: "+7 775 9932587"
|
||||
val accountPublicKey = account?.publicKey ?: "04c266b98ae5"
|
||||
val accountPrivateKey = account?.privateKey ?: ""
|
||||
val privateKeyHash = account?.privateKeyHash ?: ""
|
||||
|
||||
|
||||
// Состояние протокола для передачи в SearchScreen
|
||||
val protocolState by ProtocolManager.state.collectAsState()
|
||||
|
||||
|
||||
// Навигация между экранами
|
||||
var selectedUser by remember { mutableStateOf<SearchUser?>(null) }
|
||||
var showSearchScreen by remember { mutableStateOf(false) }
|
||||
|
||||
|
||||
// 🔥 TELEGRAM-STYLE анимация - чистый slide БЕЗ прозрачности
|
||||
AnimatedContent(
|
||||
targetState = Triple(selectedUser, showSearchScreen, Unit),
|
||||
transitionSpec = {
|
||||
val isEnteringChat = targetState.first != null && initialState.first == null
|
||||
val isExitingChat = targetState.first == null && initialState.first != null
|
||||
val isEnteringSearch = targetState.second && !initialState.second
|
||||
val isExitingSearch = !targetState.second && initialState.second
|
||||
|
||||
when {
|
||||
// 🚀 Вход в чат - плавный fade
|
||||
isEnteringChat -> {
|
||||
fadeIn(
|
||||
animationSpec = tween(200)
|
||||
) togetherWith fadeOut(
|
||||
animationSpec = tween(150)
|
||||
)
|
||||
targetState = Triple(selectedUser, showSearchScreen, Unit),
|
||||
transitionSpec = {
|
||||
val isEnteringChat = targetState.first != null && initialState.first == null
|
||||
val isExitingChat = targetState.first == null && initialState.first != null
|
||||
val isEnteringSearch = targetState.second && !initialState.second
|
||||
val isExitingSearch = !targetState.second && initialState.second
|
||||
|
||||
when {
|
||||
// 🚀 Вход в чат - плавный fade
|
||||
isEnteringChat -> {
|
||||
fadeIn(animationSpec = tween(200)) togetherWith
|
||||
fadeOut(animationSpec = tween(150))
|
||||
}
|
||||
|
||||
// 🔙 Выход из чата - плавный fade
|
||||
isExitingChat -> {
|
||||
fadeIn(animationSpec = tween(200)) togetherWith
|
||||
fadeOut(animationSpec = tween(150))
|
||||
}
|
||||
|
||||
// 🔍 Вход в Search - плавный fade
|
||||
isEnteringSearch -> {
|
||||
fadeIn(animationSpec = tween(200)) togetherWith
|
||||
fadeOut(animationSpec = tween(150))
|
||||
}
|
||||
|
||||
// 🔙 Выход из Search - плавный fade
|
||||
isEnteringSearch -> {
|
||||
fadeIn(animationSpec = tween(200)) togetherWith
|
||||
fadeOut(animationSpec = tween(150))
|
||||
}
|
||||
|
||||
// 🔙 Выход из Search - плавный fade
|
||||
isExitingSearch -> {
|
||||
fadeIn(animationSpec = tween(200)) togetherWith
|
||||
fadeOut(animationSpec = tween(150))
|
||||
}
|
||||
|
||||
// Default - мгновенный переход
|
||||
else -> {
|
||||
EnterTransition.None togetherWith ExitTransition.None
|
||||
}
|
||||
}
|
||||
|
||||
// 🔙 Выход из чата - плавный fade
|
||||
isExitingChat -> {
|
||||
fadeIn(
|
||||
animationSpec = tween(200)
|
||||
) togetherWith fadeOut(
|
||||
animationSpec = tween(150)
|
||||
)
|
||||
}
|
||||
|
||||
// 🔍 Вход в Search - плавный fade
|
||||
isEnteringSearch -> {
|
||||
fadeIn(
|
||||
animationSpec = tween(200)
|
||||
) togetherWith fadeOut(
|
||||
animationSpec = tween(150)
|
||||
)
|
||||
}
|
||||
|
||||
// 🔙 Выход из Search - плавный fade
|
||||
isEnteringSearch -> {
|
||||
fadeIn(
|
||||
animationSpec = tween(200)
|
||||
) togetherWith fadeOut(
|
||||
animationSpec = tween(150)
|
||||
)
|
||||
}
|
||||
|
||||
// 🔙 Выход из Search - плавный fade
|
||||
isExitingSearch -> {
|
||||
fadeIn(
|
||||
animationSpec = tween(200)
|
||||
) togetherWith fadeOut(
|
||||
animationSpec = tween(150)
|
||||
)
|
||||
}
|
||||
|
||||
// Default - мгновенный переход
|
||||
else -> {
|
||||
EnterTransition.None togetherWith ExitTransition.None
|
||||
}
|
||||
}
|
||||
},
|
||||
label = "screenNavigation"
|
||||
},
|
||||
label = "screenNavigation"
|
||||
) { (currentUser, isSearchOpen, _) ->
|
||||
when {
|
||||
currentUser != null -> {
|
||||
// Экран чата
|
||||
ChatDetailScreen(
|
||||
user = currentUser,
|
||||
currentUserPublicKey = accountPublicKey,
|
||||
currentUserPrivateKey = accountPrivateKey,
|
||||
isDarkTheme = isDarkTheme,
|
||||
onBack = { selectedUser = null },
|
||||
onNavigateToChat = { publicKey ->
|
||||
// 📨 Forward: переход в выбранный чат
|
||||
// Нужно получить SearchUser из публичного ключа
|
||||
// Используем минимальные данные - остальное подгрузится в ChatDetailScreen
|
||||
selectedUser = SearchUser(
|
||||
title = "",
|
||||
username = "",
|
||||
publicKey = publicKey,
|
||||
verified = 0,
|
||||
online = 0
|
||||
)
|
||||
}
|
||||
user = currentUser,
|
||||
currentUserPublicKey = accountPublicKey,
|
||||
currentUserPrivateKey = accountPrivateKey,
|
||||
isDarkTheme = isDarkTheme,
|
||||
onBack = { selectedUser = null },
|
||||
onNavigateToChat = { publicKey ->
|
||||
// 📨 Forward: переход в выбранный чат
|
||||
// Нужно получить SearchUser из публичного ключа
|
||||
// Используем минимальные данные - остальное подгрузится в
|
||||
// ChatDetailScreen
|
||||
selectedUser =
|
||||
SearchUser(
|
||||
title = "",
|
||||
username = "",
|
||||
publicKey = publicKey,
|
||||
verified = 0,
|
||||
online = 0
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
isSearchOpen -> {
|
||||
// Экран поиска
|
||||
SearchScreen(
|
||||
privateKeyHash = privateKeyHash,
|
||||
currentUserPublicKey = accountPublicKey,
|
||||
isDarkTheme = isDarkTheme,
|
||||
protocolState = protocolState,
|
||||
onBackClick = { showSearchScreen = false },
|
||||
onUserSelect = { selectedSearchUser ->
|
||||
showSearchScreen = false
|
||||
selectedUser = selectedSearchUser
|
||||
}
|
||||
privateKeyHash = privateKeyHash,
|
||||
currentUserPublicKey = accountPublicKey,
|
||||
isDarkTheme = isDarkTheme,
|
||||
protocolState = protocolState,
|
||||
onBackClick = { showSearchScreen = false },
|
||||
onUserSelect = { selectedSearchUser ->
|
||||
showSearchScreen = false
|
||||
selectedUser = selectedSearchUser
|
||||
}
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
// Список чатов
|
||||
ChatsListScreen(
|
||||
isDarkTheme = isDarkTheme,
|
||||
accountName = accountName,
|
||||
accountPhone = accountPhone,
|
||||
accountPublicKey = accountPublicKey,
|
||||
accountPrivateKey = accountPrivateKey,
|
||||
privateKeyHash = privateKeyHash,
|
||||
onToggleTheme = onToggleTheme,
|
||||
onProfileClick = {
|
||||
// TODO: Navigate to profile
|
||||
},
|
||||
onNewGroupClick = {
|
||||
// TODO: Navigate to new group
|
||||
},
|
||||
onContactsClick = {
|
||||
// TODO: Navigate to contacts
|
||||
},
|
||||
onCallsClick = {
|
||||
// TODO: Navigate to calls
|
||||
},
|
||||
onSavedMessagesClick = {
|
||||
// Открываем чат с самим собой (Saved Messages)
|
||||
selectedUser = SearchUser(
|
||||
title = "Saved Messages",
|
||||
username = "",
|
||||
publicKey = accountPublicKey,
|
||||
verified = 0,
|
||||
online = 1
|
||||
)
|
||||
},
|
||||
onSettingsClick = {
|
||||
// TODO: Navigate to settings
|
||||
},
|
||||
onInviteFriendsClick = {
|
||||
// TODO: Share invite link
|
||||
},
|
||||
onSearchClick = {
|
||||
showSearchScreen = true
|
||||
},
|
||||
onNewChat = {
|
||||
// TODO: Show new chat screen
|
||||
},
|
||||
onUserSelect = { selectedChatUser ->
|
||||
selectedUser = selectedChatUser
|
||||
},
|
||||
onLogout = onLogout
|
||||
isDarkTheme = isDarkTheme,
|
||||
accountName = accountName,
|
||||
accountPhone = accountPhone,
|
||||
accountPublicKey = accountPublicKey,
|
||||
accountPrivateKey = accountPrivateKey,
|
||||
privateKeyHash = privateKeyHash,
|
||||
onToggleTheme = onToggleTheme,
|
||||
onProfileClick = {
|
||||
// TODO: Navigate to profile
|
||||
},
|
||||
onNewGroupClick = {
|
||||
// TODO: Navigate to new group
|
||||
},
|
||||
onContactsClick = {
|
||||
// TODO: Navigate to contacts
|
||||
},
|
||||
onCallsClick = {
|
||||
// TODO: Navigate to calls
|
||||
},
|
||||
onSavedMessagesClick = {
|
||||
// Открываем чат с самим собой (Saved Messages)
|
||||
selectedUser =
|
||||
SearchUser(
|
||||
title = "Saved Messages",
|
||||
username = "",
|
||||
publicKey = accountPublicKey,
|
||||
verified = 0,
|
||||
online = 1
|
||||
)
|
||||
},
|
||||
onSettingsClick = {
|
||||
// TODO: Navigate to settings
|
||||
},
|
||||
onInviteFriendsClick = {
|
||||
// TODO: Share invite link
|
||||
},
|
||||
onSearchClick = { showSearchScreen = true },
|
||||
onNewChat = {
|
||||
// TODO: Show new chat screen
|
||||
},
|
||||
onUserSelect = { selectedChatUser -> selectedUser = selectedChatUser },
|
||||
onLogout = onLogout
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user