feat(mobile): open in browser (#26369)

* feat(mobile): open in browser

* chore: open in browser instead of webview

* chore: allow archived asset

* fix: moved openinbrowser above unstack

* feat: deeplink into favorites, trash & archived

* fix: use remoteId (for tests to succeed)

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
Yaros
2026-03-16 19:06:51 +01:00
committed by GitHub
parent b66c97b785
commit 82db581cc5
3 changed files with 72 additions and 0 deletions

View File

@@ -1651,6 +1651,7 @@
"only_favorites": "Only favorites",
"open": "Open",
"open_calendar": "Open calendar",
"open_in_browser": "Open in browser",
"open_in_map_view": "Open in map view",
"open_in_openstreetmap": "Open in OpenStreetMap",
"open_the_search_filters": "Open the search filters",

View File

@@ -0,0 +1,61 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/domain/services/timeline.service.dart';
import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
import 'package:url_launcher/url_launcher.dart';
class OpenInBrowserActionButton extends ConsumerWidget {
final String remoteId;
final TimelineOrigin origin;
final bool iconOnly;
final bool menuItem;
final Color? iconColor;
const OpenInBrowserActionButton({
super.key,
required this.remoteId,
required this.origin,
this.iconOnly = false,
this.menuItem = false,
this.iconColor,
});
void _onTap() async {
final serverEndpoint = Store.get(StoreKey.serverEndpoint).replaceFirst('/api', '');
String originPath = '';
switch (origin) {
case TimelineOrigin.favorite:
originPath = '/favorites';
break;
case TimelineOrigin.trash:
originPath = '/trash';
break;
case TimelineOrigin.archive:
originPath = '/archive';
break;
default:
break;
}
final url = '$serverEndpoint$originPath/photos/$remoteId';
if (await canLaunchUrl(Uri.parse(url))) {
await launchUrl(Uri.parse(url), mode: LaunchMode.externalApplication);
}
}
@override
Widget build(BuildContext context, WidgetRef ref) {
return BaseActionButton(
label: 'open_in_browser'.t(context: context),
iconData: Icons.open_in_browser,
iconColor: iconColor,
iconOnly: iconOnly,
menuItem: menuItem,
onPressed: _onTap,
);
}
}

View File

@@ -18,6 +18,7 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/delete_permane
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/move_to_lock_folder_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/open_in_browser_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/remove_from_lock_folder_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/set_album_cover.widget.dart';
@@ -75,6 +76,7 @@ enum ActionButtonType {
viewInTimeline,
download,
upload,
openInBrowser,
unstack,
archive,
unarchive,
@@ -149,6 +151,7 @@ enum ActionButtonType {
context.isOwner && //
!context.isInLockedView && //
context.isStacked,
ActionButtonType.openInBrowser => context.asset.hasRemote && !context.isInLockedView,
ActionButtonType.likeActivity =>
!context.isInLockedView &&
context.currentAlbum != null &&
@@ -236,6 +239,13 @@ enum ActionButtonType {
),
ActionButtonType.likeActivity => LikeActivityActionButton(iconOnly: iconOnly, menuItem: menuItem),
ActionButtonType.unstack => UnStackActionButton(source: context.source, iconOnly: iconOnly, menuItem: menuItem),
ActionButtonType.openInBrowser => OpenInBrowserActionButton(
remoteId: context.asset.remoteId!,
origin: context.timelineOrigin,
iconOnly: iconOnly,
menuItem: menuItem,
iconColor: context.originalTheme?.iconTheme.color,
),
ActionButtonType.similarPhotos => SimilarPhotosActionButton(
assetId: (context.asset as RemoteAsset).id,
iconOnly: iconOnly,