feat: adds bottom sheet map and actions (#19726)

* reduce timeline rebuilds

* feat: adds bottom sheet map and actions (#19692)

* adds bottom sheet map and actions

* PR feedbacks

* only reload the asset viewer if asset is changed

* styling tweak

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
Co-authored-by: Alex <alex.tran1502@gmail.com>

* rename singleton and remove event prefix

* adds bottom sheet map and actions

* PR feedbacks

* refactor: use provider for viewer state

* feat: adds top and bottom app bar

* add safe area to bottom app bar

* change app and bottom bar color

* viewer - always have black background

* use the full width for the bottom sheet on landscape as well

* constraint the bottom sheet to not expand all the way

* add padding for location details in landscape

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
shenlong 2025-07-05 00:38:06 +05:30 committed by GitHub
parent 4a2cf28882
commit 73733370a2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 622 additions and 155 deletions

View file

@ -9,17 +9,33 @@ class BaseActionButton extends StatelessWidget {
this.onPressed,
this.onLongPressed,
this.maxWidth = 90.0,
this.minWidth,
this.menuItem = false,
});
final String label;
final IconData iconData;
final double maxWidth;
final double? minWidth;
final bool menuItem;
final void Function()? onPressed;
final void Function()? onLongPressed;
@override
Widget build(BuildContext context) {
final minWidth = context.isMobile ? context.width / 4.5 : 75.0;
final miniWidth =
minWidth ?? (context.isMobile ? context.width / 4.5 : 75.0);
final iconTheme = IconTheme.of(context);
final iconSize = iconTheme.size ?? 24.0;
final iconColor = iconTheme.color ?? context.themeData.iconTheme.color;
final textColor = context.themeData.textTheme.labelLarge?.color;
if (menuItem) {
return IconButton(
onPressed: onPressed,
icon: Icon(iconData, size: iconSize, color: iconColor),
);
}
return ConstrainedBox(
constraints: BoxConstraints(
@ -30,19 +46,22 @@ class BaseActionButton extends StatelessWidget {
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(20)),
),
textColor: textColor,
onPressed: onPressed,
onLongPress: onLongPressed,
minWidth: minWidth,
minWidth: miniWidth,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(iconData, size: 24),
Icon(iconData, size: iconSize, color: iconColor),
const SizedBox(height: 8),
Text(
label,
style:
const TextStyle(fontSize: 14.0, fontWeight: FontWeight.w400),
style: const TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.w400,
),
maxLines: 3,
textAlign: TextAlign.center,
),

View file

@ -10,8 +10,13 @@ import 'package:immich_mobile/widgets/common/immich_toast.dart';
class FavoriteActionButton extends ConsumerWidget {
final ActionSource source;
final bool menuItem;
const FavoriteActionButton({super.key, required this.source});
const FavoriteActionButton({
super.key,
required this.source,
this.menuItem = false,
});
void _onTap(BuildContext context, WidgetRef ref) async {
if (!context.mounted) {
@ -19,6 +24,11 @@ class FavoriteActionButton extends ConsumerWidget {
}
final result = await ref.read(actionProvider.notifier).favorite(source);
if (source == ActionSource.viewer) {
return;
}
ref.read(multiSelectProvider.notifier).reset();
final successMessage = 'favorite_action_prompt'.t(
@ -43,6 +53,7 @@ class FavoriteActionButton extends ConsumerWidget {
return BaseActionButton(
iconData: Icons.favorite_border_rounded,
label: "favorite".t(context: context),
menuItem: menuItem,
onPressed: () => _onTap(context, ref),
);
}

View file

@ -10,8 +10,13 @@ import 'package:immich_mobile/widgets/common/immich_toast.dart';
class UnFavoriteActionButton extends ConsumerWidget {
final ActionSource source;
final bool menuItem;
const UnFavoriteActionButton({super.key, required this.source});
const UnFavoriteActionButton({
super.key,
required this.source,
this.menuItem = false,
});
void _onTap(BuildContext context, WidgetRef ref) async {
if (!context.mounted) {
@ -19,6 +24,11 @@ class UnFavoriteActionButton extends ConsumerWidget {
}
final result = await ref.read(actionProvider.notifier).unFavorite(source);
if (source == ActionSource.viewer) {
return;
}
ref.read(multiSelectProvider.notifier).reset();
final successMessage = 'unfavorite_action_prompt'.t(
@ -44,6 +54,7 @@ class UnFavoriteActionButton extends ConsumerWidget {
iconData: Icons.favorite_rounded,
label: "unfavorite".t(context: context),
onPressed: () => _onTap(context, ref),
menuItem: menuItem,
);
}
}