mirror of
https://github.com/immich-app/immich.git
synced 2026-07-28 14:47:30 -07:00
refactor: mobile cast and slideshow action
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/generated/translations.g.dart';
|
||||
import 'package:immich_mobile/presentation/actions/action.dart';
|
||||
import 'package:immich_mobile/providers/cast.provider.dart';
|
||||
import 'package:immich_mobile/widgets/asset_viewer/cast_dialog.dart';
|
||||
|
||||
class CastAction extends ActionBuilder {
|
||||
const CastAction();
|
||||
|
||||
@override
|
||||
ActionData build(BuildContext context, WidgetRef ref) {
|
||||
final isCasting = ref.watch(castProvider.select((state) => state.isCasting));
|
||||
|
||||
return .new(
|
||||
icon: isCasting ? Icons.cast_connected_rounded : Icons.cast_rounded,
|
||||
label: context.t.cast,
|
||||
onAction: () async => unawaited(showDialog(context: context, builder: (_) => const CastDialog())),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/generated/translations.g.dart';
|
||||
import 'package:immich_mobile/presentation/actions/action.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
|
||||
class SlideshowAction extends ActionBuilder {
|
||||
const SlideshowAction();
|
||||
|
||||
@override
|
||||
ActionData build(BuildContext context, WidgetRef ref) => .new(
|
||||
icon: Icons.slideshow,
|
||||
label: context.t.slideshow,
|
||||
onAction: () async =>
|
||||
unawaited(context.pushRoute(DriftSlideshowRoute(timeline: ref.read(timelineServiceProvider)))),
|
||||
);
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
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';
|
||||
import 'package:immich_mobile/providers/cast.provider.dart';
|
||||
import 'package:immich_mobile/widgets/asset_viewer/cast_dialog.dart';
|
||||
|
||||
class CastActionButton extends ConsumerWidget {
|
||||
const CastActionButton({super.key, this.iconOnly = false, this.menuItem = false});
|
||||
|
||||
final bool iconOnly;
|
||||
final bool menuItem;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final isCasting = ref.watch(castProvider.select((c) => c.isCasting));
|
||||
|
||||
return BaseActionButton(
|
||||
iconData: isCasting ? Icons.cast_connected_rounded : Icons.cast_rounded,
|
||||
iconColor: isCasting ? context.primaryColor : null, // null = default color
|
||||
label: "cast".t(context: context),
|
||||
onPressed: () {
|
||||
showDialog(context: context, builder: (context) => const CastDialog());
|
||||
},
|
||||
iconOnly: iconOnly,
|
||||
menuItem: menuItem,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
|
||||
class SlideshowActionButton extends ConsumerWidget {
|
||||
final bool iconOnly;
|
||||
final bool menuItem;
|
||||
|
||||
const SlideshowActionButton({super.key, this.iconOnly = false, this.menuItem = false});
|
||||
|
||||
void _onTap(BuildContext context, WidgetRef ref) {
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
context.pushRoute(DriftSlideshowRoute(timeline: ref.read(timelineServiceProvider)));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return BaseActionButton(
|
||||
iconData: Icons.slideshow,
|
||||
label: "slideshow".t(context: context),
|
||||
iconOnly: iconOnly,
|
||||
menuItem: menuItem,
|
||||
onPressed: () => _onTap(context, ref),
|
||||
maxWidth: 100,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -10,22 +10,22 @@ import 'package:immich_mobile/domain/utils/event_stream.dart';
|
||||
import 'package:immich_mobile/presentation/actions/action.widget.dart';
|
||||
import 'package:immich_mobile/presentation/actions/archive.action.dart';
|
||||
import 'package:immich_mobile/presentation/actions/asset_debug.action.dart';
|
||||
import 'package:immich_mobile/presentation/actions/cast.action.dart';
|
||||
import 'package:immich_mobile/presentation/actions/delete.action.dart';
|
||||
import 'package:immich_mobile/presentation/actions/lock.action.dart';
|
||||
import 'package:immich_mobile/presentation/actions/open_in_browser.action.dart';
|
||||
import 'package:immich_mobile/presentation/actions/restore.action.dart';
|
||||
import 'package:immich_mobile/presentation/actions/set_profile_picture.action.dart';
|
||||
import 'package:immich_mobile/presentation/actions/similar_photos.action.dart';
|
||||
import 'package:immich_mobile/presentation/actions/slideshow.action.dart';
|
||||
import 'package:immich_mobile/presentation/actions/stack.action.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/cast_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/download_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/like_activity_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/remove_from_album_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/set_album_cover.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/share_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/share_link_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/slideshow_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/upload_action_button.widget.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
|
||||
@@ -179,7 +179,7 @@ enum ActionButtonType {
|
||||
iconOnly: iconOnly,
|
||||
menuItem: menuItem,
|
||||
),
|
||||
ActionButtonType.slideshow => SlideshowActionButton(iconOnly: iconOnly, menuItem: menuItem),
|
||||
ActionButtonType.slideshow => const ActionMenuItem(action: SlideshowAction()),
|
||||
ActionButtonType.archive ||
|
||||
ActionButtonType.unarchive => ActionMenuItem(action: ArchiveAction(source: context.source)),
|
||||
ActionButtonType.download => DownloadActionButton(source: context.source, iconOnly: iconOnly, menuItem: menuItem),
|
||||
@@ -228,7 +228,7 @@ enum ActionButtonType {
|
||||
EventStream.shared.emit(ScrollToDateEvent(context.asset.createdAt));
|
||||
},
|
||||
),
|
||||
ActionButtonType.cast => CastActionButton(iconOnly: iconOnly, menuItem: menuItem),
|
||||
ActionButtonType.cast => const ActionMenuItem(action: CastAction()),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:immich_mobile/presentation/actions/action.widget.dart';
|
||||
import 'package:immich_mobile/presentation/actions/cast.action.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
|
||||
import '../presentation_context.dart';
|
||||
|
||||
void main() {
|
||||
late PresentationContext context;
|
||||
|
||||
setUp(() async {
|
||||
context = await PresentationContext.create();
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
context.dispose();
|
||||
});
|
||||
|
||||
void Function(bool) captureConnectionListener() =>
|
||||
verify(() => context.service.cast.onConnectionState = captureAny()).captured.single as void Function(bool);
|
||||
|
||||
group('CastAction', () {
|
||||
testWidgets('offers to cast when nothing is connected', (tester) async {
|
||||
await tester.pumpTestWidget(context, const ActionIconButton(action: CastAction()));
|
||||
|
||||
expect(find.byIcon(Icons.cast_rounded), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('switches to the connected icon once casting starts', (tester) async {
|
||||
await tester.pumpTestWidget(context, const ActionIconButton(action: CastAction()));
|
||||
|
||||
captureConnectionListener()(true);
|
||||
await tester.pump();
|
||||
|
||||
expect(find.byIcon(Icons.cast_connected_rounded), findsOneWidget);
|
||||
expect(find.byIcon(Icons.cast_rounded), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('switches back when casting stops', (tester) async {
|
||||
await tester.pumpTestWidget(context, const ActionIconButton(action: CastAction()));
|
||||
|
||||
final onConnectionState = captureConnectionListener();
|
||||
onConnectionState(true);
|
||||
await tester.pump();
|
||||
onConnectionState(false);
|
||||
await tester.pump();
|
||||
|
||||
expect(find.byIcon(Icons.cast_rounded), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user