mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-13 11:53:41 +00:00
feat: add WebView support for extension (#1359)
This commit is contained in:
92 files changed
+10248
-2676
No files matched your search
@@ -24,7 +24,7 @@ if (flutterVersionName == null) {
|
||||
|
||||
android {
|
||||
namespace 'com.gopeed.gopeed'
|
||||
compileSdk 35
|
||||
compileSdk 36
|
||||
ndkVersion flutter.ndkVersion
|
||||
|
||||
compileOptions {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in new issue
Block a user