mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-02-02 06:46:22 +01:00
Add remote sync to the new HomePhotos
This commit is contained in:
parent
db8f93b052
commit
1a09dc37a0
6 changed files with 326 additions and 143 deletions
|
@ -10,9 +10,11 @@ import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
import 'package:nc_photos/entity/file_descriptor.dart';
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||||
|
import 'package:nc_photos/progress_util.dart';
|
||||||
import 'package:nc_photos/rx_extension.dart';
|
import 'package:nc_photos/rx_extension.dart';
|
||||||
import 'package:nc_photos/use_case/file/list_file.dart';
|
import 'package:nc_photos/use_case/file/list_file.dart';
|
||||||
import 'package:nc_photos/use_case/remove.dart';
|
import 'package:nc_photos/use_case/remove.dart';
|
||||||
|
import 'package:nc_photos/use_case/sync_dir.dart';
|
||||||
import 'package:nc_photos/use_case/update_property.dart';
|
import 'package:nc_photos/use_case/update_property.dart';
|
||||||
import 'package:np_codegen/np_codegen.dart';
|
import 'package:np_codegen/np_codegen.dart';
|
||||||
import 'package:np_common/lazy.dart';
|
import 'package:np_common/lazy.dart';
|
||||||
|
@ -54,27 +56,52 @@ class FilesController {
|
||||||
ValueStream<FilesStreamEvent> get stream {
|
ValueStream<FilesStreamEvent> get stream {
|
||||||
if (!_isDataStreamInited) {
|
if (!_isDataStreamInited) {
|
||||||
_isDataStreamInited = true;
|
_isDataStreamInited = true;
|
||||||
unawaited(_load());
|
_load();
|
||||||
}
|
}
|
||||||
return _dataStreamController.stream;
|
return _dataStreamController.stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> reload() async {
|
Future<void> syncRemote({
|
||||||
var results = <FileDescriptor>[];
|
void Function(Progress progress)? onProgressUpdate,
|
||||||
final completer = Completer();
|
}) async {
|
||||||
ListFile(_c)(
|
if (_isSyncing) {
|
||||||
account,
|
_log.fine("[syncRemote] Skipped as another sync running");
|
||||||
file_util.unstripPath(account, accountPrefController.shareFolder.value),
|
return;
|
||||||
).listen(
|
}
|
||||||
(ev) {
|
_isSyncing = true;
|
||||||
results = ev;
|
try {
|
||||||
},
|
final shareDir = File(
|
||||||
onError: _dataStreamController.addError,
|
path: file_util.unstripPath(
|
||||||
onDone: () => completer.complete(),
|
account, accountPrefController.shareFolder.value),
|
||||||
);
|
);
|
||||||
await completer.future;
|
var isShareDirIncluded = false;
|
||||||
_dataStreamController
|
|
||||||
.add(_convertListResultsToEvent(results, hasNext: false));
|
_c.touchManager.clearTouchCache();
|
||||||
|
final progress = IntProgress(account.roots.length);
|
||||||
|
for (final r in account.roots) {
|
||||||
|
final dirPath = file_util.unstripPath(account, r);
|
||||||
|
await SyncDir(_c)(
|
||||||
|
account,
|
||||||
|
dirPath,
|
||||||
|
onProgressUpdate: (value) {
|
||||||
|
final merged = progress.progress + progress.step * value.progress;
|
||||||
|
onProgressUpdate?.call(Progress(merged, value.text));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
isShareDirIncluded |=
|
||||||
|
file_util.isOrUnderDirPath(shareDir.path, dirPath);
|
||||||
|
progress.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isShareDirIncluded) {
|
||||||
|
_log.info("[syncRemote] Explicitly scanning share folder");
|
||||||
|
await SyncDir(_c)(account, shareDir.path, isRecursive: false);
|
||||||
|
}
|
||||||
|
// load the synced content to stream
|
||||||
|
unawaited(_reload());
|
||||||
|
} finally {
|
||||||
|
_isSyncing = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update files property and return number of files updated
|
/// Update files property and return number of files updated
|
||||||
|
@ -227,6 +254,24 @@ class FilesController {
|
||||||
_dataStreamController.add(lastData.copyWith(hasNext: false));
|
_dataStreamController.add(lastData.copyWith(hasNext: false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _reload() async {
|
||||||
|
var results = <FileDescriptor>[];
|
||||||
|
final completer = Completer();
|
||||||
|
ListFile(_c)(
|
||||||
|
account,
|
||||||
|
file_util.unstripPath(account, accountPrefController.shareFolder.value),
|
||||||
|
).listen(
|
||||||
|
(ev) {
|
||||||
|
results = ev;
|
||||||
|
},
|
||||||
|
onError: _dataStreamController.addError,
|
||||||
|
onDone: () => completer.complete(),
|
||||||
|
);
|
||||||
|
await completer.future;
|
||||||
|
_dataStreamController
|
||||||
|
.add(_convertListResultsToEvent(results, hasNext: false));
|
||||||
|
}
|
||||||
|
|
||||||
_FilesStreamEvent _convertListResultsToEvent(
|
_FilesStreamEvent _convertListResultsToEvent(
|
||||||
List<FileDescriptor> results, {
|
List<FileDescriptor> results, {
|
||||||
required bool hasNext,
|
required bool hasNext,
|
||||||
|
@ -252,6 +297,7 @@ class FilesController {
|
||||||
);
|
);
|
||||||
|
|
||||||
final _mutex = Mutex();
|
final _mutex = Mutex();
|
||||||
|
var _isSyncing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@toString
|
@toString
|
||||||
|
|
|
@ -5,11 +5,11 @@ class _AppBar extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return _BlocBuilder(
|
return _BlocSelector<bool>(
|
||||||
buildWhen: (previous, current) => previous.isLoading != current.isLoading,
|
selector: (state) => state.isLoading || state.syncProgress != null,
|
||||||
builder: (context, state) => HomeSliverAppBar(
|
builder: (context, isProcessing) => HomeSliverAppBar(
|
||||||
account: context.bloc.account,
|
account: context.bloc.account,
|
||||||
isShowProgressIcon: state.isLoading,
|
isShowProgressIcon: isProcessing,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
|
||||||
on<_RemoveVisibleItem>(_onRemoveVisibleItem);
|
on<_RemoveVisibleItem>(_onRemoveVisibleItem);
|
||||||
|
|
||||||
on<_SetContentListMaxExtent>(_onSetContentListMaxExtent);
|
on<_SetContentListMaxExtent>(_onSetContentListMaxExtent);
|
||||||
|
on<_SetSyncProgress>(_onSetSyncProgress);
|
||||||
|
|
||||||
on<_StartScaling>(_onStartScaling);
|
on<_StartScaling>(_onStartScaling);
|
||||||
on<_EndScaling>(_onEndScaling);
|
on<_EndScaling>(_onEndScaling);
|
||||||
|
@ -60,7 +61,8 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
|
||||||
currentState = currentState as _State;
|
currentState = currentState as _State;
|
||||||
nextState = nextState as _State;
|
nextState = nextState as _State;
|
||||||
return currentState.scale == nextState.scale &&
|
return currentState.scale == nextState.scale &&
|
||||||
currentState.visibleItems == nextState.visibleItems;
|
currentState.visibleItems == nextState.visibleItems &&
|
||||||
|
currentState.syncProgress == nextState.syncProgress;
|
||||||
};
|
};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -80,10 +82,16 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
|
||||||
_log.info(ev);
|
_log.info(ev);
|
||||||
return emit.forEach<FilesStreamEvent>(
|
return emit.forEach<FilesStreamEvent>(
|
||||||
controller.stream,
|
controller.stream,
|
||||||
onData: (data) => state.copyWith(
|
onData: (data) {
|
||||||
files: data.data,
|
if (_isInitialLoad && !data.hasNext) {
|
||||||
isLoading: data.hasNext || _itemTransformerQueue.isProcessing,
|
_isInitialLoad = false;
|
||||||
),
|
_syncRemote();
|
||||||
|
}
|
||||||
|
return state.copyWith(
|
||||||
|
files: data.data,
|
||||||
|
isLoading: data.hasNext || _itemTransformerQueue.isProcessing,
|
||||||
|
);
|
||||||
|
},
|
||||||
onError: (e, stackTrace) {
|
onError: (e, stackTrace) {
|
||||||
_log.severe("[_onLoad] Uncaught exception", e, stackTrace);
|
_log.severe("[_onLoad] Uncaught exception", e, stackTrace);
|
||||||
return state.copyWith(
|
return state.copyWith(
|
||||||
|
@ -96,7 +104,7 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
|
||||||
|
|
||||||
void _onReload(_Reload ev, Emitter<_State> emit) {
|
void _onReload(_Reload ev, Emitter<_State> emit) {
|
||||||
_log.info(ev);
|
_log.info(ev);
|
||||||
unawaited(controller.reload());
|
_syncRemote();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onTransformItems(_TransformItems ev, Emitter<_State> emit) {
|
void _onTransformItems(_TransformItems ev, Emitter<_State> emit) {
|
||||||
|
@ -204,6 +212,11 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
|
||||||
emit(state.copyWith(contentListMaxExtent: ev.value));
|
emit(state.copyWith(contentListMaxExtent: ev.value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _onSetSyncProgress(_SetSyncProgress ev, Emitter<_State> emit) {
|
||||||
|
_log.info(ev);
|
||||||
|
emit(state.copyWith(syncProgress: ev.progress));
|
||||||
|
}
|
||||||
|
|
||||||
void _onStartScaling(_StartScaling ev, Emitter<_State> emit) {
|
void _onStartScaling(_StartScaling ev, Emitter<_State> emit) {
|
||||||
_log.info(ev);
|
_log.info(ev);
|
||||||
}
|
}
|
||||||
|
@ -264,6 +277,22 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _syncRemote() {
|
||||||
|
final stopwatch = Stopwatch()..start();
|
||||||
|
controller.syncRemote(
|
||||||
|
onProgressUpdate: (progress) {
|
||||||
|
if (!isClosed) {
|
||||||
|
add(_SetSyncProgress(progress));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
).whenComplete(() {
|
||||||
|
if (!isClosed) {
|
||||||
|
add(const _SetSyncProgress(null));
|
||||||
|
}
|
||||||
|
_log.info("[_syncRemote] Elapsed time: ${stopwatch.elapsedMilliseconds}ms");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void _clearSelection(Emitter<_State> emit) {
|
void _clearSelection(Emitter<_State> emit) {
|
||||||
emit(state.copyWith(selectedItems: const {}));
|
emit(state.copyWith(selectedItems: const {}));
|
||||||
}
|
}
|
||||||
|
@ -279,6 +308,7 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
|
||||||
ComputeQueue<_ItemTransformerArgument, _ItemTransformerResult>();
|
ComputeQueue<_ItemTransformerArgument, _ItemTransformerResult>();
|
||||||
final _subscriptions = <StreamSubscription>[];
|
final _subscriptions = <StreamSubscription>[];
|
||||||
var _isHandlingError = false;
|
var _isHandlingError = false;
|
||||||
|
var _isInitialLoad = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
_ItemTransformerResult _buildItem(_ItemTransformerArgument arg) {
|
_ItemTransformerResult _buildItem(_ItemTransformerArgument arg) {
|
||||||
|
|
|
@ -12,6 +12,7 @@ class _State {
|
||||||
required this.isEnableMemoryCollection,
|
required this.isEnableMemoryCollection,
|
||||||
required this.memoryCollections,
|
required this.memoryCollections,
|
||||||
this.contentListMaxExtent,
|
this.contentListMaxExtent,
|
||||||
|
this.syncProgress,
|
||||||
required this.zoom,
|
required this.zoom,
|
||||||
this.scale,
|
this.scale,
|
||||||
this.error,
|
this.error,
|
||||||
|
@ -45,6 +46,7 @@ class _State {
|
||||||
final List<Collection> memoryCollections;
|
final List<Collection> memoryCollections;
|
||||||
|
|
||||||
final double? contentListMaxExtent;
|
final double? contentListMaxExtent;
|
||||||
|
final Progress? syncProgress;
|
||||||
|
|
||||||
final int zoom;
|
final int zoom;
|
||||||
final double? scale;
|
final double? scale;
|
||||||
|
@ -170,6 +172,16 @@ class _SetContentListMaxExtent implements _Event {
|
||||||
final double? value;
|
final double? value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@toString
|
||||||
|
class _SetSyncProgress implements _Event {
|
||||||
|
const _SetSyncProgress(this.progress);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
|
||||||
|
final Progress? progress;
|
||||||
|
}
|
||||||
|
|
||||||
@toString
|
@toString
|
||||||
class _StartScaling implements _Event {
|
class _StartScaling implements _Event {
|
||||||
const _StartScaling();
|
const _StartScaling();
|
||||||
|
|
|
@ -29,6 +29,7 @@ import 'package:nc_photos/exception_util.dart' as exception_util;
|
||||||
import 'package:nc_photos/flutter_util.dart' as flutter_util;
|
import 'package:nc_photos/flutter_util.dart' as flutter_util;
|
||||||
import 'package:nc_photos/k.dart' as k;
|
import 'package:nc_photos/k.dart' as k;
|
||||||
import 'package:nc_photos/language_util.dart' as language_util;
|
import 'package:nc_photos/language_util.dart' as language_util;
|
||||||
|
import 'package:nc_photos/progress_util.dart';
|
||||||
import 'package:nc_photos/snack_bar_manager.dart';
|
import 'package:nc_photos/snack_bar_manager.dart';
|
||||||
import 'package:nc_photos/theme.dart';
|
import 'package:nc_photos/theme.dart';
|
||||||
import 'package:nc_photos/theme/dimension.dart';
|
import 'package:nc_photos/theme/dimension.dart';
|
||||||
|
@ -139,123 +140,200 @@ class _WrappedHomePhotosState extends State<_WrappedHomePhotos> {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
child: FingerListener(
|
child: _BlocSelector<bool>(
|
||||||
onFingerChanged: (finger) {
|
selector: (state) =>
|
||||||
setState(() {
|
state.files.isEmpty &&
|
||||||
_finger = finger;
|
state.syncProgress != null,
|
||||||
});
|
builder: (context, isInitialSyncing) {
|
||||||
|
if (isInitialSyncing) {
|
||||||
|
return const _InitialSyncBody();
|
||||||
|
} else {
|
||||||
|
return const _Body();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
child: GestureDetector(
|
),
|
||||||
onScaleStart: (_) {
|
),
|
||||||
_bloc.add(const _StartScaling());
|
);
|
||||||
},
|
}
|
||||||
onScaleUpdate: (details) {
|
|
||||||
_bloc.add(_SetScale(details.scale));
|
late final _bloc = context.bloc;
|
||||||
},
|
|
||||||
onScaleEnd: (_) {
|
final _key = GlobalKey();
|
||||||
_bloc.add(const _EndScaling());
|
bool? _isVisible;
|
||||||
},
|
}
|
||||||
child: LayoutBuilder(
|
|
||||||
builder: (context, constraints) => _BlocBuilder(
|
class _InitialSyncBody extends StatelessWidget {
|
||||||
buildWhen: (previous, current) =>
|
const _InitialSyncBody();
|
||||||
previous.contentListMaxExtent !=
|
|
||||||
current.contentListMaxExtent ||
|
@override
|
||||||
(previous.isEnableMemoryCollection &&
|
Widget build(BuildContext context) {
|
||||||
previous.memoryCollections.isNotEmpty) !=
|
return CustomScrollView(
|
||||||
(current.isEnableMemoryCollection &&
|
slivers: [
|
||||||
current.memoryCollections.isNotEmpty),
|
const _AppBar(),
|
||||||
builder: (context, state) {
|
_BlocSelector<Progress?>(
|
||||||
final scrollExtent = _getScrollViewExtent(
|
selector: (state) => state.syncProgress,
|
||||||
context: context,
|
builder: (context, syncProgress) {
|
||||||
constraints: constraints,
|
return SliverToBoxAdapter(
|
||||||
hasMemoryCollection: state.isEnableMemoryCollection &&
|
child: Padding(
|
||||||
state.memoryCollections.isNotEmpty,
|
padding: const EdgeInsets.fromLTRB(16, 56, 16, 0),
|
||||||
contentListMaxExtent: state.contentListMaxExtent,
|
child: Center(
|
||||||
);
|
child: ConstrainedBox(
|
||||||
return Stack(
|
constraints: BoxConstraints(
|
||||||
children: [
|
maxWidth: Theme.of(context).widthLimitedContentMaxWidth,
|
||||||
DraggableScrollbar.semicircle(
|
),
|
||||||
controller: _scrollController,
|
child: Column(
|
||||||
overrideMaxScrollExtent: scrollExtent,
|
mainAxisSize: MainAxisSize.min,
|
||||||
// status bar + app bar
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
topOffset: _getAppBarExtent(context),
|
children: [
|
||||||
bottomOffset:
|
Text(
|
||||||
AppDimension.of(context).homeBottomAppBarHeight,
|
L10n.global().initialSyncMessage,
|
||||||
labelTextBuilder: (_) => const _ScrollLabel(),
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
labelPadding:
|
|
||||||
const EdgeInsets.symmetric(horizontal: 40),
|
|
||||||
backgroundColor: Theme.of(context).elevate(
|
|
||||||
Theme.of(context).colorScheme.inverseSurface, 3),
|
|
||||||
heightScrollThumb: 60,
|
|
||||||
child: ScrollConfiguration(
|
|
||||||
behavior: ScrollConfiguration.of(context)
|
|
||||||
.copyWith(scrollbars: false),
|
|
||||||
child: RefreshIndicator(
|
|
||||||
onRefresh: () async {
|
|
||||||
_bloc.add(const _Reload());
|
|
||||||
await _bloc.stream.first;
|
|
||||||
},
|
|
||||||
child: CustomScrollView(
|
|
||||||
controller: _scrollController,
|
|
||||||
physics: _finger >= 2
|
|
||||||
? const NeverScrollableScrollPhysics()
|
|
||||||
: null,
|
|
||||||
slivers: [
|
|
||||||
_BlocSelector<bool>(
|
|
||||||
selector: (state) =>
|
|
||||||
state.selectedItems.isEmpty,
|
|
||||||
builder: (context, isEmpty) => isEmpty
|
|
||||||
? const _AppBar()
|
|
||||||
: const _SelectionAppBar(),
|
|
||||||
),
|
|
||||||
_BlocBuilder(
|
|
||||||
buildWhen: (previous, current) =>
|
|
||||||
(previous.isEnableMemoryCollection &&
|
|
||||||
previous
|
|
||||||
.memoryCollections.isNotEmpty) !=
|
|
||||||
(current.isEnableMemoryCollection &&
|
|
||||||
current.memoryCollections.isNotEmpty),
|
|
||||||
builder: (context, state) {
|
|
||||||
if (state.isEnableMemoryCollection &&
|
|
||||||
state.memoryCollections.isNotEmpty) {
|
|
||||||
return const _MemoryCollectionList();
|
|
||||||
} else {
|
|
||||||
return const SliverToBoxAdapter();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
_BlocSelector<double?>(
|
|
||||||
selector: (state) => state.scale,
|
|
||||||
builder: (context, scale) =>
|
|
||||||
SliverTransitionedScale(
|
|
||||||
scale: scale,
|
|
||||||
baseSliver: const _ContentList(),
|
|
||||||
overlaySliver: const _ScalingList(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SliverToBoxAdapter(
|
|
||||||
child: SizedBox(
|
|
||||||
height: AppDimension.of(context)
|
|
||||||
.homeBottomAppBarHeight,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 8),
|
||||||
Align(
|
LinearProgressIndicator(
|
||||||
alignment: Alignment.bottomCenter,
|
value: (syncProgress?.progress ?? 0) == 0
|
||||||
child: NavigationBarBlurFilter(
|
? null
|
||||||
height:
|
: syncProgress!.progress,
|
||||||
AppDimension.of(context).homeBottomAppBarHeight,
|
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 8),
|
||||||
],
|
Text(
|
||||||
);
|
syncProgress?.text ?? "",
|
||||||
},
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _Body extends StatefulWidget {
|
||||||
|
const _Body();
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<StatefulWidget> createState() => _BodyState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@npLog
|
||||||
|
class _BodyState extends State<_Body> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return FingerListener(
|
||||||
|
onFingerChanged: (finger) {
|
||||||
|
setState(() {
|
||||||
|
_finger = finger;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: GestureDetector(
|
||||||
|
onScaleStart: (_) {
|
||||||
|
_bloc.add(const _StartScaling());
|
||||||
|
},
|
||||||
|
onScaleUpdate: (details) {
|
||||||
|
_bloc.add(_SetScale(details.scale));
|
||||||
|
},
|
||||||
|
onScaleEnd: (_) {
|
||||||
|
_bloc.add(const _EndScaling());
|
||||||
|
},
|
||||||
|
child: LayoutBuilder(
|
||||||
|
builder: (context, constraints) => _BlocBuilder(
|
||||||
|
buildWhen: (previous, current) =>
|
||||||
|
previous.contentListMaxExtent != current.contentListMaxExtent ||
|
||||||
|
(previous.isEnableMemoryCollection &&
|
||||||
|
previous.memoryCollections.isNotEmpty) !=
|
||||||
|
(current.isEnableMemoryCollection &&
|
||||||
|
current.memoryCollections.isNotEmpty),
|
||||||
|
builder: (context, state) {
|
||||||
|
final scrollExtent = _getScrollViewExtent(
|
||||||
|
context: context,
|
||||||
|
constraints: constraints,
|
||||||
|
hasMemoryCollection: state.isEnableMemoryCollection &&
|
||||||
|
state.memoryCollections.isNotEmpty,
|
||||||
|
contentListMaxExtent: state.contentListMaxExtent,
|
||||||
|
);
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
DraggableScrollbar.semicircle(
|
||||||
|
controller: _scrollController,
|
||||||
|
overrideMaxScrollExtent: scrollExtent,
|
||||||
|
// status bar + app bar
|
||||||
|
topOffset: _getAppBarExtent(context),
|
||||||
|
bottomOffset:
|
||||||
|
AppDimension.of(context).homeBottomAppBarHeight,
|
||||||
|
labelTextBuilder: (_) => const _ScrollLabel(),
|
||||||
|
labelPadding: const EdgeInsets.symmetric(horizontal: 40),
|
||||||
|
backgroundColor: Theme.of(context).elevate(
|
||||||
|
Theme.of(context).colorScheme.inverseSurface, 3),
|
||||||
|
heightScrollThumb: 60,
|
||||||
|
child: ScrollConfiguration(
|
||||||
|
behavior: ScrollConfiguration.of(context)
|
||||||
|
.copyWith(scrollbars: false),
|
||||||
|
child: RefreshIndicator(
|
||||||
|
onRefresh: () async {
|
||||||
|
_bloc.add(const _Reload());
|
||||||
|
await _bloc.stream.first;
|
||||||
|
},
|
||||||
|
child: CustomScrollView(
|
||||||
|
controller: _scrollController,
|
||||||
|
physics: _finger >= 2
|
||||||
|
? const NeverScrollableScrollPhysics()
|
||||||
|
: null,
|
||||||
|
slivers: [
|
||||||
|
_BlocSelector<bool>(
|
||||||
|
selector: (state) => state.selectedItems.isEmpty,
|
||||||
|
builder: (context, isEmpty) => isEmpty
|
||||||
|
? const _AppBar()
|
||||||
|
: const _SelectionAppBar(),
|
||||||
|
),
|
||||||
|
_BlocBuilder(
|
||||||
|
buildWhen: (previous, current) =>
|
||||||
|
(previous.isEnableMemoryCollection &&
|
||||||
|
previous.memoryCollections.isNotEmpty) !=
|
||||||
|
(current.isEnableMemoryCollection &&
|
||||||
|
current.memoryCollections.isNotEmpty),
|
||||||
|
builder: (context, state) {
|
||||||
|
if (state.isEnableMemoryCollection &&
|
||||||
|
state.memoryCollections.isNotEmpty) {
|
||||||
|
return const _MemoryCollectionList();
|
||||||
|
} else {
|
||||||
|
return const SliverToBoxAdapter();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
_BlocSelector<double?>(
|
||||||
|
selector: (state) => state.scale,
|
||||||
|
builder: (context, scale) =>
|
||||||
|
SliverTransitionedScale(
|
||||||
|
scale: scale,
|
||||||
|
baseSliver: const _ContentList(),
|
||||||
|
overlaySliver: const _ScalingList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: SizedBox(
|
||||||
|
height: AppDimension.of(context)
|
||||||
|
.homeBottomAppBarHeight,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: NavigationBarBlurFilter(
|
||||||
|
height: AppDimension.of(context).homeBottomAppBarHeight,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -303,9 +381,7 @@ class _WrappedHomePhotosState extends State<_WrappedHomePhotos> {
|
||||||
|
|
||||||
late final _bloc = context.bloc;
|
late final _bloc = context.bloc;
|
||||||
|
|
||||||
final _key = GlobalKey();
|
|
||||||
final _scrollController = ScrollController();
|
final _scrollController = ScrollController();
|
||||||
bool? _isVisible;
|
|
||||||
var _finger = 0;
|
var _finger = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ abstract class $_StateCopyWithWorker {
|
||||||
bool? isEnableMemoryCollection,
|
bool? isEnableMemoryCollection,
|
||||||
List<Collection>? memoryCollections,
|
List<Collection>? memoryCollections,
|
||||||
double? contentListMaxExtent,
|
double? contentListMaxExtent,
|
||||||
|
Progress? syncProgress,
|
||||||
int? zoom,
|
int? zoom,
|
||||||
double? scale,
|
double? scale,
|
||||||
ExceptionEvent? error});
|
ExceptionEvent? error});
|
||||||
|
@ -40,6 +41,7 @@ class _$_StateCopyWithWorkerImpl implements $_StateCopyWithWorker {
|
||||||
dynamic isEnableMemoryCollection,
|
dynamic isEnableMemoryCollection,
|
||||||
dynamic memoryCollections,
|
dynamic memoryCollections,
|
||||||
dynamic contentListMaxExtent = copyWithNull,
|
dynamic contentListMaxExtent = copyWithNull,
|
||||||
|
dynamic syncProgress = copyWithNull,
|
||||||
dynamic zoom,
|
dynamic zoom,
|
||||||
dynamic scale = copyWithNull,
|
dynamic scale = copyWithNull,
|
||||||
dynamic error = copyWithNull}) {
|
dynamic error = copyWithNull}) {
|
||||||
|
@ -57,6 +59,9 @@ class _$_StateCopyWithWorkerImpl implements $_StateCopyWithWorker {
|
||||||
contentListMaxExtent: contentListMaxExtent == copyWithNull
|
contentListMaxExtent: contentListMaxExtent == copyWithNull
|
||||||
? that.contentListMaxExtent
|
? that.contentListMaxExtent
|
||||||
: contentListMaxExtent as double?,
|
: contentListMaxExtent as double?,
|
||||||
|
syncProgress: syncProgress == copyWithNull
|
||||||
|
? that.syncProgress
|
||||||
|
: syncProgress as Progress?,
|
||||||
zoom: zoom as int? ?? that.zoom,
|
zoom: zoom as int? ?? that.zoom,
|
||||||
scale: scale == copyWithNull ? that.scale : scale as double?,
|
scale: scale == copyWithNull ? that.scale : scale as double?,
|
||||||
error: error == copyWithNull ? that.error : error as ExceptionEvent?);
|
error: error == copyWithNull ? that.error : error as ExceptionEvent?);
|
||||||
|
@ -81,6 +86,13 @@ extension _$_WrappedHomePhotosStateNpLog on _WrappedHomePhotosState {
|
||||||
static final log = Logger("widget.home_photos2._WrappedHomePhotosState");
|
static final log = Logger("widget.home_photos2._WrappedHomePhotosState");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension _$_BodyStateNpLog on _BodyState {
|
||||||
|
// ignore: unused_element
|
||||||
|
Logger get _log => log;
|
||||||
|
|
||||||
|
static final log = Logger("widget.home_photos2._BodyState");
|
||||||
|
}
|
||||||
|
|
||||||
extension _$__NpLog on __ {
|
extension _$__NpLog on __ {
|
||||||
// ignore: unused_element
|
// ignore: unused_element
|
||||||
Logger get _log => log;
|
Logger get _log => log;
|
||||||
|
@ -123,7 +135,7 @@ extension _$_ContentListBodyNpLog on _ContentListBody {
|
||||||
extension _$_StateToString on _State {
|
extension _$_StateToString on _State {
|
||||||
String _$toString() {
|
String _$toString() {
|
||||||
// ignore: unnecessary_string_interpolations
|
// ignore: unnecessary_string_interpolations
|
||||||
return "_State {files: [length: ${files.length}], isLoading: $isLoading, transformedItems: [length: ${transformedItems.length}], selectedItems: {length: ${selectedItems.length}}, visibleItems: {length: ${visibleItems.length}}, isEnableMemoryCollection: $isEnableMemoryCollection, memoryCollections: [length: ${memoryCollections.length}], contentListMaxExtent: ${contentListMaxExtent == null ? null : "${contentListMaxExtent!.toStringAsFixed(3)}"}, zoom: $zoom, scale: ${scale == null ? null : "${scale!.toStringAsFixed(3)}"}, error: $error}";
|
return "_State {files: [length: ${files.length}], isLoading: $isLoading, transformedItems: [length: ${transformedItems.length}], selectedItems: {length: ${selectedItems.length}}, visibleItems: {length: ${visibleItems.length}}, isEnableMemoryCollection: $isEnableMemoryCollection, memoryCollections: [length: ${memoryCollections.length}], contentListMaxExtent: ${contentListMaxExtent == null ? null : "${contentListMaxExtent!.toStringAsFixed(3)}"}, syncProgress: $syncProgress, zoom: $zoom, scale: ${scale == null ? null : "${scale!.toStringAsFixed(3)}"}, error: $error}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,6 +224,13 @@ extension _$_SetContentListMaxExtentToString on _SetContentListMaxExtent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension _$_SetSyncProgressToString on _SetSyncProgress {
|
||||||
|
String _$toString() {
|
||||||
|
// ignore: unnecessary_string_interpolations
|
||||||
|
return "_SetSyncProgress {progress: $progress}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension _$_StartScalingToString on _StartScaling {
|
extension _$_StartScalingToString on _StartScaling {
|
||||||
String _$toString() {
|
String _$toString() {
|
||||||
// ignore: unnecessary_string_interpolations
|
// ignore: unnecessary_string_interpolations
|
||||||
|
|
Loading…
Reference in a new issue