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:value="2" />
</application>
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="*" />
</intent>
</queries>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
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<SearchRoute>(name: 'SearchRoute', path: 'search/:searchText'),
TypedGoRoute<TorProxyRoute>(name: 'TorProxyRoute', path: 'tor_proxy'),
TypedGoRoute<ContextMenuRoute>(
name: 'ContextMenuRoute',
path: 'context_menu',
),
TypedGoRoute<ContainerListRoute>(
name: 'ContainerListRoute',
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/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/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/tabs/data/models/container_data.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',
factory: $TorProxyRouteExtension._fromState,
),
GoRouteData.$route(
path: 'context_menu',
name: 'ContextMenuRoute',
factory: $ContextMenuRouteExtension._fromState,
),
GoRouteData.$route(
path: 'containers',
name: 'ContainerListRoute',
@@ -320,6 +325,24 @@ extension $TorProxyRouteExtension on TorProxyRoute {
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 {
static ContainerListRoute _fromState(GoRouterState state) =>
ContainerListRoute();
@@ -8,6 +8,7 @@ import 'package:lensai/data/models/drag_data.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/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_session.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/draggable_scrollable_header.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/readerview/presentation/widgets/reader_appearance_button.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
Widget build(BuildContext context, WidgetRef ref) {
final eventService = ref.watch(eventServiceProvider);
final displayedSheet = ref.watch(bottomSheetControllerProvider);
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(
//We need this for BackButtonListener to work downstream
//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
flutter:
sdk: flutter
flutter_auto_size_text: ^4.1.0
flutter_background_service: ^5.1.0
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.GeckoCookieApiImpl
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.GeckoFindApiImpl
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.GeckoCookieApi
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.GeckoFindApi
import eu.lensai.flutter_mozilla_components.pigeons.GeckoIconsApi
@@ -120,6 +122,7 @@ class FlutterMozillaComponentsPlugin: FlutterPlugin, ActivityAware {
selectionActionDelegate
))
GeckoDeleteBrowsingDataController.setUp(_flutterPluginBinding.binaryMessenger, GeckoDeleteBrowsingDataControllerImpl())
GeckoDownloadsApi.setUp(_flutterPluginBinding.binaryMessenger, GeckoDownloadsApiImpl())
ReaderViewEvents.setUp(
_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.ext.resize
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.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.ContentAction
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.WebExtensionAction
import mozilla.components.browser.state.state.BrowserState
import mozilla.components.concept.engine.HitResult
import mozilla.components.feature.addons.logger
import mozilla.components.lib.state.Middleware
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 -> {
//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.
*
@@ -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() {
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
return when (type) {
@@ -1256,150 +1570,205 @@ private open class GeckoPigeonCodec : StandardMessageCodec() {
}
}
142.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
TranslationOptions.fromList(it)
return (readValue(buffer) as Long?)?.let {
DownloadStatus.ofRaw(it.toInt())
}
}
143.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
ReaderState.fromList(it)
TranslationOptions.fromList(it)
}
}
144.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
LastMediaAccessState.fromList(it)
ReaderState.fromList(it)
}
}
145.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
HistoryMetadataKey.fromList(it)
LastMediaAccessState.fromList(it)
}
}
146.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
PackageCategoryValue.fromList(it)
HistoryMetadataKey.fromList(it)
}
}
147.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
ExternalPackage.fromList(it)
PackageCategoryValue.fromList(it)
}
}
148.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
LoadUrlFlagsValue.fromList(it)
ExternalPackage.fromList(it)
}
}
149.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
SourceValue.fromList(it)
LoadUrlFlagsValue.fromList(it)
}
}
150.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
TabState.fromList(it)
SourceValue.fromList(it)
}
}
151.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
RecoverableTab.fromList(it)
TabState.fromList(it)
}
}
152.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
RecoverableBrowserState.fromList(it)
RecoverableTab.fromList(it)
}
}
153.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
IconRequest.fromList(it)
RecoverableBrowserState.fromList(it)
}
}
154.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
ResourceSize.fromList(it)
IconRequest.fromList(it)
}
}
155.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
Resource.fromList(it)
ResourceSize.fromList(it)
}
}
156.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
IconResult.fromList(it)
Resource.fromList(it)
}
}
157.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
CookiePartitionKey.fromList(it)
IconResult.fromList(it)
}
}
158.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
Cookie.fromList(it)
CookiePartitionKey.fromList(it)
}
}
159.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
HistoryItem.fromList(it)
Cookie.fromList(it)
}
}
160.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
HistoryState.fromList(it)
HistoryItem.fromList(it)
}
}
161.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
ReaderableState.fromList(it)
HistoryState.fromList(it)
}
}
162.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
SecurityInfoState.fromList(it)
ReaderableState.fromList(it)
}
}
163.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
TabContentState.fromList(it)
SecurityInfoState.fromList(it)
}
}
164.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
FindResultState.fromList(it)
TabContentState.fromList(it)
}
}
165.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
CustomSelectionAction.fromList(it)
FindResultState.fromList(it)
}
}
166.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
WebExtensionData.fromList(it)
CustomSelectionAction.fromList(it)
}
}
167.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
GeckoSuggestion.fromList(it)
WebExtensionData.fromList(it)
}
}
168.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
TabContent.fromList(it)
GeckoSuggestion.fromList(it)
}
}
169.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
GeckoEngineSettings.fromList(it)
TabContent.fromList(it)
}
}
170.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
GeckoEngineSettings.fromList(it)
}
}
171.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
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)
}
}
@@ -1457,122 +1826,166 @@ private open class GeckoPigeonCodec : StandardMessageCodec() {
stream.write(141)
writeValue(stream, value.raw)
}
is TranslationOptions -> {
is DownloadStatus -> {
stream.write(142)
writeValue(stream, value.toList())
writeValue(stream, value.raw)
}
is ReaderState -> {
is TranslationOptions -> {
stream.write(143)
writeValue(stream, value.toList())
}
is LastMediaAccessState -> {
is ReaderState -> {
stream.write(144)
writeValue(stream, value.toList())
}
is HistoryMetadataKey -> {
is LastMediaAccessState -> {
stream.write(145)
writeValue(stream, value.toList())
}
is PackageCategoryValue -> {
is HistoryMetadataKey -> {
stream.write(146)
writeValue(stream, value.toList())
}
is ExternalPackage -> {
is PackageCategoryValue -> {
stream.write(147)
writeValue(stream, value.toList())
}
is LoadUrlFlagsValue -> {
is ExternalPackage -> {
stream.write(148)
writeValue(stream, value.toList())
}
is SourceValue -> {
is LoadUrlFlagsValue -> {
stream.write(149)
writeValue(stream, value.toList())
}
is TabState -> {
is SourceValue -> {
stream.write(150)
writeValue(stream, value.toList())
}
is RecoverableTab -> {
is TabState -> {
stream.write(151)
writeValue(stream, value.toList())
}
is RecoverableBrowserState -> {
is RecoverableTab -> {
stream.write(152)
writeValue(stream, value.toList())
}
is IconRequest -> {
is RecoverableBrowserState -> {
stream.write(153)
writeValue(stream, value.toList())
}
is ResourceSize -> {
is IconRequest -> {
stream.write(154)
writeValue(stream, value.toList())
}
is Resource -> {
is ResourceSize -> {
stream.write(155)
writeValue(stream, value.toList())
}
is IconResult -> {
is Resource -> {
stream.write(156)
writeValue(stream, value.toList())
}
is CookiePartitionKey -> {
is IconResult -> {
stream.write(157)
writeValue(stream, value.toList())
}
is Cookie -> {
is CookiePartitionKey -> {
stream.write(158)
writeValue(stream, value.toList())
}
is HistoryItem -> {
is Cookie -> {
stream.write(159)
writeValue(stream, value.toList())
}
is HistoryState -> {
is HistoryItem -> {
stream.write(160)
writeValue(stream, value.toList())
}
is ReaderableState -> {
is HistoryState -> {
stream.write(161)
writeValue(stream, value.toList())
}
is SecurityInfoState -> {
is ReaderableState -> {
stream.write(162)
writeValue(stream, value.toList())
}
is TabContentState -> {
is SecurityInfoState -> {
stream.write(163)
writeValue(stream, value.toList())
}
is FindResultState -> {
is TabContentState -> {
stream.write(164)
writeValue(stream, value.toList())
}
is CustomSelectionAction -> {
is FindResultState -> {
stream.write(165)
writeValue(stream, value.toList())
}
is WebExtensionData -> {
is CustomSelectionAction -> {
stream.write(166)
writeValue(stream, value.toList())
}
is GeckoSuggestion -> {
is WebExtensionData -> {
stream.write(167)
writeValue(stream, value.toList())
}
is TabContent -> {
is GeckoSuggestion -> {
stream.write(168)
writeValue(stream, value.toList())
}
is GeckoEngineSettings -> {
is TabContent -> {
stream.write(169)
writeValue(stream, value.toList())
}
is AutocompleteResult -> {
is GeckoEngineSettings -> {
stream.write(170)
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)
}
}
@@ -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. */
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_cookie.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_event.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/pigeons/gecko.g.dart'
show
AudioHitResult,
ColorScheme,
CookieBannerHandlingMode,
CookieSameSiteStatus,
EmailHitResult,
GeckoEngineSettings,
GeckoSuggestion,
GeckoSuggestionType,
GeoHitResult,
HistoryMetadataKey,
HitResult,
HttpsOnlyMode,
IconSource,
IconType,
ImageHitResult,
ImageSrcHitResult,
PhoneHitResult,
Resource,
ResourceSize,
SecurityInfoState,
TabContent,
TabContentState,
TrackingProtectionPolicy,
UnknownHitResult,
VideoHitResult,
WebContentIsolationStrategy,
WebExtensionActionType,
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 ThumbnailEvent = ({String tabId, Uint8List? bytes});
typedef FindResultsEvent = ({String tabId, List<FindResultState> results});
typedef LongPressEvent = ({String tabId, HitResult hitResult});
class GeckoEventService extends GeckoStateEvents {
// Stream controllers
@@ -29,6 +30,7 @@ class GeckoEventService extends GeckoStateEvents {
final _iconUpdateSubject = PublishSubject<IconUpdateEvent>();
final _thumbnailSubject = PublishSubject<ThumbnailEvent>();
final _findResultsSubject = PublishSubject<FindResultsEvent>();
final _longPressSubject = PublishSubject<LongPressEvent>();
final _tabAddedSubject = PublishSubject<String>();
@@ -47,6 +49,7 @@ class GeckoEventService extends GeckoStateEvents {
Stream<IconUpdateEvent> get iconUpdateEvents => _iconUpdateSubject.stream;
Stream<ThumbnailEvent> get thumbnailEvents => _thumbnailSubject.stream;
Stream<FindResultsEvent> get findResultsEvent => _findResultsSubject.stream;
Stream<LongPressEvent> get longPressEvent => _longPressSubject.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
void onTabAdded(int timestamp, String tabId) {
_tabAddedSubject.addWhenMoreRecent(timestamp, null, tabId);
@@ -170,6 +181,7 @@ class GeckoEventService extends GeckoStateEvents {
unawaited(_iconChangeSubject.close());
unawaited(_thumbnailSubject.close());
unawaited(_findResultsSubject.close());
unawaited(_longPressSubject.close());
unawaited(_tabAddedSubject.close());
}
}
@@ -125,6 +125,23 @@ enum WebContentIsolationStrategy {
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.
///
/// @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 {
const _PigeonCodec();
@@ -1361,93 +1716,126 @@ class _PigeonCodec extends StandardMessageCodec {
} else if (value is WebContentIsolationStrategy) {
buffer.putUint8(141);
writeValue(buffer, value.index);
} else if (value is TranslationOptions) {
} else if (value is DownloadStatus) {
buffer.putUint8(142);
writeValue(buffer, value.encode());
} else if (value is ReaderState) {
writeValue(buffer, value.index);
} else if (value is TranslationOptions) {
buffer.putUint8(143);
writeValue(buffer, value.encode());
} else if (value is LastMediaAccessState) {
} else if (value is ReaderState) {
buffer.putUint8(144);
writeValue(buffer, value.encode());
} else if (value is HistoryMetadataKey) {
} else if (value is LastMediaAccessState) {
buffer.putUint8(145);
writeValue(buffer, value.encode());
} else if (value is PackageCategoryValue) {
} else if (value is HistoryMetadataKey) {
buffer.putUint8(146);
writeValue(buffer, value.encode());
} else if (value is ExternalPackage) {
} else if (value is PackageCategoryValue) {
buffer.putUint8(147);
writeValue(buffer, value.encode());
} else if (value is LoadUrlFlagsValue) {
} else if (value is ExternalPackage) {
buffer.putUint8(148);
writeValue(buffer, value.encode());
} else if (value is SourceValue) {
} else if (value is LoadUrlFlagsValue) {
buffer.putUint8(149);
writeValue(buffer, value.encode());
} else if (value is TabState) {
} else if (value is SourceValue) {
buffer.putUint8(150);
writeValue(buffer, value.encode());
} else if (value is RecoverableTab) {
} else if (value is TabState) {
buffer.putUint8(151);
writeValue(buffer, value.encode());
} else if (value is RecoverableBrowserState) {
} else if (value is RecoverableTab) {
buffer.putUint8(152);
writeValue(buffer, value.encode());
} else if (value is IconRequest) {
} else if (value is RecoverableBrowserState) {
buffer.putUint8(153);
writeValue(buffer, value.encode());
} else if (value is ResourceSize) {
} else if (value is IconRequest) {
buffer.putUint8(154);
writeValue(buffer, value.encode());
} else if (value is Resource) {
} else if (value is ResourceSize) {
buffer.putUint8(155);
writeValue(buffer, value.encode());
} else if (value is IconResult) {
} else if (value is Resource) {
buffer.putUint8(156);
writeValue(buffer, value.encode());
} else if (value is CookiePartitionKey) {
} else if (value is IconResult) {
buffer.putUint8(157);
writeValue(buffer, value.encode());
} else if (value is Cookie) {
} else if (value is CookiePartitionKey) {
buffer.putUint8(158);
writeValue(buffer, value.encode());
} else if (value is HistoryItem) {
} else if (value is Cookie) {
buffer.putUint8(159);
writeValue(buffer, value.encode());
} else if (value is HistoryState) {
} else if (value is HistoryItem) {
buffer.putUint8(160);
writeValue(buffer, value.encode());
} else if (value is ReaderableState) {
} else if (value is HistoryState) {
buffer.putUint8(161);
writeValue(buffer, value.encode());
} else if (value is SecurityInfoState) {
} else if (value is ReaderableState) {
buffer.putUint8(162);
writeValue(buffer, value.encode());
} else if (value is TabContentState) {
} else if (value is SecurityInfoState) {
buffer.putUint8(163);
writeValue(buffer, value.encode());
} else if (value is FindResultState) {
} else if (value is TabContentState) {
buffer.putUint8(164);
writeValue(buffer, value.encode());
} else if (value is CustomSelectionAction) {
} else if (value is FindResultState) {
buffer.putUint8(165);
writeValue(buffer, value.encode());
} else if (value is WebExtensionData) {
} else if (value is CustomSelectionAction) {
buffer.putUint8(166);
writeValue(buffer, value.encode());
} else if (value is GeckoSuggestion) {
} else if (value is WebExtensionData) {
buffer.putUint8(167);
writeValue(buffer, value.encode());
} else if (value is TabContent) {
} else if (value is GeckoSuggestion) {
buffer.putUint8(168);
writeValue(buffer, value.encode());
} else if (value is GeckoEngineSettings) {
} else if (value is TabContent) {
buffer.putUint8(169);
writeValue(buffer, value.encode());
} else if (value is AutocompleteResult) {
} else if (value is GeckoEngineSettings) {
buffer.putUint8(170);
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 {
super.writeValue(buffer, value);
}
@@ -1496,63 +1884,86 @@ class _PigeonCodec extends StandardMessageCodec {
final int? value = readValue(buffer) as int?;
return value == null ? null : WebContentIsolationStrategy.values[value];
case 142:
return TranslationOptions.decode(readValue(buffer)!);
final int? value = readValue(buffer) as int?;
return value == null ? null : DownloadStatus.values[value];
case 143:
return ReaderState.decode(readValue(buffer)!);
return TranslationOptions.decode(readValue(buffer)!);
case 144:
return LastMediaAccessState.decode(readValue(buffer)!);
return ReaderState.decode(readValue(buffer)!);
case 145:
return HistoryMetadataKey.decode(readValue(buffer)!);
return LastMediaAccessState.decode(readValue(buffer)!);
case 146:
return PackageCategoryValue.decode(readValue(buffer)!);
return HistoryMetadataKey.decode(readValue(buffer)!);
case 147:
return ExternalPackage.decode(readValue(buffer)!);
return PackageCategoryValue.decode(readValue(buffer)!);
case 148:
return LoadUrlFlagsValue.decode(readValue(buffer)!);
return ExternalPackage.decode(readValue(buffer)!);
case 149:
return SourceValue.decode(readValue(buffer)!);
return LoadUrlFlagsValue.decode(readValue(buffer)!);
case 150:
return TabState.decode(readValue(buffer)!);
return SourceValue.decode(readValue(buffer)!);
case 151:
return RecoverableTab.decode(readValue(buffer)!);
return TabState.decode(readValue(buffer)!);
case 152:
return RecoverableBrowserState.decode(readValue(buffer)!);
return RecoverableTab.decode(readValue(buffer)!);
case 153:
return IconRequest.decode(readValue(buffer)!);
return RecoverableBrowserState.decode(readValue(buffer)!);
case 154:
return ResourceSize.decode(readValue(buffer)!);
return IconRequest.decode(readValue(buffer)!);
case 155:
return Resource.decode(readValue(buffer)!);
return ResourceSize.decode(readValue(buffer)!);
case 156:
return IconResult.decode(readValue(buffer)!);
return Resource.decode(readValue(buffer)!);
case 157:
return CookiePartitionKey.decode(readValue(buffer)!);
return IconResult.decode(readValue(buffer)!);
case 158:
return Cookie.decode(readValue(buffer)!);
return CookiePartitionKey.decode(readValue(buffer)!);
case 159:
return HistoryItem.decode(readValue(buffer)!);
return Cookie.decode(readValue(buffer)!);
case 160:
return HistoryState.decode(readValue(buffer)!);
return HistoryItem.decode(readValue(buffer)!);
case 161:
return ReaderableState.decode(readValue(buffer)!);
return HistoryState.decode(readValue(buffer)!);
case 162:
return SecurityInfoState.decode(readValue(buffer)!);
return ReaderableState.decode(readValue(buffer)!);
case 163:
return TabContentState.decode(readValue(buffer)!);
return SecurityInfoState.decode(readValue(buffer)!);
case 164:
return FindResultState.decode(readValue(buffer)!);
return TabContentState.decode(readValue(buffer)!);
case 165:
return CustomSelectionAction.decode(readValue(buffer)!);
return FindResultState.decode(readValue(buffer)!);
case 166:
return WebExtensionData.decode(readValue(buffer)!);
return CustomSelectionAction.decode(readValue(buffer)!);
case 167:
return GeckoSuggestion.decode(readValue(buffer)!);
return WebExtensionData.decode(readValue(buffer)!);
case 168:
return TabContent.decode(readValue(buffer)!);
return GeckoSuggestion.decode(readValue(buffer)!);
case 169:
return GeckoEngineSettings.decode(readValue(buffer)!);
return TabContent.decode(readValue(buffer)!);
case 170:
return GeckoEngineSettings.decode(readValue(buffer)!);
case 171:
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:
return super.readValueOfType(type, buffer);
}
@@ -2940,6 +3351,8 @@ abstract class GeckoStateEvents {
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 = '',}) {
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
// /// an element.
// sealed class HitResult {
// final String src;
/// Represents all the different supported types of data that can be found from long clicking
/// an element.
sealed class HitResult {}
// 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.
// class UnknownHitResult extends HitResult {
// UnknownHitResult(super.src);
// }
UnknownHitResult(this.src);
}
// /// If the HTML element was of type 'HTMLImageElement'.
// class ImageHitResult extends HitResult {
// final String? title;
/// If the HTML element was of type 'HTMLImageElement'.
class ImageHitResult extends HitResult {
final String src;
final String? title;
// ImageHitResult(super.src, {this.title});
// }
ImageHitResult(this.src, {this.title});
}
// /// If the HTML element was of type 'HTMLVideoElement'.
// class VideoHitResult extends HitResult {
// final String? title;
/// If the HTML element was of type 'HTMLVideoElement'.
class VideoHitResult extends HitResult {
final String src;
final String? title;
// VideoHitResult(super.src, {this.title});
// }
VideoHitResult(this.src, {this.title});
}
// /// If the HTML element was of type 'HTMLAudioElement'.
// class AudioHitResult extends HitResult {
// final String? title;
/// If the HTML element was of type 'HTMLAudioElement'.
class AudioHitResult extends HitResult {
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.
// class ImageSrcHitResult extends HitResult {
// final String uri;
/// If the HTML element was of type 'HTMLImageElement' and contained a URI.
class ImageSrcHitResult extends HitResult {
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:'.
// class PhoneHitResult extends HitResult {
// PhoneHitResult(super.src);
// }
/// The type used if the URI is prepended with 'tel:'.
class PhoneHitResult extends HitResult {
final String src;
// /// The type used if the URI is prepended with 'mailto:'.
// class EmailHitResult extends HitResult {
// EmailHitResult(super.src);
// }
PhoneHitResult(this.src);
}
// /// The type used if the URI is prepended with 'geo:'.
// class GeoHitResult extends HitResult {
// GeoHitResult(super.src);
// }
/// The type used if the URI is prepended with 'mailto:'.
class EmailHitResult extends HitResult {
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(
PigeonOptions(
@@ -933,6 +1019,7 @@ abstract class GeckoStateEvents {
void onThumbnailChange(int timestamp, String id, Uint8List? bytes);
void onFindResults(int timestamp, String id, List<FindResultState> results);
void onLongPress(int timestamp, String id, HitResult hitResult);
}
@HostApi()
@@ -1022,3 +1109,10 @@ abstract class GeckoDeleteBrowsingDataController {
@async
void deleteDownloads();
}
@HostApi()
abstract class GeckoDownloadsApi {
void requestDownload(String tabId, DownloadState state);
void copyInternetResource(String tabId, ShareInternetResourceState state);
void shareInternetResource(String tabId, ShareInternetResourceState state);
}