From 9fed148a12d4d53bffa8f6dc59b700aba3ce056d Mon Sep 17 00:00:00 2001 From: m Date: Sat, 12 Sep 2026 10:01:43 +0200 Subject: [PATCH] add local status --- .../ui/document/DocumentViewerScreen.kt | 10 +++++++++ .../ui/folderdetail/FolderDetailScreen.kt | 8 +++++++ .../mobile/ui/folderlist/FolderListScreen.kt | 8 +++++++ .../mobile/ui/navigation/NavGraph.kt | 7 +++++++ .../mobile/ui/navigation/VaultDropApp.kt | 21 +++++++++++++++---- .../mobile/ui/search/SearchScreen.kt | 14 ++++++++++++- .../mobile/ui/settings/SettingsScreen.kt | 14 ++++++++++++- .../app/src/main/res/values/strings.xml | 7 +++++++ 8 files changed, 83 insertions(+), 6 deletions(-) diff --git a/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/document/DocumentViewerScreen.kt b/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/document/DocumentViewerScreen.kt index d45f369..6028099 100644 --- a/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/document/DocumentViewerScreen.kt +++ b/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/document/DocumentViewerScreen.kt @@ -58,6 +58,8 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vaultdrop.mobile.R import com.vaultdrop.mobile.data.local.entity.FileEntity import com.vaultdrop.mobile.domain.FileCategory +import com.vaultdrop.mobile.features.connection.ConnectionStatusViewModel +import com.vaultdrop.mobile.ui.components.ServerStatusBadge import com.vaultdrop.mobile.ui.components.categoryValue import com.vaultdrop.mobile.ui.document.content.DocumentContentViewer import com.vaultdrop.mobile.ui.document.content.PdfFocusViewer @@ -81,9 +83,11 @@ import java.util.Locale fun DocumentViewerScreen( initialResourceId: String, onBack: () -> Unit, + connectionStatusViewModel: ConnectionStatusViewModel, viewModel: DocumentViewerViewModel = hiltViewModel(), ) { val documents by viewModel.documents.collectAsStateWithLifecycle() + val connectionStatus by connectionStatusViewModel.status.collectAsStateWithLifecycle() val pagerState = rememberPagerState(pageCount = { documents.size }) // Démarre une seule fois sur le document demandé, dès que la liste est chargée. @@ -160,6 +164,12 @@ fun DocumentViewerScreen( ) } }, + actions = { + ServerStatusBadge( + status = connectionStatus, + onClick = connectionStatusViewModel::checkNow, + ) + }, ) } }, diff --git a/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/folderdetail/FolderDetailScreen.kt b/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/folderdetail/FolderDetailScreen.kt index 986ba14..e6c69c5 100644 --- a/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/folderdetail/FolderDetailScreen.kt +++ b/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/folderdetail/FolderDetailScreen.kt @@ -37,7 +37,9 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vaultdrop.mobile.R import com.vaultdrop.mobile.data.local.entity.FileEntity import com.vaultdrop.mobile.data.local.entity.FolderEntity +import com.vaultdrop.mobile.features.connection.ConnectionStatusViewModel import com.vaultdrop.mobile.ui.components.FileCategoryIcon +import com.vaultdrop.mobile.ui.components.ServerStatusBadge import java.util.Locale @OptIn(ExperimentalMaterial3Api::class) @@ -47,9 +49,11 @@ fun FolderDetailScreen( onBack: () -> Unit, onOpenFolder: (String) -> Unit, onOpenDocument: (String) -> Unit, + connectionStatusViewModel: ConnectionStatusViewModel, viewModel: FolderDetailViewModel = hiltViewModel(), ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() + val connectionStatus by connectionStatusViewModel.status.collectAsStateWithLifecycle() LaunchedEffect(uiState.folderMissing) { if (uiState.folderMissing) onBack() @@ -68,6 +72,10 @@ fun FolderDetailScreen( } }, actions = { + ServerStatusBadge( + status = connectionStatus, + onClick = connectionStatusViewModel::checkNow, + ) IconButton(onClick = viewModel::refresh) { Icon(Icons.Filled.Refresh, contentDescription = stringResource(R.string.refresh)) } diff --git a/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/folderlist/FolderListScreen.kt b/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/folderlist/FolderListScreen.kt index 038ec8f..c1f71d8 100644 --- a/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/folderlist/FolderListScreen.kt +++ b/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/folderlist/FolderListScreen.kt @@ -49,8 +49,10 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vaultdrop.mobile.R import com.vaultdrop.mobile.data.local.entity.FileEntity +import com.vaultdrop.mobile.features.connection.ConnectionStatusViewModel import com.vaultdrop.mobile.features.sync.SyncViewModel import com.vaultdrop.mobile.ui.components.FileCategoryIcon +import com.vaultdrop.mobile.ui.components.ServerStatusBadge import com.vaultdrop.mobile.ui.navigation.FloatingNavBar import com.vaultdrop.mobile.ui.navigation.NavTab import java.util.Locale @@ -62,10 +64,12 @@ fun FolderListScreen( onTabSelected: (NavTab) -> Unit, onOpenDocument: (String) -> Unit, syncViewModel: SyncViewModel, + connectionStatusViewModel: ConnectionStatusViewModel, viewModel: FolderListViewModel = hiltViewModel(), ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() val importState by syncViewModel.importState.collectAsStateWithLifecycle() + val connectionStatus by connectionStatusViewModel.status.collectAsStateWithLifecycle() val context = LocalContext.current val folderLabel = stringResource(R.string.folder) @@ -91,6 +95,10 @@ fun FolderListScreen( TopAppBar( title = { Text(stringResource(R.string.files)) }, actions = { + ServerStatusBadge( + status = connectionStatus, + onClick = connectionStatusViewModel::checkNow, + ) IconButton(onClick = viewModel::refresh) { Icon(Icons.Filled.Refresh, contentDescription = stringResource(R.string.refresh)) } diff --git a/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/navigation/NavGraph.kt b/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/navigation/NavGraph.kt index cbb7089..eb7625d 100644 --- a/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/navigation/NavGraph.kt +++ b/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/navigation/NavGraph.kt @@ -15,6 +15,7 @@ import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import androidx.navigation.navArgument import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.vaultdrop.mobile.features.connection.ConnectionStatusViewModel import com.vaultdrop.mobile.features.sync.SyncViewModel import com.vaultdrop.mobile.ui.auth.AuthViewModel import com.vaultdrop.mobile.ui.document.DocumentViewerScreen @@ -47,6 +48,7 @@ private fun String?.toNavTab(): NavTab = when (this) { fun NavGraph( authViewModel: AuthViewModel, syncViewModel: SyncViewModel, + connectionStatusViewModel: ConnectionStatusViewModel, ) { val navController = rememberNavController() @@ -75,6 +77,7 @@ fun NavGraph( onTabSelected = onTabSelected, onOpenDocument = { id -> navController.navigate(Routes.document(id)) }, syncViewModel = syncViewModel, + connectionStatusViewModel = connectionStatusViewModel, ) } composable(Routes.SEARCH) { @@ -82,6 +85,7 @@ fun NavGraph( selectedTab = selectedTab, onTabSelected = onTabSelected, onOpenDocument = { id -> navController.navigate(Routes.document(id)) }, + connectionStatusViewModel = connectionStatusViewModel, ) } composable(Routes.SETTINGS) { @@ -89,6 +93,7 @@ fun NavGraph( selectedTab = selectedTab, onTabSelected = onTabSelected, authViewModel = authViewModel, + connectionStatusViewModel = connectionStatusViewModel, ) } composable( @@ -105,6 +110,7 @@ fun NavGraph( onBack = { navController.popBackStack() }, onOpenFolder = { id -> navController.navigate(Routes.folder(id)) }, onOpenDocument = { id -> navController.navigate(Routes.document(id)) }, + connectionStatusViewModel = connectionStatusViewModel, ) } composable( @@ -119,6 +125,7 @@ fun NavGraph( DocumentViewerScreen( initialResourceId = documentId, onBack = { navController.popBackStack() }, + connectionStatusViewModel = connectionStatusViewModel, ) } } diff --git a/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/navigation/VaultDropApp.kt b/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/navigation/VaultDropApp.kt index f5215f7..61c55f8 100644 --- a/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/navigation/VaultDropApp.kt +++ b/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/navigation/VaultDropApp.kt @@ -11,6 +11,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.vaultdrop.mobile.features.connection.ConnectionStatusViewModel import com.vaultdrop.mobile.features.sync.SyncViewModel import com.vaultdrop.mobile.ui.auth.AuthState import com.vaultdrop.mobile.ui.auth.AuthViewModel @@ -35,18 +36,30 @@ private fun SplashScreen() { fun VaultDropApp( authViewModel: AuthViewModel = hiltViewModel(), syncViewModel: SyncViewModel = hiltViewModel(), + connectionStatusViewModel: ConnectionStatusViewModel = hiltViewModel(), ) { val authState by authViewModel.authState.collectAsStateWithLifecycle() - // Une fois la session résolue, lancer la boucle de fond (idempotente). + // Une fois la session résolue, lancer les boucles de fond (idempotentes). LaunchedEffect(authState) { - if (authState != AuthState.Loading) syncViewModel.ensureStarted() + if (authState != AuthState.Loading) { + syncViewModel.ensureStarted() + connectionStatusViewModel.start() + } } when (authState) { AuthState.Loading -> SplashScreen() AuthState.SignedOut -> LoginScreen() - AuthState.Local -> NavGraph(authViewModel = authViewModel, syncViewModel = syncViewModel) - is AuthState.SignedIn -> NavGraph(authViewModel = authViewModel, syncViewModel = syncViewModel) + AuthState.Local -> NavGraph( + authViewModel = authViewModel, + syncViewModel = syncViewModel, + connectionStatusViewModel = connectionStatusViewModel, + ) + is AuthState.SignedIn -> NavGraph( + authViewModel = authViewModel, + syncViewModel = syncViewModel, + connectionStatusViewModel = connectionStatusViewModel, + ) } } \ No newline at end of file diff --git a/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/search/SearchScreen.kt b/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/search/SearchScreen.kt index d92c9dd..28f746c 100644 --- a/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/search/SearchScreen.kt +++ b/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/search/SearchScreen.kt @@ -44,7 +44,9 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vaultdrop.mobile.R import com.vaultdrop.mobile.data.local.entity.FileEntity import com.vaultdrop.mobile.domain.FileCategory +import com.vaultdrop.mobile.features.connection.ConnectionStatusViewModel import com.vaultdrop.mobile.ui.components.FileCategoryIcon +import com.vaultdrop.mobile.ui.components.ServerStatusBadge import com.vaultdrop.mobile.ui.components.color import com.vaultdrop.mobile.ui.components.icon import com.vaultdrop.mobile.ui.navigation.FloatingNavBar @@ -57,13 +59,23 @@ fun SearchScreen( selectedTab: NavTab, onTabSelected: (NavTab) -> Unit, onOpenDocument: (String) -> Unit, + connectionStatusViewModel: ConnectionStatusViewModel, viewModel: SearchViewModel = hiltViewModel(), ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() + val connectionStatus by connectionStatusViewModel.status.collectAsStateWithLifecycle() Scaffold( topBar = { - TopAppBar(title = { Text(stringResource(R.string.search)) }) + TopAppBar( + title = { Text(stringResource(R.string.search)) }, + actions = { + ServerStatusBadge( + status = connectionStatus, + onClick = connectionStatusViewModel::checkNow, + ) + }, + ) }, bottomBar = { FloatingNavBar(selected = selectedTab, onSelect = onTabSelected) diff --git a/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/settings/SettingsScreen.kt b/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/settings/SettingsScreen.kt index 296c7e3..350ad9e 100644 --- a/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/settings/SettingsScreen.kt +++ b/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/settings/SettingsScreen.kt @@ -46,9 +46,11 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vaultdrop.mobile.R import com.vaultdrop.mobile.domain.ThemePreference +import com.vaultdrop.mobile.features.connection.ConnectionStatusViewModel import com.vaultdrop.mobile.ui.auth.AuthState import com.vaultdrop.mobile.ui.auth.AuthViewModel import com.vaultdrop.mobile.ui.auth.authErrorResFor +import com.vaultdrop.mobile.ui.components.ServerStatusBadge import com.vaultdrop.mobile.ui.navigation.FloatingNavBar import com.vaultdrop.mobile.ui.navigation.NavTab @@ -58,11 +60,13 @@ fun SettingsScreen( selectedTab: NavTab, onTabSelected: (NavTab) -> Unit, authViewModel: AuthViewModel, + connectionStatusViewModel: ConnectionStatusViewModel, viewModel: SettingsViewModel = hiltViewModel(), ) { val theme by viewModel.theme.collectAsStateWithLifecycle() val serverUrl by viewModel.serverUrl.collectAsStateWithLifecycle() val testUiState by viewModel.testUiState.collectAsStateWithLifecycle() + val connectionStatus by connectionStatusViewModel.status.collectAsStateWithLifecycle() val authState by authViewModel.authState.collectAsStateWithLifecycle() val loginUi by authViewModel.loginUiState.collectAsStateWithLifecycle() @@ -77,7 +81,15 @@ fun SettingsScreen( Scaffold( topBar = { - TopAppBar(title = { Text(stringResource(R.string.settings)) }) + TopAppBar( + title = { Text(stringResource(R.string.settings)) }, + actions = { + ServerStatusBadge( + status = connectionStatus, + onClick = connectionStatusViewModel::checkNow, + ) + }, + ) }, bottomBar = { FloatingNavBar(selected = selectedTab, onSelect = onTabSelected) diff --git a/mobile-kotlin/app/src/main/res/values/strings.xml b/mobile-kotlin/app/src/main/res/values/strings.xml index 579b844..4a0bb7b 100644 --- a/mobile-kotlin/app/src/main/res/values/strings.xml +++ b/mobile-kotlin/app/src/main/res/values/strings.xml @@ -75,4 +75,11 @@ Nom d\'utilisateur ou mot de passe incorrect Continuer sans compte Mode local — tes dossiers restent sur cet appareil et ne sont pas synchronisés. + + + En ligne + Hors ligne + Vérification… + Mode local + Statut serveur — appuyer pour vérifier \ No newline at end of file