mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-03-27 09:24:45 +01:00
Show proper progress during initial sync
This commit is contained in:
parent
94a34f3124
commit
11279b4119
11 changed files with 346 additions and 47 deletions
57
app/lib/bloc/progress.dart
Normal file
57
app/lib/bloc/progress.dart
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:to_string/to_string.dart';
|
||||||
|
|
||||||
|
part 'progress.g.dart';
|
||||||
|
|
||||||
|
abstract class ProgressBlocEvent {
|
||||||
|
const ProgressBlocEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
@toString
|
||||||
|
class ProgressBlocUpdate extends ProgressBlocEvent {
|
||||||
|
const ProgressBlocUpdate(this.progress, [this.text]);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
|
||||||
|
final double progress;
|
||||||
|
final String? text;
|
||||||
|
}
|
||||||
|
|
||||||
|
@toString
|
||||||
|
class ProgressBlocState with EquatableMixin {
|
||||||
|
const ProgressBlocState(this.progress, this.text);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [progress, text];
|
||||||
|
|
||||||
|
final double progress;
|
||||||
|
final String? text;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A generic bloc to bubble progress update for some events
|
||||||
|
class ProgressBloc extends Bloc<ProgressBlocEvent, ProgressBlocState> {
|
||||||
|
ProgressBloc() : super(const ProgressBlocState(0, null)) {
|
||||||
|
on<ProgressBlocEvent>(_onEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onEvent(
|
||||||
|
ProgressBlocEvent ev, Emitter<ProgressBlocState> emit) async {
|
||||||
|
_log.info("[_onEvent] $ev");
|
||||||
|
if (ev is ProgressBlocUpdate) {
|
||||||
|
await _onEventUpdate(ev, emit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onEventUpdate(
|
||||||
|
ProgressBlocUpdate ev, Emitter<ProgressBlocState> emit) async {
|
||||||
|
emit(ProgressBlocState(ev.progress, ev.text));
|
||||||
|
}
|
||||||
|
|
||||||
|
static final _log = Logger("bloc.progress.ProgressBloc");
|
||||||
|
}
|
19
app/lib/bloc/progress.g.dart
Normal file
19
app/lib/bloc/progress.g.dart
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'progress.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// ToStringGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
extension _$ProgressBlocUpdateToString on ProgressBlocUpdate {
|
||||||
|
String _$toString() {
|
||||||
|
return "ProgressBlocUpdate {progress: $progress, text: $text}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension _$ProgressBlocStateToString on ProgressBlocState {
|
||||||
|
String _$toString() {
|
||||||
|
return "ProgressBlocState {progress: $progress, text: $text}";
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,12 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:bloc/bloc.dart';
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:kiwi/kiwi.dart';
|
import 'package:kiwi/kiwi.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/bloc/bloc_util.dart' as bloc_util;
|
import 'package:nc_photos/bloc/bloc_util.dart' as bloc_util;
|
||||||
|
import 'package:nc_photos/bloc/progress.dart';
|
||||||
import 'package:nc_photos/debug_util.dart';
|
import 'package:nc_photos/debug_util.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
|
@ -15,6 +17,7 @@ import 'package:nc_photos/event/native_event.dart';
|
||||||
import 'package:nc_photos/exception.dart';
|
import 'package:nc_photos/exception.dart';
|
||||||
import 'package:nc_photos/platform/k.dart' as platform_k;
|
import 'package:nc_photos/platform/k.dart' as platform_k;
|
||||||
import 'package:nc_photos/pref.dart';
|
import 'package:nc_photos/pref.dart';
|
||||||
|
import 'package:nc_photos/progress_util.dart';
|
||||||
import 'package:nc_photos/throttler.dart';
|
import 'package:nc_photos/throttler.dart';
|
||||||
import 'package:nc_photos/use_case/ls.dart';
|
import 'package:nc_photos/use_case/ls.dart';
|
||||||
import 'package:nc_photos/use_case/scan_dir.dart';
|
import 'package:nc_photos/use_case/scan_dir.dart';
|
||||||
|
@ -25,22 +28,31 @@ abstract class ScanAccountDirBlocEvent {
|
||||||
const ScanAccountDirBlocEvent();
|
const ScanAccountDirBlocEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
class ScanAccountDirBlocQueryBase extends ScanAccountDirBlocEvent {
|
abstract class ScanAccountDirBlocQueryBase extends ScanAccountDirBlocEvent {
|
||||||
const ScanAccountDirBlocQueryBase();
|
const ScanAccountDirBlocQueryBase({
|
||||||
|
this.progressBloc,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
toString() {
|
toString() {
|
||||||
return "$runtimeType {"
|
return "$runtimeType {"
|
||||||
"}";
|
"}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get notified about the query progress
|
||||||
|
final ProgressBloc? progressBloc;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ScanAccountDirBlocQuery extends ScanAccountDirBlocQueryBase {
|
class ScanAccountDirBlocQuery extends ScanAccountDirBlocQueryBase {
|
||||||
const ScanAccountDirBlocQuery();
|
const ScanAccountDirBlocQuery({
|
||||||
|
super.progressBloc,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class ScanAccountDirBlocRefresh extends ScanAccountDirBlocQueryBase {
|
class ScanAccountDirBlocRefresh extends ScanAccountDirBlocQueryBase {
|
||||||
const ScanAccountDirBlocRefresh();
|
const ScanAccountDirBlocRefresh({
|
||||||
|
super.progressBloc,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An external event has happened and may affect the state of this bloc
|
/// An external event has happened and may affect the state of this bloc
|
||||||
|
@ -72,7 +84,12 @@ class ScanAccountDirBlocInit extends ScanAccountDirBlocState {
|
||||||
}
|
}
|
||||||
|
|
||||||
class ScanAccountDirBlocLoading extends ScanAccountDirBlocState {
|
class ScanAccountDirBlocLoading extends ScanAccountDirBlocState {
|
||||||
const ScanAccountDirBlocLoading(List<FileDescriptor> files) : super(files);
|
const ScanAccountDirBlocLoading(
|
||||||
|
List<FileDescriptor> files, {
|
||||||
|
this.isInitialLoad = false,
|
||||||
|
}) : super(files);
|
||||||
|
|
||||||
|
final bool isInitialLoad;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ScanAccountDirBlocSuccess extends ScanAccountDirBlocState {
|
class ScanAccountDirBlocSuccess extends ScanAccountDirBlocState {
|
||||||
|
@ -207,10 +224,22 @@ class ScanAccountDirBloc
|
||||||
emit(ScanAccountDirBlocLoading(cacheFiles));
|
emit(ScanAccountDirBlocLoading(cacheFiles));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!hasContent && cacheFiles.isEmpty) {
|
||||||
|
emit(const ScanAccountDirBlocLoading([], isInitialLoad: true));
|
||||||
|
}
|
||||||
|
|
||||||
stopwatch.reset();
|
stopwatch.reset();
|
||||||
final bool hasUpdate;
|
final bool hasUpdate;
|
||||||
try {
|
try {
|
||||||
hasUpdate = await _syncOnline(ev);
|
hasUpdate = await _syncOnline(
|
||||||
|
ev,
|
||||||
|
onProgressUpdate: (value) {
|
||||||
|
if (ev.progressBloc?.isClosed == false) {
|
||||||
|
ev.progressBloc!
|
||||||
|
.add(ProgressBlocUpdate(value.progress, value.text));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
_log.shout("[_onEventQuery] Exception while request", e, stackTrace);
|
_log.shout("[_onEventQuery] Exception while request", e, stackTrace);
|
||||||
emit(ScanAccountDirBlocFailure(cacheFiles, e));
|
emit(ScanAccountDirBlocFailure(cacheFiles, e));
|
||||||
|
@ -230,7 +259,10 @@ class ScanAccountDirBloc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> _syncOnline(ScanAccountDirBlocQueryBase ev) async {
|
Future<bool> _syncOnline(
|
||||||
|
ScanAccountDirBlocQueryBase ev, {
|
||||||
|
ValueChanged<Progress>? onProgressUpdate,
|
||||||
|
}) async {
|
||||||
final settings = AccountPref.of(account);
|
final settings = AccountPref.of(account);
|
||||||
final shareDir =
|
final shareDir =
|
||||||
File(path: file_util.unstripPath(account, settings.getShareFolderOr()));
|
File(path: file_util.unstripPath(account, settings.getShareFolderOr()));
|
||||||
|
@ -238,11 +270,20 @@ class ScanAccountDirBloc
|
||||||
|
|
||||||
bool hasUpdate = false;
|
bool hasUpdate = false;
|
||||||
_c.touchManager.clearTouchCache();
|
_c.touchManager.clearTouchCache();
|
||||||
|
final progress = IntProgress(account.roots.length);
|
||||||
for (final r in account.roots) {
|
for (final r in account.roots) {
|
||||||
final dirPath = file_util.unstripPath(account, r);
|
final dirPath = file_util.unstripPath(account, r);
|
||||||
hasUpdate |= await SyncDir(_c)(account, dirPath);
|
hasUpdate |= await SyncDir(_c)(
|
||||||
|
account,
|
||||||
|
dirPath,
|
||||||
|
onProgressUpdate: (value) {
|
||||||
|
final merged = progress.progress + progress.step * value.progress;
|
||||||
|
onProgressUpdate?.call(Progress(merged, value.text));
|
||||||
|
},
|
||||||
|
);
|
||||||
isShareDirIncluded |=
|
isShareDirIncluded |=
|
||||||
file_util.isOrUnderDir(shareDir, File(path: dirPath));
|
file_util.isOrUnderDir(shareDir, File(path: dirPath));
|
||||||
|
progress.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isShareDirIncluded) {
|
if (!isShareDirIncluded) {
|
||||||
|
|
|
@ -1489,6 +1489,11 @@
|
||||||
"@imageSaveOptionDialogServerButtonLabel": {
|
"@imageSaveOptionDialogServerButtonLabel": {
|
||||||
"description": "Save the image on your Nextcloud server"
|
"description": "Save the image on your Nextcloud server"
|
||||||
},
|
},
|
||||||
|
"initialSyncMessage": "Syncing with your server for the first time",
|
||||||
|
"@initialSyncMessage": {
|
||||||
|
"description": "After adding a new account, the app need to sync with the server before showing anything. This message will be shown on screen instead with a proper progress bar and the folder being synced."
|
||||||
|
},
|
||||||
|
|
||||||
"errorUnauthenticated": "Unauthenticated access. Please sign-in again if the problem continues",
|
"errorUnauthenticated": "Unauthenticated access. Please sign-in again if the problem continues",
|
||||||
"@errorUnauthenticated": {
|
"@errorUnauthenticated": {
|
||||||
"description": "Error message when server responds with HTTP401"
|
"description": "Error message when server responds with HTTP401"
|
||||||
|
|
|
@ -179,6 +179,7 @@
|
||||||
"imageSaveOptionDialogContent",
|
"imageSaveOptionDialogContent",
|
||||||
"imageSaveOptionDialogDeviceButtonLabel",
|
"imageSaveOptionDialogDeviceButtonLabel",
|
||||||
"imageSaveOptionDialogServerButtonLabel",
|
"imageSaveOptionDialogServerButtonLabel",
|
||||||
|
"initialSyncMessage",
|
||||||
"errorAlbumDowngrade"
|
"errorAlbumDowngrade"
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@ -374,6 +375,7 @@
|
||||||
"imageSaveOptionDialogContent",
|
"imageSaveOptionDialogContent",
|
||||||
"imageSaveOptionDialogDeviceButtonLabel",
|
"imageSaveOptionDialogDeviceButtonLabel",
|
||||||
"imageSaveOptionDialogServerButtonLabel",
|
"imageSaveOptionDialogServerButtonLabel",
|
||||||
|
"initialSyncMessage",
|
||||||
"errorAlbumDowngrade"
|
"errorAlbumDowngrade"
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@ -459,7 +461,8 @@
|
||||||
"imageSaveOptionDialogTitle",
|
"imageSaveOptionDialogTitle",
|
||||||
"imageSaveOptionDialogContent",
|
"imageSaveOptionDialogContent",
|
||||||
"imageSaveOptionDialogDeviceButtonLabel",
|
"imageSaveOptionDialogDeviceButtonLabel",
|
||||||
"imageSaveOptionDialogServerButtonLabel"
|
"imageSaveOptionDialogServerButtonLabel",
|
||||||
|
"initialSyncMessage"
|
||||||
],
|
],
|
||||||
|
|
||||||
"es": [
|
"es": [
|
||||||
|
@ -470,7 +473,8 @@
|
||||||
"settingsSeedColorDescription",
|
"settingsSeedColorDescription",
|
||||||
"settingsSeedColorPickerTitle",
|
"settingsSeedColorPickerTitle",
|
||||||
"rootPickerSkipConfirmationDialogContent2",
|
"rootPickerSkipConfirmationDialogContent2",
|
||||||
"slideshowSetupDialogReverseTitle"
|
"slideshowSetupDialogReverseTitle",
|
||||||
|
"initialSyncMessage"
|
||||||
],
|
],
|
||||||
|
|
||||||
"fi": [
|
"fi": [
|
||||||
|
@ -478,7 +482,8 @@
|
||||||
"signInHeaderText2",
|
"signInHeaderText2",
|
||||||
"settingsSeedColorTitle",
|
"settingsSeedColorTitle",
|
||||||
"settingsSeedColorDescription",
|
"settingsSeedColorDescription",
|
||||||
"settingsSeedColorPickerTitle"
|
"settingsSeedColorPickerTitle",
|
||||||
|
"initialSyncMessage"
|
||||||
],
|
],
|
||||||
|
|
||||||
"fr": [
|
"fr": [
|
||||||
|
@ -583,7 +588,8 @@
|
||||||
"imageSaveOptionDialogTitle",
|
"imageSaveOptionDialogTitle",
|
||||||
"imageSaveOptionDialogContent",
|
"imageSaveOptionDialogContent",
|
||||||
"imageSaveOptionDialogDeviceButtonLabel",
|
"imageSaveOptionDialogDeviceButtonLabel",
|
||||||
"imageSaveOptionDialogServerButtonLabel"
|
"imageSaveOptionDialogServerButtonLabel",
|
||||||
|
"initialSyncMessage"
|
||||||
],
|
],
|
||||||
|
|
||||||
"pl": [
|
"pl": [
|
||||||
|
@ -705,7 +711,8 @@
|
||||||
"imageSaveOptionDialogTitle",
|
"imageSaveOptionDialogTitle",
|
||||||
"imageSaveOptionDialogContent",
|
"imageSaveOptionDialogContent",
|
||||||
"imageSaveOptionDialogDeviceButtonLabel",
|
"imageSaveOptionDialogDeviceButtonLabel",
|
||||||
"imageSaveOptionDialogServerButtonLabel"
|
"imageSaveOptionDialogServerButtonLabel",
|
||||||
|
"initialSyncMessage"
|
||||||
],
|
],
|
||||||
|
|
||||||
"pt": [
|
"pt": [
|
||||||
|
@ -806,7 +813,8 @@
|
||||||
"imageSaveOptionDialogTitle",
|
"imageSaveOptionDialogTitle",
|
||||||
"imageSaveOptionDialogContent",
|
"imageSaveOptionDialogContent",
|
||||||
"imageSaveOptionDialogDeviceButtonLabel",
|
"imageSaveOptionDialogDeviceButtonLabel",
|
||||||
"imageSaveOptionDialogServerButtonLabel"
|
"imageSaveOptionDialogServerButtonLabel",
|
||||||
|
"initialSyncMessage"
|
||||||
],
|
],
|
||||||
|
|
||||||
"ru": [
|
"ru": [
|
||||||
|
@ -907,7 +915,8 @@
|
||||||
"imageSaveOptionDialogTitle",
|
"imageSaveOptionDialogTitle",
|
||||||
"imageSaveOptionDialogContent",
|
"imageSaveOptionDialogContent",
|
||||||
"imageSaveOptionDialogDeviceButtonLabel",
|
"imageSaveOptionDialogDeviceButtonLabel",
|
||||||
"imageSaveOptionDialogServerButtonLabel"
|
"imageSaveOptionDialogServerButtonLabel",
|
||||||
|
"initialSyncMessage"
|
||||||
],
|
],
|
||||||
|
|
||||||
"zh": [
|
"zh": [
|
||||||
|
@ -1008,7 +1017,8 @@
|
||||||
"imageSaveOptionDialogTitle",
|
"imageSaveOptionDialogTitle",
|
||||||
"imageSaveOptionDialogContent",
|
"imageSaveOptionDialogContent",
|
||||||
"imageSaveOptionDialogDeviceButtonLabel",
|
"imageSaveOptionDialogDeviceButtonLabel",
|
||||||
"imageSaveOptionDialogServerButtonLabel"
|
"imageSaveOptionDialogServerButtonLabel",
|
||||||
|
"initialSyncMessage"
|
||||||
],
|
],
|
||||||
|
|
||||||
"zh_Hant": [
|
"zh_Hant": [
|
||||||
|
@ -1109,6 +1119,7 @@
|
||||||
"imageSaveOptionDialogTitle",
|
"imageSaveOptionDialogTitle",
|
||||||
"imageSaveOptionDialogContent",
|
"imageSaveOptionDialogContent",
|
||||||
"imageSaveOptionDialogDeviceButtonLabel",
|
"imageSaveOptionDialogDeviceButtonLabel",
|
||||||
"imageSaveOptionDialogServerButtonLabel"
|
"imageSaveOptionDialogServerButtonLabel",
|
||||||
|
"initialSyncMessage"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
34
app/lib/progress_util.dart
Normal file
34
app/lib/progress_util.dart
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
import 'package:to_string/to_string.dart';
|
||||||
|
|
||||||
|
part 'progress_util.g.dart';
|
||||||
|
|
||||||
|
@toString
|
||||||
|
class IntProgress {
|
||||||
|
IntProgress(this.max) : step = max <= 0 ? 1 : 1 / max;
|
||||||
|
|
||||||
|
void next() {
|
||||||
|
_current = math.min(_current + 1, max);
|
||||||
|
}
|
||||||
|
|
||||||
|
double get progress => max <= 0 ? 1 : _current / max;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
|
||||||
|
final int max;
|
||||||
|
final double step;
|
||||||
|
var _current = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ToString(ignoreNull: true)
|
||||||
|
class Progress {
|
||||||
|
const Progress(this.progress, [this.text]);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
|
||||||
|
final double progress;
|
||||||
|
final String? text;
|
||||||
|
}
|
19
app/lib/progress_util.g.dart
Normal file
19
app/lib/progress_util.g.dart
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'progress_util.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// ToStringGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
extension _$IntProgressToString on IntProgress {
|
||||||
|
String _$toString() {
|
||||||
|
return "IntProgress {max: $max, step: $step, _current: $_current}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension _$ProgressToString on Progress {
|
||||||
|
String _$toString() {
|
||||||
|
return "Progress {progress: $progress, ${text == null ? "" : "text: $text, "}}";
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/debug_util.dart';
|
import 'package:nc_photos/debug_util.dart';
|
||||||
|
@ -7,6 +8,7 @@ import 'package:nc_photos/entity/file/data_source.dart';
|
||||||
import 'package:nc_photos/entity/file_descriptor.dart';
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/sqlite_table_extension.dart' as sql;
|
import 'package:nc_photos/entity/sqlite_table_extension.dart' as sql;
|
||||||
import 'package:nc_photos/object_extension.dart';
|
import 'package:nc_photos/object_extension.dart';
|
||||||
|
import 'package:nc_photos/progress_util.dart';
|
||||||
import 'package:nc_photos/remote_storage_util.dart' as remote_storage_util;
|
import 'package:nc_photos/remote_storage_util.dart' as remote_storage_util;
|
||||||
import 'package:nc_photos/use_case/ls_single_file.dart';
|
import 'package:nc_photos/use_case/ls_single_file.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
|
@ -26,12 +28,18 @@ class SyncDir {
|
||||||
Account account,
|
Account account,
|
||||||
String dirPath, {
|
String dirPath, {
|
||||||
bool isRecursive = true,
|
bool isRecursive = true,
|
||||||
|
ValueChanged<Progress>? onProgressUpdate,
|
||||||
}) async {
|
}) async {
|
||||||
final dirCache = await _queryAllDirEtags(account, dirPath);
|
final dirCache = await _queryAllDirEtags(account, dirPath);
|
||||||
final remoteRoot =
|
final remoteRoot =
|
||||||
await LsSingleFile(_c.withRemoteFileRepo())(account, dirPath);
|
await LsSingleFile(_c.withRemoteFileRepo())(account, dirPath);
|
||||||
return await _syncDir(account, remoteRoot, dirCache,
|
return await _syncDir(
|
||||||
isRecursive: isRecursive);
|
account,
|
||||||
|
remoteRoot,
|
||||||
|
dirCache,
|
||||||
|
isRecursive: isRecursive,
|
||||||
|
onProgressUpdate: onProgressUpdate,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> _syncDir(
|
Future<bool> _syncDir(
|
||||||
|
@ -39,6 +47,7 @@ class SyncDir {
|
||||||
File remoteDir,
|
File remoteDir,
|
||||||
Map<int, String> dirCache, {
|
Map<int, String> dirCache, {
|
||||||
required bool isRecursive,
|
required bool isRecursive,
|
||||||
|
ValueChanged<Progress>? onProgressUpdate,
|
||||||
}) async {
|
}) async {
|
||||||
final status = await _checkContentUpdated(account, remoteDir, dirCache);
|
final status = await _checkContentUpdated(account, remoteDir, dirCache);
|
||||||
if (!status.item1) {
|
if (!status.item1) {
|
||||||
|
@ -52,16 +61,32 @@ class SyncDir {
|
||||||
if (!isRecursive) {
|
if (!isRecursive) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
for (final d in children.where((c) =>
|
final subDirs = children
|
||||||
c.isCollection == true &&
|
.where((f) =>
|
||||||
!remoteDir.compareServerIdentity(c) &&
|
f.isCollection == true &&
|
||||||
!c.path.endsWith(remote_storage_util.getRemoteStorageDir(account)))) {
|
!remoteDir.compareServerIdentity(f) &&
|
||||||
|
!f.path.endsWith(remote_storage_util.getRemoteStorageDir(account)))
|
||||||
|
.toList();
|
||||||
|
final progress = IntProgress(subDirs.length);
|
||||||
|
for (final d in subDirs) {
|
||||||
|
onProgressUpdate
|
||||||
|
?.call(Progress(progress.progress, d.strippedPathWithEmpty));
|
||||||
try {
|
try {
|
||||||
await _syncDir(account, d, dirCache, isRecursive: isRecursive);
|
await _syncDir(
|
||||||
|
account,
|
||||||
|
d,
|
||||||
|
dirCache,
|
||||||
|
isRecursive: isRecursive,
|
||||||
|
onProgressUpdate: (value) {
|
||||||
|
final merged = progress.progress + progress.step * value.progress;
|
||||||
|
onProgressUpdate?.call(Progress(merged, value.text));
|
||||||
|
},
|
||||||
|
);
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
_log.severe("[_syncDir] Failed while _syncDir: ${logFilename(d.path)}",
|
_log.severe("[_syncDir] Failed while _syncDir: ${logFilename(d.path)}",
|
||||||
e, stackTrace);
|
e, stackTrace);
|
||||||
}
|
}
|
||||||
|
progress.next();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/api/api_util.dart' as api_util;
|
import 'package:nc_photos/api/api_util.dart' as api_util;
|
||||||
import 'package:nc_photos/app_localizations.dart';
|
import 'package:nc_photos/app_localizations.dart';
|
||||||
import 'package:nc_photos/bloc/bloc_util.dart' as bloc_util;
|
import 'package:nc_photos/bloc/bloc_util.dart' as bloc_util;
|
||||||
|
import 'package:nc_photos/bloc/progress.dart';
|
||||||
import 'package:nc_photos/bloc/scan_account_dir.dart';
|
import 'package:nc_photos/bloc/scan_account_dir.dart';
|
||||||
import 'package:nc_photos/compute_queue.dart';
|
import 'package:nc_photos/compute_queue.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
|
@ -95,10 +96,7 @@ class _HomePhotosState extends State<HomePhotos>
|
||||||
return BlocListener<ScanAccountDirBloc, ScanAccountDirBlocState>(
|
return BlocListener<ScanAccountDirBloc, ScanAccountDirBlocState>(
|
||||||
bloc: _bloc,
|
bloc: _bloc,
|
||||||
listener: (context, state) => _onStateChange(context, state),
|
listener: (context, state) => _onStateChange(context, state),
|
||||||
child: BlocBuilder<ScanAccountDirBloc, ScanAccountDirBlocState>(
|
child: _buildContent(context),
|
||||||
bloc: _bloc,
|
|
||||||
builder: (context, state) => _buildContent(context, state),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,7 +146,7 @@ class _HomePhotosState extends State<HomePhotos>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildContent(BuildContext context, ScanAccountDirBlocState state) {
|
Widget _buildContent(BuildContext context) {
|
||||||
return LayoutBuilder(builder: (context, constraints) {
|
return LayoutBuilder(builder: (context, constraints) {
|
||||||
final scrollExtent = _getScrollViewExtent(context, constraints);
|
final scrollExtent = _getScrollViewExtent(context, constraints);
|
||||||
return Stack(
|
return Stack(
|
||||||
|
@ -183,21 +181,32 @@ class _HomePhotosState extends State<HomePhotos>
|
||||||
.isEnableMemoryAlbumOr(true) &&
|
.isEnableMemoryAlbumOr(true) &&
|
||||||
_smartAlbums.isNotEmpty)
|
_smartAlbums.isNotEmpty)
|
||||||
_buildSmartAlbumList(context),
|
_buildSmartAlbumList(context),
|
||||||
buildItemStreamList(
|
BlocBuilder<ScanAccountDirBloc, ScanAccountDirBlocState>(
|
||||||
maxCrossAxisExtent: _thumbSize.toDouble(),
|
bloc: _bloc,
|
||||||
onMaxExtentChanged: (value) {
|
builder: (context, state) {
|
||||||
setState(() {
|
if (_isInitialSync(state)) {
|
||||||
_itemListMaxExtent = value;
|
return _InitialLoadingProgress(
|
||||||
});
|
progressBloc: _queryProgressBloc,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return buildItemStreamList(
|
||||||
|
maxCrossAxisExtent: _thumbSize.toDouble(),
|
||||||
|
onMaxExtentChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
_itemListMaxExtent = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
isEnableVisibilityCallback: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
isEnableVisibilityCallback: true,
|
|
||||||
),
|
),
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: _calcBottomAppBarExtent(context),
|
height: _calcBottomAppBarExtent(context),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
].whereType<Widget>().toList(),
|
].whereNotNull().toList(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -279,15 +288,24 @@ class _HomePhotosState extends State<HomePhotos>
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildNormalAppBar(BuildContext context) {
|
Widget _buildNormalAppBar(BuildContext context) {
|
||||||
return BlocBuilder(
|
return BlocBuilder<ScanAccountDirBloc, ScanAccountDirBlocState>(
|
||||||
bloc: _bloc,
|
bloc: _bloc,
|
||||||
buildWhen: (previous, current) =>
|
buildWhen: (previous, current) {
|
||||||
previous is ScanAccountDirBlocLoading !=
|
if (previous is ScanAccountDirBlocLoading &&
|
||||||
current is ScanAccountDirBlocLoading,
|
current is ScanAccountDirBlocLoading) {
|
||||||
|
// both loading, check if initial flag changed
|
||||||
|
return previous.isInitialLoad != current.isInitialLoad;
|
||||||
|
} else {
|
||||||
|
// check if any one is loading == state changed from/to loading
|
||||||
|
return previous is ScanAccountDirBlocLoading ||
|
||||||
|
current is ScanAccountDirBlocLoading;
|
||||||
|
}
|
||||||
|
},
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return HomeSliverAppBar(
|
return HomeSliverAppBar(
|
||||||
account: widget.account,
|
account: widget.account,
|
||||||
isShowProgressIcon: (state is ScanAccountDirBlocLoading ||
|
isShowProgressIcon: !_isInitialSync(state) &&
|
||||||
|
(state is ScanAccountDirBlocLoading ||
|
||||||
_buildItemQueue.isProcessing) &&
|
_buildItemQueue.isProcessing) &&
|
||||||
!_isRefreshIndicatorActive,
|
!_isRefreshIndicatorActive,
|
||||||
actions: [
|
actions: [
|
||||||
|
@ -620,7 +638,9 @@ class _HomePhotosState extends State<HomePhotos>
|
||||||
}
|
}
|
||||||
|
|
||||||
void _reqQuery() {
|
void _reqQuery() {
|
||||||
_bloc.add(const ScanAccountDirBlocQuery());
|
_bloc.add(ScanAccountDirBlocQuery(
|
||||||
|
progressBloc: _queryProgressBloc,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _reqRefresh() {
|
void _reqRefresh() {
|
||||||
|
@ -691,6 +711,11 @@ class _HomePhotosState extends State<HomePhotos>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _isInitialSync(ScanAccountDirBlocState state) =>
|
||||||
|
state is ScanAccountDirBlocLoading &&
|
||||||
|
state.files.isEmpty &&
|
||||||
|
state.isInitialLoad;
|
||||||
|
|
||||||
double _calcAppBarExtent(BuildContext context) =>
|
double _calcAppBarExtent(BuildContext context) =>
|
||||||
MediaQuery.of(context).padding.top + kToolbarHeight;
|
MediaQuery.of(context).padding.top + kToolbarHeight;
|
||||||
|
|
||||||
|
@ -728,6 +753,7 @@ class _HomePhotosState extends State<HomePhotos>
|
||||||
}
|
}
|
||||||
|
|
||||||
late final _bloc = ScanAccountDirBloc.of(widget.account);
|
late final _bloc = ScanAccountDirBloc.of(widget.account);
|
||||||
|
late final _queryProgressBloc = ProgressBloc();
|
||||||
|
|
||||||
var _backingFiles = <FileDescriptor>[];
|
var _backingFiles = <FileDescriptor>[];
|
||||||
var _smartAlbums = <Album>[];
|
var _smartAlbums = <Album>[];
|
||||||
|
@ -1087,3 +1113,52 @@ class _VisibleItem implements Comparable<_VisibleItem> {
|
||||||
final int index;
|
final int index;
|
||||||
final SelectableItem item;
|
final SelectableItem item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _InitialLoadingProgress extends StatelessWidget {
|
||||||
|
const _InitialLoadingProgress({
|
||||||
|
required this.progressBloc,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocBuilder<ProgressBloc, ProgressBlocState>(
|
||||||
|
bloc: progressBloc,
|
||||||
|
buildWhen: (previous, current) => previous != current,
|
||||||
|
builder: (context, state) {
|
||||||
|
return SliverToBoxAdapter(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(16, 56, 16, 0),
|
||||||
|
child: Center(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
maxWidth: Theme.of(context).widthLimitedContentMaxWidth,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
L10n.global().initialSyncMessage,
|
||||||
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
LinearProgressIndicator(
|
||||||
|
value: state.progress == 0 ? null : state.progress,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
state.text ?? "",
|
||||||
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final ProgressBloc progressBloc;
|
||||||
|
}
|
||||||
|
|
|
@ -7,14 +7,14 @@ packages:
|
||||||
name: _fe_analyzer_shared
|
name: _fe_analyzer_shared
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "41.0.0"
|
version: "47.0.0"
|
||||||
analyzer:
|
analyzer:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: analyzer
|
name: analyzer
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.2.0"
|
version: "4.7.0"
|
||||||
analyzer_plugin:
|
analyzer_plugin:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -121,7 +121,7 @@ packages:
|
||||||
name: build
|
name: build
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.0"
|
version: "2.3.1"
|
||||||
build_config:
|
build_config:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1154,7 +1154,7 @@ packages:
|
||||||
name: source_gen
|
name: source_gen
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.2"
|
version: "1.2.6"
|
||||||
source_map_stack_trace:
|
source_map_stack_trace:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1281,6 +1281,15 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.0"
|
version: "1.0.0"
|
||||||
|
to_string:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
path: "."
|
||||||
|
ref: "2432bd99c5c8c90b11d7e691992514e312cee0fe"
|
||||||
|
resolved-ref: "2432bd99c5c8c90b11d7e691992514e312cee0fe"
|
||||||
|
url: "https://gitlab.com/nkming2/dart-to-string"
|
||||||
|
source: git
|
||||||
|
version: "1.0.0"
|
||||||
tuple:
|
tuple:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -126,6 +126,10 @@ dev_dependencies:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
# integration_test:
|
# integration_test:
|
||||||
# sdk: flutter
|
# sdk: flutter
|
||||||
|
to_string:
|
||||||
|
git:
|
||||||
|
url: https://gitlab.com/nkming2/dart-to-string
|
||||||
|
ref: 2432bd99c5c8c90b11d7e691992514e312cee0fe
|
||||||
|
|
||||||
# For information on the generic Dart part of this file, see the
|
# For information on the generic Dart part of this file, see the
|
||||||
# following page: https://dart.dev/tools/pub/pubspec
|
# following page: https://dart.dev/tools/pub/pubspec
|
||||||
|
|
Loading…
Add table
Reference in a new issue