Show proper progress during initial sync

This commit is contained in:
Ming Ming 2022-12-04 00:26:44 +08:00
parent 94a34f3124
commit 11279b4119
11 changed files with 346 additions and 47 deletions

View 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");
}

View 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}";
}
}

View file

@ -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) {

View file

@ -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"

View file

@ -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"
] ]
} }

View 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;
}

View 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, "}}";
}
}

View file

@ -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;
} }

View file

@ -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;
}

View file

@ -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:

View file

@ -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