mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-03-25 00:14:42 +01:00
Refactor: tidy up code
This commit is contained in:
parent
67289f1ccd
commit
782f73448e
6 changed files with 560 additions and 541 deletions
|
@ -72,8 +72,7 @@ part 'collection_browser/app_bar.dart';
|
||||||
part 'collection_browser/bloc.dart';
|
part 'collection_browser/bloc.dart';
|
||||||
part 'collection_browser/state_event.dart';
|
part 'collection_browser/state_event.dart';
|
||||||
part 'collection_browser/type.dart';
|
part 'collection_browser/type.dart';
|
||||||
|
part 'collection_browser/view.dart';
|
||||||
typedef _BlocBuilder = BlocBuilder<_Bloc, _State>;
|
|
||||||
|
|
||||||
class CollectionBrowserArguments {
|
class CollectionBrowserArguments {
|
||||||
const CollectionBrowserArguments(this.collection);
|
const CollectionBrowserArguments(this.collection);
|
||||||
|
@ -160,7 +159,7 @@ class _WrappedCollectionBrowserState extends State<_WrappedCollectionBrowser>
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
body: MultiBlocListener(
|
body: MultiBlocListener(
|
||||||
listeners: [
|
listeners: [
|
||||||
BlocListener<_Bloc, _State>(
|
_BlocListener(
|
||||||
listenWhen: (previous, current) =>
|
listenWhen: (previous, current) =>
|
||||||
previous.items != current.items,
|
previous.items != current.items,
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
|
@ -169,7 +168,7 @@ class _WrappedCollectionBrowserState extends State<_WrappedCollectionBrowser>
|
||||||
));
|
));
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
BlocListener<_Bloc, _State>(
|
_BlocListener(
|
||||||
listenWhen: (previous, current) =>
|
listenWhen: (previous, current) =>
|
||||||
previous.editItems != current.editItems,
|
previous.editItems != current.editItems,
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
|
@ -180,7 +179,7 @@ class _WrappedCollectionBrowserState extends State<_WrappedCollectionBrowser>
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
BlocListener<_Bloc, _State>(
|
_BlocListener(
|
||||||
listenWhen: (previous, current) =>
|
listenWhen: (previous, current) =>
|
||||||
previous.importResult != current.importResult,
|
previous.importResult != current.importResult,
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
|
@ -192,7 +191,7 @@ class _WrappedCollectionBrowserState extends State<_WrappedCollectionBrowser>
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
BlocListener<_Bloc, _State>(
|
_BlocListener(
|
||||||
listenWhen: (previous, current) =>
|
listenWhen: (previous, current) =>
|
||||||
previous.isEditMode != current.isEditMode,
|
previous.isEditMode != current.isEditMode,
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
|
@ -212,7 +211,7 @@ class _WrappedCollectionBrowserState extends State<_WrappedCollectionBrowser>
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
BlocListener<_Bloc, _State>(
|
_BlocListener(
|
||||||
listenWhen: (previous, current) =>
|
listenWhen: (previous, current) =>
|
||||||
previous.error != current.error,
|
previous.error != current.error,
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
|
@ -225,7 +224,7 @@ class _WrappedCollectionBrowserState extends State<_WrappedCollectionBrowser>
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
BlocListener<_Bloc, _State>(
|
_BlocListener(
|
||||||
listenWhen: (previous, current) =>
|
listenWhen: (previous, current) =>
|
||||||
previous.message != current.message,
|
previous.message != current.message,
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
|
@ -300,10 +299,8 @@ class _WrappedCollectionBrowserState extends State<_WrappedCollectionBrowser>
|
||||||
overlaySliver: const _ScalingList(),
|
overlaySliver: const _ScalingList(),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
if (context
|
if (context.bloc.isCollectionCapabilityPermitted(
|
||||||
.read<_Bloc>()
|
CollectionCapability.manualSort)) {
|
||||||
.isCollectionCapabilityPermitted(
|
|
||||||
CollectionCapability.manualSort)) {
|
|
||||||
return const _EditContentList();
|
return const _EditContentList();
|
||||||
} else {
|
} else {
|
||||||
return const _UnmodifiableEditContentList();
|
return const _UnmodifiableEditContentList();
|
||||||
|
@ -338,8 +335,7 @@ class _WrappedCollectionBrowserState extends State<_WrappedCollectionBrowser>
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onPointerMove(BuildContext context, PointerMoveEvent event) {
|
void _onPointerMove(BuildContext context, PointerMoveEvent event) {
|
||||||
final bloc = context.read<_Bloc>();
|
if (!context.state.isDragging) {
|
||||||
if (!bloc.state.isDragging) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (event.position.dy >= MediaQuery.of(context).size.height - 100) {
|
if (event.position.dy >= MediaQuery.of(context).size.height - 100) {
|
||||||
|
@ -391,197 +387,18 @@ class _WrappedCollectionBrowserState extends State<_WrappedCollectionBrowser>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
late final _bloc = context.read<_Bloc>();
|
late final _bloc = context.bloc;
|
||||||
final _scrollController = ScrollController();
|
final _scrollController = ScrollController();
|
||||||
bool? _isDragScrollingDown;
|
bool? _isDragScrollingDown;
|
||||||
int _finger = 0;
|
var _finger = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ContentList extends StatelessWidget {
|
typedef _BlocBuilder = BlocBuilder<_Bloc, _State>;
|
||||||
const _ContentList();
|
typedef _BlocListener = BlocListener<_Bloc, _State>;
|
||||||
|
// typedef _BlocSelector<T> = BlocSelector<_Bloc, _State, T>;
|
||||||
|
|
||||||
@override
|
extension on BuildContext {
|
||||||
Widget build(BuildContext context) {
|
_Bloc get bloc => read<_Bloc>();
|
||||||
return _BlocBuilder(
|
_State get state => bloc.state;
|
||||||
buildWhen: (previous, current) => previous.zoom != current.zoom,
|
void addEvent(_Event event) => bloc.add(event);
|
||||||
builder: (context, state) => _ContentListBody(
|
|
||||||
maxCrossAxisExtent: photo_list_util.getThumbSize(state.zoom).toDouble(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ScalingList extends StatelessWidget {
|
|
||||||
const _ScalingList();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return _BlocBuilder(
|
|
||||||
buildWhen: (previous, current) => previous.scale != current.scale,
|
|
||||||
builder: (context, state) {
|
|
||||||
if (state.scale == null) {
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
}
|
|
||||||
int nextZoom;
|
|
||||||
if (state.scale! > 1) {
|
|
||||||
nextZoom = state.zoom + 1;
|
|
||||||
} else {
|
|
||||||
nextZoom = state.zoom - 1;
|
|
||||||
}
|
|
||||||
nextZoom = nextZoom.clamp(-1, 2);
|
|
||||||
return _ContentListBody(
|
|
||||||
maxCrossAxisExtent: photo_list_util.getThumbSize(nextZoom).toDouble(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ContentListBody extends StatelessWidget {
|
|
||||||
const _ContentListBody({
|
|
||||||
required this.maxCrossAxisExtent,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final bloc = context.read<_Bloc>();
|
|
||||||
return _BlocBuilder(
|
|
||||||
buildWhen: (previous, current) =>
|
|
||||||
previous.collection != current.collection ||
|
|
||||||
previous.transformedItems != current.transformedItems ||
|
|
||||||
previous.selectedItems != current.selectedItems,
|
|
||||||
builder: (context, state) => SelectableItemList<_Item>(
|
|
||||||
maxCrossAxisExtent: maxCrossAxisExtent,
|
|
||||||
items: state.transformedItems,
|
|
||||||
itemBuilder: (context, _, item) => item.buildWidget(context),
|
|
||||||
staggeredTileBuilder: (_, item) => item.staggeredTile,
|
|
||||||
selectedItems: state.selectedItems,
|
|
||||||
onSelectionChange: (_, selected) {
|
|
||||||
bloc.add(_SetSelectedItems(items: selected.cast()));
|
|
||||||
},
|
|
||||||
onItemTap: (context, index, _) {
|
|
||||||
if (state.transformedItems[index] is! _FileItem) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final actualIndex = index -
|
|
||||||
state.transformedItems
|
|
||||||
.sublist(0, index)
|
|
||||||
.where((e) => e is! _FileItem)
|
|
||||||
.length;
|
|
||||||
Navigator.of(context).pushNamed(
|
|
||||||
Viewer.routeName,
|
|
||||||
arguments: ViewerArguments(
|
|
||||||
bloc.account,
|
|
||||||
state.transformedItems
|
|
||||||
.whereType<_FileItem>()
|
|
||||||
.map((e) => e.file)
|
|
||||||
.toList(),
|
|
||||||
actualIndex,
|
|
||||||
fromCollection: ViewerCollectionData(
|
|
||||||
state.collection,
|
|
||||||
state.transformedItems
|
|
||||||
.whereType<_ActualItem>()
|
|
||||||
.map((e) => e.original)
|
|
||||||
.toList(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final double maxCrossAxisExtent;
|
|
||||||
}
|
|
||||||
|
|
||||||
class _EditContentList extends StatelessWidget {
|
|
||||||
const _EditContentList();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return StreamBuilder<int>(
|
|
||||||
stream: context.read<PrefController>().albumBrowserZoomLevel,
|
|
||||||
initialData: context.read<PrefController>().albumBrowserZoomLevel.value,
|
|
||||||
builder: (_, zoomLevel) {
|
|
||||||
if (zoomLevel.hasError) {
|
|
||||||
context.read<_Bloc>().add(
|
|
||||||
_SetMessage(L10n.global().writePreferenceFailureNotification));
|
|
||||||
}
|
|
||||||
return _BlocBuilder(
|
|
||||||
buildWhen: (previous, current) =>
|
|
||||||
previous.editTransformedItems != current.editTransformedItems,
|
|
||||||
builder: (context, state) {
|
|
||||||
if (context.read<_Bloc>().isCollectionCapabilityPermitted(
|
|
||||||
CollectionCapability.manualSort)) {
|
|
||||||
return DraggableItemList<_Item>(
|
|
||||||
maxCrossAxisExtent: photo_list_util
|
|
||||||
.getThumbSize(zoomLevel.requireData)
|
|
||||||
.toDouble(),
|
|
||||||
items: state.editTransformedItems ?? state.transformedItems,
|
|
||||||
itemBuilder: (context, _, item) => item.buildWidget(context),
|
|
||||||
itemDragFeedbackBuilder: (context, _, item) =>
|
|
||||||
item.buildDragFeedbackWidget(context),
|
|
||||||
staggeredTileBuilder: (_, item) => item.staggeredTile,
|
|
||||||
onDragResult: (results) {
|
|
||||||
context.read<_Bloc>().add(_EditManualSort(results));
|
|
||||||
},
|
|
||||||
onDraggingChanged: (value) {
|
|
||||||
context.read<_Bloc>().add(_SetDragging(value));
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return SelectableItemList<_Item>(
|
|
||||||
maxCrossAxisExtent: photo_list_util
|
|
||||||
.getThumbSize(zoomLevel.requireData)
|
|
||||||
.toDouble(),
|
|
||||||
items: state.editTransformedItems ?? state.transformedItems,
|
|
||||||
itemBuilder: (context, _, item) => item.buildWidget(context),
|
|
||||||
staggeredTileBuilder: (_, item) => item.staggeredTile,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Unmodifiable content list under edit mode
|
|
||||||
class _UnmodifiableEditContentList extends StatelessWidget {
|
|
||||||
const _UnmodifiableEditContentList();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return SliverIgnorePointer(
|
|
||||||
ignoring: true,
|
|
||||||
sliver: SliverOpacity(
|
|
||||||
opacity: .25,
|
|
||||||
sliver: StreamBuilder<int>(
|
|
||||||
stream: context.read<PrefController>().albumBrowserZoomLevel,
|
|
||||||
initialData:
|
|
||||||
context.read<PrefController>().albumBrowserZoomLevel.value,
|
|
||||||
builder: (_, zoomLevel) {
|
|
||||||
if (zoomLevel.hasError) {
|
|
||||||
context.read<_Bloc>().add(_SetMessage(
|
|
||||||
L10n.global().writePreferenceFailureNotification));
|
|
||||||
}
|
|
||||||
return _BlocBuilder(
|
|
||||||
buildWhen: (previous, current) =>
|
|
||||||
previous.editTransformedItems != current.editTransformedItems,
|
|
||||||
builder: (context, state) {
|
|
||||||
return SelectableItemList<_Item>(
|
|
||||||
maxCrossAxisExtent: photo_list_util
|
|
||||||
.getThumbSize(zoomLevel.requireData)
|
|
||||||
.toDouble(),
|
|
||||||
items: state.editTransformedItems ?? state.transformedItems,
|
|
||||||
itemBuilder: (context, _, item) => item.buildWidget(context),
|
|
||||||
staggeredTileBuilder: (_, item) => item.staggeredTile,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,8 +11,8 @@ class _AppBar extends StatelessWidget {
|
||||||
previous.items != current.items ||
|
previous.items != current.items ||
|
||||||
previous.collection != current.collection,
|
previous.collection != current.collection,
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final bloc = context.read<_Bloc>();
|
final adapter =
|
||||||
final adapter = CollectionAdapter.of(c, bloc.account, state.collection);
|
CollectionAdapter.of(c, context.bloc.account, state.collection);
|
||||||
final canRename = adapter.isPermitted(CollectionCapability.rename);
|
final canRename = adapter.isPermitted(CollectionCapability.rename);
|
||||||
final canManualCover =
|
final canManualCover =
|
||||||
adapter.isPermitted(CollectionCapability.manualCover);
|
adapter.isPermitted(CollectionCapability.manualCover);
|
||||||
|
@ -69,7 +69,8 @@ class _AppBar extends StatelessWidget {
|
||||||
],
|
],
|
||||||
if (state.collection.contentProvider
|
if (state.collection.contentProvider
|
||||||
is CollectionAlbumProvider &&
|
is CollectionAlbumProvider &&
|
||||||
CollectionAdapter.of(c, bloc.account, state.collection)
|
CollectionAdapter.of(
|
||||||
|
c, context.bloc.account, state.collection)
|
||||||
.isPermitted(CollectionCapability.share))
|
.isPermitted(CollectionCapability.share))
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: _MenuOption.albumFixShare,
|
value: _MenuOption.albumFixShare,
|
||||||
|
|
189
app/lib/widget/collection_browser/view.dart
Normal file
189
app/lib/widget/collection_browser/view.dart
Normal file
|
@ -0,0 +1,189 @@
|
||||||
|
part of '../collection_browser.dart';
|
||||||
|
|
||||||
|
class _ContentList extends StatelessWidget {
|
||||||
|
const _ContentList();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _BlocBuilder(
|
||||||
|
buildWhen: (previous, current) => previous.zoom != current.zoom,
|
||||||
|
builder: (context, state) => _ContentListBody(
|
||||||
|
maxCrossAxisExtent: photo_list_util.getThumbSize(state.zoom).toDouble(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ScalingList extends StatelessWidget {
|
||||||
|
const _ScalingList();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _BlocBuilder(
|
||||||
|
buildWhen: (previous, current) => previous.scale != current.scale,
|
||||||
|
builder: (context, state) {
|
||||||
|
if (state.scale == null) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
int nextZoom;
|
||||||
|
if (state.scale! > 1) {
|
||||||
|
nextZoom = state.zoom + 1;
|
||||||
|
} else {
|
||||||
|
nextZoom = state.zoom - 1;
|
||||||
|
}
|
||||||
|
nextZoom = nextZoom.clamp(-1, 2);
|
||||||
|
return _ContentListBody(
|
||||||
|
maxCrossAxisExtent: photo_list_util.getThumbSize(nextZoom).toDouble(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ContentListBody extends StatelessWidget {
|
||||||
|
const _ContentListBody({
|
||||||
|
required this.maxCrossAxisExtent,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _BlocBuilder(
|
||||||
|
buildWhen: (previous, current) =>
|
||||||
|
previous.collection != current.collection ||
|
||||||
|
previous.transformedItems != current.transformedItems ||
|
||||||
|
previous.selectedItems != current.selectedItems,
|
||||||
|
builder: (context, state) => SelectableItemList<_Item>(
|
||||||
|
maxCrossAxisExtent: maxCrossAxisExtent,
|
||||||
|
items: state.transformedItems,
|
||||||
|
itemBuilder: (context, _, item) => item.buildWidget(context),
|
||||||
|
staggeredTileBuilder: (_, item) => item.staggeredTile,
|
||||||
|
selectedItems: state.selectedItems,
|
||||||
|
onSelectionChange: (_, selected) {
|
||||||
|
context.addEvent(_SetSelectedItems(items: selected.cast()));
|
||||||
|
},
|
||||||
|
onItemTap: (context, index, _) {
|
||||||
|
if (state.transformedItems[index] is! _FileItem) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final actualIndex = index -
|
||||||
|
state.transformedItems
|
||||||
|
.sublist(0, index)
|
||||||
|
.where((e) => e is! _FileItem)
|
||||||
|
.length;
|
||||||
|
Navigator.of(context).pushNamed(
|
||||||
|
Viewer.routeName,
|
||||||
|
arguments: ViewerArguments(
|
||||||
|
context.bloc.account,
|
||||||
|
state.transformedItems
|
||||||
|
.whereType<_FileItem>()
|
||||||
|
.map((e) => e.file)
|
||||||
|
.toList(),
|
||||||
|
actualIndex,
|
||||||
|
fromCollection: ViewerCollectionData(
|
||||||
|
state.collection,
|
||||||
|
state.transformedItems
|
||||||
|
.whereType<_ActualItem>()
|
||||||
|
.map((e) => e.original)
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final double maxCrossAxisExtent;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _EditContentList extends StatelessWidget {
|
||||||
|
const _EditContentList();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return StreamBuilder<int>(
|
||||||
|
stream: context.read<PrefController>().albumBrowserZoomLevel,
|
||||||
|
initialData: context.read<PrefController>().albumBrowserZoomLevel.value,
|
||||||
|
builder: (_, zoomLevel) {
|
||||||
|
if (zoomLevel.hasError) {
|
||||||
|
context.addEvent(
|
||||||
|
_SetMessage(L10n.global().writePreferenceFailureNotification));
|
||||||
|
}
|
||||||
|
return _BlocBuilder(
|
||||||
|
buildWhen: (previous, current) =>
|
||||||
|
previous.editTransformedItems != current.editTransformedItems,
|
||||||
|
builder: (context, state) {
|
||||||
|
if (context.bloc.isCollectionCapabilityPermitted(
|
||||||
|
CollectionCapability.manualSort)) {
|
||||||
|
return DraggableItemList<_Item>(
|
||||||
|
maxCrossAxisExtent: photo_list_util
|
||||||
|
.getThumbSize(zoomLevel.requireData)
|
||||||
|
.toDouble(),
|
||||||
|
items: state.editTransformedItems ?? state.transformedItems,
|
||||||
|
itemBuilder: (context, _, item) => item.buildWidget(context),
|
||||||
|
itemDragFeedbackBuilder: (context, _, item) =>
|
||||||
|
item.buildDragFeedbackWidget(context),
|
||||||
|
staggeredTileBuilder: (_, item) => item.staggeredTile,
|
||||||
|
onDragResult: (results) {
|
||||||
|
context.addEvent(_EditManualSort(results));
|
||||||
|
},
|
||||||
|
onDraggingChanged: (value) {
|
||||||
|
context.addEvent(_SetDragging(value));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return SelectableItemList<_Item>(
|
||||||
|
maxCrossAxisExtent: photo_list_util
|
||||||
|
.getThumbSize(zoomLevel.requireData)
|
||||||
|
.toDouble(),
|
||||||
|
items: state.editTransformedItems ?? state.transformedItems,
|
||||||
|
itemBuilder: (context, _, item) => item.buildWidget(context),
|
||||||
|
staggeredTileBuilder: (_, item) => item.staggeredTile,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unmodifiable content list under edit mode
|
||||||
|
class _UnmodifiableEditContentList extends StatelessWidget {
|
||||||
|
const _UnmodifiableEditContentList();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SliverIgnorePointer(
|
||||||
|
ignoring: true,
|
||||||
|
sliver: SliverOpacity(
|
||||||
|
opacity: .25,
|
||||||
|
sliver: StreamBuilder<int>(
|
||||||
|
stream: context.read<PrefController>().albumBrowserZoomLevel,
|
||||||
|
initialData:
|
||||||
|
context.read<PrefController>().albumBrowserZoomLevel.value,
|
||||||
|
builder: (_, zoomLevel) {
|
||||||
|
if (zoomLevel.hasError) {
|
||||||
|
context.addEvent(_SetMessage(
|
||||||
|
L10n.global().writePreferenceFailureNotification));
|
||||||
|
}
|
||||||
|
return _BlocBuilder(
|
||||||
|
buildWhen: (previous, current) =>
|
||||||
|
previous.editTransformedItems != current.editTransformedItems,
|
||||||
|
builder: (context, state) {
|
||||||
|
return SelectableItemList<_Item>(
|
||||||
|
maxCrossAxisExtent: photo_list_util
|
||||||
|
.getThumbSize(zoomLevel.requireData)
|
||||||
|
.toDouble(),
|
||||||
|
items: state.editTransformedItems ?? state.transformedItems,
|
||||||
|
itemBuilder: (context, _, item) => item.buildWidget(context),
|
||||||
|
staggeredTileBuilder: (_, item) => item.staggeredTile,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -46,11 +46,11 @@ import 'package:np_codegen/np_codegen.dart';
|
||||||
import 'package:to_string/to_string.dart';
|
import 'package:to_string/to_string.dart';
|
||||||
|
|
||||||
part 'home_collections.g.dart';
|
part 'home_collections.g.dart';
|
||||||
|
part 'home_collections/app_bar.dart';
|
||||||
part 'home_collections/bloc.dart';
|
part 'home_collections/bloc.dart';
|
||||||
part 'home_collections/state_event.dart';
|
part 'home_collections/state_event.dart';
|
||||||
part 'home_collections/type.dart';
|
part 'home_collections/type.dart';
|
||||||
|
part 'home_collections/view.dart';
|
||||||
typedef _BlocBuilder = BlocBuilder<_Bloc, _State>;
|
|
||||||
|
|
||||||
/// Show and manage a list of [Collection]s
|
/// Show and manage a list of [Collection]s
|
||||||
class HomeCollections extends StatelessWidget {
|
class HomeCollections extends StatelessWidget {
|
||||||
|
@ -91,14 +91,14 @@ class _WrappedHomeCollectionsState extends State<_WrappedHomeCollections>
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MultiBlocListener(
|
return MultiBlocListener(
|
||||||
listeners: [
|
listeners: [
|
||||||
BlocListener<_Bloc, _State>(
|
_BlocListener(
|
||||||
listenWhen: (previous, current) =>
|
listenWhen: (previous, current) =>
|
||||||
previous.collections != current.collections,
|
previous.collections != current.collections,
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
_bloc.add(_TransformItems(state.collections));
|
_bloc.add(_TransformItems(state.collections));
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
BlocListener<_Bloc, _State>(
|
_BlocListener(
|
||||||
listenWhen: (previous, current) => previous.error != current.error,
|
listenWhen: (previous, current) => previous.error != current.error,
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
if (state.error != null && isPageVisible()) {
|
if (state.error != null && isPageVisible()) {
|
||||||
|
@ -109,7 +109,7 @@ class _WrappedHomeCollectionsState extends State<_WrappedHomeCollections>
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
BlocListener<_Bloc, _State>(
|
_BlocListener(
|
||||||
listenWhen: (previous, current) =>
|
listenWhen: (previous, current) =>
|
||||||
previous.removeError != current.removeError,
|
previous.removeError != current.removeError,
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
|
@ -266,336 +266,12 @@ class _WrappedHomeCollectionsState extends State<_WrappedHomeCollections>
|
||||||
late final _Bloc _bloc = context.read();
|
late final _Bloc _bloc = context.read();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AppBar extends StatelessWidget {
|
typedef _BlocBuilder = BlocBuilder<_Bloc, _State>;
|
||||||
const _AppBar();
|
typedef _BlocListener = BlocListener<_Bloc, _State>;
|
||||||
|
// typedef _BlocSelector<T> = BlocSelector<_Bloc, _State, T>;
|
||||||
|
|
||||||
@override
|
extension on BuildContext {
|
||||||
Widget build(BuildContext context) {
|
_Bloc get bloc => read<_Bloc>();
|
||||||
return _BlocBuilder(
|
_State get state => bloc.state;
|
||||||
buildWhen: (previous, current) => previous.isLoading != current.isLoading,
|
void addEvent(_Event event) => bloc.add(event);
|
||||||
builder: (context, state) => HomeSliverAppBar(
|
|
||||||
account: context.read<_Bloc>().account,
|
|
||||||
isShowProgressIcon: state.isLoading,
|
|
||||||
menuActions: [
|
|
||||||
PopupMenuItem(
|
|
||||||
value: _menuValueSort,
|
|
||||||
child: Text(L10n.global().sortTooltip),
|
|
||||||
),
|
|
||||||
PopupMenuItem(
|
|
||||||
value: _menuValueImport,
|
|
||||||
child: Text(L10n.global().importFoldersTooltip),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
onSelectedMenuActions: (option) {
|
|
||||||
switch (option) {
|
|
||||||
case _menuValueSort:
|
|
||||||
_onSortPressed(context);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case _menuValueImport:
|
|
||||||
_onImportPressed(context);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _onSortPressed(BuildContext context) async {
|
|
||||||
final sort = context.read<_Bloc>().state.sort;
|
|
||||||
final result = await showDialog<collection_util.CollectionSort>(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => FancyOptionPicker(
|
|
||||||
title: Text(L10n.global().sortOptionDialogTitle),
|
|
||||||
items: [
|
|
||||||
FancyOptionPickerItem(
|
|
||||||
label: L10n.global().sortOptionTimeDescendingLabel,
|
|
||||||
isSelected: sort == collection_util.CollectionSort.dateDescending,
|
|
||||||
onSelect: () {
|
|
||||||
Navigator.of(context)
|
|
||||||
.pop(collection_util.CollectionSort.dateDescending);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
FancyOptionPickerItem(
|
|
||||||
label: L10n.global().sortOptionTimeAscendingLabel,
|
|
||||||
isSelected: sort == collection_util.CollectionSort.dateAscending,
|
|
||||||
onSelect: () {
|
|
||||||
Navigator.of(context)
|
|
||||||
.pop(collection_util.CollectionSort.dateAscending);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
FancyOptionPickerItem(
|
|
||||||
label: L10n.global().sortOptionAlbumNameLabel,
|
|
||||||
isSelected: sort == collection_util.CollectionSort.nameAscending,
|
|
||||||
onSelect: () {
|
|
||||||
Navigator.of(context)
|
|
||||||
.pop(collection_util.CollectionSort.nameAscending);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
FancyOptionPickerItem(
|
|
||||||
label: L10n.global().sortOptionAlbumNameDescendingLabel,
|
|
||||||
isSelected: sort == collection_util.CollectionSort.nameDescending,
|
|
||||||
onSelect: () {
|
|
||||||
Navigator.of(context)
|
|
||||||
.pop(collection_util.CollectionSort.nameDescending);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
if (result == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
context.read<_Bloc>().add(_SetCollectionSort(result));
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onImportPressed(BuildContext context) {
|
|
||||||
Navigator.of(context).pushNamed(AlbumImporter.routeName,
|
|
||||||
arguments: AlbumImporterArguments(context.read<_Bloc>().account));
|
|
||||||
}
|
|
||||||
|
|
||||||
static const _menuValueImport = 0;
|
|
||||||
static const _menuValueSort = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@npLog
|
|
||||||
class _SelectionAppBar extends StatelessWidget {
|
|
||||||
const _SelectionAppBar();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return _BlocBuilder(
|
|
||||||
buildWhen: (previous, current) =>
|
|
||||||
previous.selectedItems != current.selectedItems,
|
|
||||||
builder: (context, state) => SelectionAppBar(
|
|
||||||
count: state.selectedItems.length,
|
|
||||||
onClosePressed: () {
|
|
||||||
context.read<_Bloc>().add(const _SetSelectedItems(items: {}));
|
|
||||||
},
|
|
||||||
actions: [
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Icons.delete),
|
|
||||||
tooltip: L10n.global().deleteTooltip,
|
|
||||||
onPressed: () {
|
|
||||||
context.read<_Bloc>().add(const _RemoveSelectedItems());
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ButtonGrid extends StatelessWidget {
|
|
||||||
const _ButtonGrid({
|
|
||||||
required this.account,
|
|
||||||
required this.isEnabled,
|
|
||||||
this.onSharingPressed,
|
|
||||||
this.onEnhancedPhotosPressed,
|
|
||||||
this.onArchivePressed,
|
|
||||||
this.onTrashbinPressed,
|
|
||||||
this.onNewCollectionPressed,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
// needed to workaround a scrolling bug when there are more than one
|
|
||||||
// SliverStaggeredGrids in a CustomScrollView
|
|
||||||
// see: https://github.com/letsar/flutter_staggered_grid_view/issues/98 and
|
|
||||||
// https://github.com/letsar/flutter_staggered_grid_view/issues/265
|
|
||||||
return SliverToBoxAdapter(
|
|
||||||
child: StaggeredGridView.extent(
|
|
||||||
shrinkWrap: true,
|
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
|
||||||
padding: const EdgeInsets.all(0),
|
|
||||||
maxCrossAxisExtent: 256,
|
|
||||||
staggeredTiles: List.filled(5, const StaggeredTile.fit(1)),
|
|
||||||
children: [
|
|
||||||
_ButtonGridItemView(
|
|
||||||
icon: Icons.share_outlined,
|
|
||||||
label: L10n.global().collectionSharingLabel,
|
|
||||||
isShowIndicator: AccountPref.of(account).hasNewSharedAlbumOr(),
|
|
||||||
isEnabled: isEnabled,
|
|
||||||
onTap: () {
|
|
||||||
onSharingPressed?.call();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
if (features.isSupportEnhancement)
|
|
||||||
_ButtonGridItemView(
|
|
||||||
icon: Icons.auto_fix_high_outlined,
|
|
||||||
label: L10n.global().collectionEditedPhotosLabel,
|
|
||||||
isEnabled: isEnabled,
|
|
||||||
onTap: () {
|
|
||||||
onEnhancedPhotosPressed?.call();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
_ButtonGridItemView(
|
|
||||||
icon: Icons.archive_outlined,
|
|
||||||
label: L10n.global().albumArchiveLabel,
|
|
||||||
isEnabled: isEnabled,
|
|
||||||
onTap: () {
|
|
||||||
onArchivePressed?.call();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
_ButtonGridItemView(
|
|
||||||
icon: Icons.delete_outlined,
|
|
||||||
label: L10n.global().albumTrashLabel,
|
|
||||||
isEnabled: isEnabled,
|
|
||||||
onTap: () {
|
|
||||||
onTrashbinPressed?.call();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
_ButtonGridItemView(
|
|
||||||
icon: Icons.add,
|
|
||||||
label: L10n.global().createCollectionTooltip,
|
|
||||||
isEnabled: isEnabled,
|
|
||||||
onTap: () {
|
|
||||||
onNewCollectionPressed?.call();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final Account account;
|
|
||||||
final bool isEnabled;
|
|
||||||
final VoidCallback? onSharingPressed;
|
|
||||||
final VoidCallback? onEnhancedPhotosPressed;
|
|
||||||
final VoidCallback? onArchivePressed;
|
|
||||||
final VoidCallback? onTrashbinPressed;
|
|
||||||
final VoidCallback? onNewCollectionPressed;
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ButtonGridItemView extends StatelessWidget {
|
|
||||||
const _ButtonGridItemView({
|
|
||||||
required this.icon,
|
|
||||||
required this.label,
|
|
||||||
this.isShowIndicator = false,
|
|
||||||
required this.isEnabled,
|
|
||||||
this.onTap,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.all(4),
|
|
||||||
child: ActionChip(
|
|
||||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
|
||||||
labelPadding: const EdgeInsetsDirectional.fromSTEB(8, 0, 0, 0),
|
|
||||||
// specify icon size explicitly to workaround size flickering during
|
|
||||||
// theme transition
|
|
||||||
avatar: Icon(icon, size: 18),
|
|
||||||
label: Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Text(label),
|
|
||||||
),
|
|
||||||
if (isShowIndicator)
|
|
||||||
Icon(
|
|
||||||
Icons.circle,
|
|
||||||
color: Theme.of(context).colorScheme.tertiary,
|
|
||||||
size: 8,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
onPressed: isEnabled ? onTap : null,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final IconData icon;
|
|
||||||
final String label;
|
|
||||||
final bool isShowIndicator;
|
|
||||||
final bool isEnabled;
|
|
||||||
final VoidCallback? onTap;
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ItemView extends StatelessWidget {
|
|
||||||
const _ItemView({
|
|
||||||
required this.account,
|
|
||||||
required this.item,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
Widget? icon;
|
|
||||||
switch (item.itemType) {
|
|
||||||
case _ItemType.ncAlbum:
|
|
||||||
icon = const Icon(Icons.cloud);
|
|
||||||
break;
|
|
||||||
case _ItemType.album:
|
|
||||||
icon = null;
|
|
||||||
break;
|
|
||||||
case _ItemType.tagAlbum:
|
|
||||||
icon = const Icon(Icons.local_offer);
|
|
||||||
break;
|
|
||||||
case _ItemType.dirAlbum:
|
|
||||||
icon = const Icon(Icons.folder);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
String subtitle = "";
|
|
||||||
if (item.isShared) {
|
|
||||||
subtitle = "${L10n.global().albumSharedLabel} | ";
|
|
||||||
}
|
|
||||||
subtitle += item.subtitle ?? "";
|
|
||||||
return CollectionGridItem(
|
|
||||||
cover: _CollectionCover(
|
|
||||||
account: account,
|
|
||||||
url: item.coverUrl,
|
|
||||||
),
|
|
||||||
title: item.name,
|
|
||||||
subtitle: subtitle,
|
|
||||||
icon: icon,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final Account account;
|
|
||||||
final _Item item;
|
|
||||||
}
|
|
||||||
|
|
||||||
class _CollectionCover extends StatelessWidget {
|
|
||||||
const _CollectionCover({
|
|
||||||
required this.account,
|
|
||||||
required this.url,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return ClipRRect(
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
child: Container(
|
|
||||||
color: Theme.of(context).listPlaceholderBackgroundColor,
|
|
||||||
constraints: const BoxConstraints.expand(),
|
|
||||||
child: url != null
|
|
||||||
? FittedBox(
|
|
||||||
clipBehavior: Clip.hardEdge,
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
child: CachedNetworkImage(
|
|
||||||
cacheManager: CoverCacheManager.inst,
|
|
||||||
imageUrl: url!,
|
|
||||||
httpHeaders: {
|
|
||||||
"Authorization":
|
|
||||||
AuthUtil.fromAccount(account).toHeaderValue(),
|
|
||||||
},
|
|
||||||
fadeInDuration: const Duration(),
|
|
||||||
filterQuality: FilterQuality.high,
|
|
||||||
errorWidget: (context, url, error) {
|
|
||||||
// just leave it empty
|
|
||||||
return Container();
|
|
||||||
},
|
|
||||||
imageRenderMethodForWeb: ImageRenderMethodForWeb.HttpGet,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: Icon(
|
|
||||||
Icons.panorama,
|
|
||||||
color: Theme.of(context).listPlaceholderForegroundColor,
|
|
||||||
size: 88,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final Account account;
|
|
||||||
final String? url;
|
|
||||||
}
|
}
|
||||||
|
|
121
app/lib/widget/home_collections/app_bar.dart
Normal file
121
app/lib/widget/home_collections/app_bar.dart
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
part of '../home_collections.dart';
|
||||||
|
|
||||||
|
class _AppBar extends StatelessWidget {
|
||||||
|
const _AppBar();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _BlocBuilder(
|
||||||
|
buildWhen: (previous, current) => previous.isLoading != current.isLoading,
|
||||||
|
builder: (context, state) => HomeSliverAppBar(
|
||||||
|
account: context.bloc.account,
|
||||||
|
isShowProgressIcon: state.isLoading,
|
||||||
|
menuActions: [
|
||||||
|
PopupMenuItem(
|
||||||
|
value: _menuValueSort,
|
||||||
|
child: Text(L10n.global().sortTooltip),
|
||||||
|
),
|
||||||
|
PopupMenuItem(
|
||||||
|
value: _menuValueImport,
|
||||||
|
child: Text(L10n.global().importFoldersTooltip),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
onSelectedMenuActions: (option) {
|
||||||
|
switch (option) {
|
||||||
|
case _menuValueSort:
|
||||||
|
_onSortPressed(context);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case _menuValueImport:
|
||||||
|
_onImportPressed(context);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onSortPressed(BuildContext context) async {
|
||||||
|
final sort = context.state.sort;
|
||||||
|
final result = await showDialog<collection_util.CollectionSort>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => FancyOptionPicker(
|
||||||
|
title: Text(L10n.global().sortOptionDialogTitle),
|
||||||
|
items: [
|
||||||
|
FancyOptionPickerItem(
|
||||||
|
label: L10n.global().sortOptionTimeDescendingLabel,
|
||||||
|
isSelected: sort == collection_util.CollectionSort.dateDescending,
|
||||||
|
onSelect: () {
|
||||||
|
Navigator.of(context)
|
||||||
|
.pop(collection_util.CollectionSort.dateDescending);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
FancyOptionPickerItem(
|
||||||
|
label: L10n.global().sortOptionTimeAscendingLabel,
|
||||||
|
isSelected: sort == collection_util.CollectionSort.dateAscending,
|
||||||
|
onSelect: () {
|
||||||
|
Navigator.of(context)
|
||||||
|
.pop(collection_util.CollectionSort.dateAscending);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
FancyOptionPickerItem(
|
||||||
|
label: L10n.global().sortOptionAlbumNameLabel,
|
||||||
|
isSelected: sort == collection_util.CollectionSort.nameAscending,
|
||||||
|
onSelect: () {
|
||||||
|
Navigator.of(context)
|
||||||
|
.pop(collection_util.CollectionSort.nameAscending);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
FancyOptionPickerItem(
|
||||||
|
label: L10n.global().sortOptionAlbumNameDescendingLabel,
|
||||||
|
isSelected: sort == collection_util.CollectionSort.nameDescending,
|
||||||
|
onSelect: () {
|
||||||
|
Navigator.of(context)
|
||||||
|
.pop(collection_util.CollectionSort.nameDescending);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (result == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
context.addEvent(_SetCollectionSort(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onImportPressed(BuildContext context) {
|
||||||
|
Navigator.of(context).pushNamed(AlbumImporter.routeName,
|
||||||
|
arguments: AlbumImporterArguments(context.bloc.account));
|
||||||
|
}
|
||||||
|
|
||||||
|
static const _menuValueImport = 0;
|
||||||
|
static const _menuValueSort = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@npLog
|
||||||
|
class _SelectionAppBar extends StatelessWidget {
|
||||||
|
const _SelectionAppBar();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _BlocBuilder(
|
||||||
|
buildWhen: (previous, current) =>
|
||||||
|
previous.selectedItems != current.selectedItems,
|
||||||
|
builder: (context, state) => SelectionAppBar(
|
||||||
|
count: state.selectedItems.length,
|
||||||
|
onClosePressed: () {
|
||||||
|
context.addEvent(const _SetSelectedItems(items: {}));
|
||||||
|
},
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.delete),
|
||||||
|
tooltip: L10n.global().deleteTooltip,
|
||||||
|
onPressed: () {
|
||||||
|
context.addEvent(const _RemoveSelectedItems());
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
215
app/lib/widget/home_collections/view.dart
Normal file
215
app/lib/widget/home_collections/view.dart
Normal file
|
@ -0,0 +1,215 @@
|
||||||
|
part of '../home_collections.dart';
|
||||||
|
|
||||||
|
class _ButtonGrid extends StatelessWidget {
|
||||||
|
const _ButtonGrid({
|
||||||
|
required this.account,
|
||||||
|
required this.isEnabled,
|
||||||
|
this.onSharingPressed,
|
||||||
|
this.onEnhancedPhotosPressed,
|
||||||
|
this.onArchivePressed,
|
||||||
|
this.onTrashbinPressed,
|
||||||
|
this.onNewCollectionPressed,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
// needed to workaround a scrolling bug when there are more than one
|
||||||
|
// SliverStaggeredGrids in a CustomScrollView
|
||||||
|
// see: https://github.com/letsar/flutter_staggered_grid_view/issues/98 and
|
||||||
|
// https://github.com/letsar/flutter_staggered_grid_view/issues/265
|
||||||
|
return SliverToBoxAdapter(
|
||||||
|
child: StaggeredGridView.extent(
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
padding: const EdgeInsets.all(0),
|
||||||
|
maxCrossAxisExtent: 256,
|
||||||
|
staggeredTiles: List.filled(5, const StaggeredTile.fit(1)),
|
||||||
|
children: [
|
||||||
|
_ButtonGridItemView(
|
||||||
|
icon: Icons.share_outlined,
|
||||||
|
label: L10n.global().collectionSharingLabel,
|
||||||
|
isShowIndicator: AccountPref.of(account).hasNewSharedAlbumOr(),
|
||||||
|
isEnabled: isEnabled,
|
||||||
|
onTap: () {
|
||||||
|
onSharingPressed?.call();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (features.isSupportEnhancement)
|
||||||
|
_ButtonGridItemView(
|
||||||
|
icon: Icons.auto_fix_high_outlined,
|
||||||
|
label: L10n.global().collectionEditedPhotosLabel,
|
||||||
|
isEnabled: isEnabled,
|
||||||
|
onTap: () {
|
||||||
|
onEnhancedPhotosPressed?.call();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
_ButtonGridItemView(
|
||||||
|
icon: Icons.archive_outlined,
|
||||||
|
label: L10n.global().albumArchiveLabel,
|
||||||
|
isEnabled: isEnabled,
|
||||||
|
onTap: () {
|
||||||
|
onArchivePressed?.call();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
_ButtonGridItemView(
|
||||||
|
icon: Icons.delete_outlined,
|
||||||
|
label: L10n.global().albumTrashLabel,
|
||||||
|
isEnabled: isEnabled,
|
||||||
|
onTap: () {
|
||||||
|
onTrashbinPressed?.call();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
_ButtonGridItemView(
|
||||||
|
icon: Icons.add,
|
||||||
|
label: L10n.global().createCollectionTooltip,
|
||||||
|
isEnabled: isEnabled,
|
||||||
|
onTap: () {
|
||||||
|
onNewCollectionPressed?.call();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final Account account;
|
||||||
|
final bool isEnabled;
|
||||||
|
final VoidCallback? onSharingPressed;
|
||||||
|
final VoidCallback? onEnhancedPhotosPressed;
|
||||||
|
final VoidCallback? onArchivePressed;
|
||||||
|
final VoidCallback? onTrashbinPressed;
|
||||||
|
final VoidCallback? onNewCollectionPressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ButtonGridItemView extends StatelessWidget {
|
||||||
|
const _ButtonGridItemView({
|
||||||
|
required this.icon,
|
||||||
|
required this.label,
|
||||||
|
this.isShowIndicator = false,
|
||||||
|
required this.isEnabled,
|
||||||
|
this.onTap,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(4),
|
||||||
|
child: ActionChip(
|
||||||
|
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
|
labelPadding: const EdgeInsetsDirectional.fromSTEB(8, 0, 0, 0),
|
||||||
|
// specify icon size explicitly to workaround size flickering during
|
||||||
|
// theme transition
|
||||||
|
avatar: Icon(icon, size: 18),
|
||||||
|
label: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(label),
|
||||||
|
),
|
||||||
|
if (isShowIndicator)
|
||||||
|
Icon(
|
||||||
|
Icons.circle,
|
||||||
|
color: Theme.of(context).colorScheme.tertiary,
|
||||||
|
size: 8,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onPressed: isEnabled ? onTap : null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final IconData icon;
|
||||||
|
final String label;
|
||||||
|
final bool isShowIndicator;
|
||||||
|
final bool isEnabled;
|
||||||
|
final VoidCallback? onTap;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ItemView extends StatelessWidget {
|
||||||
|
const _ItemView({
|
||||||
|
required this.account,
|
||||||
|
required this.item,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
Widget? icon;
|
||||||
|
switch (item.itemType) {
|
||||||
|
case _ItemType.ncAlbum:
|
||||||
|
icon = const Icon(Icons.cloud);
|
||||||
|
break;
|
||||||
|
case _ItemType.album:
|
||||||
|
icon = null;
|
||||||
|
break;
|
||||||
|
case _ItemType.tagAlbum:
|
||||||
|
icon = const Icon(Icons.local_offer);
|
||||||
|
break;
|
||||||
|
case _ItemType.dirAlbum:
|
||||||
|
icon = const Icon(Icons.folder);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
var subtitle = "";
|
||||||
|
if (item.isShared) {
|
||||||
|
subtitle = "${L10n.global().albumSharedLabel} | ";
|
||||||
|
}
|
||||||
|
subtitle += item.subtitle ?? "";
|
||||||
|
return CollectionGridItem(
|
||||||
|
cover: _CollectionCover(
|
||||||
|
account: account,
|
||||||
|
url: item.coverUrl,
|
||||||
|
),
|
||||||
|
title: item.name,
|
||||||
|
subtitle: subtitle,
|
||||||
|
icon: icon,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final Account account;
|
||||||
|
final _Item item;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CollectionCover extends StatelessWidget {
|
||||||
|
const _CollectionCover({
|
||||||
|
required this.account,
|
||||||
|
required this.url,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
child: Container(
|
||||||
|
color: Theme.of(context).listPlaceholderBackgroundColor,
|
||||||
|
constraints: const BoxConstraints.expand(),
|
||||||
|
child: url != null
|
||||||
|
? FittedBox(
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
child: CachedNetworkImage(
|
||||||
|
cacheManager: CoverCacheManager.inst,
|
||||||
|
imageUrl: url!,
|
||||||
|
httpHeaders: {
|
||||||
|
"Authorization":
|
||||||
|
AuthUtil.fromAccount(account).toHeaderValue(),
|
||||||
|
},
|
||||||
|
fadeInDuration: const Duration(),
|
||||||
|
filterQuality: FilterQuality.high,
|
||||||
|
errorWidget: (context, url, error) {
|
||||||
|
// just leave it empty
|
||||||
|
return Container();
|
||||||
|
},
|
||||||
|
imageRenderMethodForWeb: ImageRenderMethodForWeb.HttpGet,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: Icon(
|
||||||
|
Icons.panorama,
|
||||||
|
color: Theme.of(context).listPlaceholderForegroundColor,
|
||||||
|
size: 88,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final Account account;
|
||||||
|
final String? url;
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue