mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-01-22 08:46:18 +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/state_event.dart';
|
||||
part 'collection_browser/type.dart';
|
||||
|
||||
typedef _BlocBuilder = BlocBuilder<_Bloc, _State>;
|
||||
part 'collection_browser/view.dart';
|
||||
|
||||
class CollectionBrowserArguments {
|
||||
const CollectionBrowserArguments(this.collection);
|
||||
|
@ -160,7 +159,7 @@ class _WrappedCollectionBrowserState extends State<_WrappedCollectionBrowser>
|
|||
child: Scaffold(
|
||||
body: MultiBlocListener(
|
||||
listeners: [
|
||||
BlocListener<_Bloc, _State>(
|
||||
_BlocListener(
|
||||
listenWhen: (previous, current) =>
|
||||
previous.items != current.items,
|
||||
listener: (context, state) {
|
||||
|
@ -169,7 +168,7 @@ class _WrappedCollectionBrowserState extends State<_WrappedCollectionBrowser>
|
|||
));
|
||||
},
|
||||
),
|
||||
BlocListener<_Bloc, _State>(
|
||||
_BlocListener(
|
||||
listenWhen: (previous, current) =>
|
||||
previous.editItems != current.editItems,
|
||||
listener: (context, state) {
|
||||
|
@ -180,7 +179,7 @@ class _WrappedCollectionBrowserState extends State<_WrappedCollectionBrowser>
|
|||
}
|
||||
},
|
||||
),
|
||||
BlocListener<_Bloc, _State>(
|
||||
_BlocListener(
|
||||
listenWhen: (previous, current) =>
|
||||
previous.importResult != current.importResult,
|
||||
listener: (context, state) {
|
||||
|
@ -192,7 +191,7 @@ class _WrappedCollectionBrowserState extends State<_WrappedCollectionBrowser>
|
|||
}
|
||||
},
|
||||
),
|
||||
BlocListener<_Bloc, _State>(
|
||||
_BlocListener(
|
||||
listenWhen: (previous, current) =>
|
||||
previous.isEditMode != current.isEditMode,
|
||||
listener: (context, state) {
|
||||
|
@ -212,7 +211,7 @@ class _WrappedCollectionBrowserState extends State<_WrappedCollectionBrowser>
|
|||
}
|
||||
},
|
||||
),
|
||||
BlocListener<_Bloc, _State>(
|
||||
_BlocListener(
|
||||
listenWhen: (previous, current) =>
|
||||
previous.error != current.error,
|
||||
listener: (context, state) {
|
||||
|
@ -225,7 +224,7 @@ class _WrappedCollectionBrowserState extends State<_WrappedCollectionBrowser>
|
|||
}
|
||||
},
|
||||
),
|
||||
BlocListener<_Bloc, _State>(
|
||||
_BlocListener(
|
||||
listenWhen: (previous, current) =>
|
||||
previous.message != current.message,
|
||||
listener: (context, state) {
|
||||
|
@ -300,10 +299,8 @@ class _WrappedCollectionBrowserState extends State<_WrappedCollectionBrowser>
|
|||
overlaySliver: const _ScalingList(),
|
||||
);
|
||||
} else {
|
||||
if (context
|
||||
.read<_Bloc>()
|
||||
.isCollectionCapabilityPermitted(
|
||||
CollectionCapability.manualSort)) {
|
||||
if (context.bloc.isCollectionCapabilityPermitted(
|
||||
CollectionCapability.manualSort)) {
|
||||
return const _EditContentList();
|
||||
} else {
|
||||
return const _UnmodifiableEditContentList();
|
||||
|
@ -338,8 +335,7 @@ class _WrappedCollectionBrowserState extends State<_WrappedCollectionBrowser>
|
|||
}
|
||||
|
||||
void _onPointerMove(BuildContext context, PointerMoveEvent event) {
|
||||
final bloc = context.read<_Bloc>();
|
||||
if (!bloc.state.isDragging) {
|
||||
if (!context.state.isDragging) {
|
||||
return;
|
||||
}
|
||||
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();
|
||||
bool? _isDragScrollingDown;
|
||||
int _finger = 0;
|
||||
var _finger = 0;
|
||||
}
|
||||
|
||||
class _ContentList extends StatelessWidget {
|
||||
const _ContentList();
|
||||
typedef _BlocBuilder = BlocBuilder<_Bloc, _State>;
|
||||
typedef _BlocListener = BlocListener<_Bloc, _State>;
|
||||
// typedef _BlocSelector<T> = BlocSelector<_Bloc, _State, T>;
|
||||
|
||||
@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) {
|
||||
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,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
extension on BuildContext {
|
||||
_Bloc get bloc => read<_Bloc>();
|
||||
_State get state => bloc.state;
|
||||
void addEvent(_Event event) => bloc.add(event);
|
||||
}
|
||||
|
|
|
@ -11,8 +11,8 @@ class _AppBar extends StatelessWidget {
|
|||
previous.items != current.items ||
|
||||
previous.collection != current.collection,
|
||||
builder: (context, state) {
|
||||
final bloc = context.read<_Bloc>();
|
||||
final adapter = CollectionAdapter.of(c, bloc.account, state.collection);
|
||||
final adapter =
|
||||
CollectionAdapter.of(c, context.bloc.account, state.collection);
|
||||
final canRename = adapter.isPermitted(CollectionCapability.rename);
|
||||
final canManualCover =
|
||||
adapter.isPermitted(CollectionCapability.manualCover);
|
||||
|
@ -69,7 +69,8 @@ class _AppBar extends StatelessWidget {
|
|||
],
|
||||
if (state.collection.contentProvider
|
||||
is CollectionAlbumProvider &&
|
||||
CollectionAdapter.of(c, bloc.account, state.collection)
|
||||
CollectionAdapter.of(
|
||||
c, context.bloc.account, state.collection)
|
||||
.isPermitted(CollectionCapability.share))
|
||||
PopupMenuItem(
|
||||
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';
|
||||
|
||||
part 'home_collections.g.dart';
|
||||
part 'home_collections/app_bar.dart';
|
||||
part 'home_collections/bloc.dart';
|
||||
part 'home_collections/state_event.dart';
|
||||
part 'home_collections/type.dart';
|
||||
|
||||
typedef _BlocBuilder = BlocBuilder<_Bloc, _State>;
|
||||
part 'home_collections/view.dart';
|
||||
|
||||
/// Show and manage a list of [Collection]s
|
||||
class HomeCollections extends StatelessWidget {
|
||||
|
@ -91,14 +91,14 @@ class _WrappedHomeCollectionsState extends State<_WrappedHomeCollections>
|
|||
Widget build(BuildContext context) {
|
||||
return MultiBlocListener(
|
||||
listeners: [
|
||||
BlocListener<_Bloc, _State>(
|
||||
_BlocListener(
|
||||
listenWhen: (previous, current) =>
|
||||
previous.collections != current.collections,
|
||||
listener: (context, state) {
|
||||
_bloc.add(_TransformItems(state.collections));
|
||||
},
|
||||
),
|
||||
BlocListener<_Bloc, _State>(
|
||||
_BlocListener(
|
||||
listenWhen: (previous, current) => previous.error != current.error,
|
||||
listener: (context, state) {
|
||||
if (state.error != null && isPageVisible()) {
|
||||
|
@ -109,7 +109,7 @@ class _WrappedHomeCollectionsState extends State<_WrappedHomeCollections>
|
|||
}
|
||||
},
|
||||
),
|
||||
BlocListener<_Bloc, _State>(
|
||||
_BlocListener(
|
||||
listenWhen: (previous, current) =>
|
||||
previous.removeError != current.removeError,
|
||||
listener: (context, state) {
|
||||
|
@ -266,336 +266,12 @@ class _WrappedHomeCollectionsState extends State<_WrappedHomeCollections>
|
|||
late final _Bloc _bloc = context.read();
|
||||
}
|
||||
|
||||
class _AppBar extends StatelessWidget {
|
||||
const _AppBar();
|
||||
typedef _BlocBuilder = BlocBuilder<_Bloc, _State>;
|
||||
typedef _BlocListener = BlocListener<_Bloc, _State>;
|
||||
// typedef _BlocSelector<T> = BlocSelector<_Bloc, _State, T>;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _BlocBuilder(
|
||||
buildWhen: (previous, current) => previous.isLoading != current.isLoading,
|
||||
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;
|
||||
extension on BuildContext {
|
||||
_Bloc get bloc => read<_Bloc>();
|
||||
_State get state => bloc.state;
|
||||
void addEvent(_Event event) => bloc.add(event);
|
||||
}
|
||||
|
|
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…
Reference in a new issue