package com.aiosman.ravenow.ui import ChangePasswordScreen import ImageViewer import ModificationListScreen import androidx.compose.animation.ExperimentalSharedTransitionApi import androidx.compose.animation.SharedTransitionLayout import androidx.compose.animation.core.tween import androidx.compose.animation.core.FastOutSlowInEasing import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.slideInHorizontally import androidx.compose.animation.slideOutHorizontally import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp import androidx.navigation.NavHostController import androidx.navigation.NavType import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import androidx.navigation.navArgument import com.aiosman.ravenow.LocalAnimatedContentScope import com.aiosman.ravenow.LocalNavController import com.aiosman.ravenow.LocalSharedTransitionScope import com.aiosman.ravenow.ui.about.AboutScreen import com.aiosman.ravenow.ui.account.AccountEditScreen2 import com.aiosman.ravenow.ui.account.AccountSetting import com.aiosman.ravenow.ui.account.RemoveAccountScreen import com.aiosman.ravenow.ui.account.ResetPasswordScreen import com.aiosman.ravenow.ui.agent.AddAgentScreen import com.aiosman.ravenow.ui.agent.AgentImageCropScreen import com.aiosman.ravenow.ui.group.CreateGroupChatScreen import com.aiosman.ravenow.ui.chat.ChatAiScreen import com.aiosman.ravenow.ui.chat.ChatSettingScreen import com.aiosman.ravenow.ui.chat.ChatScreen import com.aiosman.ravenow.ui.chat.GroupChatScreen import com.aiosman.ravenow.ui.comment.CommentsScreen import com.aiosman.ravenow.ui.comment.notice.CommentNoticeScreen import com.aiosman.ravenow.ui.composables.AgentCreatedSuccessIndicator import com.aiosman.ravenow.ui.crop.ImageCropScreen import com.aiosman.ravenow.ui.favourite.FavouriteListPage import com.aiosman.ravenow.ui.favourite.FavouriteNoticeScreen import com.aiosman.ravenow.ui.follower.FollowerListScreen import com.aiosman.ravenow.ui.follower.FollowerNoticeScreen import com.aiosman.ravenow.ui.follower.FollowingListScreen import com.aiosman.ravenow.ui.gallery.OfficialGalleryScreen import com.aiosman.ravenow.ui.gallery.OfficialPhotographerScreen import com.aiosman.ravenow.ui.gallery.ProfileTimelineScreen import com.aiosman.ravenow.ui.group.GroupChatInfoScreen import com.aiosman.ravenow.ui.index.IndexScreen import com.aiosman.ravenow.ui.index.tabs.message.NotificationsScreen import com.aiosman.ravenow.ui.index.tabs.search.SearchScreen import com.aiosman.ravenow.ui.like.LikeNoticeScreen import com.aiosman.ravenow.ui.location.LocationDetailScreen import com.aiosman.ravenow.ui.login.EmailSignupScreen import com.aiosman.ravenow.ui.login.LoginPage import com.aiosman.ravenow.ui.login.SignupScreen import com.aiosman.ravenow.ui.login.UserAuthScreen import com.aiosman.ravenow.ui.modification.EditModificationScreen import com.aiosman.ravenow.ui.post.NewPostImageGridScreen import com.aiosman.ravenow.ui.post.NewPostScreen import com.aiosman.ravenow.ui.post.PostScreen import com.aiosman.ravenow.ui.profile.AccountProfileV2 import com.aiosman.ravenow.ui.index.tabs.profile.vip.VipSelPage import com.aiosman.ravenow.ui.notification.NotificationScreen sealed class NavigationRoute( val route: String, ) { data object Index : NavigationRoute("Index") data object ProfileTimeline : NavigationRoute("ProfileTimeline") data object LocationDetail : NavigationRoute("LocationDetail/{x}/{y}") data object OfficialPhoto : NavigationRoute("OfficialPhoto") data object OfficialPhotographer : NavigationRoute("OfficialPhotographer") data object Post : NavigationRoute("Post/{id}/{highlightCommentId}/{initImagePagerIndex}") data object ModificationList : NavigationRoute("ModificationList") data object MyMessage : NavigationRoute("MyMessage") data object Comments : NavigationRoute("Comments") data object Likes : NavigationRoute("Likes") data object Followers : NavigationRoute("Followers") data object NewPost : NavigationRoute("NewPost") data object EditModification : NavigationRoute("EditModification") data object Login : NavigationRoute("Login") data object AccountProfile : NavigationRoute("AccountProfile/{id}?isAiAccount={isAiAccount}") data object SignUp : NavigationRoute("SignUp") data object UserAuth : NavigationRoute("UserAuth") data object EmailSignUp : NavigationRoute("EmailSignUp") data object AccountEdit : NavigationRoute("AccountEditScreen") data object ImageViewer : NavigationRoute("ImageViewer") data object ChangePasswordScreen : NavigationRoute("ChangePasswordScreen") data object FavouritesScreen : NavigationRoute("FavouritesScreen") data object NewPostImageGrid : NavigationRoute("NewPostImageGrid") data object Search : NavigationRoute("Search") data object FollowerList : NavigationRoute("FollowerList/{id}") data object FollowingList : NavigationRoute("FollowingList/{id}") data object ResetPassword : NavigationRoute("ResetPassword") data object FavouriteList : NavigationRoute("FavouriteList") data object Chat : NavigationRoute("Chat/{id}") data object ChatAi : NavigationRoute("ChatAi/{id}") data object ChatSetting : NavigationRoute("ChatSetting") data object ChatGroup : NavigationRoute("ChatGroup/{id}/{name}/{avatar}") data object CommentNoticeScreen : NavigationRoute("CommentNoticeScreen") data object ImageCrop : NavigationRoute("ImageCrop") data object AgentImageCrop : NavigationRoute("AgentImageCrop") data object AccountSetting : NavigationRoute("AccountSetting") data object AboutScreen : NavigationRoute("AboutScreen") data object AddAgent : NavigationRoute("AddAgent") data object CreateGroupChat : NavigationRoute("CreateGroupChat") data object GroupInfo : NavigationRoute("GroupInfo/{id}") data object VipSelPage : NavigationRoute("VipSelPage") data object RemoveAccountScreen: NavigationRoute("RemoveAccount") data object NotificationScreen : NavigationRoute("NotificationScreen") } @Composable fun NavigationController( navController: NavHostController, startDestination: String = NavigationRoute.Login.route ) { val navigationBarHeight = with(LocalDensity.current) { WindowInsets.navigationBars.getBottom(this).toDp() } NavHost( navController = navController, startDestination = startDestination, ) { composable(route = NavigationRoute.Index.route) { CompositionLocalProvider( LocalAnimatedContentScope provides this, ) { IndexScreen() } } composable(route = NavigationRoute.ProfileTimeline.route) { ProfileTimelineScreen() } composable( route = NavigationRoute.LocationDetail.route, arguments = listOf( navArgument("x") { type = NavType.FloatType }, navArgument("y") { type = NavType.FloatType } ) ) { Box( modifier = Modifier.padding(bottom = navigationBarHeight) ) { val x = it.arguments?.getFloat("x") ?: 0f val y = it.arguments?.getFloat("y") ?: 0f LocationDetailScreen( x, y ) } } composable(route = NavigationRoute.OfficialPhoto.route) { OfficialGalleryScreen() } composable(route = NavigationRoute.OfficialPhotographer.route) { OfficialPhotographerScreen() } composable( route = NavigationRoute.Post.route, arguments = listOf( navArgument("id") { type = NavType.StringType }, navArgument("highlightCommentId") { type = NavType.IntType }, navArgument("initImagePagerIndex") { type = NavType.IntType } ), enterTransition = { // iOS push: new screen slides in from the right slideInHorizontally( initialOffsetX = { fullWidth -> fullWidth }, animationSpec = tween(durationMillis = 280) ) }, exitTransition = { // iOS push: previous screen shifts slightly left (parallax) slideOutHorizontally( targetOffsetX = { fullWidth -> -fullWidth / 3 }, animationSpec = tween(durationMillis = 280) ) }, popEnterTransition = { // iOS pop: previous screen slides back from slight left offset slideInHorizontally( initialOffsetX = { fullWidth -> -fullWidth / 3 }, animationSpec = tween(durationMillis = 280) ) }, popExitTransition = { // iOS pop: current screen slides out to the right slideOutHorizontally( targetOffsetX = { fullWidth -> fullWidth }, animationSpec = tween(durationMillis = 280) ) } ) { backStackEntry -> val id = backStackEntry.arguments?.getString("id") val highlightCommentId = backStackEntry.arguments?.getInt("highlightCommentId")?.let { if (it == 0) null else it } val initIndex = backStackEntry.arguments?.getInt("initImagePagerIndex") PostScreen( id!!, highlightCommentId, initImagePagerIndex = initIndex ) } composable(route = NavigationRoute.ModificationList.route, enterTransition = { fadeIn(animationSpec = tween(durationMillis = 0)) }, exitTransition = { fadeOut(animationSpec = tween(durationMillis = 0)) } ) { ModificationListScreen() } composable(route = NavigationRoute.MyMessage.route, enterTransition = { fadeIn(animationSpec = tween(durationMillis = 0)) }, exitTransition = { fadeOut(animationSpec = tween(durationMillis = 0)) } ) { NotificationsScreen() } composable(route = NavigationRoute.Comments.route, enterTransition = { fadeIn(animationSpec = tween(durationMillis = 0)) }, exitTransition = { fadeOut(animationSpec = tween(durationMillis = 0)) } ) { CommentsScreen() } composable(route = NavigationRoute.Likes.route, enterTransition = { fadeIn(animationSpec = tween(durationMillis = 0)) }, exitTransition = { fadeOut(animationSpec = tween(durationMillis = 0)) } ) { LikeNoticeScreen() } composable(route = NavigationRoute.Followers.route, enterTransition = { fadeIn(animationSpec = tween(durationMillis = 0)) }, exitTransition = { fadeOut(animationSpec = tween(durationMillis = 0)) } ) { FollowerNoticeScreen() } composable( route = NavigationRoute.NewPost.route, enterTransition = { fadeIn(animationSpec = tween(durationMillis = 0)) }, exitTransition = { fadeOut(animationSpec = tween(durationMillis = 0)) } ) { NewPostScreen() } composable(route = NavigationRoute.EditModification.route) { Box( modifier = Modifier.padding(top = 64.dp) ) { EditModificationScreen() } } composable(route = NavigationRoute.Login.route) { LoginPage() } composable( route = NavigationRoute.AccountProfile.route, arguments = listOf( navArgument("id") { type = NavType.StringType }, navArgument("isAiAccount") { type = NavType.BoolType defaultValue = false } ), enterTransition = { // iOS push: new screen slides in from the right slideInHorizontally( initialOffsetX = { fullWidth -> fullWidth }, animationSpec = tween(durationMillis = 280) ) }, exitTransition = { // iOS push: previous screen shifts slightly left (parallax) slideOutHorizontally( targetOffsetX = { fullWidth -> -fullWidth / 3 }, animationSpec = tween(durationMillis = 280) ) }, popEnterTransition = { // iOS pop: previous screen slides back from slight left offset slideInHorizontally( initialOffsetX = { fullWidth -> -fullWidth / 3 }, animationSpec = tween(durationMillis = 280) ) }, popExitTransition = { // iOS pop: current screen slides out to the right slideOutHorizontally( targetOffsetX = { fullWidth -> fullWidth }, animationSpec = tween(durationMillis = 280) ) } ) { CompositionLocalProvider( LocalAnimatedContentScope provides this, ) { val id = it.arguments?.getString("id")!! val isAiAccount = it.arguments?.getBoolean("isAiAccount") ?: false AccountProfileV2(id, isAiAccount) } } composable( route = NavigationRoute.SignUp.route, enterTransition = { fadeIn(animationSpec = tween(durationMillis = 0)) }, exitTransition = { fadeOut(animationSpec = tween(durationMillis = 0)) } ) { SignupScreen() } composable( route = NavigationRoute.UserAuth.route, enterTransition = { fadeIn(animationSpec = tween(durationMillis = 0)) }, exitTransition = { fadeOut(animationSpec = tween(durationMillis = 0)) } ) { UserAuthScreen() } composable( route = NavigationRoute.EmailSignUp.route, enterTransition = { fadeIn(animationSpec = tween(durationMillis = 0)) }, exitTransition = { fadeOut(animationSpec = tween(durationMillis = 0)) } ) { EmailSignupScreen() } composable( route = NavigationRoute.AccountEdit.route, enterTransition = { // iOS风格:从底部向上滑入 slideInVertically( initialOffsetY = { fullHeight -> fullHeight }, animationSpec = tween(durationMillis = 300, easing = FastOutSlowInEasing) ) + fadeIn( animationSpec = tween(durationMillis = 300) ) }, exitTransition = { // iOS风格:向底部滑出 slideOutVertically( targetOffsetY = { fullHeight -> fullHeight }, animationSpec = tween(durationMillis = 300, easing = FastOutSlowInEasing) ) + fadeOut( animationSpec = tween(durationMillis = 300) ) }, popEnterTransition = { // 返回时从底部滑入 slideInVertically( initialOffsetY = { fullHeight -> fullHeight }, animationSpec = tween(durationMillis = 300, easing = FastOutSlowInEasing) ) + fadeIn( animationSpec = tween(durationMillis = 300) ) }, popExitTransition = { // 返回时向底部滑出 slideOutVertically( targetOffsetY = { fullHeight -> fullHeight }, animationSpec = tween(durationMillis = 300, easing = FastOutSlowInEasing) ) + fadeOut( animationSpec = tween(durationMillis = 300) ) } ) { AccountEditScreen2() } composable(route = NavigationRoute.ImageViewer.route) { ImageViewer() } composable(route = NavigationRoute.ChangePasswordScreen.route) { ChangePasswordScreen() } composable(route = NavigationRoute.RemoveAccountScreen.route) { RemoveAccountScreen() } composable(route = NavigationRoute.VipSelPage.route) { VipSelPage() } composable(route = NavigationRoute.FavouritesScreen.route) { FavouriteNoticeScreen() } composable(route = NavigationRoute.NewPostImageGrid.route) { NewPostImageGridScreen() } composable(route = NavigationRoute.Search.route) { CompositionLocalProvider( LocalAnimatedContentScope provides this, ) { SearchScreen() } } composable( route = NavigationRoute.FollowerList.route, arguments = listOf(navArgument("id") { type = NavType.IntType }) ) { CompositionLocalProvider( LocalAnimatedContentScope provides this, ) { FollowerListScreen(it.arguments?.getInt("id")!!) } } composable( route = NavigationRoute.FollowingList.route, arguments = listOf(navArgument("id") { type = NavType.IntType }) ) { CompositionLocalProvider( LocalAnimatedContentScope provides this, ) { FollowingListScreen(it.arguments?.getInt("id")!!) } } composable(route = NavigationRoute.ResetPassword.route) { CompositionLocalProvider( LocalAnimatedContentScope provides this, ) { ResetPasswordScreen() } } composable(route = NavigationRoute.FavouriteList.route) { CompositionLocalProvider( LocalAnimatedContentScope provides this, ) { FavouriteListPage() } } composable( route = NavigationRoute.Chat.route, arguments = listOf(navArgument("id") { type = NavType.StringType }) ) { CompositionLocalProvider( LocalAnimatedContentScope provides this, ) { ChatScreen(it.arguments?.getString("id")!!) } } composable( route = NavigationRoute.ChatAi.route, arguments = listOf(navArgument("id") { type = NavType.StringType }) ) { CompositionLocalProvider( LocalAnimatedContentScope provides this, ) { ChatAiScreen(it.arguments?.getString("id")!!) } } composable(route = NavigationRoute.ChatSetting.route) { CompositionLocalProvider( LocalAnimatedContentScope provides this, ) { ChatSettingScreen() } } composable( route = NavigationRoute.ChatGroup.route, arguments = listOf(navArgument("id") { type = NavType.StringType }, navArgument("name") { type = NavType.StringType }, navArgument("avatar") { type = NavType.StringType }) ) { val encodedId = it.arguments?.getString("id") val decodedId = encodedId?.let { java.net.URLDecoder.decode(it, "UTF-8") } val name = it.arguments?.getString("name") val avatar = it.arguments?.getString("avatar") CompositionLocalProvider( LocalAnimatedContentScope provides this, ) { GroupChatScreen(decodedId?:"",name?:"",avatar?:"") } } composable(route = NavigationRoute.CommentNoticeScreen.route) { CompositionLocalProvider( LocalAnimatedContentScope provides this, ) { CommentNoticeScreen() } } composable(route = NavigationRoute.ImageCrop.route) { CompositionLocalProvider( LocalAnimatedContentScope provides this, ) { ImageCropScreen() } } composable(route = NavigationRoute.AgentImageCrop.route) { CompositionLocalProvider( LocalAnimatedContentScope provides this, ) { AgentImageCropScreen() } } composable(route = NavigationRoute.AccountSetting.route) { CompositionLocalProvider( LocalAnimatedContentScope provides this, ) { AccountSetting() } } composable(route = NavigationRoute.AboutScreen.route) { CompositionLocalProvider( LocalAnimatedContentScope provides this, ) { AboutScreen() } } composable( route = NavigationRoute.AddAgent.route, ) { AddAgentScreen() } composable( route = NavigationRoute.CreateGroupChat.route, enterTransition = { slideInHorizontally( initialOffsetX = { fullWidth -> fullWidth }, animationSpec = tween(durationMillis = 280) ) }, exitTransition = { slideOutHorizontally( targetOffsetX = { fullWidth -> -fullWidth / 3 }, animationSpec = tween(durationMillis = 280) ) }, popEnterTransition = { slideInHorizontally( initialOffsetX = { fullWidth -> -fullWidth / 3 }, animationSpec = tween(durationMillis = 280) ) }, popExitTransition = { slideOutHorizontally( targetOffsetX = { fullWidth -> fullWidth }, animationSpec = tween(durationMillis = 280) ) } ) { CreateGroupChatScreen() } composable( route = NavigationRoute.GroupInfo.route, arguments = listOf(navArgument("id") { type = NavType.StringType }) ) { val encodedId = it.arguments?.getString("id") val decodedId = encodedId?.let { java.net.URLDecoder.decode(it, "UTF-8") } CompositionLocalProvider( LocalAnimatedContentScope provides this, ) { GroupChatInfoScreen(decodedId?:"") } } composable(route = NavigationRoute.NotificationScreen.route) { CompositionLocalProvider( LocalAnimatedContentScope provides this, ) { NotificationScreen() } } } } @OptIn(ExperimentalSharedTransitionApi::class) @Composable fun Navigation( startDestination: String = NavigationRoute.Login.route, onLaunch: (navController: NavHostController) -> Unit ) { val navController = rememberNavController() LaunchedEffect(Unit) { onLaunch(navController) } SharedTransitionLayout { CompositionLocalProvider( LocalNavController provides navController, LocalSharedTransitionScope provides this@SharedTransitionLayout, ) { Box { NavigationController( navController = navController, startDestination = startDestination ) AgentCreatedSuccessIndicator() } } } } fun NavHostController.navigateToPost( id: Int, highlightCommentId: Int? = 0, initImagePagerIndex: Int? = 0 ) { navigate( route = NavigationRoute.Post.route .replace("{id}", id.toString()) .replace("{highlightCommentId}", highlightCommentId.toString()) .replace("{initImagePagerIndex}", initImagePagerIndex.toString()) ) } fun NavHostController.navigateToChat(id: String) { navigate( route = NavigationRoute.Chat.route .replace("{id}", id) ) } fun NavHostController.navigateToChatAi(id: String) { navigate( route = NavigationRoute.ChatAi.route .replace("{id}", id) ) } fun NavHostController.navigateToGroupChat(id: String,name:String,avatar:String) { val encodedId = java.net.URLEncoder.encode(id, "UTF-8") val encodedName = java.net.URLEncoder.encode(name, "UTF-8") val encodedAvator = java.net.URLEncoder.encode(avatar, "UTF-8") navigate( route = NavigationRoute.ChatGroup.route .replace("{id}", encodedId) .replace("{name}", encodedName) .replace("{avatar}", encodedAvator) ) } fun NavHostController.navigateToGroupInfo(id: String) { val encodedId = java.net.URLEncoder.encode(id, "UTF-8") navigate( route = NavigationRoute.GroupInfo.route .replace("{id}", encodedId) ) } fun NavHostController.goTo( route: NavigationRoute ) { navigate(route.route) }