added context menu support

This commit is contained in:
Fabian Freund
2025-02-23 17:11:28 +01:00
parent a5133b0b90
commit cbfd218c7e
30 changed files with 2159 additions and 159 deletions
@@ -198,6 +198,12 @@
android:name="flutterEmbedding" android:name="flutterEmbedding"
android:value="2" /> android:value="2" />
</application> </application>
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="*" />
</intent>
</queries>
<!-- Required to query activities that can process text, see: <!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT. https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
+18
View File
@@ -7,6 +7,10 @@ part of 'routes.dart';
TypedGoRoute<WebPageRoute>(name: 'WebPageRoute', path: 'page/:url'), TypedGoRoute<WebPageRoute>(name: 'WebPageRoute', path: 'page/:url'),
TypedGoRoute<SearchRoute>(name: 'SearchRoute', path: 'search/:searchText'), TypedGoRoute<SearchRoute>(name: 'SearchRoute', path: 'search/:searchText'),
TypedGoRoute<TorProxyRoute>(name: 'TorProxyRoute', path: 'tor_proxy'), TypedGoRoute<TorProxyRoute>(name: 'TorProxyRoute', path: 'tor_proxy'),
TypedGoRoute<ContextMenuRoute>(
name: 'ContextMenuRoute',
path: 'context_menu',
),
TypedGoRoute<ContainerListRoute>( TypedGoRoute<ContainerListRoute>(
name: 'ContainerListRoute', name: 'ContainerListRoute',
path: 'containers', path: 'containers',
@@ -97,3 +101,17 @@ class ContainerCreateRoute extends GoRouteData {
); );
} }
} }
class ContextMenuRoute extends GoRouteData {
const ContextMenuRoute();
@override
Page<void> buildPage(BuildContext context, GoRouterState state) {
return DialogPage(
builder:
(_) => ContextMenuDialog(
hitResult: HitResultJson.fromJson(state.extra! as String),
),
);
}
}
+2
View File
@@ -8,6 +8,8 @@ import 'package:lensai/features/bangs/presentation/screens/list.dart';
import 'package:lensai/features/bangs/presentation/screens/search.dart'; import 'package:lensai/features/bangs/presentation/screens/search.dart';
import 'package:lensai/features/geckoview/features/browser/presentation/dialogs/web_page_dialog.dart'; import 'package:lensai/features/geckoview/features/browser/presentation/dialogs/web_page_dialog.dart';
import 'package:lensai/features/geckoview/features/browser/presentation/screens/browser.dart'; import 'package:lensai/features/geckoview/features/browser/presentation/screens/browser.dart';
import 'package:lensai/features/geckoview/features/contextmenu/extensions/hit_result.dart';
import 'package:lensai/features/geckoview/features/contextmenu/presentation/context_menu_dialog.dart';
import 'package:lensai/features/geckoview/features/search/presentation/screens/search.dart'; import 'package:lensai/features/geckoview/features/search/presentation/screens/search.dart';
import 'package:lensai/features/geckoview/features/tabs/data/models/container_data.dart'; import 'package:lensai/features/geckoview/features/tabs/data/models/container_data.dart';
import 'package:lensai/features/geckoview/features/tabs/presentation/screens/container_edit.dart'; import 'package:lensai/features/geckoview/features/tabs/presentation/screens/container_edit.dart';
+23
View File
@@ -227,6 +227,11 @@ RouteBase get $browserRoute => GoRouteData.$route(
name: 'TorProxyRoute', name: 'TorProxyRoute',
factory: $TorProxyRouteExtension._fromState, factory: $TorProxyRouteExtension._fromState,
), ),
GoRouteData.$route(
path: 'context_menu',
name: 'ContextMenuRoute',
factory: $ContextMenuRouteExtension._fromState,
),
GoRouteData.$route( GoRouteData.$route(
path: 'containers', path: 'containers',
name: 'ContainerListRoute', name: 'ContainerListRoute',
@@ -320,6 +325,24 @@ extension $TorProxyRouteExtension on TorProxyRoute {
void replace(BuildContext context) => context.replace(location); void replace(BuildContext context) => context.replace(location);
} }
extension $ContextMenuRouteExtension on ContextMenuRoute {
static ContextMenuRoute _fromState(GoRouterState state) =>
const ContextMenuRoute();
String get location => GoRouteData.$location(
'/context_menu',
);
void go(BuildContext context) => context.go(location);
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
void pushReplacement(BuildContext context) =>
context.pushReplacement(location);
void replace(BuildContext context) => context.replace(location);
}
extension $ContainerListRouteExtension on ContainerListRoute { extension $ContainerListRouteExtension on ContainerListRoute {
static ContainerListRoute _fromState(GoRouterState state) => static ContainerListRoute _fromState(GoRouterState state) =>
ContainerListRoute(); ContainerListRoute();
@@ -8,6 +8,7 @@ import 'package:lensai/data/models/drag_data.dart';
import 'package:lensai/extensions/media_query.dart'; import 'package:lensai/extensions/media_query.dart';
import 'package:lensai/features/geckoview/domain/controllers/bottom_sheet.dart'; import 'package:lensai/features/geckoview/domain/controllers/bottom_sheet.dart';
import 'package:lensai/features/geckoview/domain/controllers/overlay.dart'; import 'package:lensai/features/geckoview/domain/controllers/overlay.dart';
import 'package:lensai/features/geckoview/domain/providers.dart';
import 'package:lensai/features/geckoview/domain/providers/tab_list.dart'; import 'package:lensai/features/geckoview/domain/providers/tab_list.dart';
import 'package:lensai/features/geckoview/domain/providers/tab_session.dart'; import 'package:lensai/features/geckoview/domain/providers/tab_session.dart';
import 'package:lensai/features/geckoview/domain/providers/tab_state.dart'; import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
@@ -17,6 +18,7 @@ import 'package:lensai/features/geckoview/features/browser/presentation/widgets/
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/browser_modules/browser_view.dart'; import 'package:lensai/features/geckoview/features/browser/presentation/widgets/browser_modules/browser_view.dart';
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/draggable_scrollable_header.dart'; import 'package:lensai/features/geckoview/features/browser/presentation/widgets/draggable_scrollable_header.dart';
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/sheets/view_tabs.dart'; import 'package:lensai/features/geckoview/features/browser/presentation/widgets/sheets/view_tabs.dart';
import 'package:lensai/features/geckoview/features/contextmenu/extensions/hit_result.dart';
import 'package:lensai/features/geckoview/features/find_in_page/presentation/widgets/find_in_page.dart'; import 'package:lensai/features/geckoview/features/find_in_page/presentation/widgets/find_in_page.dart';
import 'package:lensai/features/geckoview/features/readerview/presentation/widgets/reader_appearance_button.dart'; import 'package:lensai/features/geckoview/features/readerview/presentation/widgets/reader_appearance_button.dart';
import 'package:lensai/features/geckoview/features/tabs/features/chat/presentation/widgets/tab_qa_chat.dart'; import 'package:lensai/features/geckoview/features/tabs/features/chat/presentation/widgets/tab_qa_chat.dart';
@@ -29,6 +31,8 @@ class BrowserScreen extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final eventService = ref.watch(eventServiceProvider);
final displayedSheet = ref.watch(bottomSheetControllerProvider); final displayedSheet = ref.watch(bottomSheetControllerProvider);
final displayedOverlay = ref.watch(overlayControllerProvider); final displayedOverlay = ref.watch(overlayControllerProvider);
@@ -48,6 +52,16 @@ class BrowserScreen extends HookConsumerWidget {
} }
}); });
useOnStreamChange(
eventService.longPressEvent,
onData: (event) async {
await context.push(
const ContextMenuRoute().location,
extra: event.hitResult.toJson(),
);
},
);
return PopScope( return PopScope(
//We need this for BackButtonListener to work downstream //We need this for BackButtonListener to work downstream
//No direct pop result will be handled here //No direct pop result will be handled here
@@ -0,0 +1,45 @@
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
class HitResultConverter {
const HitResultConverter();
Map<String, dynamic> encode(HitResult input) {
final (type, data) = switch (input) {
final UnknownHitResult result => (result.runtimeType, result.encode()),
final ImageHitResult result => (result.runtimeType, result.encode()),
final VideoHitResult result => (result.runtimeType, result.encode()),
final AudioHitResult result => (result.runtimeType, result.encode()),
final ImageSrcHitResult result => (result.runtimeType, result.encode()),
final PhoneHitResult result => (result.runtimeType, result.encode()),
final EmailHitResult result => (result.runtimeType, result.encode()),
final GeoHitResult result => (result.runtimeType, result.encode()),
};
return {'type': type.toString(), 'data': data};
}
HitResult decode(Map<String, dynamic> input) {
final {'type': String type, 'data': List<Object?> data} = input;
switch (type) {
case 'UnknownHitResult':
return UnknownHitResult.decode(data);
case 'ImageHitResult':
return ImageHitResult.decode(data);
case 'VideoHitResult':
return VideoHitResult.decode(data);
case 'AudioHitResult':
return AudioHitResult.decode(data);
case 'ImageSrcHitResult':
return ImageSrcHitResult.decode(data);
case 'PhoneHitResult':
return PhoneHitResult.decode(data);
case 'EmailHitResult':
return EmailHitResult.decode(data);
case 'GeoHitResult':
return GeoHitResult.decode(data);
default:
throw FormatException('Unknown HitResult type: $type');
}
}
}
@@ -0,0 +1,90 @@
import 'dart:convert';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:lensai/features/geckoview/features/contextmenu/domain/converters/hit_result_converter.dart';
extension HitResultJson on HitResult {
String toJson() {
const codec = HitResultConverter();
return jsonEncode(codec.encode(this));
}
static HitResult fromJson(String json) {
const codec = HitResultConverter();
return codec.decode(jsonDecode(json) as Map<String, dynamic>);
}
}
extension HitResultX on HitResult {
Uri? tryGetLink() {
final uri = switch (this) {
UnknownHitResult(src: final src) => src,
ImageHitResult(src: final src) => src,
VideoHitResult(src: final src) => src,
AudioHitResult(src: final src) => src,
ImageSrcHitResult(src: final src) => src,
PhoneHitResult(src: final src) => src,
EmailHitResult(src: final src) => src,
GeoHitResult(src: final src) => src,
};
return Uri.tryParse(uri);
}
String getTitle() {
const maxTitleLength = 2500;
return switch (this) {
UnknownHitResult(src: final src) => src,
ImageSrcHitResult(uri: final uri) => uri,
ImageHitResult(src: final src, title: final title) =>
title?.isEmpty ?? true
? (src.length > maxTitleLength ? 'image' : src)
: title!,
VideoHitResult(src: final src, title: final title) =>
(title?.isEmpty ?? true) ? src : title!,
AudioHitResult(src: final src, title: final title) =>
(title?.isEmpty ?? true) ? src : title!,
_ => 'about:blank',
};
}
bool get hasSrc => tryGetLink() != null;
bool isImage() {
return (this is ImageHitResult || this is ImageSrcHitResult) && hasSrc;
}
bool isVideoAudio() {
return (this is VideoHitResult || this is AudioHitResult) && hasSrc;
}
bool isFile() {
return this is UnknownHitResult && hasSrc;
}
bool isUri() {
return (this is UnknownHitResult && hasSrc) || this is ImageSrcHitResult;
}
bool isHttpLink() {
return isUri() &&
switch (tryGetLink()?.scheme) {
'http' => true,
'https' => true,
_ => false,
};
}
bool isIntent() {
return this is UnknownHitResult &&
hasSrc &&
tryGetLink()?.scheme == 'intent';
}
bool isMailto() {
return this is UnknownHitResult &&
hasSrc &&
tryGetLink()?.scheme == 'mailto';
}
}
@@ -0,0 +1,31 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/extensions/nullable.dart';
import 'package:lensai/features/geckoview/features/contextmenu/extensions/hit_result.dart';
class CopyEmail extends HookConsumerWidget {
final HitResult hitResult;
const CopyEmail({super.key, required this.hitResult});
static bool isSupported(HitResult hitResult) {
return hitResult.isMailto();
}
@override
Widget build(BuildContext context, WidgetRef ref) {
return ListTile(
leading: const Icon(MdiIcons.emailMultiple),
title: const Text('Share email address'),
onTap: () async {
final email = hitResult.tryGetLink().mapNotNull((url) => url.host);
if (email != null) {
await Clipboard.setData(ClipboardData(text: email));
}
},
);
}
}
@@ -0,0 +1,37 @@
import 'package:flutter/material.dart';
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
import 'package:lensai/features/geckoview/features/contextmenu/extensions/hit_result.dart';
class CopyImage extends HookConsumerWidget {
final HitResult hitResult;
const CopyImage({super.key, required this.hitResult});
static bool isSupported(HitResult hitResult) {
return hitResult.isImage();
}
@override
Widget build(BuildContext context, WidgetRef ref) {
return ListTile(
leading: const Icon(MdiIcons.imageMultiple),
title: const Text('Copy image'),
onTap: () async {
final currentTab = ref.read(selectedTabStateProvider);
final url = hitResult.tryGetLink();
if (currentTab != null && url != null) {
await GeckoDownloadsService().copyInternetResource(
currentTab.id,
url: url,
isPrivate: currentTab.isPrivate,
referrerUrl: currentTab.url,
);
}
},
);
}
}
@@ -0,0 +1,30 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/extensions/nullable.dart';
import 'package:lensai/features/geckoview/features/contextmenu/extensions/hit_result.dart';
class CopyImageLocation extends HookConsumerWidget {
final HitResult hitResult;
const CopyImageLocation({super.key, required this.hitResult});
static bool isSupported(HitResult hitResult) {
return hitResult.isImage();
}
@override
Widget build(BuildContext context, WidgetRef ref) {
return ListTile(
leading: const Icon(MdiIcons.imageMarker),
title: const Text('Copy image location'),
onTap: () async {
await hitResult.tryGetLink().mapNotNull(
(link) => Clipboard.setData(ClipboardData(text: link.toString())),
);
},
);
}
}
@@ -0,0 +1,30 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/extensions/nullable.dart';
import 'package:lensai/features/geckoview/features/contextmenu/extensions/hit_result.dart';
class CopyLink extends HookConsumerWidget {
final HitResult hitResult;
const CopyLink({super.key, required this.hitResult});
static bool isSupported(HitResult hitResult) {
return hitResult.isUri() || hitResult.isImage() || hitResult.isVideoAudio();
}
@override
Widget build(BuildContext context, WidgetRef ref) {
return ListTile(
leading: const Icon(MdiIcons.contentCopy),
title: const Text('Copy link'),
onTap: () async {
await hitResult.tryGetLink().mapNotNull(
(link) => Clipboard.setData(ClipboardData(text: link.toString())),
);
},
);
}
}
@@ -0,0 +1,31 @@
import 'package:flutter/material.dart';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/extensions/nullable.dart';
import 'package:lensai/features/geckoview/features/contextmenu/extensions/hit_result.dart';
import 'package:lensai/utils/ui_helper.dart';
import 'package:url_launcher/url_launcher.dart';
class LaunchExternal extends HookConsumerWidget {
final HitResult hitResult;
const LaunchExternal({super.key, required this.hitResult});
static Future<bool> isSupported(HitResult hitResult) async {
return hitResult.tryGetLink().mapNotNull((url) => canLaunchUrl(url)) ??
false;
}
@override
Widget build(BuildContext context, WidgetRef ref) {
return ListTile(
leading: const Icon(Icons.open_in_browser),
title: const Text('Launch External'),
onTap: () async {
await hitResult.tryGetLink().mapNotNull(
(url) => launchUrlFeedback(context, url),
);
},
);
}
}
@@ -0,0 +1,31 @@
import 'package:flutter/material.dart';
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/features/geckoview/domain/providers/selected_tab.dart';
import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
import 'package:lensai/features/geckoview/features/contextmenu/extensions/hit_result.dart';
class OpenImageInNewTab extends HookConsumerWidget {
final HitResult hitResult;
const OpenImageInNewTab({super.key, required this.hitResult});
static bool isSupported(HitResult hitResult) {
return hitResult.isImage();
}
@override
Widget build(BuildContext context, WidgetRef ref) {
return ListTile(
leading: const Icon(MdiIcons.tabPlus),
title: const Text('Open image in new tab'),
onTap: () async {
final currentTabId = ref.read(selectedTabProvider);
await ref
.read(tabRepositoryProvider.notifier)
.addTab(url: hitResult.tryGetLink(), parentId: currentTabId);
},
);
}
}
@@ -0,0 +1,29 @@
import 'package:flutter/material.dart';
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
import 'package:lensai/features/geckoview/features/contextmenu/extensions/hit_result.dart';
class OpenInNewTab extends HookConsumerWidget {
final HitResult hitResult;
const OpenInNewTab({super.key, required this.hitResult});
static bool isSupported(HitResult hitResult) {
return hitResult.isHttpLink();
}
@override
Widget build(BuildContext context, WidgetRef ref) {
return ListTile(
leading: const Icon(MdiIcons.tabPlus),
title: const Text('Open in new tab'),
onTap: () async {
await ref
.read(tabRepositoryProvider.notifier)
.addTab(url: hitResult.tryGetLink());
},
);
}
}
@@ -0,0 +1,38 @@
import 'package:flutter/material.dart';
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
import 'package:lensai/features/geckoview/features/contextmenu/extensions/hit_result.dart';
class SaveFile extends HookConsumerWidget {
final HitResult hitResult;
const SaveFile({super.key, required this.hitResult});
static bool isSupported(HitResult hitResult) {
return hitResult.isVideoAudio() || hitResult.isFile();
}
@override
Widget build(BuildContext context, WidgetRef ref) {
return ListTile(
leading: const Icon(MdiIcons.fileDownload),
title: const Text('Save file'),
onTap: () async {
final currentTab = ref.read(selectedTabStateProvider);
final url = hitResult.tryGetLink();
if (currentTab != null && url != null) {
await GeckoDownloadsService().requestDownload(
currentTab.id,
url: url,
skipConfirmation: true,
isPrivate: currentTab.isPrivate,
referrerUrl: currentTab.url,
);
}
},
);
}
}
@@ -0,0 +1,38 @@
import 'package:flutter/material.dart';
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
import 'package:lensai/features/geckoview/features/contextmenu/extensions/hit_result.dart';
class SaveImage extends HookConsumerWidget {
final HitResult hitResult;
const SaveImage({super.key, required this.hitResult});
static bool isSupported(HitResult hitResult) {
return hitResult.isImage();
}
@override
Widget build(BuildContext context, WidgetRef ref) {
return ListTile(
leading: const Icon(MdiIcons.imageMove),
title: const Text('Save image'),
onTap: () async {
final currentTab = ref.read(selectedTabStateProvider);
final url = hitResult.tryGetLink();
if (currentTab != null && url != null) {
await GeckoDownloadsService().requestDownload(
currentTab.id,
url: url,
skipConfirmation: true,
isPrivate: currentTab.isPrivate,
referrerUrl: currentTab.url,
);
}
},
);
}
}
@@ -0,0 +1,31 @@
import 'package:flutter/material.dart';
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/extensions/nullable.dart';
import 'package:lensai/features/geckoview/features/contextmenu/extensions/hit_result.dart';
import 'package:share_plus/share_plus.dart';
class ShareEmail extends HookConsumerWidget {
final HitResult hitResult;
const ShareEmail({super.key, required this.hitResult});
static bool isSupported(HitResult hitResult) {
return hitResult.isMailto();
}
@override
Widget build(BuildContext context, WidgetRef ref) {
return ListTile(
leading: const Icon(MdiIcons.emailArrowRight),
title: const Text('Share email address'),
onTap: () async {
final email = hitResult.tryGetLink().mapNotNull((url) => url.path);
if (email != null) {
await Share.share(email);
}
},
);
}
}
@@ -0,0 +1,37 @@
import 'package:flutter/material.dart';
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
import 'package:lensai/features/geckoview/features/contextmenu/extensions/hit_result.dart';
class ShareImage extends HookConsumerWidget {
final HitResult hitResult;
const ShareImage({super.key, required this.hitResult});
static bool isSupported(HitResult hitResult) {
return hitResult.isImage();
}
@override
Widget build(BuildContext context, WidgetRef ref) {
return ListTile(
leading: const Icon(MdiIcons.imagePlus),
title: const Text('Share image'),
onTap: () async {
final currentTab = ref.read(selectedTabStateProvider);
final url = hitResult.tryGetLink();
if (currentTab != null && url != null) {
await GeckoDownloadsService().shareInternetResource(
currentTab.id,
url: url,
isPrivate: currentTab.isPrivate,
referrerUrl: currentTab.url,
);
}
},
);
}
}
@@ -0,0 +1,27 @@
import 'package:flutter/material.dart';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/extensions/nullable.dart';
import 'package:lensai/features/geckoview/features/contextmenu/extensions/hit_result.dart';
import 'package:share_plus/share_plus.dart';
class ShareLink extends HookConsumerWidget {
final HitResult hitResult;
const ShareLink({super.key, required this.hitResult});
static bool isSupported(HitResult hitResult) {
return hitResult.isUri() || hitResult.isImage() || hitResult.isVideoAudio();
}
@override
Widget build(BuildContext context, WidgetRef ref) {
return ListTile(
leading: const Icon(Icons.share),
title: const Text('Share link'),
onTap: () async {
await hitResult.tryGetLink().mapNotNull((url) => Share.shareUri(url));
},
);
}
}
@@ -0,0 +1,73 @@
import 'package:flutter/material.dart';
import 'package:flutter_auto_size_text/flutter_auto_size_text.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/features/geckoview/features/contextmenu/extensions/hit_result.dart';
import 'package:lensai/features/geckoview/features/contextmenu/presentation/candidates/copy_email.dart';
import 'package:lensai/features/geckoview/features/contextmenu/presentation/candidates/copy_image.dart';
import 'package:lensai/features/geckoview/features/contextmenu/presentation/candidates/copy_image_location.dart';
import 'package:lensai/features/geckoview/features/contextmenu/presentation/candidates/copy_link.dart';
import 'package:lensai/features/geckoview/features/contextmenu/presentation/candidates/launch_external.dart';
import 'package:lensai/features/geckoview/features/contextmenu/presentation/candidates/open_image_new_tab.dart';
import 'package:lensai/features/geckoview/features/contextmenu/presentation/candidates/open_new_tab.dart';
import 'package:lensai/features/geckoview/features/contextmenu/presentation/candidates/save_file.dart';
import 'package:lensai/features/geckoview/features/contextmenu/presentation/candidates/save_image.dart';
import 'package:lensai/features/geckoview/features/contextmenu/presentation/candidates/share_email.dart';
import 'package:lensai/features/geckoview/features/contextmenu/presentation/candidates/share_image.dart';
import 'package:lensai/features/geckoview/features/contextmenu/presentation/candidates/share_link.dart';
import 'package:lensai/presentation/hooks/cached_future.dart';
import 'package:skeletonizer/skeletonizer.dart';
class ContextMenuDialog extends HookConsumerWidget {
final HitResult hitResult;
const ContextMenuDialog({super.key, required this.hitResult});
@override
Widget build(BuildContext context, WidgetRef ref) {
return SimpleDialog(
title: AutoSizeText(
hitResult.getTitle(),
minFontSize: 18,
maxFontSize: DefaultTextStyle.of(context).style.fontSize,
maxLines: 10,
overflow: TextOverflow.ellipsis,
softWrap: true,
),
children: [
if (OpenInNewTab.isSupported(hitResult))
OpenInNewTab(hitResult: hitResult),
if (CopyLink.isSupported(hitResult)) CopyLink(hitResult: hitResult),
if (SaveFile.isSupported(hitResult)) SaveFile(hitResult: hitResult),
if (ShareLink.isSupported(hitResult)) ShareLink(hitResult: hitResult),
if (ShareImage.isSupported(hitResult)) ShareImage(hitResult: hitResult),
if (OpenImageInNewTab.isSupported(hitResult))
OpenImageInNewTab(hitResult: hitResult),
if (CopyImage.isSupported(hitResult)) CopyImage(hitResult: hitResult),
if (SaveImage.isSupported(hitResult)) SaveImage(hitResult: hitResult),
if (CopyImageLocation.isSupported(hitResult))
CopyImageLocation(hitResult: hitResult),
if (ShareEmail.isSupported(hitResult)) ShareEmail(hitResult: hitResult),
if (CopyEmail.isSupported(hitResult)) CopyEmail(hitResult: hitResult),
HookBuilder(
builder: (context) {
final isSupported = useCachedFuture(
// ignore: discarded_futures useFuture
() => LaunchExternal.isSupported(hitResult),
);
if (isSupported.data == false) {
return const SizedBox.shrink();
}
return Skeletonizer(
enabled: isSupported.connectionState != ConnectionState.done,
child: LaunchExternal(hitResult: hitResult),
);
},
),
],
);
}
}
+1
View File
@@ -17,6 +17,7 @@ dependencies:
fast_equatable: ^1.1.0 fast_equatable: ^1.1.0
flutter: flutter:
sdk: flutter sdk: flutter
flutter_auto_size_text: ^4.1.0
flutter_background_service: ^5.1.0 flutter_background_service: ^5.1.0
flutter_chat_core: flutter_chat_core:
path: ../../flutter_chat_ui/packages/flutter_chat_core path: ../../flutter_chat_ui/packages/flutter_chat_core
@@ -9,6 +9,7 @@ import eu.lensai.flutter_mozilla_components.api.GeckoBrowserApiImpl
import eu.lensai.flutter_mozilla_components.api.GeckoContainerProxyApiImpl import eu.lensai.flutter_mozilla_components.api.GeckoContainerProxyApiImpl
import eu.lensai.flutter_mozilla_components.api.GeckoCookieApiImpl import eu.lensai.flutter_mozilla_components.api.GeckoCookieApiImpl
import eu.lensai.flutter_mozilla_components.api.GeckoDeleteBrowsingDataControllerImpl import eu.lensai.flutter_mozilla_components.api.GeckoDeleteBrowsingDataControllerImpl
import eu.lensai.flutter_mozilla_components.api.GeckoDownloadsApiImpl
import eu.lensai.flutter_mozilla_components.api.GeckoEngineSettingsApiImpl import eu.lensai.flutter_mozilla_components.api.GeckoEngineSettingsApiImpl
import eu.lensai.flutter_mozilla_components.api.GeckoFindApiImpl import eu.lensai.flutter_mozilla_components.api.GeckoFindApiImpl
import eu.lensai.flutter_mozilla_components.api.GeckoIconsApiImpl import eu.lensai.flutter_mozilla_components.api.GeckoIconsApiImpl
@@ -24,6 +25,7 @@ import eu.lensai.flutter_mozilla_components.pigeons.GeckoBrowserApi
import eu.lensai.flutter_mozilla_components.pigeons.GeckoContainerProxyApi import eu.lensai.flutter_mozilla_components.pigeons.GeckoContainerProxyApi
import eu.lensai.flutter_mozilla_components.pigeons.GeckoCookieApi import eu.lensai.flutter_mozilla_components.pigeons.GeckoCookieApi
import eu.lensai.flutter_mozilla_components.pigeons.GeckoDeleteBrowsingDataController import eu.lensai.flutter_mozilla_components.pigeons.GeckoDeleteBrowsingDataController
import eu.lensai.flutter_mozilla_components.pigeons.GeckoDownloadsApi
import eu.lensai.flutter_mozilla_components.pigeons.GeckoEngineSettingsApi import eu.lensai.flutter_mozilla_components.pigeons.GeckoEngineSettingsApi
import eu.lensai.flutter_mozilla_components.pigeons.GeckoFindApi import eu.lensai.flutter_mozilla_components.pigeons.GeckoFindApi
import eu.lensai.flutter_mozilla_components.pigeons.GeckoIconsApi import eu.lensai.flutter_mozilla_components.pigeons.GeckoIconsApi
@@ -120,6 +122,7 @@ class FlutterMozillaComponentsPlugin: FlutterPlugin, ActivityAware {
selectionActionDelegate selectionActionDelegate
)) ))
GeckoDeleteBrowsingDataController.setUp(_flutterPluginBinding.binaryMessenger, GeckoDeleteBrowsingDataControllerImpl()) GeckoDeleteBrowsingDataController.setUp(_flutterPluginBinding.binaryMessenger, GeckoDeleteBrowsingDataControllerImpl())
GeckoDownloadsApi.setUp(_flutterPluginBinding.binaryMessenger, GeckoDownloadsApiImpl())
ReaderViewEvents.setUp( ReaderViewEvents.setUp(
_flutterPluginBinding.binaryMessenger, _flutterPluginBinding.binaryMessenger,
@@ -0,0 +1,91 @@
package eu.lensai.flutter_mozilla_components.api
import android.os.Environment
import eu.lensai.flutter_mozilla_components.GlobalComponents
import eu.lensai.flutter_mozilla_components.pigeons.DownloadState
import eu.lensai.flutter_mozilla_components.pigeons.GeckoDownloadsApi
import eu.lensai.flutter_mozilla_components.pigeons.ShareInternetResourceState
import mozilla.components.browser.state.action.ContentAction
import mozilla.components.browser.state.action.CopyInternetResourceAction
import mozilla.components.browser.state.action.ShareInternetResourceAction
import java.util.UUID
class GeckoDownloadsApiImpl : GeckoDownloadsApi {
private val components by lazy {
requireNotNull(GlobalComponents.components) { "Components not initialized" }
}
override fun requestDownload(tabId: String, state: DownloadState) {
components.core.store.dispatch(
ContentAction.UpdateDownloadAction(
tabId,
state.toMozillaDownloadState(),
),
)
}
override fun copyInternetResource(tabId: String, state: ShareInternetResourceState) {
components.core.store.dispatch(CopyInternetResourceAction.AddCopyAction(tabId, state.toMozillaShareInternetResourceState()))
}
override fun shareInternetResource(tabId: String, state: ShareInternetResourceState) {
components.core.store.dispatch(ShareInternetResourceAction.AddShareAction(tabId, state.toMozillaShareInternetResourceState()))
}
private fun ShareInternetResourceState.toMozillaShareInternetResourceState(): mozilla.components.browser.state.state.content.ShareInternetResourceState {
return mozilla.components.browser.state.state.content.ShareInternetResourceState(
url = url,
contentType = contentType,
private = private,
response = null, // Since Pigeon class doesn't have this field
referrerUrl = referrerUrl
)
}
fun mozilla.components.browser.state.state.content.ShareInternetResourceState.toPigeonShareInternetResourceState(): eu.lensai.flutter_mozilla_components.pigeons.ShareInternetResourceState {
return eu.lensai.flutter_mozilla_components.pigeons.ShareInternetResourceState(
url = url,
contentType = contentType,
private = private,
referrerUrl = referrerUrl
)
}
private fun DownloadState.toMozillaDownloadState(): mozilla.components.browser.state.state.content.DownloadState {
return mozilla.components.browser.state.state.content.DownloadState(
url = url,
fileName = fileName,
contentType = contentType,
contentLength = contentLength,
currentBytesCopied = currentBytesCopied ?: 0L,
status = status?.toMozillaStatus() ?: mozilla.components.browser.state.state.content.DownloadState.Status.INITIATED,
userAgent = userAgent,
destinationDirectory = destinationDirectory ?: Environment.DIRECTORY_DOWNLOADS,
directoryPath = directoryPath ?: Environment.getExternalStoragePublicDirectory(
destinationDirectory ?: Environment.DIRECTORY_DOWNLOADS
).path,
referrerUrl = referrerUrl,
skipConfirmation = skipConfirmation ?: false,
openInApp = openInApp ?: false,
id = id ?: UUID.randomUUID().toString(),
sessionId = sessionId,
private = private ?: false,
createdTime = createdTime ?: System.currentTimeMillis(),
response = null, // Cannot be mapped from Pigeon model
notificationId = notificationId?.toInt()
)
}
private fun eu.lensai.flutter_mozilla_components.pigeons.DownloadStatus.toMozillaStatus(): mozilla.components.browser.state.state.content.DownloadState.Status {
return when (this) {
eu.lensai.flutter_mozilla_components.pigeons.DownloadStatus.INITIATED -> mozilla.components.browser.state.state.content.DownloadState.Status.INITIATED
eu.lensai.flutter_mozilla_components.pigeons.DownloadStatus.DOWNLOADING -> mozilla.components.browser.state.state.content.DownloadState.Status.DOWNLOADING
eu.lensai.flutter_mozilla_components.pigeons.DownloadStatus.PAUSED -> mozilla.components.browser.state.state.content.DownloadState.Status.PAUSED
eu.lensai.flutter_mozilla_components.pigeons.DownloadStatus.CANCELLED -> mozilla.components.browser.state.state.content.DownloadState.Status.CANCELLED
eu.lensai.flutter_mozilla_components.pigeons.DownloadStatus.FAILED -> mozilla.components.browser.state.state.content.DownloadState.Status.FAILED
eu.lensai.flutter_mozilla_components.pigeons.DownloadStatus.COMPLETED -> mozilla.components.browser.state.state.content.DownloadState.Status.COMPLETED
}
}
}
@@ -8,7 +8,15 @@ import android.util.Log
import eu.lensai.flutter_mozilla_components.GlobalComponents import eu.lensai.flutter_mozilla_components.GlobalComponents
import eu.lensai.flutter_mozilla_components.ext.resize import eu.lensai.flutter_mozilla_components.ext.resize
import eu.lensai.flutter_mozilla_components.ext.toWebPBytes import eu.lensai.flutter_mozilla_components.ext.toWebPBytes
import eu.lensai.flutter_mozilla_components.pigeons.AudioHitResult
import eu.lensai.flutter_mozilla_components.pigeons.EmailHitResult
import eu.lensai.flutter_mozilla_components.pigeons.GeckoStateEvents import eu.lensai.flutter_mozilla_components.pigeons.GeckoStateEvents
import eu.lensai.flutter_mozilla_components.pigeons.GeoHitResult
import eu.lensai.flutter_mozilla_components.pigeons.ImageHitResult
import eu.lensai.flutter_mozilla_components.pigeons.ImageSrcHitResult
import eu.lensai.flutter_mozilla_components.pigeons.PhoneHitResult
import eu.lensai.flutter_mozilla_components.pigeons.UnknownHitResult
import eu.lensai.flutter_mozilla_components.pigeons.VideoHitResult
import mozilla.components.browser.state.action.BrowserAction import mozilla.components.browser.state.action.BrowserAction
import mozilla.components.browser.state.action.ContentAction import mozilla.components.browser.state.action.ContentAction
import mozilla.components.browser.state.action.LastAccessAction import mozilla.components.browser.state.action.LastAccessAction
@@ -16,6 +24,7 @@ import mozilla.components.browser.state.action.ReaderAction
import mozilla.components.browser.state.action.TabListAction import mozilla.components.browser.state.action.TabListAction
import mozilla.components.browser.state.action.WebExtensionAction import mozilla.components.browser.state.action.WebExtensionAction
import mozilla.components.browser.state.state.BrowserState import mozilla.components.browser.state.state.BrowserState
import mozilla.components.concept.engine.HitResult
import mozilla.components.feature.addons.logger import mozilla.components.feature.addons.logger
import mozilla.components.lib.state.Middleware import mozilla.components.lib.state.Middleware
import mozilla.components.lib.state.MiddlewareContext import mozilla.components.lib.state.MiddlewareContext
@@ -81,6 +90,24 @@ class FlutterEventMiddleware(private val flutterEvents: GeckoStateEvents) : Midd
) { _ -> } ) { _ -> }
} }
} }
is ContentAction.UpdateHitResultAction -> {
runOnUiThread {
flutterEvents.onLongPress(
System.currentTimeMillis(),
action.sessionId,
when(val result = action.hitResult) {
is HitResult.AUDIO -> AudioHitResult(result.src, result.title)
is HitResult.EMAIL -> EmailHitResult(result.src)
is HitResult.GEO -> GeoHitResult(result.src)
is HitResult.IMAGE -> ImageHitResult(result.src, result.title)
is HitResult.IMAGE_SRC -> ImageSrcHitResult(result.src, result.uri)
is HitResult.PHONE -> PhoneHitResult(result.src)
is HitResult.UNKNOWN -> UnknownHitResult(result.src)
is HitResult.VIDEO -> VideoHitResult(result.src, result.title)
}
) { _ -> }
}
}
else -> { else -> {
//logger.debug("Event fired: " + action.javaClass.name) //logger.debug("Event fired: " + action.javaClass.name)
} }
@@ -229,6 +229,31 @@ enum class WebContentIsolationStrategy(val raw: Int) {
} }
} }
/** Status that represents every state that a download can be in. */
enum class DownloadStatus(val raw: Int) {
/** Indicates that the download is in the first state after creation but not yet [DOWNLOADING]. */
INITIATED(0),
/** Indicates that an [INITIATED] download is now actively being downloaded. */
DOWNLOADING(1),
/** Indicates that the download that has been [DOWNLOADING] has been paused. */
PAUSED(2),
/** Indicates that the download that has been [DOWNLOADING] has been cancelled. */
CANCELLED(3),
/**
* Indicates that the download that has been [DOWNLOADING] has moved to failed because
* something unexpected has happened.
*/
FAILED(4),
/** Indicates that the [DOWNLOADING] download has been completed. */
COMPLETED(5);
companion object {
fun ofRaw(raw: Int): DownloadStatus? {
return values().firstOrNull { it.raw == raw }
}
}
}
/** /**
* Translation options that map to the Gecko Translations Options. * Translation options that map to the Gecko Translations Options.
* *
@@ -1187,6 +1212,295 @@ data class AutocompleteResult (
) )
} }
} }
/**
* Represents all the different supported types of data that can be found from long clicking
* an element.
*
* Generated class from Pigeon that represents data sent in messages.
* This class should not be extended by any user class outside of the generated file.
*/
sealed class HitResult
/**
* Default type if we're unable to match the type to anything. It may or may not have a src.
*
* Generated class from Pigeon that represents data sent in messages.
*/
data class UnknownHitResult (
val src: String
) : HitResult()
{
companion object {
fun fromList(pigeonVar_list: List<Any?>): UnknownHitResult {
val src = pigeonVar_list[0] as String
return UnknownHitResult(src)
}
}
fun toList(): List<Any?> {
return listOf(
src,
)
}
}
/**
* If the HTML element was of type 'HTMLImageElement'.
*
* Generated class from Pigeon that represents data sent in messages.
*/
data class ImageHitResult (
val src: String,
val title: String? = null
) : HitResult()
{
companion object {
fun fromList(pigeonVar_list: List<Any?>): ImageHitResult {
val src = pigeonVar_list[0] as String
val title = pigeonVar_list[1] as String?
return ImageHitResult(src, title)
}
}
fun toList(): List<Any?> {
return listOf(
src,
title,
)
}
}
/**
* If the HTML element was of type 'HTMLVideoElement'.
*
* Generated class from Pigeon that represents data sent in messages.
*/
data class VideoHitResult (
val src: String,
val title: String? = null
) : HitResult()
{
companion object {
fun fromList(pigeonVar_list: List<Any?>): VideoHitResult {
val src = pigeonVar_list[0] as String
val title = pigeonVar_list[1] as String?
return VideoHitResult(src, title)
}
}
fun toList(): List<Any?> {
return listOf(
src,
title,
)
}
}
/**
* If the HTML element was of type 'HTMLAudioElement'.
*
* Generated class from Pigeon that represents data sent in messages.
*/
data class AudioHitResult (
val src: String,
val title: String? = null
) : HitResult()
{
companion object {
fun fromList(pigeonVar_list: List<Any?>): AudioHitResult {
val src = pigeonVar_list[0] as String
val title = pigeonVar_list[1] as String?
return AudioHitResult(src, title)
}
}
fun toList(): List<Any?> {
return listOf(
src,
title,
)
}
}
/**
* If the HTML element was of type 'HTMLImageElement' and contained a URI.
*
* Generated class from Pigeon that represents data sent in messages.
*/
data class ImageSrcHitResult (
val src: String,
val uri: String
) : HitResult()
{
companion object {
fun fromList(pigeonVar_list: List<Any?>): ImageSrcHitResult {
val src = pigeonVar_list[0] as String
val uri = pigeonVar_list[1] as String
return ImageSrcHitResult(src, uri)
}
}
fun toList(): List<Any?> {
return listOf(
src,
uri,
)
}
}
/**
* The type used if the URI is prepended with 'tel:'.
*
* Generated class from Pigeon that represents data sent in messages.
*/
data class PhoneHitResult (
val src: String
) : HitResult()
{
companion object {
fun fromList(pigeonVar_list: List<Any?>): PhoneHitResult {
val src = pigeonVar_list[0] as String
return PhoneHitResult(src)
}
}
fun toList(): List<Any?> {
return listOf(
src,
)
}
}
/**
* The type used if the URI is prepended with 'mailto:'.
*
* Generated class from Pigeon that represents data sent in messages.
*/
data class EmailHitResult (
val src: String
) : HitResult()
{
companion object {
fun fromList(pigeonVar_list: List<Any?>): EmailHitResult {
val src = pigeonVar_list[0] as String
return EmailHitResult(src)
}
}
fun toList(): List<Any?> {
return listOf(
src,
)
}
}
/**
* The type used if the URI is prepended with 'geo:'.
*
* Generated class from Pigeon that represents data sent in messages.
*/
data class GeoHitResult (
val src: String
) : HitResult()
{
companion object {
fun fromList(pigeonVar_list: List<Any?>): GeoHitResult {
val src = pigeonVar_list[0] as String
return GeoHitResult(src)
}
}
fun toList(): List<Any?> {
return listOf(
src,
)
}
}
/** Generated class from Pigeon that represents data sent in messages. */
data class DownloadState (
val url: String,
val fileName: String? = null,
val contentType: String? = null,
val contentLength: Long? = null,
val currentBytesCopied: Long? = null,
val status: DownloadStatus? = null,
val userAgent: String? = null,
val destinationDirectory: String? = null,
val directoryPath: String? = null,
val referrerUrl: String? = null,
val skipConfirmation: Boolean? = null,
val openInApp: Boolean? = null,
val id: String? = null,
val sessionId: String? = null,
val private: Boolean? = null,
val createdTime: Long? = null,
val notificationId: Long? = null
)
{
companion object {
fun fromList(pigeonVar_list: List<Any?>): DownloadState {
val url = pigeonVar_list[0] as String
val fileName = pigeonVar_list[1] as String?
val contentType = pigeonVar_list[2] as String?
val contentLength = pigeonVar_list[3] as Long?
val currentBytesCopied = pigeonVar_list[4] as Long?
val status = pigeonVar_list[5] as DownloadStatus?
val userAgent = pigeonVar_list[6] as String?
val destinationDirectory = pigeonVar_list[7] as String?
val directoryPath = pigeonVar_list[8] as String?
val referrerUrl = pigeonVar_list[9] as String?
val skipConfirmation = pigeonVar_list[10] as Boolean?
val openInApp = pigeonVar_list[11] as Boolean?
val id = pigeonVar_list[12] as String?
val sessionId = pigeonVar_list[13] as String?
val private = pigeonVar_list[14] as Boolean?
val createdTime = pigeonVar_list[15] as Long?
val notificationId = pigeonVar_list[16] as Long?
return DownloadState(url, fileName, contentType, contentLength, currentBytesCopied, status, userAgent, destinationDirectory, directoryPath, referrerUrl, skipConfirmation, openInApp, id, sessionId, private, createdTime, notificationId)
}
}
fun toList(): List<Any?> {
return listOf(
url,
fileName,
contentType,
contentLength,
currentBytesCopied,
status,
userAgent,
destinationDirectory,
directoryPath,
referrerUrl,
skipConfirmation,
openInApp,
id,
sessionId,
private,
createdTime,
notificationId,
)
}
}
/** Generated class from Pigeon that represents data sent in messages. */
data class ShareInternetResourceState (
val url: String,
val contentType: String? = null,
val private: Boolean,
val referrerUrl: String? = null
)
{
companion object {
fun fromList(pigeonVar_list: List<Any?>): ShareInternetResourceState {
val url = pigeonVar_list[0] as String
val contentType = pigeonVar_list[1] as String?
val private = pigeonVar_list[2] as Boolean
val referrerUrl = pigeonVar_list[3] as String?
return ShareInternetResourceState(url, contentType, private, referrerUrl)
}
}
fun toList(): List<Any?> {
return listOf(
url,
contentType,
private,
referrerUrl,
)
}
}
private open class GeckoPigeonCodec : StandardMessageCodec() { private open class GeckoPigeonCodec : StandardMessageCodec() {
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
return when (type) { return when (type) {
@@ -1256,150 +1570,205 @@ private open class GeckoPigeonCodec : StandardMessageCodec() {
} }
} }
142.toByte() -> { 142.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as Long?)?.let {
TranslationOptions.fromList(it) DownloadStatus.ofRaw(it.toInt())
} }
} }
143.toByte() -> { 143.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
ReaderState.fromList(it) TranslationOptions.fromList(it)
} }
} }
144.toByte() -> { 144.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
LastMediaAccessState.fromList(it) ReaderState.fromList(it)
} }
} }
145.toByte() -> { 145.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
HistoryMetadataKey.fromList(it) LastMediaAccessState.fromList(it)
} }
} }
146.toByte() -> { 146.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
PackageCategoryValue.fromList(it) HistoryMetadataKey.fromList(it)
} }
} }
147.toByte() -> { 147.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
ExternalPackage.fromList(it) PackageCategoryValue.fromList(it)
} }
} }
148.toByte() -> { 148.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
LoadUrlFlagsValue.fromList(it) ExternalPackage.fromList(it)
} }
} }
149.toByte() -> { 149.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
SourceValue.fromList(it) LoadUrlFlagsValue.fromList(it)
} }
} }
150.toByte() -> { 150.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
TabState.fromList(it) SourceValue.fromList(it)
} }
} }
151.toByte() -> { 151.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
RecoverableTab.fromList(it) TabState.fromList(it)
} }
} }
152.toByte() -> { 152.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
RecoverableBrowserState.fromList(it) RecoverableTab.fromList(it)
} }
} }
153.toByte() -> { 153.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
IconRequest.fromList(it) RecoverableBrowserState.fromList(it)
} }
} }
154.toByte() -> { 154.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
ResourceSize.fromList(it) IconRequest.fromList(it)
} }
} }
155.toByte() -> { 155.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
Resource.fromList(it) ResourceSize.fromList(it)
} }
} }
156.toByte() -> { 156.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
IconResult.fromList(it) Resource.fromList(it)
} }
} }
157.toByte() -> { 157.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
CookiePartitionKey.fromList(it) IconResult.fromList(it)
} }
} }
158.toByte() -> { 158.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
Cookie.fromList(it) CookiePartitionKey.fromList(it)
} }
} }
159.toByte() -> { 159.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
HistoryItem.fromList(it) Cookie.fromList(it)
} }
} }
160.toByte() -> { 160.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
HistoryState.fromList(it) HistoryItem.fromList(it)
} }
} }
161.toByte() -> { 161.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
ReaderableState.fromList(it) HistoryState.fromList(it)
} }
} }
162.toByte() -> { 162.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
SecurityInfoState.fromList(it) ReaderableState.fromList(it)
} }
} }
163.toByte() -> { 163.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
TabContentState.fromList(it) SecurityInfoState.fromList(it)
} }
} }
164.toByte() -> { 164.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
FindResultState.fromList(it) TabContentState.fromList(it)
} }
} }
165.toByte() -> { 165.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
CustomSelectionAction.fromList(it) FindResultState.fromList(it)
} }
} }
166.toByte() -> { 166.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
WebExtensionData.fromList(it) CustomSelectionAction.fromList(it)
} }
} }
167.toByte() -> { 167.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
GeckoSuggestion.fromList(it) WebExtensionData.fromList(it)
} }
} }
168.toByte() -> { 168.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
TabContent.fromList(it) GeckoSuggestion.fromList(it)
} }
} }
169.toByte() -> { 169.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
GeckoEngineSettings.fromList(it) TabContent.fromList(it)
} }
} }
170.toByte() -> { 170.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
GeckoEngineSettings.fromList(it)
}
}
171.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let { return (readValue(buffer) as? List<Any?>)?.let {
AutocompleteResult.fromList(it) AutocompleteResult.fromList(it)
} }
} }
172.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
UnknownHitResult.fromList(it)
}
}
173.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
ImageHitResult.fromList(it)
}
}
174.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
VideoHitResult.fromList(it)
}
}
175.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
AudioHitResult.fromList(it)
}
}
176.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
ImageSrcHitResult.fromList(it)
}
}
177.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
PhoneHitResult.fromList(it)
}
}
178.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
EmailHitResult.fromList(it)
}
}
179.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
GeoHitResult.fromList(it)
}
}
180.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
DownloadState.fromList(it)
}
}
181.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
ShareInternetResourceState.fromList(it)
}
}
else -> super.readValueOfType(type, buffer) else -> super.readValueOfType(type, buffer)
} }
} }
@@ -1457,122 +1826,166 @@ private open class GeckoPigeonCodec : StandardMessageCodec() {
stream.write(141) stream.write(141)
writeValue(stream, value.raw) writeValue(stream, value.raw)
} }
is TranslationOptions -> { is DownloadStatus -> {
stream.write(142) stream.write(142)
writeValue(stream, value.toList()) writeValue(stream, value.raw)
} }
is ReaderState -> { is TranslationOptions -> {
stream.write(143) stream.write(143)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is LastMediaAccessState -> { is ReaderState -> {
stream.write(144) stream.write(144)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is HistoryMetadataKey -> { is LastMediaAccessState -> {
stream.write(145) stream.write(145)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is PackageCategoryValue -> { is HistoryMetadataKey -> {
stream.write(146) stream.write(146)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is ExternalPackage -> { is PackageCategoryValue -> {
stream.write(147) stream.write(147)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is LoadUrlFlagsValue -> { is ExternalPackage -> {
stream.write(148) stream.write(148)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is SourceValue -> { is LoadUrlFlagsValue -> {
stream.write(149) stream.write(149)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is TabState -> { is SourceValue -> {
stream.write(150) stream.write(150)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is RecoverableTab -> { is TabState -> {
stream.write(151) stream.write(151)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is RecoverableBrowserState -> { is RecoverableTab -> {
stream.write(152) stream.write(152)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is IconRequest -> { is RecoverableBrowserState -> {
stream.write(153) stream.write(153)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is ResourceSize -> { is IconRequest -> {
stream.write(154) stream.write(154)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is Resource -> { is ResourceSize -> {
stream.write(155) stream.write(155)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is IconResult -> { is Resource -> {
stream.write(156) stream.write(156)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is CookiePartitionKey -> { is IconResult -> {
stream.write(157) stream.write(157)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is Cookie -> { is CookiePartitionKey -> {
stream.write(158) stream.write(158)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is HistoryItem -> { is Cookie -> {
stream.write(159) stream.write(159)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is HistoryState -> { is HistoryItem -> {
stream.write(160) stream.write(160)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is ReaderableState -> { is HistoryState -> {
stream.write(161) stream.write(161)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is SecurityInfoState -> { is ReaderableState -> {
stream.write(162) stream.write(162)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is TabContentState -> { is SecurityInfoState -> {
stream.write(163) stream.write(163)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is FindResultState -> { is TabContentState -> {
stream.write(164) stream.write(164)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is CustomSelectionAction -> { is FindResultState -> {
stream.write(165) stream.write(165)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is WebExtensionData -> { is CustomSelectionAction -> {
stream.write(166) stream.write(166)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is GeckoSuggestion -> { is WebExtensionData -> {
stream.write(167) stream.write(167)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is TabContent -> { is GeckoSuggestion -> {
stream.write(168) stream.write(168)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is GeckoEngineSettings -> { is TabContent -> {
stream.write(169) stream.write(169)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is AutocompleteResult -> { is GeckoEngineSettings -> {
stream.write(170) stream.write(170)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is AutocompleteResult -> {
stream.write(171)
writeValue(stream, value.toList())
}
is UnknownHitResult -> {
stream.write(172)
writeValue(stream, value.toList())
}
is ImageHitResult -> {
stream.write(173)
writeValue(stream, value.toList())
}
is VideoHitResult -> {
stream.write(174)
writeValue(stream, value.toList())
}
is AudioHitResult -> {
stream.write(175)
writeValue(stream, value.toList())
}
is ImageSrcHitResult -> {
stream.write(176)
writeValue(stream, value.toList())
}
is PhoneHitResult -> {
stream.write(177)
writeValue(stream, value.toList())
}
is EmailHitResult -> {
stream.write(178)
writeValue(stream, value.toList())
}
is GeoHitResult -> {
stream.write(179)
writeValue(stream, value.toList())
}
is DownloadState -> {
stream.write(180)
writeValue(stream, value.toList())
}
is ShareInternetResourceState -> {
stream.write(181)
writeValue(stream, value.toList())
}
else -> super.writeValue(stream, value) else -> super.writeValue(stream, value)
} }
} }
@@ -2988,6 +3401,23 @@ class GeckoStateEvents(private val binaryMessenger: BinaryMessenger, private val
} }
} }
} }
fun onLongPress(timestampArg: Long, idArg: String, hitResultArg: HitResult, callback: (Result<Unit>) -> Unit)
{
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
val channelName = "dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onLongPress$separatedMessageChannelSuffix"
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
channel.send(listOf(timestampArg, idArg, hitResultArg)) {
if (it is List<*>) {
if (it.size > 1) {
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
} else {
callback(Result.success(Unit))
}
} else {
callback(Result.failure(createConnectionError(channelName)))
}
}
}
} }
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */ /** Generated interface from Pigeon that represents a handler of messages from Flutter. */
interface ReaderViewEvents { interface ReaderViewEvents {
@@ -3469,3 +3899,78 @@ interface GeckoDeleteBrowsingDataController {
} }
} }
} }
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
interface GeckoDownloadsApi {
fun requestDownload(tabId: String, state: DownloadState)
fun copyInternetResource(tabId: String, state: ShareInternetResourceState)
fun shareInternetResource(tabId: String, state: ShareInternetResourceState)
companion object {
/** The codec used by GeckoDownloadsApi. */
val codec: MessageCodec<Any?> by lazy {
GeckoPigeonCodec()
}
/** Sets up an instance of `GeckoDownloadsApi` to handle messages through the `binaryMessenger`. */
@JvmOverloads
fun setUp(binaryMessenger: BinaryMessenger, api: GeckoDownloadsApi?, messageChannelSuffix: String = "") {
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoDownloadsApi.requestDownload$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val tabIdArg = args[0] as String
val stateArg = args[1] as DownloadState
val wrapped: List<Any?> = try {
api.requestDownload(tabIdArg, stateArg)
listOf(null)
} catch (exception: Throwable) {
wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoDownloadsApi.copyInternetResource$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val tabIdArg = args[0] as String
val stateArg = args[1] as ShareInternetResourceState
val wrapped: List<Any?> = try {
api.copyInternetResource(tabIdArg, stateArg)
listOf(null)
} catch (exception: Throwable) {
wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoDownloadsApi.shareInternetResource$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val tabIdArg = args[0] as String
val stateArg = args[1] as ShareInternetResourceState
val wrapped: List<Any?> = try {
api.shareInternetResource(tabIdArg, stateArg)
listOf(null)
} catch (exception: Throwable) {
wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
}
}
}
@@ -6,6 +6,7 @@ export 'src/domain/services/gecko_addon.dart';
export 'src/domain/services/gecko_container_proxy.dart'; export 'src/domain/services/gecko_container_proxy.dart';
export 'src/domain/services/gecko_cookie.dart'; export 'src/domain/services/gecko_cookie.dart';
export 'src/domain/services/gecko_delete_browser_data.dart'; export 'src/domain/services/gecko_delete_browser_data.dart';
export 'src/domain/services/gecko_downloads.dart';
export 'src/domain/services/gecko_engine_settings.dart'; export 'src/domain/services/gecko_engine_settings.dart';
export 'src/domain/services/gecko_event.dart'; export 'src/domain/services/gecko_event.dart';
export 'src/domain/services/gecko_find_in_page.dart'; export 'src/domain/services/gecko_find_in_page.dart';
@@ -20,22 +21,31 @@ export 'src/domain/services/gecko_tab_content.dart';
export 'src/geckoview_widget.dart'; export 'src/geckoview_widget.dart';
export 'src/pigeons/gecko.g.dart' export 'src/pigeons/gecko.g.dart'
show show
AudioHitResult,
ColorScheme, ColorScheme,
CookieBannerHandlingMode, CookieBannerHandlingMode,
CookieSameSiteStatus, CookieSameSiteStatus,
EmailHitResult,
GeckoEngineSettings, GeckoEngineSettings,
GeckoSuggestion, GeckoSuggestion,
GeckoSuggestionType, GeckoSuggestionType,
GeoHitResult,
HistoryMetadataKey, HistoryMetadataKey,
HitResult,
HttpsOnlyMode, HttpsOnlyMode,
IconSource, IconSource,
IconType, IconType,
ImageHitResult,
ImageSrcHitResult,
PhoneHitResult,
Resource, Resource,
ResourceSize, ResourceSize,
SecurityInfoState, SecurityInfoState,
TabContent, TabContent,
TabContentState, TabContentState,
TrackingProtectionPolicy, TrackingProtectionPolicy,
UnknownHitResult,
VideoHitResult,
WebContentIsolationStrategy, WebContentIsolationStrategy,
WebExtensionActionType, WebExtensionActionType,
WebExtensionData; WebExtensionData;
@@ -0,0 +1,69 @@
import 'package:flutter_mozilla_components/src/pigeons/gecko.g.dart';
final _apiInstance = GeckoDownloadsApi();
class GeckoDownloadsService {
final GeckoDownloadsApi _api;
GeckoDownloadsService({GeckoDownloadsApi? api}) : _api = api ?? _apiInstance;
Future<void> copyInternetResource(
String tabId, {
required Uri url,
Uri? referrerUrl,
String? contentType,
bool isPrivate = false,
}) {
return _api.copyInternetResource(
tabId,
ShareInternetResourceState(
url: url.toString(),
referrerUrl: referrerUrl?.toString(),
private: isPrivate,
contentType: contentType,
),
);
}
Future<void> shareInternetResource(
String tabId, {
required Uri url,
Uri? referrerUrl,
String? contentType,
bool isPrivate = false,
}) {
return _api.shareInternetResource(
tabId,
ShareInternetResourceState(
url: url.toString(),
referrerUrl: referrerUrl?.toString(),
private: isPrivate,
contentType: contentType,
),
);
}
Future<void> requestDownload(
String tabId, {
required Uri url,
String? fileName,
bool? openInApp,
bool? skipConfirmation,
Uri? referrerUrl,
bool isPrivate = false,
}) {
return _api.requestDownload(
tabId,
DownloadState(
url: url.toString(),
fileName: fileName,
openInApp: openInApp,
skipConfirmation: skipConfirmation,
referrerUrl: referrerUrl?.toString(),
private: isPrivate,
),
);
}
}
@@ -12,6 +12,7 @@ typedef IconChangeEvent = ({String tabId, Uint8List? bytes});
typedef IconUpdateEvent = ({String url, Uint8List bytes}); typedef IconUpdateEvent = ({String url, Uint8List bytes});
typedef ThumbnailEvent = ({String tabId, Uint8List? bytes}); typedef ThumbnailEvent = ({String tabId, Uint8List? bytes});
typedef FindResultsEvent = ({String tabId, List<FindResultState> results}); typedef FindResultsEvent = ({String tabId, List<FindResultState> results});
typedef LongPressEvent = ({String tabId, HitResult hitResult});
class GeckoEventService extends GeckoStateEvents { class GeckoEventService extends GeckoStateEvents {
// Stream controllers // Stream controllers
@@ -29,6 +30,7 @@ class GeckoEventService extends GeckoStateEvents {
final _iconUpdateSubject = PublishSubject<IconUpdateEvent>(); final _iconUpdateSubject = PublishSubject<IconUpdateEvent>();
final _thumbnailSubject = PublishSubject<ThumbnailEvent>(); final _thumbnailSubject = PublishSubject<ThumbnailEvent>();
final _findResultsSubject = PublishSubject<FindResultsEvent>(); final _findResultsSubject = PublishSubject<FindResultsEvent>();
final _longPressSubject = PublishSubject<LongPressEvent>();
final _tabAddedSubject = PublishSubject<String>(); final _tabAddedSubject = PublishSubject<String>();
@@ -47,6 +49,7 @@ class GeckoEventService extends GeckoStateEvents {
Stream<IconUpdateEvent> get iconUpdateEvents => _iconUpdateSubject.stream; Stream<IconUpdateEvent> get iconUpdateEvents => _iconUpdateSubject.stream;
Stream<ThumbnailEvent> get thumbnailEvents => _thumbnailSubject.stream; Stream<ThumbnailEvent> get thumbnailEvents => _thumbnailSubject.stream;
Stream<FindResultsEvent> get findResultsEvent => _findResultsSubject.stream; Stream<FindResultsEvent> get findResultsEvent => _findResultsSubject.stream;
Stream<LongPressEvent> get longPressEvent => _longPressSubject.stream;
Stream<String> get tabAddedStream => _tabAddedSubject.stream; Stream<String> get tabAddedStream => _tabAddedSubject.stream;
@@ -144,6 +147,14 @@ class GeckoEventService extends GeckoStateEvents {
)); ));
} }
@override
void onLongPress(int timestamp, String id, HitResult hitResult) {
_longPressSubject.addWhenMoreRecent(timestamp, id, (
tabId: id,
hitResult: hitResult,
));
}
@override @override
void onTabAdded(int timestamp, String tabId) { void onTabAdded(int timestamp, String tabId) {
_tabAddedSubject.addWhenMoreRecent(timestamp, null, tabId); _tabAddedSubject.addWhenMoreRecent(timestamp, null, tabId);
@@ -170,6 +181,7 @@ class GeckoEventService extends GeckoStateEvents {
unawaited(_iconChangeSubject.close()); unawaited(_iconChangeSubject.close());
unawaited(_thumbnailSubject.close()); unawaited(_thumbnailSubject.close());
unawaited(_findResultsSubject.close()); unawaited(_findResultsSubject.close());
unawaited(_longPressSubject.close());
unawaited(_tabAddedSubject.close()); unawaited(_tabAddedSubject.close());
} }
} }
@@ -125,6 +125,23 @@ enum WebContentIsolationStrategy {
isolateHighValue, isolateHighValue,
} }
/// Status that represents every state that a download can be in.
enum DownloadStatus {
/// Indicates that the download is in the first state after creation but not yet [DOWNLOADING].
initiated,
/// Indicates that an [INITIATED] download is now actively being downloaded.
downloading,
/// Indicates that the download that has been [DOWNLOADING] has been paused.
paused,
/// Indicates that the download that has been [DOWNLOADING] has been cancelled.
cancelled,
/// Indicates that the download that has been [DOWNLOADING] has moved to failed because
/// something unexpected has happened.
failed,
/// Indicates that the [DOWNLOADING] download has been completed.
completed,
}
/// Translation options that map to the Gecko Translations Options. /// Translation options that map to the Gecko Translations Options.
/// ///
/// @property downloadModel If the necessary models should be downloaded on request. If false, then /// @property downloadModel If the necessary models should be downloaded on request. If false, then
@@ -1314,6 +1331,344 @@ class AutocompleteResult {
} }
} }
/// Represents all the different supported types of data that can be found from long clicking
/// an element.
sealed class HitResult {
}
/// Default type if we're unable to match the type to anything. It may or may not have a src.
class UnknownHitResult extends HitResult {
UnknownHitResult({
required this.src,
});
String src;
Object encode() {
return <Object?>[
src,
];
}
static UnknownHitResult decode(Object result) {
result as List<Object?>;
return UnknownHitResult(
src: result[0]! as String,
);
}
}
/// If the HTML element was of type 'HTMLImageElement'.
class ImageHitResult extends HitResult {
ImageHitResult({
required this.src,
this.title,
});
String src;
String? title;
Object encode() {
return <Object?>[
src,
title,
];
}
static ImageHitResult decode(Object result) {
result as List<Object?>;
return ImageHitResult(
src: result[0]! as String,
title: result[1] as String?,
);
}
}
/// If the HTML element was of type 'HTMLVideoElement'.
class VideoHitResult extends HitResult {
VideoHitResult({
required this.src,
this.title,
});
String src;
String? title;
Object encode() {
return <Object?>[
src,
title,
];
}
static VideoHitResult decode(Object result) {
result as List<Object?>;
return VideoHitResult(
src: result[0]! as String,
title: result[1] as String?,
);
}
}
/// If the HTML element was of type 'HTMLAudioElement'.
class AudioHitResult extends HitResult {
AudioHitResult({
required this.src,
this.title,
});
String src;
String? title;
Object encode() {
return <Object?>[
src,
title,
];
}
static AudioHitResult decode(Object result) {
result as List<Object?>;
return AudioHitResult(
src: result[0]! as String,
title: result[1] as String?,
);
}
}
/// If the HTML element was of type 'HTMLImageElement' and contained a URI.
class ImageSrcHitResult extends HitResult {
ImageSrcHitResult({
required this.src,
required this.uri,
});
String src;
String uri;
Object encode() {
return <Object?>[
src,
uri,
];
}
static ImageSrcHitResult decode(Object result) {
result as List<Object?>;
return ImageSrcHitResult(
src: result[0]! as String,
uri: result[1]! as String,
);
}
}
/// The type used if the URI is prepended with 'tel:'.
class PhoneHitResult extends HitResult {
PhoneHitResult({
required this.src,
});
String src;
Object encode() {
return <Object?>[
src,
];
}
static PhoneHitResult decode(Object result) {
result as List<Object?>;
return PhoneHitResult(
src: result[0]! as String,
);
}
}
/// The type used if the URI is prepended with 'mailto:'.
class EmailHitResult extends HitResult {
EmailHitResult({
required this.src,
});
String src;
Object encode() {
return <Object?>[
src,
];
}
static EmailHitResult decode(Object result) {
result as List<Object?>;
return EmailHitResult(
src: result[0]! as String,
);
}
}
/// The type used if the URI is prepended with 'geo:'.
class GeoHitResult extends HitResult {
GeoHitResult({
required this.src,
});
String src;
Object encode() {
return <Object?>[
src,
];
}
static GeoHitResult decode(Object result) {
result as List<Object?>;
return GeoHitResult(
src: result[0]! as String,
);
}
}
class DownloadState {
DownloadState({
required this.url,
this.fileName,
this.contentType,
this.contentLength,
this.currentBytesCopied,
this.status,
this.userAgent,
this.destinationDirectory,
this.directoryPath,
this.referrerUrl,
this.skipConfirmation,
this.openInApp,
this.id,
this.sessionId,
this.private,
this.createdTime,
this.notificationId,
});
String url;
String? fileName;
String? contentType;
int? contentLength;
int? currentBytesCopied;
DownloadStatus? status;
String? userAgent;
String? destinationDirectory;
String? directoryPath;
String? referrerUrl;
bool? skipConfirmation;
bool? openInApp;
String? id;
String? sessionId;
bool? private;
int? createdTime;
int? notificationId;
Object encode() {
return <Object?>[
url,
fileName,
contentType,
contentLength,
currentBytesCopied,
status,
userAgent,
destinationDirectory,
directoryPath,
referrerUrl,
skipConfirmation,
openInApp,
id,
sessionId,
private,
createdTime,
notificationId,
];
}
static DownloadState decode(Object result) {
result as List<Object?>;
return DownloadState(
url: result[0]! as String,
fileName: result[1] as String?,
contentType: result[2] as String?,
contentLength: result[3] as int?,
currentBytesCopied: result[4] as int?,
status: result[5] as DownloadStatus?,
userAgent: result[6] as String?,
destinationDirectory: result[7] as String?,
directoryPath: result[8] as String?,
referrerUrl: result[9] as String?,
skipConfirmation: result[10] as bool?,
openInApp: result[11] as bool?,
id: result[12] as String?,
sessionId: result[13] as String?,
private: result[14] as bool?,
createdTime: result[15] as int?,
notificationId: result[16] as int?,
);
}
}
class ShareInternetResourceState {
ShareInternetResourceState({
required this.url,
this.contentType,
required this.private,
this.referrerUrl,
});
String url;
String? contentType;
bool private;
String? referrerUrl;
Object encode() {
return <Object?>[
url,
contentType,
private,
referrerUrl,
];
}
static ShareInternetResourceState decode(Object result) {
result as List<Object?>;
return ShareInternetResourceState(
url: result[0]! as String,
contentType: result[1] as String?,
private: result[2]! as bool,
referrerUrl: result[3] as String?,
);
}
}
class _PigeonCodec extends StandardMessageCodec { class _PigeonCodec extends StandardMessageCodec {
const _PigeonCodec(); const _PigeonCodec();
@@ -1361,93 +1716,126 @@ class _PigeonCodec extends StandardMessageCodec {
} else if (value is WebContentIsolationStrategy) { } else if (value is WebContentIsolationStrategy) {
buffer.putUint8(141); buffer.putUint8(141);
writeValue(buffer, value.index); writeValue(buffer, value.index);
} else if (value is TranslationOptions) { } else if (value is DownloadStatus) {
buffer.putUint8(142); buffer.putUint8(142);
writeValue(buffer, value.encode()); writeValue(buffer, value.index);
} else if (value is ReaderState) { } else if (value is TranslationOptions) {
buffer.putUint8(143); buffer.putUint8(143);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is LastMediaAccessState) { } else if (value is ReaderState) {
buffer.putUint8(144); buffer.putUint8(144);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is HistoryMetadataKey) { } else if (value is LastMediaAccessState) {
buffer.putUint8(145); buffer.putUint8(145);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is PackageCategoryValue) { } else if (value is HistoryMetadataKey) {
buffer.putUint8(146); buffer.putUint8(146);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is ExternalPackage) { } else if (value is PackageCategoryValue) {
buffer.putUint8(147); buffer.putUint8(147);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is LoadUrlFlagsValue) { } else if (value is ExternalPackage) {
buffer.putUint8(148); buffer.putUint8(148);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is SourceValue) { } else if (value is LoadUrlFlagsValue) {
buffer.putUint8(149); buffer.putUint8(149);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is TabState) { } else if (value is SourceValue) {
buffer.putUint8(150); buffer.putUint8(150);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is RecoverableTab) { } else if (value is TabState) {
buffer.putUint8(151); buffer.putUint8(151);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is RecoverableBrowserState) { } else if (value is RecoverableTab) {
buffer.putUint8(152); buffer.putUint8(152);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is IconRequest) { } else if (value is RecoverableBrowserState) {
buffer.putUint8(153); buffer.putUint8(153);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is ResourceSize) { } else if (value is IconRequest) {
buffer.putUint8(154); buffer.putUint8(154);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is Resource) { } else if (value is ResourceSize) {
buffer.putUint8(155); buffer.putUint8(155);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is IconResult) { } else if (value is Resource) {
buffer.putUint8(156); buffer.putUint8(156);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is CookiePartitionKey) { } else if (value is IconResult) {
buffer.putUint8(157); buffer.putUint8(157);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is Cookie) { } else if (value is CookiePartitionKey) {
buffer.putUint8(158); buffer.putUint8(158);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is HistoryItem) { } else if (value is Cookie) {
buffer.putUint8(159); buffer.putUint8(159);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is HistoryState) { } else if (value is HistoryItem) {
buffer.putUint8(160); buffer.putUint8(160);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is ReaderableState) { } else if (value is HistoryState) {
buffer.putUint8(161); buffer.putUint8(161);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is SecurityInfoState) { } else if (value is ReaderableState) {
buffer.putUint8(162); buffer.putUint8(162);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is TabContentState) { } else if (value is SecurityInfoState) {
buffer.putUint8(163); buffer.putUint8(163);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is FindResultState) { } else if (value is TabContentState) {
buffer.putUint8(164); buffer.putUint8(164);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is CustomSelectionAction) { } else if (value is FindResultState) {
buffer.putUint8(165); buffer.putUint8(165);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is WebExtensionData) { } else if (value is CustomSelectionAction) {
buffer.putUint8(166); buffer.putUint8(166);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is GeckoSuggestion) { } else if (value is WebExtensionData) {
buffer.putUint8(167); buffer.putUint8(167);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is TabContent) { } else if (value is GeckoSuggestion) {
buffer.putUint8(168); buffer.putUint8(168);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is GeckoEngineSettings) { } else if (value is TabContent) {
buffer.putUint8(169); buffer.putUint8(169);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is AutocompleteResult) { } else if (value is GeckoEngineSettings) {
buffer.putUint8(170); buffer.putUint8(170);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is AutocompleteResult) {
buffer.putUint8(171);
writeValue(buffer, value.encode());
} else if (value is UnknownHitResult) {
buffer.putUint8(172);
writeValue(buffer, value.encode());
} else if (value is ImageHitResult) {
buffer.putUint8(173);
writeValue(buffer, value.encode());
} else if (value is VideoHitResult) {
buffer.putUint8(174);
writeValue(buffer, value.encode());
} else if (value is AudioHitResult) {
buffer.putUint8(175);
writeValue(buffer, value.encode());
} else if (value is ImageSrcHitResult) {
buffer.putUint8(176);
writeValue(buffer, value.encode());
} else if (value is PhoneHitResult) {
buffer.putUint8(177);
writeValue(buffer, value.encode());
} else if (value is EmailHitResult) {
buffer.putUint8(178);
writeValue(buffer, value.encode());
} else if (value is GeoHitResult) {
buffer.putUint8(179);
writeValue(buffer, value.encode());
} else if (value is DownloadState) {
buffer.putUint8(180);
writeValue(buffer, value.encode());
} else if (value is ShareInternetResourceState) {
buffer.putUint8(181);
writeValue(buffer, value.encode());
} else { } else {
super.writeValue(buffer, value); super.writeValue(buffer, value);
} }
@@ -1496,63 +1884,86 @@ class _PigeonCodec extends StandardMessageCodec {
final int? value = readValue(buffer) as int?; final int? value = readValue(buffer) as int?;
return value == null ? null : WebContentIsolationStrategy.values[value]; return value == null ? null : WebContentIsolationStrategy.values[value];
case 142: case 142:
return TranslationOptions.decode(readValue(buffer)!); final int? value = readValue(buffer) as int?;
return value == null ? null : DownloadStatus.values[value];
case 143: case 143:
return ReaderState.decode(readValue(buffer)!); return TranslationOptions.decode(readValue(buffer)!);
case 144: case 144:
return LastMediaAccessState.decode(readValue(buffer)!); return ReaderState.decode(readValue(buffer)!);
case 145: case 145:
return HistoryMetadataKey.decode(readValue(buffer)!); return LastMediaAccessState.decode(readValue(buffer)!);
case 146: case 146:
return PackageCategoryValue.decode(readValue(buffer)!); return HistoryMetadataKey.decode(readValue(buffer)!);
case 147: case 147:
return ExternalPackage.decode(readValue(buffer)!); return PackageCategoryValue.decode(readValue(buffer)!);
case 148: case 148:
return LoadUrlFlagsValue.decode(readValue(buffer)!); return ExternalPackage.decode(readValue(buffer)!);
case 149: case 149:
return SourceValue.decode(readValue(buffer)!); return LoadUrlFlagsValue.decode(readValue(buffer)!);
case 150: case 150:
return TabState.decode(readValue(buffer)!); return SourceValue.decode(readValue(buffer)!);
case 151: case 151:
return RecoverableTab.decode(readValue(buffer)!); return TabState.decode(readValue(buffer)!);
case 152: case 152:
return RecoverableBrowserState.decode(readValue(buffer)!); return RecoverableTab.decode(readValue(buffer)!);
case 153: case 153:
return IconRequest.decode(readValue(buffer)!); return RecoverableBrowserState.decode(readValue(buffer)!);
case 154: case 154:
return ResourceSize.decode(readValue(buffer)!); return IconRequest.decode(readValue(buffer)!);
case 155: case 155:
return Resource.decode(readValue(buffer)!); return ResourceSize.decode(readValue(buffer)!);
case 156: case 156:
return IconResult.decode(readValue(buffer)!); return Resource.decode(readValue(buffer)!);
case 157: case 157:
return CookiePartitionKey.decode(readValue(buffer)!); return IconResult.decode(readValue(buffer)!);
case 158: case 158:
return Cookie.decode(readValue(buffer)!); return CookiePartitionKey.decode(readValue(buffer)!);
case 159: case 159:
return HistoryItem.decode(readValue(buffer)!); return Cookie.decode(readValue(buffer)!);
case 160: case 160:
return HistoryState.decode(readValue(buffer)!); return HistoryItem.decode(readValue(buffer)!);
case 161: case 161:
return ReaderableState.decode(readValue(buffer)!); return HistoryState.decode(readValue(buffer)!);
case 162: case 162:
return SecurityInfoState.decode(readValue(buffer)!); return ReaderableState.decode(readValue(buffer)!);
case 163: case 163:
return TabContentState.decode(readValue(buffer)!); return SecurityInfoState.decode(readValue(buffer)!);
case 164: case 164:
return FindResultState.decode(readValue(buffer)!); return TabContentState.decode(readValue(buffer)!);
case 165: case 165:
return CustomSelectionAction.decode(readValue(buffer)!); return FindResultState.decode(readValue(buffer)!);
case 166: case 166:
return WebExtensionData.decode(readValue(buffer)!); return CustomSelectionAction.decode(readValue(buffer)!);
case 167: case 167:
return GeckoSuggestion.decode(readValue(buffer)!); return WebExtensionData.decode(readValue(buffer)!);
case 168: case 168:
return TabContent.decode(readValue(buffer)!); return GeckoSuggestion.decode(readValue(buffer)!);
case 169: case 169:
return GeckoEngineSettings.decode(readValue(buffer)!); return TabContent.decode(readValue(buffer)!);
case 170: case 170:
return GeckoEngineSettings.decode(readValue(buffer)!);
case 171:
return AutocompleteResult.decode(readValue(buffer)!); return AutocompleteResult.decode(readValue(buffer)!);
case 172:
return UnknownHitResult.decode(readValue(buffer)!);
case 173:
return ImageHitResult.decode(readValue(buffer)!);
case 174:
return VideoHitResult.decode(readValue(buffer)!);
case 175:
return AudioHitResult.decode(readValue(buffer)!);
case 176:
return ImageSrcHitResult.decode(readValue(buffer)!);
case 177:
return PhoneHitResult.decode(readValue(buffer)!);
case 178:
return EmailHitResult.decode(readValue(buffer)!);
case 179:
return GeoHitResult.decode(readValue(buffer)!);
case 180:
return DownloadState.decode(readValue(buffer)!);
case 181:
return ShareInternetResourceState.decode(readValue(buffer)!);
default: default:
return super.readValueOfType(type, buffer); return super.readValueOfType(type, buffer);
} }
@@ -2940,6 +3351,8 @@ abstract class GeckoStateEvents {
void onFindResults(int timestamp, String id, List<FindResultState> results); void onFindResults(int timestamp, String id, List<FindResultState> results);
void onLongPress(int timestamp, String id, HitResult hitResult);
static void setUp(GeckoStateEvents? api, {BinaryMessenger? binaryMessenger, String messageChannelSuffix = '',}) { static void setUp(GeckoStateEvents? api, {BinaryMessenger? binaryMessenger, String messageChannelSuffix = '',}) {
messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
{ {
@@ -3321,6 +3734,37 @@ abstract class GeckoStateEvents {
}); });
} }
} }
{
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onLongPress$messageChannelSuffix', pigeonChannelCodec,
binaryMessenger: binaryMessenger);
if (api == null) {
pigeonVar_channel.setMessageHandler(null);
} else {
pigeonVar_channel.setMessageHandler((Object? message) async {
assert(message != null,
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onLongPress was null.');
final List<Object?> args = (message as List<Object?>?)!;
final int? arg_timestamp = (args[0] as int?);
assert(arg_timestamp != null,
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onLongPress was null, expected non-null int.');
final String? arg_id = (args[1] as String?);
assert(arg_id != null,
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onLongPress was null, expected non-null String.');
final HitResult? arg_hitResult = (args[2] as HitResult?);
assert(arg_hitResult != null,
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onLongPress was null, expected non-null HitResult.');
try {
api.onLongPress(arg_timestamp!, arg_id!, arg_hitResult!);
return wrapResponse(empty: true);
} on PlatformException catch (e) {
return wrapResponse(error: e);
} catch (e) {
return wrapResponse(error: PlatformException(code: 'error', message: e.toString()));
}
});
}
}
} }
} }
@@ -3960,3 +4404,86 @@ class GeckoDeleteBrowsingDataController {
} }
} }
} }
class GeckoDownloadsApi {
/// Constructor for [GeckoDownloadsApi]. The [binaryMessenger] named argument is
/// available for dependency injection. If it is left null, the default
/// BinaryMessenger will be used which routes to the host platform.
GeckoDownloadsApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
: pigeonVar_binaryMessenger = binaryMessenger,
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
final BinaryMessenger? pigeonVar_binaryMessenger;
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
final String pigeonVar_messageChannelSuffix;
Future<void> requestDownload(String tabId, DownloadState state) async {
final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoDownloadsApi.requestDownload$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[tabId, state]);
final List<Object?>? pigeonVar_replyList =
await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else {
return;
}
}
Future<void> copyInternetResource(String tabId, ShareInternetResourceState state) async {
final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoDownloadsApi.copyInternetResource$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[tabId, state]);
final List<Object?>? pigeonVar_replyList =
await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else {
return;
}
}
Future<void> shareInternetResource(String tabId, ShareInternetResourceState state) async {
final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoDownloadsApi.shareInternetResource$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[tabId, state]);
final List<Object?>? pigeonVar_replyList =
await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else {
return;
}
}
}
@@ -580,61 +580,147 @@ class AutocompleteResult {
); );
} }
// /// Represents all the different supported types of data that can be found from long clicking /// Represents all the different supported types of data that can be found from long clicking
// /// an element. /// an element.
// sealed class HitResult { sealed class HitResult {}
// final String src;
// HitResult(this.src); /// Default type if we're unable to match the type to anything. It may or may not have a src.
// } class UnknownHitResult extends HitResult {
final String src;
// /// Default type if we're unable to match the type to anything. It may or may not have a src. UnknownHitResult(this.src);
// class UnknownHitResult extends HitResult { }
// UnknownHitResult(super.src);
// }
// /// If the HTML element was of type 'HTMLImageElement'. /// If the HTML element was of type 'HTMLImageElement'.
// class ImageHitResult extends HitResult { class ImageHitResult extends HitResult {
// final String? title; final String src;
final String? title;
// ImageHitResult(super.src, {this.title}); ImageHitResult(this.src, {this.title});
// } }
// /// If the HTML element was of type 'HTMLVideoElement'. /// If the HTML element was of type 'HTMLVideoElement'.
// class VideoHitResult extends HitResult { class VideoHitResult extends HitResult {
// final String? title; final String src;
final String? title;
// VideoHitResult(super.src, {this.title}); VideoHitResult(this.src, {this.title});
// } }
// /// If the HTML element was of type 'HTMLAudioElement'. /// If the HTML element was of type 'HTMLAudioElement'.
// class AudioHitResult extends HitResult { class AudioHitResult extends HitResult {
// final String? title; final String src;
final String? title;
// AudioHitResult(super.src, {this.title}); AudioHitResult(this.src, {this.title});
// } }
// /// If the HTML element was of type 'HTMLImageElement' and contained a URI. /// If the HTML element was of type 'HTMLImageElement' and contained a URI.
// class ImageSrcHitResult extends HitResult { class ImageSrcHitResult extends HitResult {
// final String uri; final String src;
final String uri;
// ImageSrcHitResult(super.src, this.uri); ImageSrcHitResult(this.src, this.uri);
// } }
// /// The type used if the URI is prepended with 'tel:'. /// The type used if the URI is prepended with 'tel:'.
// class PhoneHitResult extends HitResult { class PhoneHitResult extends HitResult {
// PhoneHitResult(super.src); final String src;
// }
// /// The type used if the URI is prepended with 'mailto:'. PhoneHitResult(this.src);
// class EmailHitResult extends HitResult { }
// EmailHitResult(super.src);
// }
// /// The type used if the URI is prepended with 'geo:'. /// The type used if the URI is prepended with 'mailto:'.
// class GeoHitResult extends HitResult { class EmailHitResult extends HitResult {
// GeoHitResult(super.src); final String src;
// }
EmailHitResult(this.src);
}
/// The type used if the URI is prepended with 'geo:'.
class GeoHitResult extends HitResult {
final String src;
GeoHitResult(this.src);
}
/// Status that represents every state that a download can be in.
enum DownloadStatus {
/// Indicates that the download is in the first state after creation but not yet [DOWNLOADING].
initiated,
/// Indicates that an [INITIATED] download is now actively being downloaded.
downloading,
/// Indicates that the download that has been [DOWNLOADING] has been paused.
paused,
/// Indicates that the download that has been [DOWNLOADING] has been cancelled.
cancelled,
/// Indicates that the download that has been [DOWNLOADING] has moved to failed because
/// something unexpected has happened.
failed,
/// Indicates that the [DOWNLOADING] download has been completed.
completed,
}
class DownloadState {
final String url;
final String? fileName;
final String? contentType;
final int? contentLength;
final int? currentBytesCopied;
final DownloadStatus? status;
final String? userAgent;
final String? destinationDirectory;
final String? directoryPath;
final String? referrerUrl;
final bool? skipConfirmation;
final bool? openInApp;
final String? id;
final String? sessionId;
final bool? private;
final int? createdTime;
//final Response? response;
final int? notificationId;
DownloadState(
this.url,
this.fileName,
this.contentType,
this.contentLength,
this.currentBytesCopied,
this.status,
this.userAgent,
this.destinationDirectory,
this.directoryPath,
this.referrerUrl,
this.skipConfirmation,
this.openInApp,
this.id,
this.sessionId,
this.private,
this.createdTime,
this.notificationId,
);
}
class ShareInternetResourceState {
final String url;
final String? contentType;
final bool private;
// final Response? response ;
final String? referrerUrl;
ShareInternetResourceState(
this.url,
this.contentType,
this.private,
this.referrerUrl,
);
}
@ConfigurePigeon( @ConfigurePigeon(
PigeonOptions( PigeonOptions(
@@ -933,6 +1019,7 @@ abstract class GeckoStateEvents {
void onThumbnailChange(int timestamp, String id, Uint8List? bytes); void onThumbnailChange(int timestamp, String id, Uint8List? bytes);
void onFindResults(int timestamp, String id, List<FindResultState> results); void onFindResults(int timestamp, String id, List<FindResultState> results);
void onLongPress(int timestamp, String id, HitResult hitResult);
} }
@HostApi() @HostApi()
@@ -1022,3 +1109,10 @@ abstract class GeckoDeleteBrowsingDataController {
@async @async
void deleteDownloads(); void deleteDownloads();
} }
@HostApi()
abstract class GeckoDownloadsApi {
void requestDownload(String tabId, DownloadState state);
void copyInternetResource(String tabId, ShareInternetResourceState state);
void shareInternetResource(String tabId, ShareInternetResourceState state);
}