Initial commit — SyncFlow Android file sync app

Supports WebDAV, SFTP, SFTPGo, Nextcloud, ownCloud, Google Drive,
Dropbox, and OneDrive. Credentials encrypted with Android Keystore.
Biometric app-lock, conflict resolution, and auto-sync via WorkManager.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-22 20:21:20 +00:00
commit cff4233de6
95 changed files with 5381 additions and 0 deletions
@@ -0,0 +1,64 @@
package com.syncflow.ui.navigation
import androidx.compose.runtime.Composable
import androidx.navigation.NavHostController
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.navArgument
import com.syncflow.ui.addpair.AddPairScreen
import com.syncflow.ui.auth.AccountSetupScreen
import com.syncflow.ui.conflict.ConflictScreen
import com.syncflow.ui.main.MainShell
import com.syncflow.ui.pairdetail.PairDetailScreen
sealed class Screen(val route: String) {
object Main : Screen("main")
object AddPair : Screen("add_pair?pairId={pairId}") {
fun route(pairId: Long? = null) = if (pairId != null) "add_pair?pairId=$pairId" else "add_pair"
}
object PairDetail : Screen("pair/{pairId}") {
fun route(pairId: Long) = "pair/$pairId"
}
object Conflicts : Screen("conflicts/{pairId}") {
fun route(pairId: Long) = "conflicts/$pairId"
}
object AddAccount : Screen("add_account")
}
@Composable
fun SyncFlowNavGraph(navController: NavHostController) {
NavHost(navController = navController, startDestination = Screen.Main.route) {
composable(Screen.Main.route) {
MainShell(
onAddPair = { navController.navigate(Screen.AddPair.route()) },
onPairClick = { id -> navController.navigate(Screen.PairDetail.route(id)) },
onAddAccount = { navController.navigate(Screen.AddAccount.route) },
)
}
composable(
route = "add_pair?pairId={pairId}",
arguments = listOf(navArgument("pairId") { type = NavType.LongType; defaultValue = -1L }),
) {
AddPairScreen(onDone = { navController.popBackStack() })
}
composable(
route = "pair/{pairId}",
arguments = listOf(navArgument("pairId") { type = NavType.LongType }),
) {
PairDetailScreen(
onBack = { navController.popBackStack() },
onConflicts = { id -> navController.navigate(Screen.Conflicts.route(id)) },
)
}
composable(
route = "conflicts/{pairId}",
arguments = listOf(navArgument("pairId") { type = NavType.LongType }),
) {
ConflictScreen(onBack = { navController.popBackStack() })
}
composable(Screen.AddAccount.route) {
AccountSetupScreen(onDone = { navController.popBackStack() })
}
}
}