feat: add WebView support for extension (#1359)

This commit is contained in:
Levi authored and GitHub committed 2026-04-24 22:16:33 +08:00
1 parent a205c0b8d7
commit def54f8998
92 files changed
+10248 -2676

No files matched your search

+1 -1
View File
@@ -24,7 +24,7 @@ if (flutterVersionName == null) {
android {
namespace 'com.gopeed.gopeed'
compileSdk 35
compileSdk 36
ndkVersion flutter.ndkVersion
compileOptions {
+1 -1
View File
@@ -46,7 +46,7 @@ class _Client {
dio.options.contentType = Headers.jsonContentType;
dio.options.sendTimeout = const Duration(seconds: 5);
dio.options.connectTimeout = const Duration(seconds: 5);
dio.options.receiveTimeout = const Duration(seconds: 60);
dio.options.receiveTimeout = const Duration(seconds: 120);
dio.interceptors.add(InterceptorsWrapper(
onRequest: (options, handler) {
if (apiToken.isNotEmpty) {
@@ -49,7 +49,7 @@ Map<String, dynamic> _$CreateTaskBatchItemToJson(CreateTaskBatchItem instance) {
}
}
writeNotNull('req', instance.req);
writeNotNull('opts', instance.opts);
writeNotNull('req', instance.req?.toJson());
writeNotNull('opts', instance.opts?.toJson());
return val;
}
+2
View File
@@ -4,6 +4,7 @@ part 'request.g.dart';
@JsonSerializable(explicitToJson: true)
class Request {
String? rawUrl;
String url;
Object? extra;
Map<String, String>? labels;
@@ -11,6 +12,7 @@ class Request {
bool skipVerifyCert;
Request({
this.rawUrl,
required this.url,
this.extra,
this.labels,
+2
View File
@@ -7,6 +7,7 @@ part of 'request.dart';
// **************************************************************************
Request _$RequestFromJson(Map<String, dynamic> json) => Request(
rawUrl: json['rawUrl'] as String?,
url: json['url'] as String,
extra: json['extra'],
labels: (json['labels'] as Map<String, dynamic>?)?.map(
@@ -20,6 +21,7 @@ Request _$RequestFromJson(Map<String, dynamic> json) => Request(
Map<String, dynamic> _$RequestToJson(Request instance) {
final val = <String, dynamic>{
'rawUrl': instance.rawUrl,
'url': instance.url,
};
+1 -1
View File
@@ -6,7 +6,7 @@ part 'task.g.dart';
enum Status { ready, running, pause, wait, error, done }
enum Protocol { http, bt, ed2k }
enum Protocol { http, bt, ed2k, gblob }
// ExtractStatus enum matching Go backend
enum ExtractStatus {
+1
View File
@@ -52,6 +52,7 @@ const _$ProtocolEnumMap = {
Protocol.http: 'http',
Protocol.bt: 'bt',
Protocol.ed2k: 'ed2k',
Protocol.gblob: 'gblob',
};
Progress _$ProgressFromJson(Map<String, dynamic> json) => Progress(
@@ -4,7 +4,6 @@ import 'dart:io';
import 'dart:ui';
import 'package:app_links/app_links.dart';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_foreground_task/flutter_foreground_task.dart';
import 'package:get/get.dart';
@@ -17,13 +16,11 @@ import 'package:uri_to_file/uri_to_file.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:window_manager/window_manager.dart';
import '../../../../api/api.dart';
import '../../../../api/model/create_task.dart';
import '../../../../api/model/downloader_config.dart';
import '../../../../api/model/install_extension.dart';
import '../../../../api/model/request.dart';
import '../../../../api/model/result.dart';
import '../../../../core/common/start_config.dart';
import '../../../../core/libgopeed_boot.dart';
import '../../../../database/database.dart';
@@ -37,7 +34,8 @@ import '../../../../util/package_info.dart';
import '../../../../util/updater.dart';
import '../../../../util/util.dart';
import '../../../routes/app_pages.dart';
import '../../../rpc/rpc.dart';
import '../../../rpc/host_rpc_service.dart';
import '../../../rpc/webview_rpc_service.dart';
import '../../redirect/views/redirect_view.dart';
import '../../../services/notification_service.dart';
@@ -98,7 +96,7 @@ class AppController extends GetxController with WindowListener, TrayListener {
_initWindows().onError((error, stackTrace) =>
logger.w("initWindows error", error, stackTrace));
if (Util.isDesktop()) {
Get.put(NotificationService());
}
@@ -126,6 +124,8 @@ class AppController extends GetxController with WindowListener, TrayListener {
void onClose() {
_linkSubscription?.cancel();
trayManager.removeListener(this);
HostRpcService.instance.stop();
WebViewRpcService.instance.stop();
LibgopeedBoot.instance.stop();
}
@@ -330,54 +330,33 @@ class AppController extends GetxController with WindowListener, TrayListener {
return;
}
try {
await startRpcServer({
"/create": (ctx) async {
final meta =
ctx.request.headers["X-Gopeed-Host-Meta"]?.firstOrNull ?? "{}";
final jsonMeta = jsonDecode(meta);
final silent = jsonMeta['silent'] as bool? ?? false;
final params = await ctx.readText();
final createTaskParams = CreateTask.fromJson(_decodeParams(params));
await HostRpcService.instance.start(
onCreate: (createTaskParams, silent) async {
if (!silent || pendingUpdateTask.value != null) {
await windowManager.show();
_handleToCreate0(createTaskParams);
} else {
try {
await createTask(createTaskParams);
} catch (e) {
logger.w(
"create task from extension fail", e, StackTrace.current);
}
return;
}
},
"/forward": (ctx) async {
try {
final body = await ctx.readJSON();
final method = (body['method'] as String?)?.toUpperCase() ?? 'GET';
final path = (body['path'] as String?) ?? "/";
final data = body['data'];
final query = body['query'] as Map<String, dynamic>?;
// Forward request to gopeed REST API
final response = await forward(
path,
method: method,
data: data,
queryParameters: query,
);
// Return raw response
await ctx.writeJSON(response.data);
await createTask(createTaskParams);
} catch (e) {
if (e is DioException && e.response != null) {
// Return API error response
await ctx.writeJSON(e.response!.data);
} else {
await ctx.writeJSON(Result(code: 1, msg: e.toString()).toJson());
}
logger.w("create task from extension fail", e, StackTrace.current);
}
},
});
onForward: ({
required String path,
required String method,
data,
Map<String, dynamic>? query,
}) {
return forward(
path,
method: method,
data: data,
queryParameters: query,
);
},
);
} catch (e) {
logger.w("start rpc server fail", e, StackTrace.current);
}
@@ -8,6 +8,8 @@ import '../../../../i18n/message.dart';
import '../../../../theme/theme.dart';
import '../../../../util/locale_manager.dart';
import '../../../../util/util.dart'; // Import the required packages
import '../../../rpc/webview_rpc_overlay.dart';
import '../../../rpc/webview_rpc_service.dart';
import '../../../routes/app_pages.dart';
import '../controllers/app_controller.dart';
@@ -46,9 +48,13 @@ class AppView extends GetView<AppController> {
}
// Fix for GetX Overlay issue with Flutter 3.38.1+
// Reference: https://github.com/jonataslaw/getx/issues/3425
return Overlay(
initialEntries: [OverlayEntry(builder: (_) => child!)],
);
final entries = <OverlayEntry>[
OverlayEntry(builder: (_) => child!),
];
if (WebViewRpcService.instance.supported) {
entries.add(OverlayEntry(builder: (_) => const WebViewRpcOverlay()));
}
return Overlay(initialEntries: entries);
},
),
);
@@ -17,6 +17,14 @@ import 'task_downloading_view.dart';
class TaskView extends GetView<TaskController> {
const TaskView({Key? key}) : super(key: key);
String? _displayTaskUrl(Task? task) {
final rawUrl = task?.meta.req.rawUrl;
if (rawUrl != null && rawUrl.isNotEmpty) {
return rawUrl;
}
return task?.meta.req.url;
}
@override
Widget build(BuildContext context) {
final selectTask = controller.selectTask;
@@ -85,9 +93,8 @@ class TaskView extends GetView<TaskController> {
subtitle: buildTooltipSubtitle(selectTask.value?.name)),
ListTile(
title: Text('taskUrl'.tr),
subtitle:
buildTooltipSubtitle(selectTask.value?.meta.req.url),
trailing: CopyButton(selectTask.value?.meta.req.url),
subtitle: buildTooltipSubtitle(_displayTaskUrl(selectTask.value)),
trailing: CopyButton(_displayTaskUrl(selectTask.value)),
),
ListTile(
title: Text('downloadPath'.tr),
@@ -0,0 +1,90 @@
import 'dart:convert';
import 'package:dio/dio.dart';
import '../../api/model/create_task.dart';
import '../../api/model/result.dart';
import 'server.dart';
typedef HostCreateHandler = Future<void> Function(
CreateTask createTask,
bool silent,
);
typedef HostForwardHandler = Future<dynamic> Function({
required String path,
required String method,
dynamic data,
Map<String, dynamic>? query,
});
class HostRpcService {
HostRpcService._();
static final HostRpcService instance = HostRpcService._();
RpcServerHandle? _server;
Future<void> start({
required HostCreateHandler onCreate,
required HostForwardHandler onForward,
}) async {
if (_server != null) {
return;
}
_server = await startRpcServer(
routes: {
'/create': (ctx) async {
final meta =
ctx.request.headers['X-Gopeed-Host-Meta']?.firstOrNull ?? '{}';
final jsonMeta = jsonDecode(meta);
final silent = jsonMeta['silent'] as bool? ?? false;
final params = await ctx.readText();
final createTask = CreateTask.fromJson(_decodeParams(params));
await onCreate(createTask, silent);
},
'/forward': (ctx) async {
try {
final body = await ctx.readJSON();
final method = (body['method'] as String?)?.toUpperCase() ?? 'GET';
final path = (body['path'] as String?) ?? '/';
final data = body['data'];
final query = body['query'] as Map<String, dynamic>?;
final response = await onForward(
path: path,
method: method,
data: data,
query: query,
);
if (response is Map<String, dynamic>) {
await ctx.writeJSON(response);
} else {
await ctx.writeJSON((response as dynamic).data);
}
} catch (e) {
if (e is DioException && e.response != null) {
await ctx.writeJSON(e.response!.data);
} else {
await ctx.writeJSON(Result(code: 1, msg: e.toString()).toJson());
}
}
},
},
);
}
Future<void> stop() async {
final server = _server;
_server = null;
if (server != null) {
await server.close();
}
}
Map<String, dynamic> _decodeParams(String params) {
final safeParams = params.replaceAll('"', '').replaceAll(' ', '+');
final paramsJson =
String.fromCharCodes(base64Decode(base64.normalize(safeParams)));
return jsonDecode(paramsJson);
}
}
+198
View File
@@ -0,0 +1,198 @@
import 'dart:convert';
import 'dart:io';
import 'package:dart_ipc/dart_ipc.dart';
import '../../util/util.dart';
class RpcContext {
final HttpRequest request;
final HttpResponse response;
String? _bodyCache;
RpcContext(this.request, this.response);
Future<Map<String, dynamic>> readJSON() async {
if (_bodyCache == null) {
final content = <int>[];
await for (final data in request) {
content.addAll(data);
}
_bodyCache = String.fromCharCodes(content);
}
if (_bodyCache!.isEmpty) {
return {};
}
return jsonDecode(_bodyCache!) as Map<String, dynamic>;
}
Future<String> readText() async {
if (_bodyCache == null) {
final content = <int>[];
await for (final data in request) {
content.addAll(data);
}
_bodyCache = String.fromCharCodes(content);
}
return _bodyCache ?? '';
}
Future<void> writeJSON(Map<String, dynamic> data) async {
response.headers.contentType = ContentType.json;
response.write(jsonEncode(data));
}
Future<void> writeError(String message, [int statusCode = 500]) async {
response.statusCode = statusCode;
await writeJSON({'error': message});
}
}
typedef RouteHandler = Future<void> Function(RpcContext ctx);
class RouteRegistry {
final Map<String, RouteHandler> _routes = {};
void register(String path, RouteHandler handler) {
_routes[path] = handler;
}
RouteHandler? getHandler(String path) {
return _routes[path];
}
}
class RpcBinding {
final String network;
final String address;
const RpcBinding({
required this.network,
required this.address,
});
}
class RpcServerHandle {
final RpcBinding binding;
final HttpServer server;
final Future<void> Function()? _cleanup;
RpcServerHandle({
required this.binding,
required this.server,
Future<void> Function()? cleanup,
}) : _cleanup = cleanup;
Future<void> close({bool force = true}) async {
await server.close(force: force);
if (_cleanup != null) {
await _cleanup!();
}
}
}
Future<RpcBinding> defaultHostRpcBinding() async {
if (Util.isWindows()) {
return const RpcBinding(
network: 'pipe',
address: r'\\.\pipe\gopeed_host',
);
}
return RpcBinding(
network: 'unix',
address: await Util.homePathJoin('gopeed_host.sock'),
);
}
Future<RpcBinding> defaultWebViewRpcBinding() async {
if (Util.supportUnixSocket()) {
return RpcBinding(
network: 'unix',
address: await Util.homePathJoin('gopeed_webview.sock'),
);
}
return const RpcBinding(
network: 'tcp',
address: '127.0.0.1:0',
);
}
Future<RpcServerHandle> startRpcServer({
RpcBinding? binding,
Map<String, RouteHandler>? routes,
}) async {
final rpcBinding = binding ?? await defaultHostRpcBinding();
Future<void> Function()? cleanup;
late final HttpServer httpServer;
switch (rpcBinding.network) {
case 'pipe':
final serverSocket = await bind(rpcBinding.address);
httpServer = HttpServer.listenOn(serverSocket);
break;
case 'unix':
final socketFile = File(rpcBinding.address);
if (await socketFile.exists()) {
try {
await socketFile.delete();
} catch (_) {}
}
final serverSocket = await bind(rpcBinding.address);
httpServer = HttpServer.listenOn(serverSocket);
cleanup = () async {
if (await socketFile.exists()) {
try {
await socketFile.delete();
} catch (_) {}
}
};
break;
case 'tcp':
final separator = rpcBinding.address.lastIndexOf(':');
if (separator <= 0 || separator == rpcBinding.address.length - 1) {
throw ArgumentError('invalid tcp rpc address: ${rpcBinding.address}');
}
final host = rpcBinding.address.substring(0, separator);
final port = int.parse(rpcBinding.address.substring(separator + 1));
httpServer = await HttpServer.bind(host, port);
break;
default:
throw ArgumentError('unsupported rpc network: ${rpcBinding.network}');
}
final registry = RouteRegistry();
routes?.forEach(registry.register);
httpServer.forEach((request) async {
final ctx = RpcContext(request, request.response);
try {
final handler = registry.getHandler(request.uri.path);
if (handler != null) {
await handler(ctx);
} else {
await ctx.writeError('Route not found: ${request.uri.path}', 404);
}
} catch (e) {
await ctx.writeError('Internal server error: $e', 500);
} finally {
await request.response.close();
}
});
var resolvedBinding = rpcBinding;
if (rpcBinding.network == 'tcp') {
resolvedBinding = RpcBinding(
network: rpcBinding.network,
address: '${httpServer.address.address}:${httpServer.port}',
);
}
return RpcServerHandle(
binding: resolvedBinding,
server: httpServer,
cleanup: cleanup,
);
}
@@ -0,0 +1,97 @@
import 'package:flutter/material.dart';
import 'webview_rpc_service.dart';
class WebViewRpcOverlay extends StatefulWidget {
const WebViewRpcOverlay({super.key});
@override
State<WebViewRpcOverlay> createState() => _WebViewRpcOverlayState();
}
class _WebViewRpcOverlayState extends State<WebViewRpcOverlay> {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
WebViewRpcService.instance.markOverlayReady();
});
}
@override
Widget build(BuildContext context) {
return ValueListenableBuilder<List<WebViewRpcPageSession>>(
valueListenable: WebViewRpcService.instance.pages,
builder: (context, pages, _) {
if (pages.isEmpty) {
return const SizedBox.shrink();
}
return IgnorePointer(
ignoring: false,
child: Stack(
children: pages
.map((page) => _WebViewRpcPageView(page: page))
.toList(growable: false),
),
);
},
);
}
}
class _WebViewRpcPageView extends StatefulWidget {
const _WebViewRpcPageView({required this.page});
final WebViewRpcPageSession page;
@override
State<_WebViewRpcPageView> createState() => _WebViewRpcPageViewState();
}
class _WebViewRpcPageViewState extends State<_WebViewRpcPageView> {
@override
Widget build(BuildContext context) {
final page = widget.page;
final content = page.buildWebView();
return Positioned.fill(
child: Material(
color: Colors.black54,
child: Center(
child: Container(
width: page.width > 0 ? page.width.toDouble() : 960,
height: page.height > 0 ? page.height.toDouble() : 720,
clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.circular(12),
boxShadow: const [
BoxShadow(
blurRadius: 24,
color: Colors.black26,
),
],
),
child: Column(
children: [
Container(
height: 44,
padding: const EdgeInsets.symmetric(horizontal: 16),
alignment: Alignment.centerLeft,
color: Theme.of(context).colorScheme.surfaceContainerHighest,
child: Text(
page.title.isNotEmpty ? page.title : 'WebView',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.titleSmall,
),
),
Expanded(child: content),
],
),
),
),
),
);
}
}
File diff suppressed because it is too large. Load diff
@@ -119,7 +119,10 @@ class BuildTaskListView extends GetView {
}
Future<void> showUpdateUrlDialog(BuildContext context, Task task) async {
final urlController = TextEditingController(text: task.meta.req.url);
final displayUrl = (task.meta.req.rawUrl?.isNotEmpty ?? false)
? task.meta.req.rawUrl!
: task.meta.req.url;
final urlController = TextEditingController(text: displayUrl);
final headerControllers =
<MapEntry<TextEditingController, TextEditingController>>[];
@@ -2,6 +2,20 @@ import 'package:json_annotation/json_annotation.dart';
part 'start_config.g.dart';
@JsonSerializable()
class WebViewRpcConfig {
late String network;
late String address;
String? token;
WebViewRpcConfig();
factory WebViewRpcConfig.fromJson(Map<String, dynamic> json) =>
_$WebViewRpcConfigFromJson(json);
Map<String, dynamic> toJson() => _$WebViewRpcConfigToJson(this);
}
@JsonSerializable()
class StartConfig {
late String network;
@@ -10,6 +24,7 @@ class StartConfig {
late String storageDir;
late int refreshInterval;
late String apiToken;
WebViewRpcConfig? webViewRpcConfig;
StartConfig();
+19 -1
View File
@@ -6,13 +6,30 @@ part of 'start_config.dart';
// JsonSerializableGenerator
// **************************************************************************
WebViewRpcConfig _$WebViewRpcConfigFromJson(Map<String, dynamic> json) =>
WebViewRpcConfig()
..network = json['network'] as String
..address = json['address'] as String
..token = json['token'] as String?;
Map<String, dynamic> _$WebViewRpcConfigToJson(WebViewRpcConfig instance) =>
<String, dynamic>{
'network': instance.network,
'address': instance.address,
'token': instance.token,
};
StartConfig _$StartConfigFromJson(Map<String, dynamic> json) => StartConfig()
..network = json['network'] as String
..address = json['address'] as String
..storage = json['storage'] as String
..storageDir = json['storageDir'] as String
..refreshInterval = (json['refreshInterval'] as num).toInt()
..apiToken = json['apiToken'] as String;
..apiToken = json['apiToken'] as String
..webViewRpcConfig = json['webViewRpcConfig'] == null
? null
: WebViewRpcConfig.fromJson(
json['webViewRpcConfig'] as Map<String, dynamic>);
Map<String, dynamic> _$StartConfigToJson(StartConfig instance) =>
<String, dynamic>{
@@ -22,4 +39,5 @@ Map<String, dynamic> _$StartConfigToJson(StartConfig instance) =>
'storageDir': instance.storageDir,
'refreshInterval': instance.refreshInterval,
'apiToken': instance.apiToken,
'webViewRpcConfig': instance.webViewRpcConfig,
};
+5
View File
@@ -10,6 +10,7 @@ import 'package:window_manager/window_manager.dart';
import 'api/api.dart' as api;
import 'app/modules/app/controllers/app_controller.dart';
import 'app/rpc/webview_rpc_service.dart';
import 'app/modules/app/views/app_view.dart';
import 'core/libgopeed_boot.dart';
import 'database/database.dart';
@@ -109,6 +110,10 @@ Future<void> init(StartupArgs args) async {
try {
await controller.loadStartConfig();
final startCfg = controller.startConfig.value;
final webViewRpcConfig = await WebViewRpcService.instance.start();
if (webViewRpcConfig != null) {
startCfg.webViewRpcConfig = webViewRpcConfig;
}
controller.runningPort.value = await LibgopeedBoot.instance.start(startCfg);
api.init(startCfg.network, controller.runningAddress(), startCfg.apiToken);
} catch (e) {
+16
View File
@@ -5,6 +5,11 @@ PODS:
- FlutterMacOS
- device_info_plus (0.0.1):
- FlutterMacOS
- flutter_inappwebview_macos (0.0.1):
- FlutterMacOS
- OrderedSet (~> 6.0.3)
- flutter_local_notifications (0.0.1):
- FlutterMacOS
- FlutterMacOS (1.0.0)
- HotKey (0.2.1)
- hotkey_manager_macos (0.0.1):
@@ -12,6 +17,7 @@ PODS:
- HotKey
- open_dir_macos (0.0.1):
- FlutterMacOS
- OrderedSet (6.0.3)
- package_info_plus (0.0.1):
- FlutterMacOS
- path_provider_foundation (0.0.1):
@@ -35,6 +41,8 @@ DEPENDENCIES:
- app_links (from `Flutter/ephemeral/.symlinks/plugins/app_links/macos`)
- desktop_drop (from `Flutter/ephemeral/.symlinks/plugins/desktop_drop/macos`)
- device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`)
- flutter_inappwebview_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_inappwebview_macos/macos`)
- flutter_local_notifications (from `Flutter/ephemeral/.symlinks/plugins/flutter_local_notifications/macos`)
- FlutterMacOS (from `Flutter/ephemeral`)
- hotkey_manager_macos (from `Flutter/ephemeral/.symlinks/plugins/hotkey_manager_macos/macos`)
- open_dir_macos (from `Flutter/ephemeral/.symlinks/plugins/open_dir_macos/macos`)
@@ -50,6 +58,7 @@ DEPENDENCIES:
SPEC REPOS:
trunk:
- HotKey
- OrderedSet
EXTERNAL SOURCES:
app_links:
@@ -58,6 +67,10 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/desktop_drop/macos
device_info_plus:
:path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos
flutter_inappwebview_macos:
:path: Flutter/ephemeral/.symlinks/plugins/flutter_inappwebview_macos/macos
flutter_local_notifications:
:path: Flutter/ephemeral/.symlinks/plugins/flutter_local_notifications/macos
FlutterMacOS:
:path: Flutter/ephemeral
hotkey_manager_macos:
@@ -85,10 +98,13 @@ SPEC CHECKSUMS:
app_links: 9028728e32c83a0831d9db8cf91c526d16cc5468
desktop_drop: e0b672a7d84c0a6cbc378595e82cdb15f2970a43
device_info_plus: 724ebb83a99d517430554a1810445ff28fe71fa3
flutter_inappwebview_macos: c2d68649f9f8f1831bfcd98d73fd6256366d9d1d
flutter_local_notifications: 4bf37a31afde695b56091b4ae3e4d9c7a7e6cda0
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
HotKey: 400beb7caa29054ea8d864c96f5ba7e5b4852277
hotkey_manager_macos: a4317849af96d2430fa89944d3c58977ca089fbe
open_dir_macos: 79810d7921d777f8732bf6358b3a0839a8fad9b5
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
package_info_plus: 8147fd5037235202932580f35ebe195f7a55e3cd
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
screen_retriever: 4f97c103641aab8ce183fa5af3b87029df167936
+64
View File
@@ -398,6 +398,70 @@ packages:
url: "https://pub.dev"
source: hosted
version: "10.2.0"
flutter_inappwebview:
dependency: "direct main"
description:
name: flutter_inappwebview
sha256: "80092d13d3e29b6227e25b67973c67c7210bd5e35c4b747ca908e31eb71a46d5"
url: "https://pub.dev"
source: hosted
version: "6.1.5"
flutter_inappwebview_android:
dependency: transitive
description:
name: flutter_inappwebview_android
sha256: "62557c15a5c2db5d195cb3892aab74fcaec266d7b86d59a6f0027abd672cddba"
url: "https://pub.dev"
source: hosted
version: "1.1.3"
flutter_inappwebview_internal_annotations:
dependency: transitive
description:
name: flutter_inappwebview_internal_annotations
sha256: e30fba942e3debea7b7e6cdd4f0f59ce89dd403a9865193e3221293b6d1544c6
url: "https://pub.dev"
source: hosted
version: "1.3.0"
flutter_inappwebview_ios:
dependency: transitive
description:
name: flutter_inappwebview_ios
sha256: "5818cf9b26cf0cbb0f62ff50772217d41ea8d3d9cc00279c45f8aabaa1b4025d"
url: "https://pub.dev"
source: hosted
version: "1.1.2"
flutter_inappwebview_macos:
dependency: transitive
description:
name: flutter_inappwebview_macos
sha256: c1fbb86af1a3738e3541364d7d1866315ffb0468a1a77e34198c9be571287da1
url: "https://pub.dev"
source: hosted
version: "1.1.2"
flutter_inappwebview_platform_interface:
dependency: transitive
description:
name: flutter_inappwebview_platform_interface
sha256: cf5323e194096b6ede7a1ca808c3e0a078e4b33cc3f6338977d75b4024ba2500
url: "https://pub.dev"
source: hosted
version: "1.3.0+1"
flutter_inappwebview_web:
dependency: transitive
description:
name: flutter_inappwebview_web
sha256: "55f89c83b0a0d3b7893306b3bb545ba4770a4df018204917148ebb42dc14a598"
url: "https://pub.dev"
source: hosted
version: "1.1.2"
flutter_inappwebview_windows:
dependency: transitive
description:
name: flutter_inappwebview_windows
sha256: "8b4d3a46078a2cdc636c4a3d10d10f2a16882f6be607962dbfff8874d1642055"
url: "https://pub.dev"
source: hosted
version: "0.6.0"
flutter_launcher_icons:
dependency: "direct dev"
description:
+1
View File
@@ -83,6 +83,7 @@ dependencies:
flutter_svg: ^2.0.17
flutter_markdown_plus: ^1.0.3
dart_ipc: ^1.0.1
flutter_inappwebview: ^6.1.5
flutter_local_notifications: 20.1.0
dependency_overrides:
permission_handler_windows: