feat: Add file sharing support and collections configuration management

- Add intent filters for markdown and text file sharing (text/*, application/octet-stream)
- Implement file content reading with filename extraction and automatic tagging
- Enable edge-to-edge mode with proper IME insets handling for keyboard
- Add collections configuration dirty flag and bookmark ID tracking to TokenManager
- Create CollectionsConfigDto and CollectionConfigDto for JSON serialization
- Add collection query methods
This commit is contained in:
2026-01-30 20:33:20 -05:00
parent fdacf2248a
commit 4021aacc1d
22 changed files with 4188 additions and 657 deletions
@@ -16,9 +16,21 @@ import java.net.URLEncoder
sealed class Screen(val route: String) {
object Login : Screen("login")
object Feed : Screen("feed?tag={tag}") {
fun createRoute(tag: String? = null): String {
return if (tag != null) "feed?tag=$tag" else "feed"
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}")
@@ -36,6 +48,9 @@ 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
) {
val navController = rememberNavController()
@@ -45,14 +60,22 @@ fun AppNavGraph(
composable(Screen.Login.route) {
com.shaarit.presentation.auth.LoginScreen(
onLoginSuccess = {
if (shareUrl != null) {
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") {
navController.navigate("add?url=$encodedUrl&title=$encodedTitle&isShare=true&isFileShare=false&description=&tags=") {
popUpTo(Screen.Login.route) { inclusive = true }
}
} else if (initialDeepLink != null) {
@@ -70,12 +93,16 @@ fun AppNavGraph(
}
composable(
route = "feed?tag={tag}",
route = "feed?tag={tag}&collectionId={collectionId}",
arguments = listOf(
navArgument("tag") {
type = NavType.StringType
nullable = true
defaultValue = null
},
navArgument("collectionId") {
type = NavType.LongType
defaultValue = -1L
}
),
deepLinks = listOf(
@@ -84,6 +111,9 @@ fun AppNavGraph(
)
) { 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 ->
@@ -93,12 +123,13 @@ fun AppNavGraph(
onNavigateToCollections = { navController.navigate(Screen.Collections.route) },
onNavigateToSettings = { navController.navigate(Screen.Settings.route) },
onNavigateToRandom = { },
initialTagFilter = tag
initialTagFilter = tag,
initialCollectionId = collectionId
)
}
composable(
route = "add?url={url}&title={title}&isShare={isShare}",
route = "add?url={url}&title={title}&isShare={isShare}&isFileShare={isFileShare}&description={description}&tags={tags}",
arguments = listOf(
navArgument("url") {
type = NavType.StringType
@@ -113,6 +144,20 @@ fun AppNavGraph(
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(
@@ -165,9 +210,14 @@ fun AppNavGraph(
) {
com.shaarit.presentation.collections.CollectionsScreen(
onNavigateBack = { navController.popBackStack() },
onCollectionClick = { collectionId ->
// Naviguer vers le feed avec le filtre de collection
navController.navigate(Screen.Feed.createRoute()) {
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 }
}
}