379 lines
15 KiB
Kotlin

package com.shaarit.presentation.nav
import android.app.Activity
import android.content.Intent
import android.net.Uri
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import androidx.core.net.toUri
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import androidx.navigation.navDeepLink
import java.net.URLEncoder
sealed class Screen(val route: String) {
object Login : Screen("login")
object Feed : Screen("feed?tag={tag}&collectionId={collectionId}") {
fun createRoute(tag: String? = null, collectionId: Long? = null): String {
val params = mutableListOf<String>()
if (tag != null) {
val encoded = URLEncoder.encode(tag, "UTF-8")
params.add("tag=$encoded")
}
if (collectionId != null) {
params.add("collectionId=$collectionId")
}
return if (params.isEmpty()) {
"feed"
} else {
"feed?" + params.joinToString("&")
}
}
}
object Add : Screen("add?url={url}&title={title}&isShare={isShare}")
object Edit : Screen("edit/{linkId}") {
fun createRoute(linkId: Int): String = "edit/$linkId"
}
object Tags : Screen("tags")
object Collections : Screen("collections")
object Dashboard : Screen("dashboard")
object Settings : Screen("settings")
object Help : Screen("help")
object DeadLinks : Screen("dead_links")
object Pinned : Screen("pinned")
object Reader : Screen("reader/{linkId}") {
fun createRoute(linkId: Int): String = "reader/$linkId"
}
object Reminders : Screen("reminders")
object Todo : Screen("todo")
object TodoDetail : Screen("todoDetail/{linkId}") {
fun createRoute(linkId: Int): String = "todoDetail/$linkId"
}
}
@Composable
fun AppNavGraph(
startDestination: String = Screen.Login.route,
shareUrl: String? = null,
shareTitle: String? = null,
shareDescription: String? = null,
shareTags: List<String>? = null,
isFileShare: Boolean = false,
initialDeepLink: String? = null,
onPlayAudio: ((com.shaarit.domain.model.ShaarliLink) -> Unit)? = null
) {
val navController = rememberNavController()
val context = LocalContext.current
// If user is already logged in and has share data, navigate directly to Add screen
val hasShareData = shareUrl != null || (isFileShare && shareTitle != null)
androidx.compose.runtime.LaunchedEffect(hasShareData) {
if (hasShareData && startDestination != Screen.Login.route) {
val route = if (isFileShare && shareTitle != null) {
val encodedTitle = URLEncoder.encode(shareTitle, "UTF-8")
val encodedDesc = if (shareDescription != null) URLEncoder.encode(shareDescription, "UTF-8") else ""
val encodedTags = shareTags?.joinToString(",") { URLEncoder.encode(it, "UTF-8") } ?: ""
"add?url=&title=$encodedTitle&isShare=true&isFileShare=true&description=$encodedDesc&tags=$encodedTags"
} else if (shareUrl != null) {
val encodedUrl = URLEncoder.encode(shareUrl, "UTF-8")
val encodedTitle = if (shareTitle != null) URLEncoder.encode(shareTitle, "UTF-8") else ""
"add?url=$encodedUrl&title=$encodedTitle&isShare=true&isFileShare=false&description=&tags="
} else null
route?.let {
navController.navigate(it) {
popUpTo(startDestination) { inclusive = true }
}
}
}
}
NavHost(navController = navController, startDestination = startDestination) {
composable(Screen.Login.route) {
com.shaarit.presentation.auth.LoginScreen(
onLoginSuccess = {
if (isFileShare && shareTitle != null) {
// File share - navigate to add screen with file data
val encodedTitle = URLEncoder.encode(shareTitle, "UTF-8")
val encodedDesc = if (shareDescription != null) URLEncoder.encode(shareDescription, "UTF-8") else ""
val encodedTags = shareTags?.joinToString(",") { URLEncoder.encode(it, "UTF-8") } ?: ""
navController.navigate("add?url=&title=$encodedTitle&isShare=true&isFileShare=true&description=$encodedDesc&tags=$encodedTags") {
popUpTo(Screen.Login.route) { inclusive = true }
}
} else if (shareUrl != null) {
// Use proper URL encoding that handles spaces correctly
val encodedUrl = URLEncoder.encode(shareUrl, "UTF-8")
val encodedTitle =
if (shareTitle != null) {
URLEncoder.encode(shareTitle, "UTF-8")
} else ""
navController.navigate("add?url=$encodedUrl&title=$encodedTitle&isShare=true&isFileShare=false&description=&tags=") {
popUpTo(Screen.Login.route) { inclusive = true }
}
} else if (initialDeepLink != null) {
// Handle deep link after login
navController.navigate(initialDeepLink) {
popUpTo(Screen.Login.route) { inclusive = true }
}
} else {
navController.navigate(Screen.Feed.createRoute()) {
popUpTo(Screen.Login.route) { inclusive = true }
}
}
}
)
}
composable(
route = "feed?tag={tag}&collectionId={collectionId}",
arguments = listOf(
navArgument("tag") {
type = NavType.StringType
nullable = true
defaultValue = null
},
navArgument("collectionId") {
type = NavType.LongType
nullable = false
defaultValue = -1L
}
),
deepLinks = listOf(
navDeepLink { uriPattern = "shaarit://feed" },
navDeepLink { uriPattern = "shaarit://search" }
)
) { backStackEntry ->
val tag = backStackEntry.arguments?.getString("tag")
val collectionId = backStackEntry.arguments
?.getLong("collectionId")
?.takeIf { it != -1L }
com.shaarit.presentation.feed.FeedScreen(
onNavigateToAdd = { navController.navigate("add?url=&title=&isShare=false") },
onNavigateToEdit = { linkId ->
navController.navigate(Screen.Edit.createRoute(linkId))
},
onNavigateToTags = { navController.navigate(Screen.Tags.route) },
onNavigateToCollections = { navController.navigate(Screen.Collections.route) },
onNavigateToSettings = { navController.navigate(Screen.Settings.route) },
onNavigateToHelp = { navController.navigate(Screen.Help.route) },
onNavigateToDeadLinks = { navController.navigate(Screen.DeadLinks.route) },
onNavigateToPinned = { navController.navigate(Screen.Pinned.route) },
onNavigateToReader = { linkId ->
navController.navigate(Screen.Reader.createRoute(linkId))
},
onNavigateToReminders = { navController.navigate(Screen.Reminders.route) },
onNavigateToTodo = { navController.navigate(Screen.Todo.route) },
onNavigateToTodoDetail = { linkId -> navController.navigate(Screen.TodoDetail.createRoute(linkId)) },
onPlayAudio = onPlayAudio,
initialTagFilter = tag,
initialCollectionId = collectionId
)
}
composable(
route = "add?url={url}&title={title}&isShare={isShare}&isFileShare={isFileShare}&description={description}&tags={tags}",
arguments = listOf(
navArgument("url") {
type = NavType.StringType
defaultValue = ""
nullable = true
},
navArgument("title") {
type = NavType.StringType
defaultValue = ""
nullable = true
},
navArgument("isShare") {
type = NavType.BoolType
defaultValue = false
},
navArgument("isFileShare") {
type = NavType.BoolType
defaultValue = false
},
navArgument("description") {
type = NavType.StringType
defaultValue = ""
nullable = true
},
navArgument("tags") {
type = NavType.StringType
defaultValue = ""
nullable = true
}
),
deepLinks = listOf(
navDeepLink { uriPattern = "shaarit://add" }
)
) { backStackEntry ->
val isShare = backStackEntry.arguments?.getBoolean("isShare") ?: false
com.shaarit.presentation.add.AddLinkScreen(
onNavigateBack = { navController.popBackStack() },
onShareSuccess = if (isShare) {
{ (context as? Activity)?.finish() }
} else null
)
}
composable(
route = "edit/{linkId}",
arguments = listOf(
navArgument("linkId") {
type = NavType.IntType
}
)
) {
com.shaarit.presentation.edit.EditLinkScreen(
onNavigateBack = { navController.popBackStack() }
)
}
composable(
route = Screen.Tags.route,
deepLinks = listOf(
navDeepLink { uriPattern = "shaarit://tags" }
)
) {
com.shaarit.presentation.tags.TagsScreen(
onNavigateBack = { navController.popBackStack() },
onNavigateToFeedWithTag = { tag ->
navController.navigate(Screen.Feed.createRoute(tag)) {
popUpTo(Screen.Tags.route) { inclusive = true }
}
}
)
}
composable(
route = Screen.Collections.route,
deepLinks = listOf(
navDeepLink { uriPattern = "shaarit://collections" }
)
) {
com.shaarit.presentation.collections.CollectionsScreen(
onNavigateBack = { navController.popBackStack() },
onCollectionClick = { collectionId, isSmart, query ->
navController.navigate(
if (isSmart) {
Screen.Feed.createRoute(tag = query)
} else {
Screen.Feed.createRoute(collectionId = collectionId)
}
) {
popUpTo(Screen.Collections.route) { inclusive = true }
}
}
)
}
composable(
route = Screen.Dashboard.route,
deepLinks = listOf(
navDeepLink { uriPattern = "shaarit://dashboard" }
)
) {
com.shaarit.presentation.dashboard.DashboardScreen(
onNavigateBack = { navController.popBackStack() }
)
}
composable(
route = Screen.Settings.route
) {
com.shaarit.presentation.settings.SettingsScreen(
onNavigateBack = { navController.popBackStack() },
onNavigateToDashboard = { navController.navigate(Screen.Dashboard.route) }
)
}
composable(
route = Screen.Help.route,
deepLinks = listOf(
navDeepLink { uriPattern = "shaarit://help" }
)
) {
com.shaarit.presentation.help.HelpScreen(
onNavigateBack = { navController.popBackStack() }
)
}
composable(
route = Screen.DeadLinks.route
) {
com.shaarit.presentation.deadlinks.DeadLinksScreen(
onNavigateBack = { navController.popBackStack() },
onNavigateToEdit = { linkId ->
navController.navigate(Screen.Edit.createRoute(linkId))
}
)
}
composable(
route = Screen.Pinned.route
) {
com.shaarit.presentation.pinned.PinnedScreen(
onNavigateBack = { navController.popBackStack() },
onNavigateToEdit = { linkId ->
navController.navigate(Screen.Edit.createRoute(linkId))
}
)
}
composable(
route = "reader/{linkId}",
arguments = listOf(
navArgument("linkId") {
type = NavType.IntType
}
),
deepLinks = listOf(
navDeepLink { uriPattern = "shaarit://reader/{linkId}" }
)
) {
com.shaarit.presentation.reader.ReaderModeScreen(
onNavigateBack = { navController.popBackStack() }
)
}
composable(
route = Screen.Reminders.route
) {
com.shaarit.presentation.reminders.RemindersScreen(
onNavigateBack = { navController.popBackStack() },
onNavigateToReader = { linkId ->
navController.navigate(Screen.Reader.createRoute(linkId))
}
)
}
composable(
route = Screen.Todo.route,
deepLinks = listOf(
navDeepLink { uriPattern = "shaarit://todo" }
)
) {
com.shaarit.presentation.todo.TodoScreen(
onNavigateBack = { navController.popBackStack() }
)
}
composable(
route = Screen.TodoDetail.route,
arguments = listOf(
navArgument("linkId") {
type = NavType.IntType
}
)
) { backStackEntry ->
val linkId = backStackEntry.arguments?.getInt("linkId") ?: 0
com.shaarit.presentation.todo.TodoDetailScreen(
linkId = linkId,
onNavigateBack = { navController.popBackStack() }
)
}
}
}