mirror of
https://github.com/immich-app/immich
synced 2025-11-07 17:27:20 +00:00
feat: manual stack assets (#4198)
This commit is contained in:
parent
5ead4af2dc
commit
cf08ac7538
59 changed files with 2190 additions and 138 deletions
|
|
@ -32,6 +32,7 @@ class ImmichAssetGrid extends HookConsumerWidget {
|
|||
final Widget? topWidget;
|
||||
final bool shrinkWrap;
|
||||
final bool showDragScroll;
|
||||
final bool showStack;
|
||||
|
||||
const ImmichAssetGrid({
|
||||
super.key,
|
||||
|
|
@ -51,6 +52,7 @@ class ImmichAssetGrid extends HookConsumerWidget {
|
|||
this.topWidget,
|
||||
this.shrinkWrap = false,
|
||||
this.showDragScroll = true,
|
||||
this.showStack = false,
|
||||
});
|
||||
|
||||
@override
|
||||
|
|
@ -114,6 +116,7 @@ class ImmichAssetGrid extends HookConsumerWidget {
|
|||
heroOffset: heroOffset(),
|
||||
shrinkWrap: shrinkWrap,
|
||||
showDragScroll: showDragScroll,
|
||||
showStack: showStack,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ class ImmichAssetGridView extends StatefulWidget {
|
|||
final int heroOffset;
|
||||
final bool shrinkWrap;
|
||||
final bool showDragScroll;
|
||||
final bool showStack;
|
||||
|
||||
const ImmichAssetGridView({
|
||||
super.key,
|
||||
|
|
@ -56,6 +57,7 @@ class ImmichAssetGridView extends StatefulWidget {
|
|||
this.heroOffset = 0,
|
||||
this.shrinkWrap = false,
|
||||
this.showDragScroll = true,
|
||||
this.showStack = false,
|
||||
});
|
||||
|
||||
@override
|
||||
|
|
@ -71,7 +73,7 @@ class ImmichAssetGridViewState extends State<ImmichAssetGridView> {
|
|||
|
||||
bool _scrolling = false;
|
||||
final Set<Asset> _selectedAssets =
|
||||
HashSet(equals: (a, b) => a.id == b.id, hashCode: (a) => a.id);
|
||||
LinkedHashSet(equals: (a, b) => a.id == b.id, hashCode: (a) => a.id);
|
||||
|
||||
Set<Asset> _getSelectedAssets() {
|
||||
return Set.from(_selectedAssets);
|
||||
|
|
@ -90,7 +92,13 @@ class ImmichAssetGridViewState extends State<ImmichAssetGridView> {
|
|||
|
||||
void _deselectAssets(List<Asset> assets) {
|
||||
setState(() {
|
||||
_selectedAssets.removeAll(assets);
|
||||
_selectedAssets.removeAll(
|
||||
assets.where(
|
||||
(a) =>
|
||||
widget.canDeselect ||
|
||||
!(widget.preselectedAssets?.contains(a) ?? false),
|
||||
),
|
||||
);
|
||||
_callSelectionListener(_selectedAssets.isNotEmpty);
|
||||
});
|
||||
}
|
||||
|
|
@ -129,6 +137,7 @@ class ImmichAssetGridViewState extends State<ImmichAssetGridView> {
|
|||
useGrayBoxPlaceholder: true,
|
||||
showStorageIndicator: widget.showStorageIndicator,
|
||||
heroOffset: widget.heroOffset,
|
||||
showStack: widget.showStack,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -377,10 +386,6 @@ class ImmichAssetGridViewState extends State<ImmichAssetGridView> {
|
|||
setState(() {
|
||||
_selectedAssets.clear();
|
||||
});
|
||||
} else if (widget.preselectedAssets != null) {
|
||||
setState(() {
|
||||
_selectedAssets.addAll(widget.preselectedAssets!);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ class ThumbnailImage extends StatelessWidget {
|
|||
final Asset Function(int index) loadAsset;
|
||||
final int totalAssets;
|
||||
final bool showStorageIndicator;
|
||||
final bool showStack;
|
||||
final bool useGrayBoxPlaceholder;
|
||||
final bool isSelected;
|
||||
final bool multiselectEnabled;
|
||||
|
|
@ -26,6 +27,7 @@ class ThumbnailImage extends StatelessWidget {
|
|||
required this.loadAsset,
|
||||
required this.totalAssets,
|
||||
this.showStorageIndicator = true,
|
||||
this.showStack = false,
|
||||
this.useGrayBoxPlaceholder = false,
|
||||
this.isSelected = false,
|
||||
this.multiselectEnabled = false,
|
||||
|
|
@ -93,6 +95,35 @@ class ThumbnailImage extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
|
||||
Widget buildStackIcon() {
|
||||
return Positioned(
|
||||
top: 5,
|
||||
right: 5,
|
||||
child: Row(
|
||||
children: [
|
||||
if (asset.stackCount > 1)
|
||||
Text(
|
||||
"${asset.stackCount}",
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
if (asset.stackCount > 1)
|
||||
const SizedBox(
|
||||
width: 3,
|
||||
),
|
||||
const Icon(
|
||||
Icons.burst_mode_rounded,
|
||||
color: Colors.white,
|
||||
size: 18,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildImage() {
|
||||
final image = SizedBox(
|
||||
width: 300,
|
||||
|
|
@ -113,9 +144,9 @@ class ThumbnailImage extends StatelessWidget {
|
|||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
width: 0,
|
||||
color: assetContainerColor,
|
||||
color: onDeselect == null ? Colors.grey : assetContainerColor,
|
||||
),
|
||||
color: assetContainerColor,
|
||||
color: onDeselect == null ? Colors.grey : assetContainerColor,
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.only(
|
||||
|
|
@ -144,6 +175,7 @@ class ThumbnailImage extends StatelessWidget {
|
|||
loadAsset: loadAsset,
|
||||
totalAssets: totalAssets,
|
||||
heroOffset: heroOffset,
|
||||
showStack: showStack,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -196,6 +228,7 @@ class ThumbnailImage extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
if (!asset.isImage) buildVideoIcon(),
|
||||
if (asset.isImage && asset.stackCount > 0) buildStackIcon(),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ import 'package:easy_localization/easy_localization.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/modules/album/ui/add_to_album_sliverlist.dart';
|
||||
import 'package:immich_mobile/modules/home/models/selection_state.dart';
|
||||
import 'package:immich_mobile/modules/home/ui/delete_dialog.dart';
|
||||
import 'package:immich_mobile/modules/home/ui/upload_dialog.dart';
|
||||
import 'package:immich_mobile/shared/models/asset.dart';
|
||||
import 'package:immich_mobile/shared/providers/server_info.provider.dart';
|
||||
import 'package:immich_mobile/shared/ui/drag_sheet.dart';
|
||||
import 'package:immich_mobile/shared/models/album.dart';
|
||||
|
|
@ -19,11 +19,12 @@ class ControlBottomAppBar extends ConsumerWidget {
|
|||
final Function(Album album) onAddToAlbum;
|
||||
final void Function() onCreateNewAlbum;
|
||||
final void Function() onUpload;
|
||||
final void Function() onStack;
|
||||
|
||||
final List<Album> albums;
|
||||
final List<Album> sharedAlbums;
|
||||
final bool enabled;
|
||||
final AssetState selectionAssetState;
|
||||
final SelectionAssetState selectionAssetState;
|
||||
|
||||
const ControlBottomAppBar({
|
||||
Key? key,
|
||||
|
|
@ -36,19 +37,24 @@ class ControlBottomAppBar extends ConsumerWidget {
|
|||
required this.onAddToAlbum,
|
||||
required this.onCreateNewAlbum,
|
||||
required this.onUpload,
|
||||
this.selectionAssetState = AssetState.remote,
|
||||
required this.onStack,
|
||||
this.selectionAssetState = const SelectionAssetState(),
|
||||
this.enabled = true,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
var isDarkMode = Theme.of(context).brightness == Brightness.dark;
|
||||
var hasRemote = selectionAssetState == AssetState.remote;
|
||||
var hasRemote =
|
||||
selectionAssetState.hasRemote || selectionAssetState.hasMerged;
|
||||
var hasLocal = selectionAssetState.hasLocal;
|
||||
final trashEnabled =
|
||||
ref.watch(serverInfoProvider.select((v) => v.serverFeatures.trash));
|
||||
|
||||
Widget renderActionButtons() {
|
||||
return Row(
|
||||
return Wrap(
|
||||
spacing: 10,
|
||||
runSpacing: 15,
|
||||
children: [
|
||||
ControlBoxButton(
|
||||
iconData: Platform.isAndroid
|
||||
|
|
@ -92,7 +98,7 @@ class ControlBottomAppBar extends ConsumerWidget {
|
|||
if (!hasRemote)
|
||||
ControlBoxButton(
|
||||
iconData: Icons.backup_outlined,
|
||||
label: "Upload",
|
||||
label: "control_bottom_app_bar_upload".tr(),
|
||||
onPressed: enabled
|
||||
? () => showDialog(
|
||||
context: context,
|
||||
|
|
@ -104,6 +110,12 @@ class ControlBottomAppBar extends ConsumerWidget {
|
|||
)
|
||||
: null,
|
||||
),
|
||||
if (!hasLocal)
|
||||
ControlBoxButton(
|
||||
iconData: Icons.filter_none_rounded,
|
||||
label: "control_bottom_app_bar_stack".tr(),
|
||||
onPressed: enabled ? onStack : null,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
@ -111,7 +123,7 @@ class ControlBottomAppBar extends ConsumerWidget {
|
|||
return DraggableScrollableSheet(
|
||||
initialChildSize: hasRemote ? 0.30 : 0.18,
|
||||
minChildSize: 0.18,
|
||||
maxChildSize: hasRemote ? 0.57 : 0.18,
|
||||
maxChildSize: hasRemote ? 0.60 : 0.18,
|
||||
snap: true,
|
||||
builder: (
|
||||
BuildContext context,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue