From 9261ec341e94fdb8d62c58b6324b17db171b19da Mon Sep 17 00:00:00 2001 From: Julian Mutter Date: Fri, 6 Feb 2026 20:43:26 +0100 Subject: [PATCH] Add sync status to app drawer --- lib/core/services/sync_service.dart | 23 ++++++++-- lib/features/home/home_page.dart | 1 + lib/features/home/widgets/app_drawer.dart | 56 +++++++++++++++++++---- 3 files changed, 67 insertions(+), 13 deletions(-) diff --git a/lib/core/services/sync_service.dart b/lib/core/services/sync_service.dart index 47e40bd..bfe831f 100644 --- a/lib/core/services/sync_service.dart +++ b/lib/core/services/sync_service.dart @@ -11,12 +11,16 @@ class SyncResult { final bool isOnline; final int changesSynced; final int annotationsSynced; + final int changesUnsynced; + final int annotationsUnsynced; SyncResult({ required this.sheets, required this.isOnline, - this.changesSynced = 0, - this.annotationsSynced = 0, + required this.changesSynced, + required this.annotationsSynced, + required this.changesUnsynced, + required this.annotationsUnsynced, }); } @@ -35,8 +39,8 @@ class SyncService { SyncService({ required ApiClient apiClient, required StorageService storageService, - }) : _apiClient = apiClient, - _storageService = storageService; + }) : _apiClient = apiClient, + _storageService = storageService; /// Performs a full sync operation. /// @@ -80,6 +84,8 @@ class SyncService { // 3. Upload pending annotations annotationsSynced = await _uploadPendingAnnotations(); + final remainingAnnotations = await _storageService + .readPendingAnnotationUploads(); // 4. Apply any remaining local changes (in case some failed to upload) final changeQueue = await _storageService.readChangeQueue(); @@ -102,6 +108,8 @@ class SyncService { isOnline: true, changesSynced: changesSynced, annotationsSynced: annotationsSynced, + changesUnsynced: changeQueue.length, + annotationsUnsynced: remainingAnnotations.length, ); } @@ -127,9 +135,16 @@ class SyncService { } } + final remainingAnnotations = await _storageService + .readPendingAnnotationUploads(); + return SyncResult( sheets: sheets, isOnline: false, + changesSynced: 0, + annotationsSynced: 0, + changesUnsynced: changeQueue.length, + annotationsUnsynced: remainingAnnotations.length, ); } diff --git a/lib/features/home/home_page.dart b/lib/features/home/home_page.dart index 15aed53..832875a 100644 --- a/lib/features/home/home_page.dart +++ b/lib/features/home/home_page.dart @@ -219,6 +219,7 @@ class _HomePageState extends State with RouteAware { onLogout: _handleLogout, appName: _appName, appVersion: _appVersion, + syncFuture: _syncFuture, ), body: RefreshIndicator(onRefresh: _refreshSheets, child: _buildBody()), ); diff --git a/lib/features/home/widgets/app_drawer.dart b/lib/features/home/widgets/app_drawer.dart index c403d71..8e6283a 100644 --- a/lib/features/home/widgets/app_drawer.dart +++ b/lib/features/home/widgets/app_drawer.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:sheetless/core/services/sync_service.dart'; /// Callback for shuffle state changes. typedef ShuffleCallback = void Function(bool enabled); @@ -12,12 +13,14 @@ class AppDrawer extends StatelessWidget { final VoidCallback onLogout; final String? appName; final String? appVersion; + final Future syncFuture; const AppDrawer({ super.key, required this.isShuffling, required this.onShuffleChanged, required this.onLogout, + required this.syncFuture, this.appName, this.appVersion, }); @@ -32,7 +35,7 @@ class AppDrawer extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ _buildActions(), - _buildAppInfo(), + Column(children: [_buildSyncStatus(), _buildAppInfo()]), ], ), ), @@ -44,10 +47,7 @@ class AppDrawer extends StatelessWidget { return Column( children: [ ListTile( - leading: Icon( - Icons.shuffle, - color: isShuffling ? Colors.blue : null, - ), + leading: Icon(Icons.shuffle, color: isShuffling ? Colors.blue : null), title: const Text('Shuffle'), onTap: () => onShuffleChanged(!isShuffling), ), @@ -60,6 +60,47 @@ class AppDrawer extends StatelessWidget { ); } + Widget _buildSyncStatus() { + return Center( + // padding: const EdgeInsets.all(5.0), + child: FutureBuilder( + future: syncFuture, + builder: (context, snapshot) { + if (snapshot.connectionState != ConnectionState.done) { + return const Center(child: CircularProgressIndicator()); + } + + if (snapshot.hasError) { + return Text( + "Error: ${snapshot.error.toString()}", + style: const TextStyle(color: Colors.red), + textAlign: TextAlign.center, + ); + } + + if (snapshot.hasData) { + final changes = snapshot.data!.changesUnsynced; + final annotations = snapshot.data!.annotationsUnsynced; + if (changes == 0 && annotations == 0) { + return Text( + "All synced!", + style: const TextStyle(color: Colors.black), + textAlign: TextAlign.center, + ); + } + return Text( + "$changes changes and $annotations annotations unsynchronized!", + style: const TextStyle(color: Colors.red), + textAlign: TextAlign.center, + ); + } + + return const Center(child: CircularProgressIndicator()); + }, + ), + ); + } + Widget _buildAppInfo() { final versionText = appName != null && appVersion != null ? '$appName v$appVersion' @@ -67,10 +108,7 @@ class AppDrawer extends StatelessWidget { return Padding( padding: const EdgeInsets.all(16.0), - child: Text( - versionText, - style: const TextStyle(color: Colors.grey), - ), + child: Text(versionText, style: const TextStyle(color: Colors.grey)), ); } }