Files
mobile-android/app/src/main/java/com/rosetta/messenger/MainActivity.kt

321 lines
15 KiB
Kotlin

package com.rosetta.messenger
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.animation.*
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
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.dp
import androidx.compose.ui.unit.sp
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.ProtocolManager
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.SearchScreen
import com.rosetta.messenger.network.SearchUser
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
class MainActivity : ComponentActivity() {
private lateinit var preferencesManager: PreferencesManager
private lateinit var accountManager: AccountManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
preferencesManager = PreferencesManager(this)
accountManager = AccountManager(this)
RecentSearchesManager.init(this)
setContent {
val scope = rememberCoroutineScope()
val isDarkTheme by preferencesManager.isDarkTheme.collectAsState(initial = true)
val isLoggedIn by accountManager.isLoggedIn.collectAsState(initial = null)
var showSplash by remember { mutableStateOf(true) }
var showOnboarding by remember { mutableStateOf(true) }
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()
}
}
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)
)
return@setContent
}
RosettaAndroidTheme(
darkTheme = isDarkTheme,
animated = true
) {
Surface(
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"
) { screen ->
when (screen) {
"splash" -> {
SplashScreen(
isDarkTheme = isDarkTheme,
onSplashComplete = { showSplash = false }
)
}
"onboarding" -> {
OnboardingScreen(
isDarkTheme = isDarkTheme,
onThemeToggle = {
scope.launch {
preferencesManager.setDarkTheme(!isDarkTheme)
}
},
onStartMessaging = {
showOnboarding = false
}
)
}
"auth_new", "auth_new", "auth_unlock" -> {
AuthFlow(
isDarkTheme = isDarkTheme,
hasExistingAccount = screen == "auth_unlock",
accounts = accountInfoList,
onAuthComplete = { account ->
currentAccount = account
hasExistingAccount = true
// 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
)
}
}
},
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)
}
},
onLogout = {
// Set currentAccount to null immediately to prevent UI lag
currentAccount = null
scope.launch {
com.rosetta.messenger.network.ProtocolManager.disconnect()
accountManager.logout()
}
}
)
}
}
}
}
}
}
}
}
@Composable
fun MainScreen(
account: DecryptedAccount? = null,
isDarkTheme: Boolean = true,
onToggleTheme: () -> Unit = {},
onLogout: () -> Unit = {}
) {
val accountName = account?.publicKey ?: "04c266b98ae5"
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) }
// Анимированный переход между экранами
AnimatedContent(
targetState = Triple(selectedUser, showSearchScreen, Unit),
transitionSpec = {
// Только плавный fade без смещения - чтобы header не прыгал
fadeIn(
animationSpec = tween(
durationMillis = 200,
easing = FastOutSlowInEasing
)
) togetherWith fadeOut(
animationSpec = tween(
durationMillis = 150,
easing = FastOutSlowInEasing
)
) using SizeTransform(clip = false)
},
label = "screenNavigation"
) { (user, isSearchOpen, _) ->
when {
user != null -> {
// Экран чата
ChatDetailScreen(
user = user,
currentUserPublicKey = accountPublicKey,
currentUserPrivateKey = accountPrivateKey,
isDarkTheme = isDarkTheme,
onBack = { selectedUser = null }
)
}
isSearchOpen -> {
// Экран поиска
SearchScreen(
privateKeyHash = privateKeyHash,
currentUserPublicKey = accountPublicKey,
isDarkTheme = isDarkTheme,
protocolState = protocolState,
onBackClick = { showSearchScreen = false },
onUserSelect = { user ->
showSearchScreen = false
selectedUser = user
}
)
}
else -> {
// Список чатов
ChatsListScreen(
isDarkTheme = isDarkTheme,
accountName = accountName,
accountPhone = accountPhone,
accountPublicKey = accountPublicKey,
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 = {
// TODO: Navigate to saved messages
},
onSettingsClick = {
// TODO: Navigate to settings
},
onInviteFriendsClick = {
// TODO: Share invite link
},
onSearchClick = {
showSearchScreen = true
},
onNewChat = {
// TODO: Show new chat screen
},
onUserSelect = { user ->
selectedUser = user
},
onLogout = onLogout
)
}
}
}
}