mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-01-22 08:46:18 +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 'package:bloc/bloc.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:kiwi/kiwi.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:nc_photos/account.dart';
|
||||
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/di_container.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/platform/k.dart' as platform_k;
|
||||
import 'package:nc_photos/pref.dart';
|
||||
import 'package:nc_photos/progress_util.dart';
|
||||
import 'package:nc_photos/throttler.dart';
|
||||
import 'package:nc_photos/use_case/ls.dart';
|
||||
import 'package:nc_photos/use_case/scan_dir.dart';
|
||||
|
@ -25,22 +28,31 @@ abstract class ScanAccountDirBlocEvent {
|
|||
const ScanAccountDirBlocEvent();
|
||||
}
|
||||
|
||||
class ScanAccountDirBlocQueryBase extends ScanAccountDirBlocEvent {
|
||||
const ScanAccountDirBlocQueryBase();
|
||||
abstract class ScanAccountDirBlocQueryBase extends ScanAccountDirBlocEvent {
|
||||
const ScanAccountDirBlocQueryBase({
|
||||
this.progressBloc,
|
||||
});
|
||||
|
||||
@override
|
||||
toString() {
|
||||
return "$runtimeType {"
|
||||
"}";
|
||||
}
|
||||
|
||||
/// Get notified about the query progress
|
||||
final ProgressBloc? progressBloc;
|
||||
}
|
||||
|
||||
class ScanAccountDirBlocQuery extends ScanAccountDirBlocQueryBase {
|
||||
const ScanAccountDirBlocQuery();
|
||||
const ScanAccountDirBlocQuery({
|
||||
super.progressBloc,
|
||||
});
|
||||
}
|
||||
|
||||
class ScanAccountDirBlocRefresh extends ScanAccountDirBlocQueryBase {
|
||||
const ScanAccountDirBlocRefresh();
|
||||
const ScanAccountDirBlocRefresh({
|
||||
super.progressBloc,
|
||||
});
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
const ScanAccountDirBlocLoading(List<FileDescriptor> files) : super(files);
|
||||
const ScanAccountDirBlocLoading(
|
||||
List<FileDescriptor> files, {
|
||||
this.isInitialLoad = false,
|
||||
}) : super(files);
|
||||
|
||||
final bool isInitialLoad;
|
||||
}
|
||||
|
||||
class ScanAccountDirBlocSuccess extends ScanAccountDirBlocState {
|
||||
|
@ -207,10 +224,22 @@ class ScanAccountDirBloc
|
|||
emit(ScanAccountDirBlocLoading(cacheFiles));
|
||||
}
|
||||
|
||||
if (!hasContent && cacheFiles.isEmpty) {
|
||||
emit(const ScanAccountDirBlocLoading([], isInitialLoad: true));
|
||||
}
|
||||
|
||||
stopwatch.reset();
|
||||
final bool hasUpdate;
|
||||
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) {
|
||||
_log.shout("[_onEventQuery] Exception while request", e, stackTrace);
|
||||
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 shareDir =
|
||||
File(path: file_util.unstripPath(account, settings.getShareFolderOr()));
|
||||
|
@ -238,11 +270,20 @@ class ScanAccountDirBloc
|
|||
|
||||
bool hasUpdate = false;
|
||||
_c.touchManager.clearTouchCache();
|
||||
final progress = IntProgress(account.roots.length);
|
||||
for (final r in account.roots) {
|
||||
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 |=
|
||||
file_util.isOrUnderDir(shareDir, File(path: dirPath));
|
||||
progress.next();
|
||||
}
|
||||
|
||||
if (!isShareDirIncluded) {
|
||||
|
|
|
@ -1489,6 +1489,11 @@
|
|||
"@imageSaveOptionDialogServerButtonLabel": {
|
||||
"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": {
|
||||
"description": "Error message when server responds with HTTP401"
|
||||
|
|
|
@ -179,6 +179,7 @@
|
|||
"imageSaveOptionDialogContent",
|
||||
"imageSaveOptionDialogDeviceButtonLabel",
|
||||
"imageSaveOptionDialogServerButtonLabel",
|
||||
"initialSyncMessage",
|
||||
"errorAlbumDowngrade"
|
||||
],
|
||||
|
||||
|
@ -374,6 +375,7 @@
|
|||
"imageSaveOptionDialogContent",
|
||||
"imageSaveOptionDialogDeviceButtonLabel",
|
||||
"imageSaveOptionDialogServerButtonLabel",
|
||||
"initialSyncMessage",
|
||||
"errorAlbumDowngrade"
|
||||
],
|
||||
|
||||
|
@ -459,7 +461,8 @@
|
|||
"imageSaveOptionDialogTitle",
|
||||
"imageSaveOptionDialogContent",
|
||||
"imageSaveOptionDialogDeviceButtonLabel",
|
||||
"imageSaveOptionDialogServerButtonLabel"
|
||||
"imageSaveOptionDialogServerButtonLabel",
|
||||
"initialSyncMessage"
|
||||
],
|
||||
|
||||
"es": [
|
||||
|
@ -470,7 +473,8 @@
|
|||
"settingsSeedColorDescription",
|
||||
"settingsSeedColorPickerTitle",
|
||||
"rootPickerSkipConfirmationDialogContent2",
|
||||
"slideshowSetupDialogReverseTitle"
|
||||
"slideshowSetupDialogReverseTitle",
|
||||
"initialSyncMessage"
|
||||
],
|
||||
|
||||
"fi": [
|
||||
|
@ -478,7 +482,8 @@
|
|||
"signInHeaderText2",
|
||||
"settingsSeedColorTitle",
|
||||
"settingsSeedColorDescription",
|
||||
"settingsSeedColorPickerTitle"
|
||||
"settingsSeedColorPickerTitle",
|
||||
"initialSyncMessage"
|
||||
],
|
||||
|
||||
"fr": [
|
||||
|
@ -583,7 +588,8 @@
|
|||
"imageSaveOptionDialogTitle",
|
||||
"imageSaveOptionDialogContent",
|
||||
"imageSaveOptionDialogDeviceButtonLabel",
|
||||
"imageSaveOptionDialogServerButtonLabel"
|
||||
"imageSaveOptionDialogServerButtonLabel",
|
||||
"initialSyncMessage"
|
||||
],
|
||||
|
||||
"pl": [
|
||||
|
@ -705,7 +711,8 @@
|
|||
"imageSaveOptionDialogTitle",
|
||||
"imageSaveOptionDialogContent",
|
||||
"imageSaveOptionDialogDeviceButtonLabel",
|
||||
"imageSaveOptionDialogServerButtonLabel"
|
||||
"imageSaveOptionDialogServerButtonLabel",
|
||||
"initialSyncMessage"
|
||||
],
|
||||
|
||||
"pt": [
|
||||
|
@ -806,7 +813,8 @@
|
|||
"imageSaveOptionDialogTitle",
|
||||
"imageSaveOptionDialogContent",
|
||||
"imageSaveOptionDialogDeviceButtonLabel",
|
||||
"imageSaveOptionDialogServerButtonLabel"
|
||||
"imageSaveOptionDialogServerButtonLabel",
|
||||
"initialSyncMessage"
|
||||
],
|
||||
|
||||
"ru": [
|
||||
|
@ -907,7 +915,8 @@
|
|||
"imageSaveOptionDialogTitle",
|
||||
"imageSaveOptionDialogContent",
|
||||
"imageSaveOptionDialogDeviceButtonLabel",
|
||||
"imageSaveOptionDialogServerButtonLabel"
|
||||
"imageSaveOptionDialogServerButtonLabel",
|
||||
"initialSyncMessage"
|
||||
],
|
||||
|
||||
"zh": [
|
||||
|
@ -1008,7 +1017,8 @@
|
|||
"imageSaveOptionDialogTitle",
|
||||
"imageSaveOptionDialogContent",
|
||||
"imageSaveOptionDialogDeviceButtonLabel",
|
||||
"imageSaveOptionDialogServerButtonLabel"
|
||||
"imageSaveOptionDialogServerButtonLabel",
|
||||
"initialSyncMessage"
|
||||
],
|
||||
|
||||
"zh_Hant": [
|
||||
|
@ -1109,6 +1119,7 @@
|
|||
"imageSaveOptionDialogTitle",
|
||||
"imageSaveOptionDialogContent",
|
||||
"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:nc_photos/account.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/sqlite_table_extension.dart' as sql;
|
||||
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/use_case/ls_single_file.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
@ -26,12 +28,18 @@ class SyncDir {
|
|||
Account account,
|
||||
String dirPath, {
|
||||
bool isRecursive = true,
|
||||
ValueChanged<Progress>? onProgressUpdate,
|
||||
}) async {
|
||||
final dirCache = await _queryAllDirEtags(account, dirPath);
|
||||
final remoteRoot =
|
||||
await LsSingleFile(_c.withRemoteFileRepo())(account, dirPath);
|
||||
return await _syncDir(account, remoteRoot, dirCache,
|
||||
isRecursive: isRecursive);
|
||||
return await _syncDir(
|
||||
account,
|
||||
remoteRoot,
|
||||
dirCache,
|
||||
isRecursive: isRecursive,
|
||||
onProgressUpdate: onProgressUpdate,
|
||||
);
|
||||
}
|
||||
|
||||
Future<bool> _syncDir(
|
||||
|
@ -39,6 +47,7 @@ class SyncDir {
|
|||
File remoteDir,
|
||||
Map<int, String> dirCache, {
|
||||
required bool isRecursive,
|
||||
ValueChanged<Progress>? onProgressUpdate,
|
||||
}) async {
|
||||
final status = await _checkContentUpdated(account, remoteDir, dirCache);
|
||||
if (!status.item1) {
|
||||
|
@ -52,16 +61,32 @@ class SyncDir {
|
|||
if (!isRecursive) {
|
||||
return true;
|
||||
}
|
||||
for (final d in children.where((c) =>
|
||||
c.isCollection == true &&
|
||||
!remoteDir.compareServerIdentity(c) &&
|
||||
!c.path.endsWith(remote_storage_util.getRemoteStorageDir(account)))) {
|
||||
final subDirs = children
|
||||
.where((f) =>
|
||||
f.isCollection == true &&
|
||||
!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 {
|
||||
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) {
|
||||
_log.severe("[_syncDir] Failed while _syncDir: ${logFilename(d.path)}",
|
||||
e, stackTrace);
|
||||
}
|
||||
progress.next();
|
||||
}
|
||||
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/app_localizations.dart';
|
||||
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/compute_queue.dart';
|
||||
import 'package:nc_photos/di_container.dart';
|
||||
|
@ -95,10 +96,7 @@ class _HomePhotosState extends State<HomePhotos>
|
|||
return BlocListener<ScanAccountDirBloc, ScanAccountDirBlocState>(
|
||||
bloc: _bloc,
|
||||
listener: (context, state) => _onStateChange(context, state),
|
||||
child: BlocBuilder<ScanAccountDirBloc, ScanAccountDirBlocState>(
|
||||
bloc: _bloc,
|
||||
builder: (context, state) => _buildContent(context, state),
|
||||
),
|
||||
child: _buildContent(context),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -148,7 +146,7 @@ class _HomePhotosState extends State<HomePhotos>
|
|||
}
|
||||
}
|
||||
|
||||
Widget _buildContent(BuildContext context, ScanAccountDirBlocState state) {
|
||||
Widget _buildContent(BuildContext context) {
|
||||
return LayoutBuilder(builder: (context, constraints) {
|
||||
final scrollExtent = _getScrollViewExtent(context, constraints);
|
||||
return Stack(
|
||||
|
@ -183,21 +181,32 @@ class _HomePhotosState extends State<HomePhotos>
|
|||
.isEnableMemoryAlbumOr(true) &&
|
||||
_smartAlbums.isNotEmpty)
|
||||
_buildSmartAlbumList(context),
|
||||
buildItemStreamList(
|
||||
maxCrossAxisExtent: _thumbSize.toDouble(),
|
||||
onMaxExtentChanged: (value) {
|
||||
setState(() {
|
||||
_itemListMaxExtent = value;
|
||||
});
|
||||
BlocBuilder<ScanAccountDirBloc, ScanAccountDirBlocState>(
|
||||
bloc: _bloc,
|
||||
builder: (context, state) {
|
||||
if (_isInitialSync(state)) {
|
||||
return _InitialLoadingProgress(
|
||||
progressBloc: _queryProgressBloc,
|
||||
);
|
||||
} else {
|
||||
return buildItemStreamList(
|
||||
maxCrossAxisExtent: _thumbSize.toDouble(),
|
||||
onMaxExtentChanged: (value) {
|
||||
setState(() {
|
||||
_itemListMaxExtent = value;
|
||||
});
|
||||
},
|
||||
isEnableVisibilityCallback: true,
|
||||
);
|
||||
}
|
||||
},
|
||||
isEnableVisibilityCallback: true,
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
height: _calcBottomAppBarExtent(context),
|
||||
),
|
||||
),
|
||||
].whereType<Widget>().toList(),
|
||||
].whereNotNull().toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -279,15 +288,24 @@ class _HomePhotosState extends State<HomePhotos>
|
|||
}
|
||||
|
||||
Widget _buildNormalAppBar(BuildContext context) {
|
||||
return BlocBuilder(
|
||||
return BlocBuilder<ScanAccountDirBloc, ScanAccountDirBlocState>(
|
||||
bloc: _bloc,
|
||||
buildWhen: (previous, current) =>
|
||||
previous is ScanAccountDirBlocLoading !=
|
||||
current is ScanAccountDirBlocLoading,
|
||||
buildWhen: (previous, current) {
|
||||
if (previous 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) {
|
||||
return HomeSliverAppBar(
|
||||
account: widget.account,
|
||||
isShowProgressIcon: (state is ScanAccountDirBlocLoading ||
|
||||
isShowProgressIcon: !_isInitialSync(state) &&
|
||||
(state is ScanAccountDirBlocLoading ||
|
||||
_buildItemQueue.isProcessing) &&
|
||||
!_isRefreshIndicatorActive,
|
||||
actions: [
|
||||
|
@ -620,7 +638,9 @@ class _HomePhotosState extends State<HomePhotos>
|
|||
}
|
||||
|
||||
void _reqQuery() {
|
||||
_bloc.add(const ScanAccountDirBlocQuery());
|
||||
_bloc.add(ScanAccountDirBlocQuery(
|
||||
progressBloc: _queryProgressBloc,
|
||||
));
|
||||
}
|
||||
|
||||
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) =>
|
||||
MediaQuery.of(context).padding.top + kToolbarHeight;
|
||||
|
||||
|
@ -728,6 +753,7 @@ class _HomePhotosState extends State<HomePhotos>
|
|||
}
|
||||
|
||||
late final _bloc = ScanAccountDirBloc.of(widget.account);
|
||||
late final _queryProgressBloc = ProgressBloc();
|
||||
|
||||
var _backingFiles = <FileDescriptor>[];
|
||||
var _smartAlbums = <Album>[];
|
||||
|
@ -1087,3 +1113,52 @@ class _VisibleItem implements Comparable<_VisibleItem> {
|
|||
final int index;
|
||||
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
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "41.0.0"
|
||||
version: "47.0.0"
|
||||
analyzer:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: analyzer
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.2.0"
|
||||
version: "4.7.0"
|
||||
analyzer_plugin:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -121,7 +121,7 @@ packages:
|
|||
name: build
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
version: "2.3.1"
|
||||
build_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1154,7 +1154,7 @@ packages:
|
|||
name: source_gen
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.2"
|
||||
version: "1.2.6"
|
||||
source_map_stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1281,6 +1281,15 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
|
@ -126,6 +126,10 @@ dev_dependencies:
|
|||
sdk: flutter
|
||||
# integration_test:
|
||||
# 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
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
|
Loading…
Reference in a new issue