mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
Merge branch 'main' into feature/bottom-buttons-order
This commit is contained in:
@@ -53,7 +53,7 @@ class BaseActionButton extends ConsumerWidget {
|
||||
style: MenuItemButton.styleFrom(alignment: Alignment.centerLeft, padding: const EdgeInsets.all(16)),
|
||||
leadingIcon: Icon(iconData, color: effectiveIconColor),
|
||||
onPressed: onPressed,
|
||||
child: Text(label, style: theme.textTheme.labelLarge?.copyWith(fontSize: 16)),
|
||||
child: Text(label, style: theme.textTheme.labelLarge?.copyWith(fontSize: 16, color: iconColor)),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -92,6 +92,8 @@ class AssetViewer extends ConsumerStatefulWidget {
|
||||
if (asset.isVideo || asset.isMotionPhoto) {
|
||||
ref.read(videoPlaybackValueProvider.notifier).reset();
|
||||
ref.read(videoPlayerControlsProvider.notifier).pause();
|
||||
// Hide controls by default for videos and motion photos
|
||||
ref.read(assetViewerProvider.notifier).setControls(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -525,7 +527,13 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
||||
|
||||
void _onScaleStateChanged(PhotoViewScaleState scaleState) {
|
||||
if (scaleState != PhotoViewScaleState.initial) {
|
||||
ref.read(assetViewerProvider.notifier).setControls(false);
|
||||
ref.read(videoPlayerControlsProvider.notifier).pause();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!showingBottomSheet) {
|
||||
ref.read(assetViewerProvider.notifier).setControls(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -119,7 +119,7 @@ class _GeneralBottomSheetState extends ConsumerState<GeneralBottomSheet> {
|
||||
const MoveToLockFolderActionButton(source: ActionSource.timeline),
|
||||
if (multiselect.selectedAssets.length > 1) const StackActionButton(source: ActionSource.timeline),
|
||||
if (multiselect.hasStacked) const UnStackActionButton(source: ActionSource.timeline),
|
||||
const DeleteActionButton(source: ActionSource.timeline),
|
||||
if (multiselect.hasLocal || multiselect.hasMerged) const DeleteActionButton(source: ActionSource.timeline),
|
||||
],
|
||||
if (multiselect.hasLocal || multiselect.hasMerged) const DeleteLocalActionButton(source: ActionSource.timeline),
|
||||
if (multiselect.hasLocal) const UploadActionButton(source: ActionSource.timeline),
|
||||
|
||||
@@ -12,6 +12,8 @@ import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/bottom_sheet/map_bottom_sheet.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/map/map.state.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/map/map_utils.dart';
|
||||
import 'package:immich_mobile/providers/routes.provider.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/utils/async_mutex.dart';
|
||||
import 'package:immich_mobile/utils/debounce.dart';
|
||||
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
||||
@@ -114,6 +116,14 @@ class _DriftMapState extends ConsumerState<DriftMap> {
|
||||
return;
|
||||
}
|
||||
|
||||
// When the AssetViewer is open, the DriftMap route stays alive in the background.
|
||||
// If we continue to update bounds, the map-scoped timeline service gets recreated and the previous one disposed,
|
||||
// which can invalidate the TimelineService instance that was passed into AssetViewerRoute (causing "loading forever").
|
||||
final currentRoute = ref.read(currentRouteNameProvider);
|
||||
if (currentRoute == AssetViewerRoute.name || currentRoute == GalleryViewerRoute.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
final bounds = await controller.getVisibleRegion();
|
||||
unawaited(
|
||||
_reloadMutex.run(() async {
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
|
||||
|
||||
class DriftRemoteAlbumOption extends ConsumerWidget {
|
||||
const DriftRemoteAlbumOption({
|
||||
@@ -14,6 +15,8 @@ class DriftRemoteAlbumOption extends ConsumerWidget {
|
||||
this.onToggleAlbumOrder,
|
||||
this.onEditAlbum,
|
||||
this.onShowOptions,
|
||||
this.iconColor,
|
||||
this.iconShadows,
|
||||
});
|
||||
|
||||
final VoidCallback? onAddPhotos;
|
||||
@@ -24,73 +27,131 @@ class DriftRemoteAlbumOption extends ConsumerWidget {
|
||||
final VoidCallback? onToggleAlbumOrder;
|
||||
final VoidCallback? onEditAlbum;
|
||||
final VoidCallback? onShowOptions;
|
||||
final Color? iconColor;
|
||||
final List<Shadow>? iconShadows;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
TextStyle textStyle = Theme.of(context).textTheme.bodyLarge!.copyWith(fontWeight: FontWeight.w600);
|
||||
final theme = context.themeData;
|
||||
final menuChildren = <Widget>[];
|
||||
|
||||
return SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 24.0),
|
||||
child: ListView(
|
||||
shrinkWrap: true,
|
||||
children: [
|
||||
if (onEditAlbum != null)
|
||||
ListTile(
|
||||
leading: const Icon(Icons.edit),
|
||||
title: Text('edit_album'.t(context: context), style: textStyle),
|
||||
onTap: onEditAlbum,
|
||||
),
|
||||
if (onAddPhotos != null)
|
||||
ListTile(
|
||||
leading: const Icon(Icons.add_a_photo),
|
||||
title: Text('add_photos'.t(context: context), style: textStyle),
|
||||
onTap: onAddPhotos,
|
||||
),
|
||||
if (onAddUsers != null)
|
||||
ListTile(
|
||||
leading: const Icon(Icons.group_add),
|
||||
title: Text('album_viewer_page_share_add_users'.t(context: context), style: textStyle),
|
||||
onTap: onAddUsers,
|
||||
),
|
||||
if (onLeaveAlbum != null)
|
||||
ListTile(
|
||||
leading: const Icon(Icons.person_remove_rounded),
|
||||
title: Text('leave_album'.t(context: context), style: textStyle),
|
||||
onTap: onLeaveAlbum,
|
||||
),
|
||||
if (onToggleAlbumOrder != null)
|
||||
ListTile(
|
||||
leading: const Icon(Icons.swap_vert_rounded),
|
||||
title: Text('change_display_order'.t(context: context), style: textStyle),
|
||||
onTap: onToggleAlbumOrder,
|
||||
),
|
||||
if (onCreateSharedLink != null)
|
||||
ListTile(
|
||||
leading: const Icon(Icons.link),
|
||||
title: Text('create_shared_link'.t(context: context), style: textStyle),
|
||||
onTap: onCreateSharedLink,
|
||||
),
|
||||
if (onShowOptions != null)
|
||||
ListTile(
|
||||
leading: const Icon(Icons.settings),
|
||||
title: Text('options'.t(context: context), style: textStyle),
|
||||
onTap: onShowOptions,
|
||||
),
|
||||
if (onDeleteAlbum != null) ...[
|
||||
const Divider(indent: 16, endIndent: 16),
|
||||
ListTile(
|
||||
leading: Icon(Icons.delete, color: context.isDarkTheme ? Colors.red[400] : Colors.red[800]),
|
||||
title: Text(
|
||||
'delete_album'.t(context: context),
|
||||
style: textStyle.copyWith(color: context.isDarkTheme ? Colors.red[400] : Colors.red[800]),
|
||||
),
|
||||
onTap: onDeleteAlbum,
|
||||
),
|
||||
],
|
||||
],
|
||||
if (onEditAlbum != null) {
|
||||
menuChildren.add(
|
||||
BaseActionButton(
|
||||
label: 'edit_album'.t(context: context),
|
||||
iconData: Icons.edit,
|
||||
onPressed: onEditAlbum,
|
||||
menuItem: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (onAddPhotos != null) {
|
||||
menuChildren.add(
|
||||
BaseActionButton(
|
||||
label: 'add_photos'.t(context: context),
|
||||
iconData: Icons.add_a_photo,
|
||||
onPressed: onAddPhotos,
|
||||
menuItem: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (onAddUsers != null) {
|
||||
menuChildren.add(
|
||||
BaseActionButton(
|
||||
label: 'album_viewer_page_share_add_users'.t(context: context),
|
||||
iconData: Icons.group_add,
|
||||
onPressed: onAddUsers,
|
||||
menuItem: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (onLeaveAlbum != null) {
|
||||
menuChildren.add(
|
||||
BaseActionButton(
|
||||
label: 'leave_album'.t(context: context),
|
||||
iconData: Icons.person_remove_rounded,
|
||||
onPressed: onLeaveAlbum,
|
||||
menuItem: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (onToggleAlbumOrder != null) {
|
||||
menuChildren.add(
|
||||
BaseActionButton(
|
||||
label: 'change_display_order'.t(context: context),
|
||||
iconData: Icons.swap_vert_rounded,
|
||||
onPressed: onToggleAlbumOrder,
|
||||
menuItem: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (onCreateSharedLink != null) {
|
||||
menuChildren.add(
|
||||
BaseActionButton(
|
||||
label: 'create_shared_link'.t(context: context),
|
||||
iconData: Icons.link,
|
||||
onPressed: onCreateSharedLink,
|
||||
menuItem: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (onShowOptions != null) {
|
||||
menuChildren.add(
|
||||
BaseActionButton(
|
||||
label: 'options'.t(context: context),
|
||||
iconData: Icons.settings,
|
||||
onPressed: onShowOptions,
|
||||
menuItem: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (onDeleteAlbum != null) {
|
||||
menuChildren.add(const Divider(height: 1));
|
||||
menuChildren.add(
|
||||
BaseActionButton(
|
||||
label: 'delete_album'.t(context: context),
|
||||
iconData: Icons.delete,
|
||||
iconColor: context.isDarkTheme ? Colors.red[400] : Colors.red[800],
|
||||
onPressed: onDeleteAlbum,
|
||||
menuItem: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return MenuAnchor(
|
||||
consumeOutsideTap: true,
|
||||
style: MenuStyle(
|
||||
backgroundColor: WidgetStatePropertyAll(theme.scaffoldBackgroundColor),
|
||||
surfaceTintColor: const WidgetStatePropertyAll(Colors.grey),
|
||||
elevation: const WidgetStatePropertyAll(4),
|
||||
shape: const WidgetStatePropertyAll(
|
||||
RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(12))),
|
||||
),
|
||||
padding: const WidgetStatePropertyAll(EdgeInsets.symmetric(vertical: 6)),
|
||||
),
|
||||
menuChildren: [
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(minWidth: 150),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: menuChildren,
|
||||
),
|
||||
),
|
||||
],
|
||||
builder: (context, controller, child) {
|
||||
return IconButton(
|
||||
icon: Icon(Icons.more_vert_rounded, color: iconColor ?? Colors.white, shadows: iconShadows),
|
||||
onPressed: () => controller.isOpen ? controller.close() : controller.open(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ class Timeline extends StatelessWidget {
|
||||
this.withScrubber = true,
|
||||
this.snapToMonth = true,
|
||||
this.initialScrollOffset,
|
||||
this.readOnly = false,
|
||||
});
|
||||
|
||||
final Widget? topSliverWidget;
|
||||
@@ -54,6 +55,7 @@ class Timeline extends StatelessWidget {
|
||||
final bool withScrubber;
|
||||
final bool snapToMonth;
|
||||
final double? initialScrollOffset;
|
||||
final bool readOnly;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -73,6 +75,7 @@ class Timeline extends StatelessWidget {
|
||||
groupBy: groupBy,
|
||||
),
|
||||
),
|
||||
if (readOnly) readonlyModeProvider.overrideWith(() => _AlwaysReadOnlyNotifier()),
|
||||
],
|
||||
child: _SliverTimeline(
|
||||
topSliverWidget: topSliverWidget,
|
||||
@@ -89,6 +92,17 @@ class Timeline extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _AlwaysReadOnlyNotifier extends ReadOnlyModeNotifier {
|
||||
@override
|
||||
bool build() => true;
|
||||
|
||||
@override
|
||||
void setReadonlyMode(bool value) {}
|
||||
|
||||
@override
|
||||
void toggleReadonlyMode() {}
|
||||
}
|
||||
|
||||
class _SliverTimeline extends ConsumerStatefulWidget {
|
||||
const _SliverTimeline({
|
||||
this.topSliverWidget,
|
||||
|
||||
Reference in New Issue
Block a user