Files
forkmessager/client-mobile/navigation/AppNavigation.kt
2026-04-16 15:41:22 +03:00

272 lines
12 KiB
Kotlin

package navigation
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavHostController
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import auth.presentation.LoginScreen
import auth.presentation.RegisterScreen
import auth.presentation.AuthViewModel
import core.presentation.settings.SettingsScreen
import core.presentation.settings.SettingsViewModel
import chats.presentation.chat_list.ChatListScreen
import chats.presentation.chat_list.ChatListViewModel
import chats.presentation.chat_detail.ChatDetailScreen
import chats.presentation.chat_detail.ChatDetailViewModel
import contacts.presentation.ContactListScreen
import contacts.presentation.ContactListViewModel
import core.presentation.components.GlassNavigationBar
import profiles.presentation.ProfileScreen
import profiles.presentation.EditProfileScreen
import profiles.presentation.viewmodel.ProfileViewModel
import stories.presentation.StoryViewModel
sealed class Screen(val route: String) {
object Login : Screen("login")
object Register : Screen("register")
object ChatList : Screen("chat_list")
object ChatDetail : Screen("chat_detail/{chatId}/{chatName}") {
fun createRoute(chatId: String, chatName: String) = "chat_detail/$chatId/$chatName"
}
object Profile : Screen("profile?profileId={profileId}") {
fun createRoute(profileId: String? = null) = if (profileId != null) "profile?profileId=$profileId" else "profile"
}
object EditProfile : Screen("edit_profile")
object Settings : Screen("settings")
object Contacts : Screen("contacts")
}
@Composable
fun AppNavigation(
navController: NavHostController = rememberNavController(),
navigationManager: core.utils.NavigationManager? = null
) {
val authViewModel: AuthViewModel = hiltViewModel()
val authState by authViewModel.state.collectAsState()
val startDestination = if (authState.isAuthenticated) Screen.ChatList.route else Screen.Login.route
androidx.compose.runtime.LaunchedEffect(navigationManager) {
navigationManager?.events?.collect { event ->
when (event) {
is core.utils.NavEvent.OpenChat -> {
if (authState.isAuthenticated) {
navController.navigate(Screen.ChatDetail.createRoute(event.chatId, "Chat")) {
launchSingleTop = true
}
}
}
core.utils.NavEvent.Logout -> {
navController.navigate(Screen.Login.route) {
popUpTo(0) { inclusive = true }
}
}
}
}
}
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
// Определяем, нужно ли показывать нижнюю панель
val showBottomBar = currentRoute in listOf(
Screen.ChatList.route,
Screen.Contacts.route,
Screen.Settings.route,
Screen.Profile.route
)
val profileViewModel: ProfileViewModel = hiltViewModel()
val profileState by profileViewModel.state.collectAsState()
androidx.compose.runtime.LaunchedEffect(authState.isAuthenticated) {
if (authState.isAuthenticated) {
profileViewModel.loadProfile()
}
}
Scaffold(
bottomBar = {
if (showBottomBar) {
GlassNavigationBar(
currentRoute = currentRoute ?: "",
onNavigate = { route ->
navController.navigate(route) {
popUpTo(Screen.ChatList.route) { saveState = true }
launchSingleTop = true
restoreState = true
}
},
userAvatarUrl = profileState.profile?.avatarUrl,
username = profileState.profile?.displayName ?: profileState.profile?.username ?: ""
)
}
}
) { paddingValues ->
Box(modifier = Modifier.padding(if (showBottomBar) paddingValues else PaddingValues(0.dp))) {
NavHost(
navController = navController,
startDestination = startDestination
) {
composable(Screen.Login.route) {
val viewModel: AuthViewModel = hiltViewModel()
LoginScreen(
viewModel = viewModel,
onNavigateToRegister = { navController.navigate(Screen.Register.route) },
onNavigateToSettings = { navController.navigate(Screen.Settings.route) },
onLoginSuccess = {
navController.navigate(Screen.ChatList.route) {
popUpTo(Screen.Login.route) { inclusive = true }
}
}
)
}
composable(Screen.Register.route) {
val viewModel: AuthViewModel = hiltViewModel()
RegisterScreen(
viewModel = viewModel,
onNavigateToLogin = { navController.popBackStack() },
onRegisterSuccess = {
navController.navigate(Screen.ChatList.route) {
popUpTo(Screen.Login.route) { inclusive = true }
}
}
)
}
composable(Screen.ChatList.route) {
val viewModel: ChatListViewModel = hiltViewModel()
val storyViewModel: StoryViewModel = hiltViewModel()
ChatListScreen(
viewModel = viewModel,
storyViewModel = storyViewModel,
onChatClick = { chatId ->
navController.navigate(Screen.ChatDetail.createRoute(chatId, "Chat"))
},
onStoryClick = { /* Story click logic */ }
)
}
composable(
route = Screen.ChatDetail.route,
arguments = listOf(
navArgument("chatId") { type = NavType.StringType },
navArgument("chatName") { type = NavType.StringType }
)
) { backStackEntry ->
val chatId = backStackEntry.arguments?.getString("chatId") ?: ""
val chatName = backStackEntry.arguments?.getString("chatName") ?: "Chat"
val viewModel: ChatDetailViewModel = hiltViewModel()
ChatDetailScreen(
chatId = chatId,
chatName = chatName,
viewModel = viewModel,
onBack = { navController.popBackStack() }
)
}
composable(Screen.Contacts.route) {
val viewModel: ContactListViewModel = hiltViewModel()
val state by viewModel.state.collectAsState()
ContactListScreen(
contacts = state.contacts,
requests = state.requests,
isLoading = state.isLoading,
onContactClick = { id -> navController.navigate(Screen.Profile.createRoute(id)) },
onSearchChange = { viewModel.onSearchChange(it) },
onAddContact = { id -> viewModel.addContact(id) },
onStartChat = { id -> viewModel.startChat(id) },
onAcceptRequest = { id -> viewModel.acceptRequest(id) },
onDeclineRequest = { id -> viewModel.declineRequest(id) }
)
}
composable(
route = Screen.Profile.route,
arguments = listOf(
navArgument("profileId") {
type = NavType.StringType
nullable = true
}
)
) { backStackEntry ->
val profileId = backStackEntry.arguments?.getString("profileId")
val viewModel: ProfileViewModel = hiltViewModel()
ProfileScreen(
profileId = profileId,
viewModel = viewModel,
onEditProfile = { navController.navigate(Screen.EditProfile.route) },
onBack = { navController.popBackStack() },
onSendMessage = { id -> navController.navigate(Screen.ChatDetail.createRoute(id, "Chat")) }
)
}
composable(Screen.EditProfile.route) {
// Используем ViewModel от предыдущего экрана (Profile), чтобы данные не терялись
val profileEntry = remember(it) {
navController.getBackStackEntry(Screen.Profile.route)
}
val viewModel: ProfileViewModel = hiltViewModel(profileEntry)
val state by viewModel.state.collectAsState()
// Получаем profileId из родительского backStackEntry
val profileId = profileEntry.arguments?.getString("profileId")
androidx.compose.runtime.LaunchedEffect(state.profile) {
// Если профиль загружен (или обновился), мы можем инициализировать/обновить поля
}
val isSaving = state.isSaving
val error = state.error
// Автоматически возвращаемся назад после успешного сохранения
var wasSaving by remember { mutableStateOf(false) }
androidx.compose.runtime.LaunchedEffect(isSaving) {
if (isSaving) wasSaving = true
if (wasSaving && !isSaving && error == null) {
// Перезагружаем профиль перед возвратом
viewModel.loadProfile(profileId)
// Небольшая задержка чтобы данные обновились
kotlinx.coroutines.delay(300)
navController.popBackStack()
}
}
EditProfileScreen(
currentDisplayName = state.profile?.displayName,
currentUsername = state.profile?.username ?: "",
currentBio = state.profile?.bio,
currentBirthday = state.profile?.birthday,
currentAvatarUrl = state.profile?.avatarUrl,
isSaving = isSaving,
error = error,
onSave = { name, bio, birthday, uri ->
viewModel.updateProfile(name, bio, birthday, uri)
},
onBack = { navController.popBackStack() }
)
}
composable(Screen.Settings.route) {
val viewModel: SettingsViewModel = hiltViewModel()
SettingsScreen(
viewModel = viewModel,
onBack = { navController.popBackStack() },
onLogout = {
navController.navigate(Screen.Login.route) {
popUpTo(0) { inclusive = true }
}
}
)
}
}
}
}
}