Display uploading status on the refresh indicator
This commit is contained in:
parent
4d77ccc8ba
commit
bdb317956a
4 changed files with 123 additions and 39 deletions
7
lib/events/photo_upload_event.dart
Normal file
7
lib/events/photo_upload_event.dart
Normal file
|
@ -0,0 +1,7 @@
|
|||
class PhotoUploadEvent {
|
||||
final int completed;
|
||||
final int total;
|
||||
final bool hasError;
|
||||
|
||||
PhotoUploadEvent({this.completed, this.total, this.hasError = false});
|
||||
}
|
|
@ -5,6 +5,7 @@ import 'dart:math';
|
|||
import 'package:logging/logging.dart';
|
||||
import 'package:photos/core/event_bus.dart';
|
||||
import 'package:photos/db/photo_db.dart';
|
||||
import 'package:photos/events/photo_upload_event.dart';
|
||||
import 'package:photos/events/user_authenticated_event.dart';
|
||||
import 'package:photos/photo_repository.dart';
|
||||
import 'package:photos/photo_provider.dart';
|
||||
|
@ -23,6 +24,7 @@ class PhotoSyncManager {
|
|||
final _dio = Dio();
|
||||
final _db = PhotoDB.instance;
|
||||
bool _isSyncInProgress = false;
|
||||
Future<void> _existingSync;
|
||||
|
||||
static final _lastSyncTimestampKey = "last_sync_timestamp_0";
|
||||
static final _lastDBUpdateTimestampKey = "last_db_update_timestamp";
|
||||
|
@ -40,11 +42,23 @@ class PhotoSyncManager {
|
|||
Future<void> sync() async {
|
||||
if (_isSyncInProgress) {
|
||||
_logger.warning("Sync already in progress, skipping.");
|
||||
return;
|
||||
return _existingSync;
|
||||
}
|
||||
_isSyncInProgress = true;
|
||||
_logger.info("Syncing...");
|
||||
_existingSync = Future<void>(() async {
|
||||
_logger.info("Syncing...");
|
||||
try {
|
||||
await _doSync();
|
||||
} catch (e) {
|
||||
throw e;
|
||||
} finally {
|
||||
_isSyncInProgress = false;
|
||||
}
|
||||
});
|
||||
return _existingSync;
|
||||
}
|
||||
|
||||
Future<void> _doSync() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final syncStartTimestamp = DateTime.now().microsecondsSinceEpoch;
|
||||
var lastDBUpdateTimestamp = prefs.getInt(_lastDBUpdateTimestampKey);
|
||||
|
@ -69,18 +83,13 @@ class PhotoSyncManager {
|
|||
await _addToPhotos(recents, lastDBUpdateTimestamp, photos);
|
||||
}
|
||||
|
||||
if (photos.isEmpty) {
|
||||
_isSyncInProgress = false;
|
||||
_syncWithRemote(prefs);
|
||||
} else {
|
||||
if (photos.isNotEmpty) {
|
||||
photos.sort((first, second) =>
|
||||
first.createTimestamp.compareTo(second.createTimestamp));
|
||||
_updateDatabase(photos, prefs, lastDBUpdateTimestamp, syncStartTimestamp)
|
||||
.then((_) {
|
||||
_isSyncInProgress = false;
|
||||
_syncWithRemote(prefs);
|
||||
});
|
||||
await _updateDatabase(
|
||||
photos, prefs, lastDBUpdateTimestamp, syncStartTimestamp);
|
||||
}
|
||||
await _syncWithRemote(prefs);
|
||||
}
|
||||
|
||||
Future _addToPhotos(AssetPathEntity pathEntity, int lastDBUpdateTimestamp,
|
||||
|
@ -102,17 +111,15 @@ class PhotoSyncManager {
|
|||
}
|
||||
}
|
||||
|
||||
_syncWithRemote(SharedPreferences prefs) {
|
||||
Future<void> _syncWithRemote(SharedPreferences prefs) async {
|
||||
// TODO: Fix race conditions triggered due to concurrent syncs.
|
||||
// Add device_id/last_sync_timestamp to the upload request?
|
||||
if (!Configuration.instance.hasConfiguredAccount()) {
|
||||
return;
|
||||
return Future.error("Account not configured yet");
|
||||
}
|
||||
_downloadDiff(prefs).then((_) {
|
||||
_uploadDiff(prefs).then((_) {
|
||||
_deletePhotosOnServer();
|
||||
});
|
||||
});
|
||||
await _downloadDiff(prefs);
|
||||
await _uploadDiff(prefs);
|
||||
await _deletePhotosOnServer();
|
||||
}
|
||||
|
||||
Future<bool> _updateDatabase(
|
||||
|
@ -149,19 +156,22 @@ class PhotoSyncManager {
|
|||
return lastSyncTimestamp;
|
||||
}
|
||||
|
||||
Future _uploadDiff(SharedPreferences prefs) async {
|
||||
Future<void> _uploadDiff(SharedPreferences prefs) async {
|
||||
List<Photo> photosToBeUploaded = await _db.getPhotosToBeUploaded();
|
||||
for (Photo photo in photosToBeUploaded) {
|
||||
for (int i = 0; i < photosToBeUploaded.length; i++) {
|
||||
Photo photo = photosToBeUploaded[i];
|
||||
_logger.info("Uploading " + photo.toString());
|
||||
try {
|
||||
var uploadedPhoto = await _uploadFile(photo);
|
||||
if (uploadedPhoto == null) {
|
||||
return;
|
||||
}
|
||||
await _db.updatePhoto(photo.generatedId, uploadedPhoto.uploadedFileId,
|
||||
uploadedPhoto.remotePath, uploadedPhoto.updateTimestamp);
|
||||
prefs.setInt(_lastSyncTimestampKey, uploadedPhoto.updateTimestamp);
|
||||
|
||||
Bus.instance.fire(PhotoUploadEvent(
|
||||
completed: i + 1, total: photosToBeUploaded.length));
|
||||
} catch (e) {
|
||||
_logger.severe(e);
|
||||
Bus.instance.fire(PhotoUploadEvent(hasError: true));
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -222,16 +232,14 @@ class PhotoSyncManager {
|
|||
)
|
||||
.then((response) {
|
||||
return Photo.fromJson(response.data);
|
||||
}).catchError((e) {
|
||||
_logger.severe("Error in uploading ", e);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _deletePhotosOnServer() async {
|
||||
_db.getAllDeletedPhotos().then((deletedPhotos) {
|
||||
return _db.getAllDeletedPhotos().then((deletedPhotos) async {
|
||||
for (Photo deletedPhoto in deletedPhotos) {
|
||||
_deletePhotoOnServer(deletedPhoto)
|
||||
.then((value) => _db.deletePhoto(deletedPhoto));
|
||||
await _deletePhotoOnServer(deletedPhoto);
|
||||
await _db.deletePhoto(deletedPhoto);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -6,9 +6,11 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter/services.dart';
|
||||
import 'package:photos/core/event_bus.dart';
|
||||
import 'package:photos/events/photo_opened_event.dart';
|
||||
import 'package:photos/events/photo_upload_event.dart';
|
||||
import 'package:photos/models/photo.dart';
|
||||
import 'package:photos/ui/detail_page.dart';
|
||||
import 'package:photos/ui/loading_widget.dart';
|
||||
import 'package:photos/ui/sync_indicator.dart';
|
||||
import 'package:photos/ui/thumbnail_widget.dart';
|
||||
import 'package:photos/utils/date_time_util.dart';
|
||||
import 'package:pull_to_refresh/pull_to_refresh.dart';
|
||||
|
@ -41,23 +43,32 @@ class _GalleryState extends State<Gallery> {
|
|||
Set<Photo> _selectedPhotos = HashSet<Photo>();
|
||||
List<Photo> _photos;
|
||||
RefreshController _refreshController = RefreshController();
|
||||
StreamSubscription<PhotoOpenedEvent> _subscription;
|
||||
StreamSubscription<PhotoOpenedEvent> _photoOpenEventSubscription;
|
||||
StreamSubscription<PhotoUploadEvent> _photoUploadEventSubscription;
|
||||
Photo _openedPhoto;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_requiresLoad = true;
|
||||
_subscription = Bus.instance.on<PhotoOpenedEvent>().listen((event) {
|
||||
_photoOpenEventSubscription =
|
||||
Bus.instance.on<PhotoOpenedEvent>().listen((event) {
|
||||
setState(() {
|
||||
_openedPhoto = event.photo;
|
||||
});
|
||||
});
|
||||
_photoUploadEventSubscription =
|
||||
Bus.instance.on<PhotoUploadEvent>().listen((event) {
|
||||
if (event.hasError) {
|
||||
_refreshController.refreshFailed();
|
||||
}
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_subscription.cancel();
|
||||
_photoOpenEventSubscription.cancel();
|
||||
_photoUploadEventSubscription.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
@ -103,13 +114,7 @@ class _GalleryState extends State<Gallery> {
|
|||
return SmartRefresher(
|
||||
controller: _refreshController,
|
||||
child: list,
|
||||
header: ClassicHeader(
|
||||
idleText: "Pull down to sync.",
|
||||
refreshingText: "Syncing...",
|
||||
releaseText: "Release to sync.",
|
||||
completeText: "Sync completed.",
|
||||
failedText: "Sync unsuccessful.",
|
||||
),
|
||||
header: SyncIndicator(),
|
||||
onRefresh: () {
|
||||
widget.syncFunction().then((_) {
|
||||
_refreshController.refreshCompleted();
|
||||
|
@ -118,6 +123,7 @@ class _GalleryState extends State<Gallery> {
|
|||
}));
|
||||
}).catchError((e) {
|
||||
_refreshController.refreshFailed();
|
||||
setState(() {});
|
||||
});
|
||||
},
|
||||
);
|
||||
|
|
63
lib/ui/sync_indicator.dart
Normal file
63
lib/ui/sync_indicator.dart
Normal file
|
@ -0,0 +1,63 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:photos/core/event_bus.dart';
|
||||
import 'package:photos/events/photo_upload_event.dart';
|
||||
import 'package:pull_to_refresh/pull_to_refresh.dart';
|
||||
|
||||
class SyncIndicator extends StatefulWidget {
|
||||
@override
|
||||
_SyncIndicatorState createState() => _SyncIndicatorState();
|
||||
}
|
||||
|
||||
class _SyncIndicatorState extends State<SyncIndicator> {
|
||||
PhotoUploadEvent _event;
|
||||
StreamSubscription<PhotoUploadEvent> _subscription;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_subscription = Bus.instance.on<PhotoUploadEvent>().listen((event) {
|
||||
setState(() {
|
||||
_event = event;
|
||||
});
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_subscription.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ClassicHeader(
|
||||
idleText: "Pull down to sync.",
|
||||
refreshingText: _getRefreshingText(),
|
||||
releaseText: "Release to sync.",
|
||||
completeText: "Sync completed.",
|
||||
failedText: "Sync unsuccessful.",
|
||||
completeDuration: const Duration(milliseconds: 600),
|
||||
refreshStyle: RefreshStyle.UnFollow,
|
||||
);
|
||||
}
|
||||
|
||||
String _getRefreshingText() {
|
||||
if (_event == null) {
|
||||
return "Syncing...";
|
||||
} else {
|
||||
var s;
|
||||
if (_event.hasError) {
|
||||
s = "Upload failed.";
|
||||
} else {
|
||||
s = "Uploading " +
|
||||
_event.completed.toString() +
|
||||
"/" +
|
||||
_event.total.toString();
|
||||
}
|
||||
_event = null;
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue