mirror of
https://github.com/StarCitizenToolBox/app.git
synced 2026-01-13 19:50:28 +00:00
Merge pull request #161 from StarCitizenToolBox/feat-wry_webview
feat: Replace desktop_webview_window with tao&wry , from tauri
This commit is contained in:
commit
855ea1fe8f
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -1,3 +1,4 @@
|
||||
{
|
||||
"dart.flutterSdkPath": ".fvm/versions/stable"
|
||||
"dart.flutterSdkPath": ".fvm/versions/stable",
|
||||
"cmake.ignoreCMakeListsMissing": true
|
||||
}
|
||||
@ -10,7 +10,7 @@
|
||||
font-family: 'Material Icons';
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-size: 24px;
|
||||
font-size: 1.5rem; /* 24px */
|
||||
line-height: 1;
|
||||
letter-spacing: normal;
|
||||
text-transform: none;
|
||||
|
||||
@ -207,7 +207,7 @@ function ReportUnTranslate(k, v) {
|
||||
const jsRegex = /(?:^|[^<])<script[^>]*>[\s\S]*?<\/script>(?:[^>]|$)/i;
|
||||
if (k.trim() !== "" && !cnPattern.test(k) && !htmlPattern.test(k) && !cssRegex.test(k) && !jsRegex.test(k)
|
||||
&& enPattern.test(k) && !k.startsWith("http://") && !k.startsWith("https://")) {
|
||||
window.chrome.webview.postMessage({ action: 'webview_localization_capture', key: k, value: v });
|
||||
window.ipc.postMessage(JSON.stringify({ action: 'webview_localization_capture', key: k, value: v }));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -217,93 +217,149 @@ InitWebLocalization();
|
||||
|
||||
/// ----- Login Script ----
|
||||
async function getRSILauncherToken(channelId) {
|
||||
if (!window.location.href.includes("robertsspaceindustries.com")) return;
|
||||
|
||||
// check if logged in and fix redirect
|
||||
if (window.location.href.endsWith('/connect?jumpto=/account/dashboard')) {
|
||||
if (document.body.textContent.trim() === "/account/dashboard") {
|
||||
window.location.href = "https://robertsspaceindustries.com/account/dashboard";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let loginBodyElement = $(".c-form authenticationForm sign_in");
|
||||
loginBodyElement.show();
|
||||
// wait login
|
||||
window.chrome.webview.postMessage({ action: 'webview_rsi_login_show_window' });
|
||||
|
||||
// get claims
|
||||
let claimsR = await fetch("https://robertsspaceindustries.com/api/launcher/v3/games/claims", {
|
||||
method: 'POST', headers: {
|
||||
'x-rsi-token': $.cookie('Rsi-Token'),
|
||||
},
|
||||
});
|
||||
if (claimsR.status !== 200) return;
|
||||
|
||||
loginBodyElement.hide();
|
||||
SCTShowToast("登录游戏中...");
|
||||
|
||||
let claimsData = (await claimsR.json())["data"];
|
||||
|
||||
let tokenFormData = new FormData();
|
||||
tokenFormData.append('claims', claimsData);
|
||||
tokenFormData.append('gameId', 'SC');
|
||||
let tokenR = await fetch("https://robertsspaceindustries.com/api/launcher/v3/games/token", {
|
||||
method: 'POST', headers: {
|
||||
'x-rsi-token': $.cookie('Rsi-Token'),
|
||||
},
|
||||
body: tokenFormData
|
||||
});
|
||||
|
||||
if (tokenR.status !== 200) return;
|
||||
let TokenData = (await tokenR.json())["data"]["token"];
|
||||
console.log(TokenData);
|
||||
|
||||
// get release Data
|
||||
let releaseFormData = new FormData();
|
||||
releaseFormData.append("channelId", channelId);
|
||||
releaseFormData.append("claims", claimsData);
|
||||
releaseFormData.append("gameId", "SC");
|
||||
releaseFormData.append("platformId", "prod");
|
||||
let releaseR = await fetch("https://robertsspaceindustries.com/api/launcher/v3/games/release", {
|
||||
method: 'POST', headers: {
|
||||
'x-rsi-token': $.cookie('Rsi-Token'),
|
||||
},
|
||||
body: releaseFormData
|
||||
});
|
||||
if (releaseR.status !== 200) return;
|
||||
let releaseDataJson = (await releaseR.json())['data'];
|
||||
console.log(releaseDataJson);
|
||||
// get game library
|
||||
let libraryR = await fetch("https://robertsspaceindustries.com/api/launcher/v3/games/library", {
|
||||
method: 'POST', headers: {
|
||||
'x-rsi-token': $.cookie('Rsi-Token'),
|
||||
},
|
||||
body: releaseFormData
|
||||
});
|
||||
|
||||
let libraryData = (await libraryR.json())["data"]
|
||||
|
||||
// get user avatar
|
||||
let avatarUrl = $(".orion-c-avatar__image").attr("src");
|
||||
|
||||
//post message
|
||||
window.chrome.webview.postMessage({
|
||||
action: 'webview_rsi_login_success', data: {
|
||||
'webToken': $.cookie('Rsi-Token'),
|
||||
'claims': claimsData,
|
||||
'authToken': TokenData,
|
||||
'releaseInfo': releaseDataJson,
|
||||
"avatar": avatarUrl,
|
||||
'libraryData': libraryData,
|
||||
console.log('[SCToolbox] getRSILauncherToken called with channel:', channelId);
|
||||
|
||||
try {
|
||||
if (!window.location.href.includes("robertsspaceindustries.com")) {
|
||||
console.log('[SCToolbox] Not on RSI site, skipping');
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
// check if logged in and fix redirect
|
||||
if (window.location.href.endsWith('/connect?jumpto=/account/dashboard')) {
|
||||
if (document.body.textContent.trim() === "/account/dashboard") {
|
||||
window.location.href = "https://robertsspaceindustries.com/account/dashboard";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for jQuery to be ready
|
||||
let waitCount = 0;
|
||||
while (typeof $ === 'undefined' && waitCount < 50) {
|
||||
console.log('[SCToolbox] Waiting for jQuery... attempt', waitCount);
|
||||
await new Promise(r => setTimeout(r, 100));
|
||||
waitCount++;
|
||||
}
|
||||
|
||||
if (typeof $ === 'undefined') {
|
||||
console.error('[SCToolbox] jQuery not available after waiting');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get RSI token from cookie (don't rely on $.cookie plugin)
|
||||
function getCookie(name) {
|
||||
const value = `; ${document.cookie}`;
|
||||
const parts = value.split(`; ${name}=`);
|
||||
if (parts.length === 2) return parts.pop().split(';').shift();
|
||||
return null;
|
||||
}
|
||||
|
||||
const rsiToken = getCookie('Rsi-Token');
|
||||
console.log('[SCToolbox] RSI Token available:', !!rsiToken);
|
||||
|
||||
if (!rsiToken) {
|
||||
console.log('[SCToolbox] No RSI token, showing login window');
|
||||
let loginBodyElement = $(".c-form authenticationForm sign_in");
|
||||
loginBodyElement.show();
|
||||
window.ipc.postMessage(JSON.stringify({ action: 'webview_rsi_login_show_window' }));
|
||||
return;
|
||||
}
|
||||
|
||||
// get claims
|
||||
console.log('[SCToolbox] Fetching claims...');
|
||||
let claimsR = await fetch("https://robertsspaceindustries.com/api/launcher/v3/games/claims", {
|
||||
method: 'POST', headers: {
|
||||
'x-rsi-token': rsiToken,
|
||||
},
|
||||
});
|
||||
|
||||
console.log('[SCToolbox] Claims response status:', claimsR.status);
|
||||
if (claimsR.status !== 200) {
|
||||
console.error('[SCToolbox] Claims request failed');
|
||||
return;
|
||||
}
|
||||
|
||||
SCTShowToast("登录游戏中...");
|
||||
|
||||
let claimsData = (await claimsR.json())["data"];
|
||||
console.log('[SCToolbox] Claims data received');
|
||||
|
||||
let tokenFormData = new FormData();
|
||||
tokenFormData.append('claims', claimsData);
|
||||
tokenFormData.append('gameId', 'SC');
|
||||
let tokenR = await fetch("https://robertsspaceindustries.com/api/launcher/v3/games/token", {
|
||||
method: 'POST', headers: {
|
||||
'x-rsi-token': rsiToken,
|
||||
},
|
||||
body: tokenFormData
|
||||
});
|
||||
|
||||
console.log('[SCToolbox] Token response status:', tokenR.status);
|
||||
if (tokenR.status !== 200) {
|
||||
console.error('[SCToolbox] Token request failed');
|
||||
return;
|
||||
}
|
||||
let TokenData = (await tokenR.json())["data"]["token"];
|
||||
console.log('[SCToolbox] Token received');
|
||||
|
||||
// get release Data
|
||||
let releaseFormData = new FormData();
|
||||
releaseFormData.append("channelId", channelId);
|
||||
releaseFormData.append("claims", claimsData);
|
||||
releaseFormData.append("gameId", "SC");
|
||||
releaseFormData.append("platformId", "prod");
|
||||
let releaseR = await fetch("https://robertsspaceindustries.com/api/launcher/v3/games/release", {
|
||||
method: 'POST', headers: {
|
||||
'x-rsi-token': rsiToken,
|
||||
},
|
||||
body: releaseFormData
|
||||
});
|
||||
|
||||
console.log('[SCToolbox] Release response status:', releaseR.status);
|
||||
if (releaseR.status !== 200) {
|
||||
console.error('[SCToolbox] Release request failed');
|
||||
return;
|
||||
}
|
||||
let releaseDataJson = (await releaseR.json())['data'];
|
||||
console.log('[SCToolbox] Release data received');
|
||||
|
||||
// get game library
|
||||
let libraryR = await fetch("https://robertsspaceindustries.com/api/launcher/v3/games/library", {
|
||||
method: 'POST', headers: {
|
||||
'x-rsi-token': rsiToken,
|
||||
},
|
||||
body: releaseFormData
|
||||
});
|
||||
|
||||
let libraryData = (await libraryR.json())["data"];
|
||||
console.log('[SCToolbox] Library data received');
|
||||
|
||||
// get user avatar
|
||||
let avatarUrl = $(".orion-c-avatar__image").attr("src") || '';
|
||||
console.log('[SCToolbox] Avatar URL:', avatarUrl);
|
||||
|
||||
//post message
|
||||
console.log('[SCToolbox] Sending login success message...');
|
||||
window.ipc.postMessage(JSON.stringify({
|
||||
action: 'webview_rsi_login_success', data: {
|
||||
'webToken': rsiToken,
|
||||
'claims': claimsData,
|
||||
'authToken': TokenData,
|
||||
'releaseInfo': releaseDataJson,
|
||||
"avatar": avatarUrl,
|
||||
'libraryData': libraryData,
|
||||
}
|
||||
}));
|
||||
console.log('[SCToolbox] Login success message sent');
|
||||
} catch (error) {
|
||||
console.error('[SCToolbox] Error in getRSILauncherToken:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function SCTShowToast(message) {
|
||||
let m = document.createElement('div');
|
||||
m.innerHTML = message;
|
||||
m.style.cssText = "font-family:siyuan;max-width:60%;min-width: 150px;padding:0 14px;height: 40px;color: rgb(255, 255, 255);line-height: 40px;text-align: center;border-radius: 4px;position: fixed;top: 50%;left: 50%;transform: translate(-50%, -50%);z-index: 999999;background: rgba(0, 0, 0,.7);font-size: 16px;";
|
||||
m.style.cssText = "font-family:siyuan;max-width:60%;min-width: 9.375rem;padding:0 0.875rem;height: 2.5rem;color: rgb(255, 255, 255);line-height: 2.5rem;text-align: center;border-radius: 0.25rem;position: fixed;top: 50%;left: 50%;transform: translate(-50%, -50%);z-index: 999999;background: rgba(0, 0, 0,.7);font-size: 1rem;";
|
||||
document.body.appendChild(m);
|
||||
setTimeout(function () {
|
||||
let d = 0.5;
|
||||
|
||||
142
lib/common/rust/api/webview_api.dart
Normal file
142
lib/common/rust/api/webview_api.dart
Normal file
@ -0,0 +1,142 @@
|
||||
// This file is automatically generated, so please do not edit it.
|
||||
// @generated by `flutter_rust_bridge`@ 2.11.1.
|
||||
|
||||
// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import
|
||||
|
||||
import '../frb_generated.dart';
|
||||
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart' hide protected;
|
||||
part 'webview_api.freezed.dart';
|
||||
|
||||
// These functions are ignored because they are not marked as `pub`: `handle_command`, `load_app_icon`, `run_webview_loop`, `send_command`
|
||||
// These types are ignored because they are neither used by any `pub` functions nor (for structs and enums) marked `#[frb(unignore)]`: `UserEvent`, `WebViewCommand`, `WebViewInstance`
|
||||
// These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `clone`, `clone`, `clone`, `clone`, `clone`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`
|
||||
|
||||
/// Create a new WebView window and return its ID
|
||||
String webviewCreate({required WebViewConfiguration config}) =>
|
||||
RustLib.instance.api.crateApiWebviewApiWebviewCreate(config: config);
|
||||
|
||||
/// Navigate to a URL
|
||||
void webviewNavigate({required String id, required String url}) =>
|
||||
RustLib.instance.api.crateApiWebviewApiWebviewNavigate(id: id, url: url);
|
||||
|
||||
/// Go back in history
|
||||
void webviewGoBack({required String id}) =>
|
||||
RustLib.instance.api.crateApiWebviewApiWebviewGoBack(id: id);
|
||||
|
||||
/// Go forward in history
|
||||
void webviewGoForward({required String id}) =>
|
||||
RustLib.instance.api.crateApiWebviewApiWebviewGoForward(id: id);
|
||||
|
||||
/// Reload the current page
|
||||
void webviewReload({required String id}) =>
|
||||
RustLib.instance.api.crateApiWebviewApiWebviewReload(id: id);
|
||||
|
||||
/// Stop loading
|
||||
void webviewStop({required String id}) =>
|
||||
RustLib.instance.api.crateApiWebviewApiWebviewStop(id: id);
|
||||
|
||||
/// Execute JavaScript in the WebView
|
||||
void webviewExecuteScript({required String id, required String script}) =>
|
||||
RustLib.instance.api.crateApiWebviewApiWebviewExecuteScript(
|
||||
id: id,
|
||||
script: script,
|
||||
);
|
||||
|
||||
/// Set window visibility
|
||||
void webviewSetVisibility({required String id, required bool visible}) =>
|
||||
RustLib.instance.api.crateApiWebviewApiWebviewSetVisibility(
|
||||
id: id,
|
||||
visible: visible,
|
||||
);
|
||||
|
||||
/// Close the WebView window
|
||||
void webviewClose({required String id}) =>
|
||||
RustLib.instance.api.crateApiWebviewApiWebviewClose(id: id);
|
||||
|
||||
/// Set window size
|
||||
void webviewSetWindowSize({
|
||||
required String id,
|
||||
required int width,
|
||||
required int height,
|
||||
}) => RustLib.instance.api.crateApiWebviewApiWebviewSetWindowSize(
|
||||
id: id,
|
||||
width: width,
|
||||
height: height,
|
||||
);
|
||||
|
||||
/// Set window position
|
||||
void webviewSetWindowPosition({
|
||||
required String id,
|
||||
required int x,
|
||||
required int y,
|
||||
}) => RustLib.instance.api.crateApiWebviewApiWebviewSetWindowPosition(
|
||||
id: id,
|
||||
x: x,
|
||||
y: y,
|
||||
);
|
||||
|
||||
/// Get the current navigation state
|
||||
WebViewNavigationState webviewGetState({required String id}) =>
|
||||
RustLib.instance.api.crateApiWebviewApiWebviewGetState(id: id);
|
||||
|
||||
/// Check if the WebView is closed
|
||||
bool webviewIsClosed({required String id}) =>
|
||||
RustLib.instance.api.crateApiWebviewApiWebviewIsClosed(id: id);
|
||||
|
||||
/// Poll for events from the WebView (non-blocking)
|
||||
List<WebViewEvent> webviewPollEvents({required String id}) =>
|
||||
RustLib.instance.api.crateApiWebviewApiWebviewPollEvents(id: id);
|
||||
|
||||
/// Get a list of all active WebView IDs
|
||||
List<String> webviewListAll() =>
|
||||
RustLib.instance.api.crateApiWebviewApiWebviewListAll();
|
||||
|
||||
/// WebView window configuration
|
||||
@freezed
|
||||
sealed class WebViewConfiguration with _$WebViewConfiguration {
|
||||
const WebViewConfiguration._();
|
||||
const factory WebViewConfiguration({
|
||||
required String title,
|
||||
required int width,
|
||||
required int height,
|
||||
String? userDataFolder,
|
||||
required bool enableDevtools,
|
||||
required bool transparent,
|
||||
String? userAgent,
|
||||
}) = _WebViewConfiguration;
|
||||
static Future<WebViewConfiguration> default_() =>
|
||||
RustLib.instance.api.crateApiWebviewApiWebViewConfigurationDefault();
|
||||
}
|
||||
|
||||
@freezed
|
||||
sealed class WebViewEvent with _$WebViewEvent {
|
||||
const WebViewEvent._();
|
||||
|
||||
const factory WebViewEvent.navigationStarted({required String url}) =
|
||||
WebViewEvent_NavigationStarted;
|
||||
const factory WebViewEvent.navigationCompleted({required String url}) =
|
||||
WebViewEvent_NavigationCompleted;
|
||||
const factory WebViewEvent.titleChanged({required String title}) =
|
||||
WebViewEvent_TitleChanged;
|
||||
const factory WebViewEvent.webMessage({required String message}) =
|
||||
WebViewEvent_WebMessage;
|
||||
const factory WebViewEvent.windowClosed() = WebViewEvent_WindowClosed;
|
||||
const factory WebViewEvent.error({required String message}) =
|
||||
WebViewEvent_Error;
|
||||
}
|
||||
|
||||
/// Navigation state of the WebView
|
||||
@freezed
|
||||
sealed class WebViewNavigationState with _$WebViewNavigationState {
|
||||
const WebViewNavigationState._();
|
||||
const factory WebViewNavigationState({
|
||||
required String url,
|
||||
required String title,
|
||||
required bool canGoBack,
|
||||
required bool canGoForward,
|
||||
required bool isLoading,
|
||||
}) = _WebViewNavigationState;
|
||||
static Future<WebViewNavigationState> default_() =>
|
||||
RustLib.instance.api.crateApiWebviewApiWebViewNavigationStateDefault();
|
||||
}
|
||||
1092
lib/common/rust/api/webview_api.freezed.dart
Normal file
1092
lib/common/rust/api/webview_api.freezed.dart
Normal file
File diff suppressed because it is too large
Load Diff
@ -6,6 +6,7 @@
|
||||
import '../frb_generated.dart';
|
||||
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
||||
|
||||
// These functions are ignored because they are not marked as `pub`: `get_process_path`
|
||||
// These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `clone`, `fmt`
|
||||
|
||||
Future<void> sendNotify({
|
||||
|
||||
@ -8,6 +8,7 @@ import 'api/http_api.dart';
|
||||
import 'api/ort_api.dart';
|
||||
import 'api/rs_process.dart';
|
||||
import 'api/unp4k_api.dart';
|
||||
import 'api/webview_api.dart';
|
||||
import 'api/win32_api.dart';
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
@ -70,7 +71,7 @@ class RustLib extends BaseEntrypoint<RustLibApi, RustLibApiImpl, RustLibWire> {
|
||||
String get codegenVersion => '2.11.1';
|
||||
|
||||
@override
|
||||
int get rustContentHash => 1801517256;
|
||||
int get rustContentHash => -1082688871;
|
||||
|
||||
static const kDefaultExternalLibraryLoaderConfig =
|
||||
ExternalLibraryLoaderConfig(
|
||||
@ -175,6 +176,62 @@ abstract class RustLibApi extends BaseApi {
|
||||
|
||||
Future<void> crateApiOrtApiUnloadTranslationModel({required String modelKey});
|
||||
|
||||
Future<WebViewConfiguration> crateApiWebviewApiWebViewConfigurationDefault();
|
||||
|
||||
Future<WebViewNavigationState>
|
||||
crateApiWebviewApiWebViewNavigationStateDefault();
|
||||
|
||||
void crateApiWebviewApiWebviewClose({required String id});
|
||||
|
||||
String crateApiWebviewApiWebviewCreate({
|
||||
required WebViewConfiguration config,
|
||||
});
|
||||
|
||||
void crateApiWebviewApiWebviewExecuteScript({
|
||||
required String id,
|
||||
required String script,
|
||||
});
|
||||
|
||||
WebViewNavigationState crateApiWebviewApiWebviewGetState({
|
||||
required String id,
|
||||
});
|
||||
|
||||
void crateApiWebviewApiWebviewGoBack({required String id});
|
||||
|
||||
void crateApiWebviewApiWebviewGoForward({required String id});
|
||||
|
||||
bool crateApiWebviewApiWebviewIsClosed({required String id});
|
||||
|
||||
List<String> crateApiWebviewApiWebviewListAll();
|
||||
|
||||
void crateApiWebviewApiWebviewNavigate({
|
||||
required String id,
|
||||
required String url,
|
||||
});
|
||||
|
||||
List<WebViewEvent> crateApiWebviewApiWebviewPollEvents({required String id});
|
||||
|
||||
void crateApiWebviewApiWebviewReload({required String id});
|
||||
|
||||
void crateApiWebviewApiWebviewSetVisibility({
|
||||
required String id,
|
||||
required bool visible,
|
||||
});
|
||||
|
||||
void crateApiWebviewApiWebviewSetWindowPosition({
|
||||
required String id,
|
||||
required int x,
|
||||
required int y,
|
||||
});
|
||||
|
||||
void crateApiWebviewApiWebviewSetWindowSize({
|
||||
required String id,
|
||||
required int width,
|
||||
required int height,
|
||||
});
|
||||
|
||||
void crateApiWebviewApiWebviewStop({required String id});
|
||||
|
||||
Future<void> crateApiRsProcessWrite({
|
||||
required int rsPid,
|
||||
required String data,
|
||||
@ -893,6 +950,451 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
argNames: ["modelKey"],
|
||||
);
|
||||
|
||||
@override
|
||||
Future<WebViewConfiguration> crateApiWebviewApiWebViewConfigurationDefault() {
|
||||
return handler.executeNormal(
|
||||
NormalTask(
|
||||
callFfi: (port_) {
|
||||
return wire
|
||||
.wire__crate__api__webview_api__web_view_configuration_default(
|
||||
port_,
|
||||
);
|
||||
},
|
||||
codec: DcoCodec(
|
||||
decodeSuccessData: dco_decode_web_view_configuration,
|
||||
decodeErrorData: null,
|
||||
),
|
||||
constMeta: kCrateApiWebviewApiWebViewConfigurationDefaultConstMeta,
|
||||
argValues: [],
|
||||
apiImpl: this,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
TaskConstMeta get kCrateApiWebviewApiWebViewConfigurationDefaultConstMeta =>
|
||||
const TaskConstMeta(
|
||||
debugName: "web_view_configuration_default",
|
||||
argNames: [],
|
||||
);
|
||||
|
||||
@override
|
||||
Future<WebViewNavigationState>
|
||||
crateApiWebviewApiWebViewNavigationStateDefault() {
|
||||
return handler.executeNormal(
|
||||
NormalTask(
|
||||
callFfi: (port_) {
|
||||
return wire
|
||||
.wire__crate__api__webview_api__web_view_navigation_state_default(
|
||||
port_,
|
||||
);
|
||||
},
|
||||
codec: DcoCodec(
|
||||
decodeSuccessData: dco_decode_web_view_navigation_state,
|
||||
decodeErrorData: null,
|
||||
),
|
||||
constMeta: kCrateApiWebviewApiWebViewNavigationStateDefaultConstMeta,
|
||||
argValues: [],
|
||||
apiImpl: this,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
TaskConstMeta get kCrateApiWebviewApiWebViewNavigationStateDefaultConstMeta =>
|
||||
const TaskConstMeta(
|
||||
debugName: "web_view_navigation_state_default",
|
||||
argNames: [],
|
||||
);
|
||||
|
||||
@override
|
||||
void crateApiWebviewApiWebviewClose({required String id}) {
|
||||
return handler.executeSync(
|
||||
SyncTask(
|
||||
callFfi: () {
|
||||
var arg0 = cst_encode_String(id);
|
||||
return wire.wire__crate__api__webview_api__webview_close(arg0);
|
||||
},
|
||||
codec: DcoCodec(
|
||||
decodeSuccessData: dco_decode_unit,
|
||||
decodeErrorData: dco_decode_String,
|
||||
),
|
||||
constMeta: kCrateApiWebviewApiWebviewCloseConstMeta,
|
||||
argValues: [id],
|
||||
apiImpl: this,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
TaskConstMeta get kCrateApiWebviewApiWebviewCloseConstMeta =>
|
||||
const TaskConstMeta(debugName: "webview_close", argNames: ["id"]);
|
||||
|
||||
@override
|
||||
String crateApiWebviewApiWebviewCreate({
|
||||
required WebViewConfiguration config,
|
||||
}) {
|
||||
return handler.executeSync(
|
||||
SyncTask(
|
||||
callFfi: () {
|
||||
var arg0 = cst_encode_box_autoadd_web_view_configuration(config);
|
||||
return wire.wire__crate__api__webview_api__webview_create(arg0);
|
||||
},
|
||||
codec: DcoCodec(
|
||||
decodeSuccessData: dco_decode_String,
|
||||
decodeErrorData: dco_decode_String,
|
||||
),
|
||||
constMeta: kCrateApiWebviewApiWebviewCreateConstMeta,
|
||||
argValues: [config],
|
||||
apiImpl: this,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
TaskConstMeta get kCrateApiWebviewApiWebviewCreateConstMeta =>
|
||||
const TaskConstMeta(debugName: "webview_create", argNames: ["config"]);
|
||||
|
||||
@override
|
||||
void crateApiWebviewApiWebviewExecuteScript({
|
||||
required String id,
|
||||
required String script,
|
||||
}) {
|
||||
return handler.executeSync(
|
||||
SyncTask(
|
||||
callFfi: () {
|
||||
var arg0 = cst_encode_String(id);
|
||||
var arg1 = cst_encode_String(script);
|
||||
return wire.wire__crate__api__webview_api__webview_execute_script(
|
||||
arg0,
|
||||
arg1,
|
||||
);
|
||||
},
|
||||
codec: DcoCodec(
|
||||
decodeSuccessData: dco_decode_unit,
|
||||
decodeErrorData: dco_decode_String,
|
||||
),
|
||||
constMeta: kCrateApiWebviewApiWebviewExecuteScriptConstMeta,
|
||||
argValues: [id, script],
|
||||
apiImpl: this,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
TaskConstMeta get kCrateApiWebviewApiWebviewExecuteScriptConstMeta =>
|
||||
const TaskConstMeta(
|
||||
debugName: "webview_execute_script",
|
||||
argNames: ["id", "script"],
|
||||
);
|
||||
|
||||
@override
|
||||
WebViewNavigationState crateApiWebviewApiWebviewGetState({
|
||||
required String id,
|
||||
}) {
|
||||
return handler.executeSync(
|
||||
SyncTask(
|
||||
callFfi: () {
|
||||
var arg0 = cst_encode_String(id);
|
||||
return wire.wire__crate__api__webview_api__webview_get_state(arg0);
|
||||
},
|
||||
codec: DcoCodec(
|
||||
decodeSuccessData: dco_decode_web_view_navigation_state,
|
||||
decodeErrorData: dco_decode_String,
|
||||
),
|
||||
constMeta: kCrateApiWebviewApiWebviewGetStateConstMeta,
|
||||
argValues: [id],
|
||||
apiImpl: this,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
TaskConstMeta get kCrateApiWebviewApiWebviewGetStateConstMeta =>
|
||||
const TaskConstMeta(debugName: "webview_get_state", argNames: ["id"]);
|
||||
|
||||
@override
|
||||
void crateApiWebviewApiWebviewGoBack({required String id}) {
|
||||
return handler.executeSync(
|
||||
SyncTask(
|
||||
callFfi: () {
|
||||
var arg0 = cst_encode_String(id);
|
||||
return wire.wire__crate__api__webview_api__webview_go_back(arg0);
|
||||
},
|
||||
codec: DcoCodec(
|
||||
decodeSuccessData: dco_decode_unit,
|
||||
decodeErrorData: dco_decode_String,
|
||||
),
|
||||
constMeta: kCrateApiWebviewApiWebviewGoBackConstMeta,
|
||||
argValues: [id],
|
||||
apiImpl: this,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
TaskConstMeta get kCrateApiWebviewApiWebviewGoBackConstMeta =>
|
||||
const TaskConstMeta(debugName: "webview_go_back", argNames: ["id"]);
|
||||
|
||||
@override
|
||||
void crateApiWebviewApiWebviewGoForward({required String id}) {
|
||||
return handler.executeSync(
|
||||
SyncTask(
|
||||
callFfi: () {
|
||||
var arg0 = cst_encode_String(id);
|
||||
return wire.wire__crate__api__webview_api__webview_go_forward(arg0);
|
||||
},
|
||||
codec: DcoCodec(
|
||||
decodeSuccessData: dco_decode_unit,
|
||||
decodeErrorData: dco_decode_String,
|
||||
),
|
||||
constMeta: kCrateApiWebviewApiWebviewGoForwardConstMeta,
|
||||
argValues: [id],
|
||||
apiImpl: this,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
TaskConstMeta get kCrateApiWebviewApiWebviewGoForwardConstMeta =>
|
||||
const TaskConstMeta(debugName: "webview_go_forward", argNames: ["id"]);
|
||||
|
||||
@override
|
||||
bool crateApiWebviewApiWebviewIsClosed({required String id}) {
|
||||
return handler.executeSync(
|
||||
SyncTask(
|
||||
callFfi: () {
|
||||
var arg0 = cst_encode_String(id);
|
||||
return wire.wire__crate__api__webview_api__webview_is_closed(arg0);
|
||||
},
|
||||
codec: DcoCodec(
|
||||
decodeSuccessData: dco_decode_bool,
|
||||
decodeErrorData: null,
|
||||
),
|
||||
constMeta: kCrateApiWebviewApiWebviewIsClosedConstMeta,
|
||||
argValues: [id],
|
||||
apiImpl: this,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
TaskConstMeta get kCrateApiWebviewApiWebviewIsClosedConstMeta =>
|
||||
const TaskConstMeta(debugName: "webview_is_closed", argNames: ["id"]);
|
||||
|
||||
@override
|
||||
List<String> crateApiWebviewApiWebviewListAll() {
|
||||
return handler.executeSync(
|
||||
SyncTask(
|
||||
callFfi: () {
|
||||
return wire.wire__crate__api__webview_api__webview_list_all();
|
||||
},
|
||||
codec: DcoCodec(
|
||||
decodeSuccessData: dco_decode_list_String,
|
||||
decodeErrorData: null,
|
||||
),
|
||||
constMeta: kCrateApiWebviewApiWebviewListAllConstMeta,
|
||||
argValues: [],
|
||||
apiImpl: this,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
TaskConstMeta get kCrateApiWebviewApiWebviewListAllConstMeta =>
|
||||
const TaskConstMeta(debugName: "webview_list_all", argNames: []);
|
||||
|
||||
@override
|
||||
void crateApiWebviewApiWebviewNavigate({
|
||||
required String id,
|
||||
required String url,
|
||||
}) {
|
||||
return handler.executeSync(
|
||||
SyncTask(
|
||||
callFfi: () {
|
||||
var arg0 = cst_encode_String(id);
|
||||
var arg1 = cst_encode_String(url);
|
||||
return wire.wire__crate__api__webview_api__webview_navigate(
|
||||
arg0,
|
||||
arg1,
|
||||
);
|
||||
},
|
||||
codec: DcoCodec(
|
||||
decodeSuccessData: dco_decode_unit,
|
||||
decodeErrorData: dco_decode_String,
|
||||
),
|
||||
constMeta: kCrateApiWebviewApiWebviewNavigateConstMeta,
|
||||
argValues: [id, url],
|
||||
apiImpl: this,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
TaskConstMeta get kCrateApiWebviewApiWebviewNavigateConstMeta =>
|
||||
const TaskConstMeta(
|
||||
debugName: "webview_navigate",
|
||||
argNames: ["id", "url"],
|
||||
);
|
||||
|
||||
@override
|
||||
List<WebViewEvent> crateApiWebviewApiWebviewPollEvents({required String id}) {
|
||||
return handler.executeSync(
|
||||
SyncTask(
|
||||
callFfi: () {
|
||||
var arg0 = cst_encode_String(id);
|
||||
return wire.wire__crate__api__webview_api__webview_poll_events(arg0);
|
||||
},
|
||||
codec: DcoCodec(
|
||||
decodeSuccessData: dco_decode_list_web_view_event,
|
||||
decodeErrorData: null,
|
||||
),
|
||||
constMeta: kCrateApiWebviewApiWebviewPollEventsConstMeta,
|
||||
argValues: [id],
|
||||
apiImpl: this,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
TaskConstMeta get kCrateApiWebviewApiWebviewPollEventsConstMeta =>
|
||||
const TaskConstMeta(debugName: "webview_poll_events", argNames: ["id"]);
|
||||
|
||||
@override
|
||||
void crateApiWebviewApiWebviewReload({required String id}) {
|
||||
return handler.executeSync(
|
||||
SyncTask(
|
||||
callFfi: () {
|
||||
var arg0 = cst_encode_String(id);
|
||||
return wire.wire__crate__api__webview_api__webview_reload(arg0);
|
||||
},
|
||||
codec: DcoCodec(
|
||||
decodeSuccessData: dco_decode_unit,
|
||||
decodeErrorData: dco_decode_String,
|
||||
),
|
||||
constMeta: kCrateApiWebviewApiWebviewReloadConstMeta,
|
||||
argValues: [id],
|
||||
apiImpl: this,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
TaskConstMeta get kCrateApiWebviewApiWebviewReloadConstMeta =>
|
||||
const TaskConstMeta(debugName: "webview_reload", argNames: ["id"]);
|
||||
|
||||
@override
|
||||
void crateApiWebviewApiWebviewSetVisibility({
|
||||
required String id,
|
||||
required bool visible,
|
||||
}) {
|
||||
return handler.executeSync(
|
||||
SyncTask(
|
||||
callFfi: () {
|
||||
var arg0 = cst_encode_String(id);
|
||||
var arg1 = cst_encode_bool(visible);
|
||||
return wire.wire__crate__api__webview_api__webview_set_visibility(
|
||||
arg0,
|
||||
arg1,
|
||||
);
|
||||
},
|
||||
codec: DcoCodec(
|
||||
decodeSuccessData: dco_decode_unit,
|
||||
decodeErrorData: dco_decode_String,
|
||||
),
|
||||
constMeta: kCrateApiWebviewApiWebviewSetVisibilityConstMeta,
|
||||
argValues: [id, visible],
|
||||
apiImpl: this,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
TaskConstMeta get kCrateApiWebviewApiWebviewSetVisibilityConstMeta =>
|
||||
const TaskConstMeta(
|
||||
debugName: "webview_set_visibility",
|
||||
argNames: ["id", "visible"],
|
||||
);
|
||||
|
||||
@override
|
||||
void crateApiWebviewApiWebviewSetWindowPosition({
|
||||
required String id,
|
||||
required int x,
|
||||
required int y,
|
||||
}) {
|
||||
return handler.executeSync(
|
||||
SyncTask(
|
||||
callFfi: () {
|
||||
var arg0 = cst_encode_String(id);
|
||||
var arg1 = cst_encode_i_32(x);
|
||||
var arg2 = cst_encode_i_32(y);
|
||||
return wire
|
||||
.wire__crate__api__webview_api__webview_set_window_position(
|
||||
arg0,
|
||||
arg1,
|
||||
arg2,
|
||||
);
|
||||
},
|
||||
codec: DcoCodec(
|
||||
decodeSuccessData: dco_decode_unit,
|
||||
decodeErrorData: dco_decode_String,
|
||||
),
|
||||
constMeta: kCrateApiWebviewApiWebviewSetWindowPositionConstMeta,
|
||||
argValues: [id, x, y],
|
||||
apiImpl: this,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
TaskConstMeta get kCrateApiWebviewApiWebviewSetWindowPositionConstMeta =>
|
||||
const TaskConstMeta(
|
||||
debugName: "webview_set_window_position",
|
||||
argNames: ["id", "x", "y"],
|
||||
);
|
||||
|
||||
@override
|
||||
void crateApiWebviewApiWebviewSetWindowSize({
|
||||
required String id,
|
||||
required int width,
|
||||
required int height,
|
||||
}) {
|
||||
return handler.executeSync(
|
||||
SyncTask(
|
||||
callFfi: () {
|
||||
var arg0 = cst_encode_String(id);
|
||||
var arg1 = cst_encode_u_32(width);
|
||||
var arg2 = cst_encode_u_32(height);
|
||||
return wire.wire__crate__api__webview_api__webview_set_window_size(
|
||||
arg0,
|
||||
arg1,
|
||||
arg2,
|
||||
);
|
||||
},
|
||||
codec: DcoCodec(
|
||||
decodeSuccessData: dco_decode_unit,
|
||||
decodeErrorData: dco_decode_String,
|
||||
),
|
||||
constMeta: kCrateApiWebviewApiWebviewSetWindowSizeConstMeta,
|
||||
argValues: [id, width, height],
|
||||
apiImpl: this,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
TaskConstMeta get kCrateApiWebviewApiWebviewSetWindowSizeConstMeta =>
|
||||
const TaskConstMeta(
|
||||
debugName: "webview_set_window_size",
|
||||
argNames: ["id", "width", "height"],
|
||||
);
|
||||
|
||||
@override
|
||||
void crateApiWebviewApiWebviewStop({required String id}) {
|
||||
return handler.executeSync(
|
||||
SyncTask(
|
||||
callFfi: () {
|
||||
var arg0 = cst_encode_String(id);
|
||||
return wire.wire__crate__api__webview_api__webview_stop(arg0);
|
||||
},
|
||||
codec: DcoCodec(
|
||||
decodeSuccessData: dco_decode_unit,
|
||||
decodeErrorData: dco_decode_String,
|
||||
),
|
||||
constMeta: kCrateApiWebviewApiWebviewStopConstMeta,
|
||||
argValues: [id],
|
||||
apiImpl: this,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
TaskConstMeta get kCrateApiWebviewApiWebviewStopConstMeta =>
|
||||
const TaskConstMeta(debugName: "webview_stop", argNames: ["id"]);
|
||||
|
||||
@override
|
||||
Future<void> crateApiRsProcessWrite({
|
||||
required int rsPid,
|
||||
@ -974,6 +1476,14 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
return dco_decode_u_64(raw);
|
||||
}
|
||||
|
||||
@protected
|
||||
WebViewConfiguration dco_decode_box_autoadd_web_view_configuration(
|
||||
dynamic raw,
|
||||
) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
return dco_decode_web_view_configuration(raw);
|
||||
}
|
||||
|
||||
@protected
|
||||
int dco_decode_i_32(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
@ -1022,6 +1532,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
return (raw as List<dynamic>).map(dco_decode_record_string_string).toList();
|
||||
}
|
||||
|
||||
@protected
|
||||
List<WebViewEvent> dco_decode_list_web_view_event(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
return (raw as List<dynamic>).map(dco_decode_web_view_event).toList();
|
||||
}
|
||||
|
||||
@protected
|
||||
MyHttpVersion dco_decode_my_http_version(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
@ -1187,6 +1703,59 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
return dcoDecodeU64(raw);
|
||||
}
|
||||
|
||||
@protected
|
||||
WebViewConfiguration dco_decode_web_view_configuration(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
final arr = raw as List<dynamic>;
|
||||
if (arr.length != 7)
|
||||
throw Exception('unexpected arr length: expect 7 but see ${arr.length}');
|
||||
return WebViewConfiguration(
|
||||
title: dco_decode_String(arr[0]),
|
||||
width: dco_decode_u_32(arr[1]),
|
||||
height: dco_decode_u_32(arr[2]),
|
||||
userDataFolder: dco_decode_opt_String(arr[3]),
|
||||
enableDevtools: dco_decode_bool(arr[4]),
|
||||
transparent: dco_decode_bool(arr[5]),
|
||||
userAgent: dco_decode_opt_String(arr[6]),
|
||||
);
|
||||
}
|
||||
|
||||
@protected
|
||||
WebViewEvent dco_decode_web_view_event(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
switch (raw[0]) {
|
||||
case 0:
|
||||
return WebViewEvent_NavigationStarted(url: dco_decode_String(raw[1]));
|
||||
case 1:
|
||||
return WebViewEvent_NavigationCompleted(url: dco_decode_String(raw[1]));
|
||||
case 2:
|
||||
return WebViewEvent_TitleChanged(title: dco_decode_String(raw[1]));
|
||||
case 3:
|
||||
return WebViewEvent_WebMessage(message: dco_decode_String(raw[1]));
|
||||
case 4:
|
||||
return WebViewEvent_WindowClosed();
|
||||
case 5:
|
||||
return WebViewEvent_Error(message: dco_decode_String(raw[1]));
|
||||
default:
|
||||
throw Exception("unreachable");
|
||||
}
|
||||
}
|
||||
|
||||
@protected
|
||||
WebViewNavigationState dco_decode_web_view_navigation_state(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
final arr = raw as List<dynamic>;
|
||||
if (arr.length != 5)
|
||||
throw Exception('unexpected arr length: expect 5 but see ${arr.length}');
|
||||
return WebViewNavigationState(
|
||||
url: dco_decode_String(arr[0]),
|
||||
title: dco_decode_String(arr[1]),
|
||||
canGoBack: dco_decode_bool(arr[2]),
|
||||
canGoForward: dco_decode_bool(arr[3]),
|
||||
isLoading: dco_decode_bool(arr[4]),
|
||||
);
|
||||
}
|
||||
|
||||
@protected
|
||||
AnyhowException sse_decode_AnyhowException(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
@ -1245,6 +1814,14 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
return (sse_decode_u_64(deserializer));
|
||||
}
|
||||
|
||||
@protected
|
||||
WebViewConfiguration sse_decode_box_autoadd_web_view_configuration(
|
||||
SseDeserializer deserializer,
|
||||
) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
return (sse_decode_web_view_configuration(deserializer));
|
||||
}
|
||||
|
||||
@protected
|
||||
int sse_decode_i_32(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
@ -1323,6 +1900,20 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
return ans_;
|
||||
}
|
||||
|
||||
@protected
|
||||
List<WebViewEvent> sse_decode_list_web_view_event(
|
||||
SseDeserializer deserializer,
|
||||
) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
|
||||
var len_ = sse_decode_i_32(deserializer);
|
||||
var ans_ = <WebViewEvent>[];
|
||||
for (var idx_ = 0; idx_ < len_; ++idx_) {
|
||||
ans_.add(sse_decode_web_view_event(deserializer));
|
||||
}
|
||||
return ans_;
|
||||
}
|
||||
|
||||
@protected
|
||||
MyHttpVersion sse_decode_my_http_version(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
@ -1525,6 +2116,76 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
return deserializer.buffer.getBigUint64();
|
||||
}
|
||||
|
||||
@protected
|
||||
WebViewConfiguration sse_decode_web_view_configuration(
|
||||
SseDeserializer deserializer,
|
||||
) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
var var_title = sse_decode_String(deserializer);
|
||||
var var_width = sse_decode_u_32(deserializer);
|
||||
var var_height = sse_decode_u_32(deserializer);
|
||||
var var_userDataFolder = sse_decode_opt_String(deserializer);
|
||||
var var_enableDevtools = sse_decode_bool(deserializer);
|
||||
var var_transparent = sse_decode_bool(deserializer);
|
||||
var var_userAgent = sse_decode_opt_String(deserializer);
|
||||
return WebViewConfiguration(
|
||||
title: var_title,
|
||||
width: var_width,
|
||||
height: var_height,
|
||||
userDataFolder: var_userDataFolder,
|
||||
enableDevtools: var_enableDevtools,
|
||||
transparent: var_transparent,
|
||||
userAgent: var_userAgent,
|
||||
);
|
||||
}
|
||||
|
||||
@protected
|
||||
WebViewEvent sse_decode_web_view_event(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
|
||||
var tag_ = sse_decode_i_32(deserializer);
|
||||
switch (tag_) {
|
||||
case 0:
|
||||
var var_url = sse_decode_String(deserializer);
|
||||
return WebViewEvent_NavigationStarted(url: var_url);
|
||||
case 1:
|
||||
var var_url = sse_decode_String(deserializer);
|
||||
return WebViewEvent_NavigationCompleted(url: var_url);
|
||||
case 2:
|
||||
var var_title = sse_decode_String(deserializer);
|
||||
return WebViewEvent_TitleChanged(title: var_title);
|
||||
case 3:
|
||||
var var_message = sse_decode_String(deserializer);
|
||||
return WebViewEvent_WebMessage(message: var_message);
|
||||
case 4:
|
||||
return WebViewEvent_WindowClosed();
|
||||
case 5:
|
||||
var var_message = sse_decode_String(deserializer);
|
||||
return WebViewEvent_Error(message: var_message);
|
||||
default:
|
||||
throw UnimplementedError('');
|
||||
}
|
||||
}
|
||||
|
||||
@protected
|
||||
WebViewNavigationState sse_decode_web_view_navigation_state(
|
||||
SseDeserializer deserializer,
|
||||
) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
var var_url = sse_decode_String(deserializer);
|
||||
var var_title = sse_decode_String(deserializer);
|
||||
var var_canGoBack = sse_decode_bool(deserializer);
|
||||
var var_canGoForward = sse_decode_bool(deserializer);
|
||||
var var_isLoading = sse_decode_bool(deserializer);
|
||||
return WebViewNavigationState(
|
||||
url: var_url,
|
||||
title: var_title,
|
||||
canGoBack: var_canGoBack,
|
||||
canGoForward: var_canGoForward,
|
||||
isLoading: var_isLoading,
|
||||
);
|
||||
}
|
||||
|
||||
@protected
|
||||
bool cst_encode_bool(bool raw) {
|
||||
// Codec=Cst (C-struct based), see doc to use other codecs
|
||||
@ -1650,6 +2311,15 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
sse_encode_u_64(self, serializer);
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_web_view_configuration(
|
||||
WebViewConfiguration self,
|
||||
SseSerializer serializer,
|
||||
) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
sse_encode_web_view_configuration(self, serializer);
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_i_32(int self, SseSerializer serializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
@ -1729,6 +2399,18 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
}
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_list_web_view_event(
|
||||
List<WebViewEvent> self,
|
||||
SseSerializer serializer,
|
||||
) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
sse_encode_i_32(self.length, serializer);
|
||||
for (final item in self) {
|
||||
sse_encode_web_view_event(item, serializer);
|
||||
}
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_my_http_version(
|
||||
MyHttpVersion self,
|
||||
@ -1908,4 +2590,56 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
serializer.buffer.putBigUint64(self);
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_web_view_configuration(
|
||||
WebViewConfiguration self,
|
||||
SseSerializer serializer,
|
||||
) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
sse_encode_String(self.title, serializer);
|
||||
sse_encode_u_32(self.width, serializer);
|
||||
sse_encode_u_32(self.height, serializer);
|
||||
sse_encode_opt_String(self.userDataFolder, serializer);
|
||||
sse_encode_bool(self.enableDevtools, serializer);
|
||||
sse_encode_bool(self.transparent, serializer);
|
||||
sse_encode_opt_String(self.userAgent, serializer);
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_web_view_event(WebViewEvent self, SseSerializer serializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
switch (self) {
|
||||
case WebViewEvent_NavigationStarted(url: final url):
|
||||
sse_encode_i_32(0, serializer);
|
||||
sse_encode_String(url, serializer);
|
||||
case WebViewEvent_NavigationCompleted(url: final url):
|
||||
sse_encode_i_32(1, serializer);
|
||||
sse_encode_String(url, serializer);
|
||||
case WebViewEvent_TitleChanged(title: final title):
|
||||
sse_encode_i_32(2, serializer);
|
||||
sse_encode_String(title, serializer);
|
||||
case WebViewEvent_WebMessage(message: final message):
|
||||
sse_encode_i_32(3, serializer);
|
||||
sse_encode_String(message, serializer);
|
||||
case WebViewEvent_WindowClosed():
|
||||
sse_encode_i_32(4, serializer);
|
||||
case WebViewEvent_Error(message: final message):
|
||||
sse_encode_i_32(5, serializer);
|
||||
sse_encode_String(message, serializer);
|
||||
}
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_web_view_navigation_state(
|
||||
WebViewNavigationState self,
|
||||
SseSerializer serializer,
|
||||
) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
sse_encode_String(self.url, serializer);
|
||||
sse_encode_String(self.title, serializer);
|
||||
sse_encode_bool(self.canGoBack, serializer);
|
||||
sse_encode_bool(self.canGoForward, serializer);
|
||||
sse_encode_bool(self.isLoading, serializer);
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ import 'api/http_api.dart';
|
||||
import 'api/ort_api.dart';
|
||||
import 'api/rs_process.dart';
|
||||
import 'api/unp4k_api.dart';
|
||||
import 'api/webview_api.dart';
|
||||
import 'api/win32_api.dart';
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
@ -51,6 +52,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
BigInt dco_decode_box_autoadd_u_64(dynamic raw);
|
||||
|
||||
@protected
|
||||
WebViewConfiguration dco_decode_box_autoadd_web_view_configuration(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
int dco_decode_i_32(dynamic raw);
|
||||
|
||||
@ -75,6 +81,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
List<(String, String)> dco_decode_list_record_string_string(dynamic raw);
|
||||
|
||||
@protected
|
||||
List<WebViewEvent> dco_decode_list_web_view_event(dynamic raw);
|
||||
|
||||
@protected
|
||||
MyHttpVersion dco_decode_my_http_version(dynamic raw);
|
||||
|
||||
@ -135,6 +144,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
BigInt dco_decode_usize(dynamic raw);
|
||||
|
||||
@protected
|
||||
WebViewConfiguration dco_decode_web_view_configuration(dynamic raw);
|
||||
|
||||
@protected
|
||||
WebViewEvent dco_decode_web_view_event(dynamic raw);
|
||||
|
||||
@protected
|
||||
WebViewNavigationState dco_decode_web_view_navigation_state(dynamic raw);
|
||||
|
||||
@protected
|
||||
AnyhowException sse_decode_AnyhowException(SseDeserializer deserializer);
|
||||
|
||||
@ -166,6 +184,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
BigInt sse_decode_box_autoadd_u_64(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
WebViewConfiguration sse_decode_box_autoadd_web_view_configuration(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
int sse_decode_i_32(SseDeserializer deserializer);
|
||||
|
||||
@ -194,6 +217,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
List<WebViewEvent> sse_decode_list_web_view_event(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
MyHttpVersion sse_decode_my_http_version(SseDeserializer deserializer);
|
||||
|
||||
@ -264,6 +292,19 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
BigInt sse_decode_usize(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
WebViewConfiguration sse_decode_web_view_configuration(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
WebViewEvent sse_decode_web_view_event(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
WebViewNavigationState sse_decode_web_view_navigation_state(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
ffi.Pointer<wire_cst_list_prim_u_8_strict> cst_encode_AnyhowException(
|
||||
AnyhowException raw,
|
||||
@ -324,6 +365,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
return wire.cst_new_box_autoadd_u_64(cst_encode_u_64(raw));
|
||||
}
|
||||
|
||||
@protected
|
||||
ffi.Pointer<wire_cst_web_view_configuration>
|
||||
cst_encode_box_autoadd_web_view_configuration(WebViewConfiguration raw) {
|
||||
// Codec=Cst (C-struct based), see doc to use other codecs
|
||||
final ptr = wire.cst_new_box_autoadd_web_view_configuration();
|
||||
cst_api_fill_to_wire_web_view_configuration(raw, ptr.ref);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
@protected
|
||||
int cst_encode_i_64(PlatformInt64 raw) {
|
||||
// Codec=Cst (C-struct based), see doc to use other codecs
|
||||
@ -395,6 +445,18 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
return ans;
|
||||
}
|
||||
|
||||
@protected
|
||||
ffi.Pointer<wire_cst_list_web_view_event> cst_encode_list_web_view_event(
|
||||
List<WebViewEvent> raw,
|
||||
) {
|
||||
// Codec=Cst (C-struct based), see doc to use other codecs
|
||||
final ans = wire.cst_new_list_web_view_event(raw.length);
|
||||
for (var i = 0; i < raw.length; ++i) {
|
||||
cst_api_fill_to_wire_web_view_event(raw[i], ans.ref.ptr[i]);
|
||||
}
|
||||
return ans;
|
||||
}
|
||||
|
||||
@protected
|
||||
ffi.Pointer<wire_cst_list_record_string_string>
|
||||
cst_encode_opt_Map_String_String_None(Map<String, String>? raw) {
|
||||
@ -449,6 +511,14 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
cst_api_fill_to_wire_rsi_launcher_asar_data(apiObj, wireObj.ref);
|
||||
}
|
||||
|
||||
@protected
|
||||
void cst_api_fill_to_wire_box_autoadd_web_view_configuration(
|
||||
WebViewConfiguration apiObj,
|
||||
ffi.Pointer<wire_cst_web_view_configuration> wireObj,
|
||||
) {
|
||||
cst_api_fill_to_wire_web_view_configuration(apiObj, wireObj.ref);
|
||||
}
|
||||
|
||||
@protected
|
||||
void cst_api_fill_to_wire_p_4_k_file_item(
|
||||
P4kFileItem apiObj,
|
||||
@ -518,6 +588,73 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
wireObj.data = cst_encode_opt_list_prim_u_8_strict(apiObj.data);
|
||||
}
|
||||
|
||||
@protected
|
||||
void cst_api_fill_to_wire_web_view_configuration(
|
||||
WebViewConfiguration apiObj,
|
||||
wire_cst_web_view_configuration wireObj,
|
||||
) {
|
||||
wireObj.title = cst_encode_String(apiObj.title);
|
||||
wireObj.width = cst_encode_u_32(apiObj.width);
|
||||
wireObj.height = cst_encode_u_32(apiObj.height);
|
||||
wireObj.user_data_folder = cst_encode_opt_String(apiObj.userDataFolder);
|
||||
wireObj.enable_devtools = cst_encode_bool(apiObj.enableDevtools);
|
||||
wireObj.transparent = cst_encode_bool(apiObj.transparent);
|
||||
wireObj.user_agent = cst_encode_opt_String(apiObj.userAgent);
|
||||
}
|
||||
|
||||
@protected
|
||||
void cst_api_fill_to_wire_web_view_event(
|
||||
WebViewEvent apiObj,
|
||||
wire_cst_web_view_event wireObj,
|
||||
) {
|
||||
if (apiObj is WebViewEvent_NavigationStarted) {
|
||||
var pre_url = cst_encode_String(apiObj.url);
|
||||
wireObj.tag = 0;
|
||||
wireObj.kind.NavigationStarted.url = pre_url;
|
||||
return;
|
||||
}
|
||||
if (apiObj is WebViewEvent_NavigationCompleted) {
|
||||
var pre_url = cst_encode_String(apiObj.url);
|
||||
wireObj.tag = 1;
|
||||
wireObj.kind.NavigationCompleted.url = pre_url;
|
||||
return;
|
||||
}
|
||||
if (apiObj is WebViewEvent_TitleChanged) {
|
||||
var pre_title = cst_encode_String(apiObj.title);
|
||||
wireObj.tag = 2;
|
||||
wireObj.kind.TitleChanged.title = pre_title;
|
||||
return;
|
||||
}
|
||||
if (apiObj is WebViewEvent_WebMessage) {
|
||||
var pre_message = cst_encode_String(apiObj.message);
|
||||
wireObj.tag = 3;
|
||||
wireObj.kind.WebMessage.message = pre_message;
|
||||
return;
|
||||
}
|
||||
if (apiObj is WebViewEvent_WindowClosed) {
|
||||
wireObj.tag = 4;
|
||||
return;
|
||||
}
|
||||
if (apiObj is WebViewEvent_Error) {
|
||||
var pre_message = cst_encode_String(apiObj.message);
|
||||
wireObj.tag = 5;
|
||||
wireObj.kind.Error.message = pre_message;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@protected
|
||||
void cst_api_fill_to_wire_web_view_navigation_state(
|
||||
WebViewNavigationState apiObj,
|
||||
wire_cst_web_view_navigation_state wireObj,
|
||||
) {
|
||||
wireObj.url = cst_encode_String(apiObj.url);
|
||||
wireObj.title = cst_encode_String(apiObj.title);
|
||||
wireObj.can_go_back = cst_encode_bool(apiObj.canGoBack);
|
||||
wireObj.can_go_forward = cst_encode_bool(apiObj.canGoForward);
|
||||
wireObj.is_loading = cst_encode_bool(apiObj.isLoading);
|
||||
}
|
||||
|
||||
@protected
|
||||
bool cst_encode_bool(bool raw);
|
||||
|
||||
@ -581,6 +718,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
void sse_encode_box_autoadd_u_64(BigInt self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_web_view_configuration(
|
||||
WebViewConfiguration self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_i_32(int self, SseSerializer serializer);
|
||||
|
||||
@ -617,6 +760,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_list_web_view_event(
|
||||
List<WebViewEvent> self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_my_http_version(MyHttpVersion self, SseSerializer serializer);
|
||||
|
||||
@ -697,6 +846,21 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
|
||||
@protected
|
||||
void sse_encode_usize(BigInt self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_web_view_configuration(
|
||||
WebViewConfiguration self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_web_view_event(WebViewEvent self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_web_view_navigation_state(
|
||||
WebViewNavigationState self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
}
|
||||
|
||||
// Section: wire_class
|
||||
@ -1368,6 +1532,416 @@ class RustLibWire implements BaseWire {
|
||||
void Function(int, ffi.Pointer<wire_cst_list_prim_u_8_strict>)
|
||||
>();
|
||||
|
||||
void wire__crate__api__webview_api__web_view_configuration_default(
|
||||
int port_,
|
||||
) {
|
||||
return _wire__crate__api__webview_api__web_view_configuration_default(
|
||||
port_,
|
||||
);
|
||||
}
|
||||
|
||||
late final _wire__crate__api__webview_api__web_view_configuration_defaultPtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Int64)>>(
|
||||
'frbgen_starcitizen_doctor_wire__crate__api__webview_api__web_view_configuration_default',
|
||||
);
|
||||
late final _wire__crate__api__webview_api__web_view_configuration_default =
|
||||
_wire__crate__api__webview_api__web_view_configuration_defaultPtr
|
||||
.asFunction<void Function(int)>();
|
||||
|
||||
void wire__crate__api__webview_api__web_view_navigation_state_default(
|
||||
int port_,
|
||||
) {
|
||||
return _wire__crate__api__webview_api__web_view_navigation_state_default(
|
||||
port_,
|
||||
);
|
||||
}
|
||||
|
||||
late final _wire__crate__api__webview_api__web_view_navigation_state_defaultPtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Int64)>>(
|
||||
'frbgen_starcitizen_doctor_wire__crate__api__webview_api__web_view_navigation_state_default',
|
||||
);
|
||||
late final _wire__crate__api__webview_api__web_view_navigation_state_default =
|
||||
_wire__crate__api__webview_api__web_view_navigation_state_defaultPtr
|
||||
.asFunction<void Function(int)>();
|
||||
|
||||
WireSyncRust2DartDco wire__crate__api__webview_api__webview_close(
|
||||
ffi.Pointer<wire_cst_list_prim_u_8_strict> id,
|
||||
) {
|
||||
return _wire__crate__api__webview_api__webview_close(id);
|
||||
}
|
||||
|
||||
late final _wire__crate__api__webview_api__webview_closePtr =
|
||||
_lookup<
|
||||
ffi.NativeFunction<
|
||||
WireSyncRust2DartDco Function(
|
||||
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
|
||||
)
|
||||
>
|
||||
>(
|
||||
'frbgen_starcitizen_doctor_wire__crate__api__webview_api__webview_close',
|
||||
);
|
||||
late final _wire__crate__api__webview_api__webview_close =
|
||||
_wire__crate__api__webview_api__webview_closePtr
|
||||
.asFunction<
|
||||
WireSyncRust2DartDco Function(
|
||||
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
|
||||
)
|
||||
>();
|
||||
|
||||
WireSyncRust2DartDco wire__crate__api__webview_api__webview_create(
|
||||
ffi.Pointer<wire_cst_web_view_configuration> config,
|
||||
) {
|
||||
return _wire__crate__api__webview_api__webview_create(config);
|
||||
}
|
||||
|
||||
late final _wire__crate__api__webview_api__webview_createPtr =
|
||||
_lookup<
|
||||
ffi.NativeFunction<
|
||||
WireSyncRust2DartDco Function(
|
||||
ffi.Pointer<wire_cst_web_view_configuration>,
|
||||
)
|
||||
>
|
||||
>(
|
||||
'frbgen_starcitizen_doctor_wire__crate__api__webview_api__webview_create',
|
||||
);
|
||||
late final _wire__crate__api__webview_api__webview_create =
|
||||
_wire__crate__api__webview_api__webview_createPtr
|
||||
.asFunction<
|
||||
WireSyncRust2DartDco Function(
|
||||
ffi.Pointer<wire_cst_web_view_configuration>,
|
||||
)
|
||||
>();
|
||||
|
||||
WireSyncRust2DartDco wire__crate__api__webview_api__webview_execute_script(
|
||||
ffi.Pointer<wire_cst_list_prim_u_8_strict> id,
|
||||
ffi.Pointer<wire_cst_list_prim_u_8_strict> script,
|
||||
) {
|
||||
return _wire__crate__api__webview_api__webview_execute_script(id, script);
|
||||
}
|
||||
|
||||
late final _wire__crate__api__webview_api__webview_execute_scriptPtr =
|
||||
_lookup<
|
||||
ffi.NativeFunction<
|
||||
WireSyncRust2DartDco Function(
|
||||
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
|
||||
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
|
||||
)
|
||||
>
|
||||
>(
|
||||
'frbgen_starcitizen_doctor_wire__crate__api__webview_api__webview_execute_script',
|
||||
);
|
||||
late final _wire__crate__api__webview_api__webview_execute_script =
|
||||
_wire__crate__api__webview_api__webview_execute_scriptPtr
|
||||
.asFunction<
|
||||
WireSyncRust2DartDco Function(
|
||||
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
|
||||
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
|
||||
)
|
||||
>();
|
||||
|
||||
WireSyncRust2DartDco wire__crate__api__webview_api__webview_get_state(
|
||||
ffi.Pointer<wire_cst_list_prim_u_8_strict> id,
|
||||
) {
|
||||
return _wire__crate__api__webview_api__webview_get_state(id);
|
||||
}
|
||||
|
||||
late final _wire__crate__api__webview_api__webview_get_statePtr =
|
||||
_lookup<
|
||||
ffi.NativeFunction<
|
||||
WireSyncRust2DartDco Function(
|
||||
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
|
||||
)
|
||||
>
|
||||
>(
|
||||
'frbgen_starcitizen_doctor_wire__crate__api__webview_api__webview_get_state',
|
||||
);
|
||||
late final _wire__crate__api__webview_api__webview_get_state =
|
||||
_wire__crate__api__webview_api__webview_get_statePtr
|
||||
.asFunction<
|
||||
WireSyncRust2DartDco Function(
|
||||
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
|
||||
)
|
||||
>();
|
||||
|
||||
WireSyncRust2DartDco wire__crate__api__webview_api__webview_go_back(
|
||||
ffi.Pointer<wire_cst_list_prim_u_8_strict> id,
|
||||
) {
|
||||
return _wire__crate__api__webview_api__webview_go_back(id);
|
||||
}
|
||||
|
||||
late final _wire__crate__api__webview_api__webview_go_backPtr =
|
||||
_lookup<
|
||||
ffi.NativeFunction<
|
||||
WireSyncRust2DartDco Function(
|
||||
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
|
||||
)
|
||||
>
|
||||
>(
|
||||
'frbgen_starcitizen_doctor_wire__crate__api__webview_api__webview_go_back',
|
||||
);
|
||||
late final _wire__crate__api__webview_api__webview_go_back =
|
||||
_wire__crate__api__webview_api__webview_go_backPtr
|
||||
.asFunction<
|
||||
WireSyncRust2DartDco Function(
|
||||
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
|
||||
)
|
||||
>();
|
||||
|
||||
WireSyncRust2DartDco wire__crate__api__webview_api__webview_go_forward(
|
||||
ffi.Pointer<wire_cst_list_prim_u_8_strict> id,
|
||||
) {
|
||||
return _wire__crate__api__webview_api__webview_go_forward(id);
|
||||
}
|
||||
|
||||
late final _wire__crate__api__webview_api__webview_go_forwardPtr =
|
||||
_lookup<
|
||||
ffi.NativeFunction<
|
||||
WireSyncRust2DartDco Function(
|
||||
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
|
||||
)
|
||||
>
|
||||
>(
|
||||
'frbgen_starcitizen_doctor_wire__crate__api__webview_api__webview_go_forward',
|
||||
);
|
||||
late final _wire__crate__api__webview_api__webview_go_forward =
|
||||
_wire__crate__api__webview_api__webview_go_forwardPtr
|
||||
.asFunction<
|
||||
WireSyncRust2DartDco Function(
|
||||
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
|
||||
)
|
||||
>();
|
||||
|
||||
WireSyncRust2DartDco wire__crate__api__webview_api__webview_is_closed(
|
||||
ffi.Pointer<wire_cst_list_prim_u_8_strict> id,
|
||||
) {
|
||||
return _wire__crate__api__webview_api__webview_is_closed(id);
|
||||
}
|
||||
|
||||
late final _wire__crate__api__webview_api__webview_is_closedPtr =
|
||||
_lookup<
|
||||
ffi.NativeFunction<
|
||||
WireSyncRust2DartDco Function(
|
||||
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
|
||||
)
|
||||
>
|
||||
>(
|
||||
'frbgen_starcitizen_doctor_wire__crate__api__webview_api__webview_is_closed',
|
||||
);
|
||||
late final _wire__crate__api__webview_api__webview_is_closed =
|
||||
_wire__crate__api__webview_api__webview_is_closedPtr
|
||||
.asFunction<
|
||||
WireSyncRust2DartDco Function(
|
||||
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
|
||||
)
|
||||
>();
|
||||
|
||||
WireSyncRust2DartDco wire__crate__api__webview_api__webview_list_all() {
|
||||
return _wire__crate__api__webview_api__webview_list_all();
|
||||
}
|
||||
|
||||
late final _wire__crate__api__webview_api__webview_list_allPtr =
|
||||
_lookup<ffi.NativeFunction<WireSyncRust2DartDco Function()>>(
|
||||
'frbgen_starcitizen_doctor_wire__crate__api__webview_api__webview_list_all',
|
||||
);
|
||||
late final _wire__crate__api__webview_api__webview_list_all =
|
||||
_wire__crate__api__webview_api__webview_list_allPtr
|
||||
.asFunction<WireSyncRust2DartDco Function()>();
|
||||
|
||||
WireSyncRust2DartDco wire__crate__api__webview_api__webview_navigate(
|
||||
ffi.Pointer<wire_cst_list_prim_u_8_strict> id,
|
||||
ffi.Pointer<wire_cst_list_prim_u_8_strict> url,
|
||||
) {
|
||||
return _wire__crate__api__webview_api__webview_navigate(id, url);
|
||||
}
|
||||
|
||||
late final _wire__crate__api__webview_api__webview_navigatePtr =
|
||||
_lookup<
|
||||
ffi.NativeFunction<
|
||||
WireSyncRust2DartDco Function(
|
||||
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
|
||||
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
|
||||
)
|
||||
>
|
||||
>(
|
||||
'frbgen_starcitizen_doctor_wire__crate__api__webview_api__webview_navigate',
|
||||
);
|
||||
late final _wire__crate__api__webview_api__webview_navigate =
|
||||
_wire__crate__api__webview_api__webview_navigatePtr
|
||||
.asFunction<
|
||||
WireSyncRust2DartDco Function(
|
||||
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
|
||||
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
|
||||
)
|
||||
>();
|
||||
|
||||
WireSyncRust2DartDco wire__crate__api__webview_api__webview_poll_events(
|
||||
ffi.Pointer<wire_cst_list_prim_u_8_strict> id,
|
||||
) {
|
||||
return _wire__crate__api__webview_api__webview_poll_events(id);
|
||||
}
|
||||
|
||||
late final _wire__crate__api__webview_api__webview_poll_eventsPtr =
|
||||
_lookup<
|
||||
ffi.NativeFunction<
|
||||
WireSyncRust2DartDco Function(
|
||||
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
|
||||
)
|
||||
>
|
||||
>(
|
||||
'frbgen_starcitizen_doctor_wire__crate__api__webview_api__webview_poll_events',
|
||||
);
|
||||
late final _wire__crate__api__webview_api__webview_poll_events =
|
||||
_wire__crate__api__webview_api__webview_poll_eventsPtr
|
||||
.asFunction<
|
||||
WireSyncRust2DartDco Function(
|
||||
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
|
||||
)
|
||||
>();
|
||||
|
||||
WireSyncRust2DartDco wire__crate__api__webview_api__webview_reload(
|
||||
ffi.Pointer<wire_cst_list_prim_u_8_strict> id,
|
||||
) {
|
||||
return _wire__crate__api__webview_api__webview_reload(id);
|
||||
}
|
||||
|
||||
late final _wire__crate__api__webview_api__webview_reloadPtr =
|
||||
_lookup<
|
||||
ffi.NativeFunction<
|
||||
WireSyncRust2DartDco Function(
|
||||
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
|
||||
)
|
||||
>
|
||||
>(
|
||||
'frbgen_starcitizen_doctor_wire__crate__api__webview_api__webview_reload',
|
||||
);
|
||||
late final _wire__crate__api__webview_api__webview_reload =
|
||||
_wire__crate__api__webview_api__webview_reloadPtr
|
||||
.asFunction<
|
||||
WireSyncRust2DartDco Function(
|
||||
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
|
||||
)
|
||||
>();
|
||||
|
||||
WireSyncRust2DartDco wire__crate__api__webview_api__webview_set_visibility(
|
||||
ffi.Pointer<wire_cst_list_prim_u_8_strict> id,
|
||||
bool visible,
|
||||
) {
|
||||
return _wire__crate__api__webview_api__webview_set_visibility(id, visible);
|
||||
}
|
||||
|
||||
late final _wire__crate__api__webview_api__webview_set_visibilityPtr =
|
||||
_lookup<
|
||||
ffi.NativeFunction<
|
||||
WireSyncRust2DartDco Function(
|
||||
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
|
||||
ffi.Bool,
|
||||
)
|
||||
>
|
||||
>(
|
||||
'frbgen_starcitizen_doctor_wire__crate__api__webview_api__webview_set_visibility',
|
||||
);
|
||||
late final _wire__crate__api__webview_api__webview_set_visibility =
|
||||
_wire__crate__api__webview_api__webview_set_visibilityPtr
|
||||
.asFunction<
|
||||
WireSyncRust2DartDco Function(
|
||||
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
|
||||
bool,
|
||||
)
|
||||
>();
|
||||
|
||||
WireSyncRust2DartDco
|
||||
wire__crate__api__webview_api__webview_set_window_position(
|
||||
ffi.Pointer<wire_cst_list_prim_u_8_strict> id,
|
||||
int x,
|
||||
int y,
|
||||
) {
|
||||
return _wire__crate__api__webview_api__webview_set_window_position(
|
||||
id,
|
||||
x,
|
||||
y,
|
||||
);
|
||||
}
|
||||
|
||||
late final _wire__crate__api__webview_api__webview_set_window_positionPtr =
|
||||
_lookup<
|
||||
ffi.NativeFunction<
|
||||
WireSyncRust2DartDco Function(
|
||||
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
|
||||
ffi.Int32,
|
||||
ffi.Int32,
|
||||
)
|
||||
>
|
||||
>(
|
||||
'frbgen_starcitizen_doctor_wire__crate__api__webview_api__webview_set_window_position',
|
||||
);
|
||||
late final _wire__crate__api__webview_api__webview_set_window_position =
|
||||
_wire__crate__api__webview_api__webview_set_window_positionPtr
|
||||
.asFunction<
|
||||
WireSyncRust2DartDco Function(
|
||||
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
|
||||
int,
|
||||
int,
|
||||
)
|
||||
>();
|
||||
|
||||
WireSyncRust2DartDco wire__crate__api__webview_api__webview_set_window_size(
|
||||
ffi.Pointer<wire_cst_list_prim_u_8_strict> id,
|
||||
int width,
|
||||
int height,
|
||||
) {
|
||||
return _wire__crate__api__webview_api__webview_set_window_size(
|
||||
id,
|
||||
width,
|
||||
height,
|
||||
);
|
||||
}
|
||||
|
||||
late final _wire__crate__api__webview_api__webview_set_window_sizePtr =
|
||||
_lookup<
|
||||
ffi.NativeFunction<
|
||||
WireSyncRust2DartDco Function(
|
||||
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
|
||||
ffi.Uint32,
|
||||
ffi.Uint32,
|
||||
)
|
||||
>
|
||||
>(
|
||||
'frbgen_starcitizen_doctor_wire__crate__api__webview_api__webview_set_window_size',
|
||||
);
|
||||
late final _wire__crate__api__webview_api__webview_set_window_size =
|
||||
_wire__crate__api__webview_api__webview_set_window_sizePtr
|
||||
.asFunction<
|
||||
WireSyncRust2DartDco Function(
|
||||
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
|
||||
int,
|
||||
int,
|
||||
)
|
||||
>();
|
||||
|
||||
WireSyncRust2DartDco wire__crate__api__webview_api__webview_stop(
|
||||
ffi.Pointer<wire_cst_list_prim_u_8_strict> id,
|
||||
) {
|
||||
return _wire__crate__api__webview_api__webview_stop(id);
|
||||
}
|
||||
|
||||
late final _wire__crate__api__webview_api__webview_stopPtr =
|
||||
_lookup<
|
||||
ffi.NativeFunction<
|
||||
WireSyncRust2DartDco Function(
|
||||
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
|
||||
)
|
||||
>
|
||||
>(
|
||||
'frbgen_starcitizen_doctor_wire__crate__api__webview_api__webview_stop',
|
||||
);
|
||||
late final _wire__crate__api__webview_api__webview_stop =
|
||||
_wire__crate__api__webview_api__webview_stopPtr
|
||||
.asFunction<
|
||||
WireSyncRust2DartDco Function(
|
||||
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
|
||||
)
|
||||
>();
|
||||
|
||||
void wire__crate__api__rs_process__write(
|
||||
int port_,
|
||||
int rs_pid,
|
||||
@ -1431,6 +2005,23 @@ class RustLibWire implements BaseWire {
|
||||
late final _cst_new_box_autoadd_u_64 = _cst_new_box_autoadd_u_64Ptr
|
||||
.asFunction<ffi.Pointer<ffi.Uint64> Function(int)>();
|
||||
|
||||
ffi.Pointer<wire_cst_web_view_configuration>
|
||||
cst_new_box_autoadd_web_view_configuration() {
|
||||
return _cst_new_box_autoadd_web_view_configuration();
|
||||
}
|
||||
|
||||
late final _cst_new_box_autoadd_web_view_configurationPtr =
|
||||
_lookup<
|
||||
ffi.NativeFunction<
|
||||
ffi.Pointer<wire_cst_web_view_configuration> Function()
|
||||
>
|
||||
>('frbgen_starcitizen_doctor_cst_new_box_autoadd_web_view_configuration');
|
||||
late final _cst_new_box_autoadd_web_view_configuration =
|
||||
_cst_new_box_autoadd_web_view_configurationPtr
|
||||
.asFunction<
|
||||
ffi.Pointer<wire_cst_web_view_configuration> Function()
|
||||
>();
|
||||
|
||||
ffi.Pointer<wire_cst_list_String> cst_new_list_String(int len) {
|
||||
return _cst_new_list_String(len);
|
||||
}
|
||||
@ -1519,6 +2110,21 @@ class RustLibWire implements BaseWire {
|
||||
ffi.Pointer<wire_cst_list_record_string_string> Function(int)
|
||||
>();
|
||||
|
||||
ffi.Pointer<wire_cst_list_web_view_event> cst_new_list_web_view_event(
|
||||
int len,
|
||||
) {
|
||||
return _cst_new_list_web_view_event(len);
|
||||
}
|
||||
|
||||
late final _cst_new_list_web_view_eventPtr =
|
||||
_lookup<
|
||||
ffi.NativeFunction<
|
||||
ffi.Pointer<wire_cst_list_web_view_event> Function(ffi.Int32)
|
||||
>
|
||||
>('frbgen_starcitizen_doctor_cst_new_list_web_view_event');
|
||||
late final _cst_new_list_web_view_event = _cst_new_list_web_view_eventPtr
|
||||
.asFunction<ffi.Pointer<wire_cst_list_web_view_event> Function(int)>();
|
||||
|
||||
int dummy_method_to_enforce_bundling() {
|
||||
return _dummy_method_to_enforce_bundling();
|
||||
}
|
||||
@ -1582,6 +2188,26 @@ final class wire_cst_list_prim_u_8_loose extends ffi.Struct {
|
||||
external int len;
|
||||
}
|
||||
|
||||
final class wire_cst_web_view_configuration extends ffi.Struct {
|
||||
external ffi.Pointer<wire_cst_list_prim_u_8_strict> title;
|
||||
|
||||
@ffi.Uint32()
|
||||
external int width;
|
||||
|
||||
@ffi.Uint32()
|
||||
external int height;
|
||||
|
||||
external ffi.Pointer<wire_cst_list_prim_u_8_strict> user_data_folder;
|
||||
|
||||
@ffi.Bool()
|
||||
external bool enable_devtools;
|
||||
|
||||
@ffi.Bool()
|
||||
external bool transparent;
|
||||
|
||||
external ffi.Pointer<wire_cst_list_prim_u_8_strict> user_agent;
|
||||
}
|
||||
|
||||
final class wire_cst_p_4_k_file_item extends ffi.Struct {
|
||||
external ffi.Pointer<wire_cst_list_prim_u_8_strict> name;
|
||||
|
||||
@ -1621,6 +2247,52 @@ final class wire_cst_list_process_info extends ffi.Struct {
|
||||
external int len;
|
||||
}
|
||||
|
||||
final class wire_cst_WebViewEvent_NavigationStarted extends ffi.Struct {
|
||||
external ffi.Pointer<wire_cst_list_prim_u_8_strict> url;
|
||||
}
|
||||
|
||||
final class wire_cst_WebViewEvent_NavigationCompleted extends ffi.Struct {
|
||||
external ffi.Pointer<wire_cst_list_prim_u_8_strict> url;
|
||||
}
|
||||
|
||||
final class wire_cst_WebViewEvent_TitleChanged extends ffi.Struct {
|
||||
external ffi.Pointer<wire_cst_list_prim_u_8_strict> title;
|
||||
}
|
||||
|
||||
final class wire_cst_WebViewEvent_WebMessage extends ffi.Struct {
|
||||
external ffi.Pointer<wire_cst_list_prim_u_8_strict> message;
|
||||
}
|
||||
|
||||
final class wire_cst_WebViewEvent_Error extends ffi.Struct {
|
||||
external ffi.Pointer<wire_cst_list_prim_u_8_strict> message;
|
||||
}
|
||||
|
||||
final class WebViewEventKind extends ffi.Union {
|
||||
external wire_cst_WebViewEvent_NavigationStarted NavigationStarted;
|
||||
|
||||
external wire_cst_WebViewEvent_NavigationCompleted NavigationCompleted;
|
||||
|
||||
external wire_cst_WebViewEvent_TitleChanged TitleChanged;
|
||||
|
||||
external wire_cst_WebViewEvent_WebMessage WebMessage;
|
||||
|
||||
external wire_cst_WebViewEvent_Error Error;
|
||||
}
|
||||
|
||||
final class wire_cst_web_view_event extends ffi.Struct {
|
||||
@ffi.Int32()
|
||||
external int tag;
|
||||
|
||||
external WebViewEventKind kind;
|
||||
}
|
||||
|
||||
final class wire_cst_list_web_view_event extends ffi.Struct {
|
||||
external ffi.Pointer<wire_cst_web_view_event> ptr;
|
||||
|
||||
@ffi.Int32()
|
||||
external int len;
|
||||
}
|
||||
|
||||
final class wire_cst_rs_process_stream_data extends ffi.Struct {
|
||||
@ffi.Int32()
|
||||
external int data_type;
|
||||
@ -1648,3 +2320,18 @@ final class wire_cst_rust_http_response extends ffi.Struct {
|
||||
|
||||
external ffi.Pointer<wire_cst_list_prim_u_8_strict> data;
|
||||
}
|
||||
|
||||
final class wire_cst_web_view_navigation_state extends ffi.Struct {
|
||||
external ffi.Pointer<wire_cst_list_prim_u_8_strict> url;
|
||||
|
||||
external ffi.Pointer<wire_cst_list_prim_u_8_strict> title;
|
||||
|
||||
@ffi.Bool()
|
||||
external bool can_go_back;
|
||||
|
||||
@ffi.Bool()
|
||||
external bool can_go_forward;
|
||||
|
||||
@ffi.Bool()
|
||||
external bool is_loading;
|
||||
}
|
||||
|
||||
341
lib/common/rust/rust_webview_controller.dart
Normal file
341
lib/common/rust/rust_webview_controller.dart
Normal file
@ -0,0 +1,341 @@
|
||||
// Rust WebView 管理器
|
||||
// 使用 wry + tao 实现的 WebView 窗口管理
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:starcitizen_doctor/common/rust/api/webview_api.dart' as rust_webview;
|
||||
import 'package:starcitizen_doctor/common/utils/log.dart';
|
||||
|
||||
typedef OnWebMessageCallback = void Function(String message);
|
||||
typedef OnNavigationCallback = void Function(String url);
|
||||
typedef OnNavigationCompletedCallback = void Function(String url);
|
||||
typedef OnWindowClosedCallback = void Function();
|
||||
|
||||
/// Rust WebView 控制器
|
||||
/// 封装 Rust wry + tao WebView 的业务逻辑
|
||||
class RustWebViewController {
|
||||
final String id;
|
||||
final List<OnWebMessageCallback> _messageCallbacks = [];
|
||||
final List<OnNavigationCallback> _navigationCallbacks = [];
|
||||
final List<OnNavigationCompletedCallback> _navigationCompletedCallbacks = [];
|
||||
final List<OnWindowClosedCallback> _closeCallbacks = [];
|
||||
|
||||
Timer? _pollTimer;
|
||||
bool _isDisposed = false;
|
||||
|
||||
/// 本地化脚本(从 assets 加载)
|
||||
String _localizationScript = "";
|
||||
|
||||
/// 请求拦截器脚本
|
||||
String _requestInterceptorScript = "";
|
||||
|
||||
/// 当前 URL
|
||||
String _currentUrl = "";
|
||||
String get currentUrl => _currentUrl;
|
||||
|
||||
RustWebViewController._(this.id);
|
||||
|
||||
/// 创建新的 WebView 窗口
|
||||
static Future<RustWebViewController> create({
|
||||
String title = "WebView",
|
||||
int width = 1280,
|
||||
int height = 720,
|
||||
String? userDataFolder,
|
||||
bool enableDevtools = false,
|
||||
bool transparent = false,
|
||||
String? userAgent,
|
||||
}) async {
|
||||
try {
|
||||
final config = rust_webview.WebViewConfiguration(
|
||||
title: title,
|
||||
width: width,
|
||||
height: height,
|
||||
userDataFolder: userDataFolder,
|
||||
enableDevtools: enableDevtools,
|
||||
transparent: transparent,
|
||||
userAgent: userAgent,
|
||||
);
|
||||
|
||||
final id = rust_webview.webviewCreate(config: config);
|
||||
final controller = RustWebViewController._(id);
|
||||
|
||||
// 加载脚本资源
|
||||
await controller._loadScripts();
|
||||
|
||||
// 启动事件轮询
|
||||
controller._startEventPolling();
|
||||
|
||||
return controller;
|
||||
} catch (e) {
|
||||
throw Exception("Failed to create WebView: $e");
|
||||
}
|
||||
}
|
||||
|
||||
/// 加载本地化和拦截器脚本
|
||||
Future<void> _loadScripts() async {
|
||||
try {
|
||||
_localizationScript = await rootBundle.loadString('assets/web_script.js');
|
||||
_requestInterceptorScript = await rootBundle.loadString('assets/request_interceptor.js');
|
||||
} catch (e) {
|
||||
dPrint("Failed to load scripts: $e");
|
||||
}
|
||||
}
|
||||
|
||||
/// 启动事件轮询
|
||||
void _startEventPolling() {
|
||||
_pollTimer = Timer.periodic(const Duration(milliseconds: 50), (_) {
|
||||
if (_isDisposed) return;
|
||||
_pollEvents();
|
||||
});
|
||||
}
|
||||
|
||||
/// 轮询事件
|
||||
void _pollEvents() {
|
||||
try {
|
||||
final events = rust_webview.webviewPollEvents(id: id);
|
||||
for (final event in events) {
|
||||
_handleEvent(event);
|
||||
}
|
||||
} catch (e) {
|
||||
// WebView 可能已关闭
|
||||
if (!_isDisposed) {
|
||||
dPrint("Error polling events: $e");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 处理事件
|
||||
void _handleEvent(rust_webview.WebViewEvent event) {
|
||||
switch (event) {
|
||||
case rust_webview.WebViewEvent_NavigationStarted(:final url):
|
||||
dPrint("Navigation started: $url");
|
||||
_currentUrl = url;
|
||||
// 导航开始时通知
|
||||
for (final callback in _navigationCallbacks) {
|
||||
callback(url);
|
||||
}
|
||||
break;
|
||||
|
||||
case rust_webview.WebViewEvent_NavigationCompleted(:final url):
|
||||
dPrint("Navigation completed: $url");
|
||||
_currentUrl = url;
|
||||
// 注入请求拦截器
|
||||
if (_requestInterceptorScript.isNotEmpty) {
|
||||
executeScript(_requestInterceptorScript);
|
||||
}
|
||||
// 导航完成回调(用于注入脚本)
|
||||
for (final callback in _navigationCompletedCallbacks) {
|
||||
callback(url);
|
||||
}
|
||||
for (final callback in _navigationCallbacks) {
|
||||
callback(url);
|
||||
}
|
||||
break;
|
||||
|
||||
case rust_webview.WebViewEvent_TitleChanged(:final title):
|
||||
dPrint("Title changed: $title");
|
||||
break;
|
||||
|
||||
case rust_webview.WebViewEvent_WebMessage(:final message):
|
||||
_handleWebMessage(message);
|
||||
break;
|
||||
|
||||
case rust_webview.WebViewEvent_WindowClosed():
|
||||
dPrint("Window closed");
|
||||
for (final callback in _closeCallbacks) {
|
||||
callback();
|
||||
}
|
||||
dispose();
|
||||
break;
|
||||
|
||||
case rust_webview.WebViewEvent_Error(:final message):
|
||||
dPrint("WebView error: $message");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// 处理来自 WebView 的消息
|
||||
void _handleWebMessage(String message) {
|
||||
dPrint("Web message: $message");
|
||||
try {
|
||||
final data = json.decode(message);
|
||||
final action = data["action"];
|
||||
|
||||
switch (action) {
|
||||
case "navigation_state":
|
||||
// 从 JS 获取导航状态更新
|
||||
final url = data["url"] ?? "";
|
||||
final isLoading = data["isLoading"] ?? false;
|
||||
_currentUrl = url;
|
||||
if (!isLoading) {
|
||||
for (final callback in _navigationCallbacks) {
|
||||
callback(url);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "close_window":
|
||||
// 处理来自导航栏的关闭请求
|
||||
close();
|
||||
break;
|
||||
default:
|
||||
// 转发其他消息给回调
|
||||
for (final callback in _messageCallbacks) {
|
||||
callback(message);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// 非 JSON 消息,直接转发
|
||||
for (final callback in _messageCallbacks) {
|
||||
callback(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 导航到 URL
|
||||
void navigate(String url) {
|
||||
if (_isDisposed) return;
|
||||
_currentUrl = url;
|
||||
rust_webview.webviewNavigate(id: id, url: url);
|
||||
}
|
||||
|
||||
/// 后退
|
||||
void goBack() {
|
||||
if (_isDisposed) return;
|
||||
rust_webview.webviewGoBack(id: id);
|
||||
}
|
||||
|
||||
/// 前进
|
||||
void goForward() {
|
||||
if (_isDisposed) return;
|
||||
rust_webview.webviewGoForward(id: id);
|
||||
}
|
||||
|
||||
/// 刷新
|
||||
void reload() {
|
||||
if (_isDisposed) return;
|
||||
rust_webview.webviewReload(id: id);
|
||||
}
|
||||
|
||||
/// 停止加载
|
||||
void stop() {
|
||||
if (_isDisposed) return;
|
||||
rust_webview.webviewStop(id: id);
|
||||
}
|
||||
|
||||
/// 执行 JavaScript
|
||||
void executeScript(String script) {
|
||||
if (_isDisposed) return;
|
||||
rust_webview.webviewExecuteScript(id: id, script: script);
|
||||
}
|
||||
|
||||
/// 设置窗口可见性
|
||||
void setVisible(bool visible) {
|
||||
if (_isDisposed) return;
|
||||
rust_webview.webviewSetVisibility(id: id, visible: visible);
|
||||
}
|
||||
|
||||
/// 关闭窗口
|
||||
void close() {
|
||||
if (_isDisposed) return;
|
||||
rust_webview.webviewClose(id: id);
|
||||
dispose();
|
||||
}
|
||||
|
||||
/// 设置窗口大小
|
||||
void setWindowSize(int width, int height) {
|
||||
if (_isDisposed) return;
|
||||
rust_webview.webviewSetWindowSize(id: id, width: width, height: height);
|
||||
}
|
||||
|
||||
/// 设置窗口位置
|
||||
void setWindowPosition(int x, int y) {
|
||||
if (_isDisposed) return;
|
||||
rust_webview.webviewSetWindowPosition(id: id, x: x, y: y);
|
||||
}
|
||||
|
||||
/// 获取当前导航状态
|
||||
rust_webview.WebViewNavigationState getState() {
|
||||
return rust_webview.webviewGetState(id: id);
|
||||
}
|
||||
|
||||
/// 检查窗口是否已关闭
|
||||
bool get isClosed {
|
||||
if (_isDisposed) return true;
|
||||
return rust_webview.webviewIsClosed(id: id);
|
||||
}
|
||||
|
||||
/// 添加消息回调
|
||||
void addOnWebMessageCallback(OnWebMessageCallback callback) {
|
||||
_messageCallbacks.add(callback);
|
||||
}
|
||||
|
||||
/// 移除消息回调
|
||||
void removeOnWebMessageCallback(OnWebMessageCallback callback) {
|
||||
_messageCallbacks.remove(callback);
|
||||
}
|
||||
|
||||
/// 添加导航回调
|
||||
void addOnNavigationCallback(OnNavigationCallback callback) {
|
||||
_navigationCallbacks.add(callback);
|
||||
}
|
||||
|
||||
/// 移除导航回调
|
||||
void removeOnNavigationCallback(OnNavigationCallback callback) {
|
||||
_navigationCallbacks.remove(callback);
|
||||
}
|
||||
|
||||
/// 添加导航完成回调(用于在页面加载完成后注入脚本)
|
||||
void addOnNavigationCompletedCallback(OnNavigationCompletedCallback callback) {
|
||||
_navigationCompletedCallbacks.add(callback);
|
||||
}
|
||||
|
||||
/// 移除导航完成回调
|
||||
void removeOnNavigationCompletedCallback(OnNavigationCompletedCallback callback) {
|
||||
_navigationCompletedCallbacks.remove(callback);
|
||||
}
|
||||
|
||||
/// 添加关闭回调
|
||||
void addOnCloseCallback(OnWindowClosedCallback callback) {
|
||||
_closeCallbacks.add(callback);
|
||||
}
|
||||
|
||||
/// 移除关闭回调
|
||||
void removeOnCloseCallback(OnWindowClosedCallback callback) {
|
||||
_closeCallbacks.remove(callback);
|
||||
}
|
||||
|
||||
/// 注入本地化脚本
|
||||
void injectLocalizationScript() {
|
||||
if (_localizationScript.isNotEmpty) {
|
||||
executeScript(_localizationScript);
|
||||
}
|
||||
}
|
||||
|
||||
/// 初始化网页本地化
|
||||
void initWebLocalization() {
|
||||
executeScript("InitWebLocalization()");
|
||||
}
|
||||
|
||||
/// 更新翻译词典
|
||||
void updateReplaceWords(List<Map<String, String>> words, bool enableCapture) {
|
||||
final jsonWords = json.encode(words);
|
||||
executeScript("WebLocalizationUpdateReplaceWords($jsonWords, $enableCapture)");
|
||||
}
|
||||
|
||||
/// 执行 RSI 登录脚本
|
||||
void executeRsiLogin(String channel) {
|
||||
executeScript('getRSILauncherToken("$channel");');
|
||||
}
|
||||
|
||||
/// 释放资源
|
||||
void dispose() {
|
||||
if (_isDisposed) return;
|
||||
_isDisposed = true;
|
||||
_pollTimer?.cancel();
|
||||
_messageCallbacks.clear();
|
||||
_navigationCallbacks.clear();
|
||||
_navigationCompletedCallbacks.clear();
|
||||
_closeCallbacks.clear();
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,5 @@
|
||||
import 'dart:io';
|
||||
import 'package:desktop_multi_window/desktop_multi_window.dart';
|
||||
import 'package:desktop_webview_window/desktop_webview_window.dart';
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
@ -12,15 +11,6 @@ import 'app.dart';
|
||||
import 'common/utils/multi_window_manager.dart';
|
||||
|
||||
Future<void> main(List<String> args) async {
|
||||
// webview window
|
||||
if (runWebViewTitleBarWidget(
|
||||
args,
|
||||
backgroundColor: const Color.fromRGBO(19, 36, 49, 1),
|
||||
builder: _defaultWebviewTitleBar,
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await windowManager.ensureInitialized();
|
||||
|
||||
@ -133,33 +123,3 @@ class App extends HookConsumerWidget with WindowListener {
|
||||
super.onWindowClose();
|
||||
}
|
||||
}
|
||||
|
||||
Widget _defaultWebviewTitleBar(BuildContext context) {
|
||||
final state = TitleBarWebViewState.of(context);
|
||||
final controller = TitleBarWebViewController.of(context);
|
||||
return FluentTheme(
|
||||
data: FluentThemeData.dark(),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
if (Platform.isMacOS) const SizedBox(width: 96),
|
||||
IconButton(onPressed: !state.canGoBack ? null : controller.back, icon: const Icon(FluentIcons.chevron_left)),
|
||||
const SizedBox(width: 12),
|
||||
IconButton(
|
||||
onPressed: !state.canGoForward ? null : controller.forward,
|
||||
icon: const Icon(FluentIcons.chevron_right),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
if (state.isLoading)
|
||||
IconButton(onPressed: controller.stop, icon: const Icon(FluentIcons.chrome_close))
|
||||
else
|
||||
IconButton(onPressed: controller.reload, icon: const Icon(FluentIcons.refresh)),
|
||||
const SizedBox(width: 12),
|
||||
(state.isLoading) ? const SizedBox(width: 24, height: 24, child: ProgressRing()) : const SizedBox(width: 24),
|
||||
const SizedBox(width: 12),
|
||||
SelectableText(state.url ?? ""),
|
||||
const Spacer(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -41,7 +41,7 @@ final class Unp4kCModelProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$unp4kCModelHash() => r'b46274b1409dc904db2d96acf692869edf034b9f';
|
||||
String _$unp4kCModelHash() => r'72ee23ad9864cdfb73a588ea1a0509b083e7dee8';
|
||||
|
||||
abstract class _$Unp4kCModel extends $Notifier<Unp4kcState> {
|
||||
Unp4kcState build();
|
||||
|
||||
@ -5,7 +5,6 @@ import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:hive_ce/hive.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:desktop_webview_window/desktop_webview_window.dart';
|
||||
import 'package:jwt_decode/jwt_decode.dart';
|
||||
import 'package:starcitizen_doctor/common/helper/system_helper.dart';
|
||||
import 'package:starcitizen_doctor/common/utils/base_utils.dart';
|
||||
@ -14,7 +13,6 @@ import 'package:starcitizen_doctor/common/utils/provider.dart';
|
||||
import 'package:starcitizen_doctor/data/rsi_game_library_data.dart';
|
||||
import 'package:starcitizen_doctor/ui/home/home_ui_model.dart';
|
||||
import 'package:starcitizen_doctor/ui/webview/webview.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
part 'home_game_login_dialog_ui_model.freezed.dart';
|
||||
@ -47,82 +45,87 @@ class HomeGameLoginUIModel extends _$HomeGameLoginUIModel {
|
||||
Future<void> launchWebLogin(BuildContext context) async {
|
||||
final homeState = ref.read(homeUIModelProvider);
|
||||
if (!context.mounted) return;
|
||||
goWebView(context, S.current.home_action_login_rsi_account,
|
||||
"https://robertsspaceindustries.com/en/connect?jumpto=/account/dashboard",
|
||||
loginMode: true, rsiLoginCallback: (message, ok) async {
|
||||
// dPrint(
|
||||
// "======rsiLoginCallback=== $ok ===== data==\n${json.encode(message)}");
|
||||
if (message == null || !ok) {
|
||||
Navigator.pop(context);
|
||||
return;
|
||||
}
|
||||
goWebView(
|
||||
context,
|
||||
S.current.home_action_login_rsi_account,
|
||||
"https://robertsspaceindustries.com/en/connect?jumpto=/account/dashboard",
|
||||
loginMode: true,
|
||||
rsiLoginCallback: (message, ok) async {
|
||||
// dPrint(
|
||||
// "======rsiLoginCallback=== $ok ===== data==\n${json.encode(message)}");
|
||||
if (message == null || !ok) {
|
||||
Navigator.pop(context);
|
||||
return;
|
||||
}
|
||||
|
||||
// final emailBox = await Hive.openBox("quick_login_email");
|
||||
final data = message["data"];
|
||||
final authToken = data["authToken"];
|
||||
final webToken = data["webToken"];
|
||||
final releaseInfo = data["releaseInfo"];
|
||||
final libraryData = RsiGameLibraryData.fromJson(data["libraryData"]);
|
||||
var avatarUrl = data["avatar"]
|
||||
?.toString()
|
||||
.replaceAll("url(\"", "")
|
||||
.replaceAll("\")", "");
|
||||
if (avatarUrl?.startsWith("/") ?? false) {
|
||||
avatarUrl = "https://robertsspaceindustries.com$avatarUrl";
|
||||
}
|
||||
final Map<String, dynamic> payload = Jwt.parseJwt(authToken!);
|
||||
final nickname = payload["nickname"] ?? "";
|
||||
// final emailBox = await Hive.openBox("quick_login_email");
|
||||
final data = message["data"];
|
||||
final authToken = data["authToken"];
|
||||
final webToken = data["webToken"];
|
||||
final releaseInfo = data["releaseInfo"];
|
||||
final libraryData = RsiGameLibraryData.fromJson(data["libraryData"]);
|
||||
var avatarUrl = data["avatar"]?.toString().replaceAll("url(\"", "").replaceAll("\")", "");
|
||||
if (avatarUrl?.startsWith("/") ?? false) {
|
||||
avatarUrl = "https://robertsspaceindustries.com$avatarUrl";
|
||||
}
|
||||
final Map<String, dynamic> payload = Jwt.parseJwt(authToken!);
|
||||
final nickname = payload["nickname"] ?? "";
|
||||
|
||||
state = state.copyWith(
|
||||
nickname: nickname,
|
||||
avatarUrl: avatarUrl,
|
||||
authToken: authToken,
|
||||
webToken: webToken,
|
||||
releaseInfo: releaseInfo,
|
||||
libraryData: libraryData,
|
||||
);
|
||||
state = state.copyWith(
|
||||
nickname: nickname,
|
||||
avatarUrl: avatarUrl,
|
||||
authToken: authToken,
|
||||
webToken: webToken,
|
||||
releaseInfo: releaseInfo,
|
||||
libraryData: libraryData,
|
||||
);
|
||||
|
||||
final buildInfoFile =
|
||||
File("${homeState.scInstalledPath}\\build_manifest.id");
|
||||
if (await buildInfoFile.exists()) {
|
||||
final buildInfo =
|
||||
json.decode(await buildInfoFile.readAsString())["Data"];
|
||||
final buildInfoFile = File("${homeState.scInstalledPath}\\build_manifest.id");
|
||||
if (await buildInfoFile.exists()) {
|
||||
final buildInfo = json.decode(await buildInfoFile.readAsString())["Data"];
|
||||
|
||||
if (releaseInfo?["versionLabel"] != null &&
|
||||
buildInfo["RequestedP4ChangeNum"] != null) {
|
||||
if (!(releaseInfo!["versionLabel"]!
|
||||
.toString()
|
||||
.endsWith(buildInfo["RequestedP4ChangeNum"]!.toString()))) {
|
||||
if (!context.mounted) return;
|
||||
final ok = await showConfirmDialogs(
|
||||
if (releaseInfo?["versionLabel"] != null && buildInfo["RequestedP4ChangeNum"] != null) {
|
||||
if (!(releaseInfo!["versionLabel"]!.toString().endsWith(buildInfo["RequestedP4ChangeNum"]!.toString()))) {
|
||||
if (!context.mounted) return;
|
||||
final ok = await showConfirmDialogs(
|
||||
context,
|
||||
S.current.home_login_info_game_version_outdated,
|
||||
Text(S.current.home_login_info_rsi_server_report(
|
||||
Text(
|
||||
S.current.home_login_info_rsi_server_report(
|
||||
releaseInfo?["versionLabel"],
|
||||
buildInfo["RequestedP4ChangeNum"])),
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: MediaQuery.of(context).size.width * .4),
|
||||
cancel: S.current.home_login_info_action_ignore);
|
||||
if (ok == true) {
|
||||
if (!context.mounted) return;
|
||||
Navigator.pop(context);
|
||||
return;
|
||||
buildInfo["RequestedP4ChangeNum"],
|
||||
),
|
||||
),
|
||||
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * .4),
|
||||
cancel: S.current.home_login_info_action_ignore,
|
||||
);
|
||||
if (ok == true) {
|
||||
if (!context.mounted) return;
|
||||
Navigator.pop(context);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!context.mounted) return;
|
||||
_readyForLaunch(homeState, context);
|
||||
}, useLocalization: true, homeState: homeState);
|
||||
if (!context.mounted) return;
|
||||
_readyForLaunch(homeState, context);
|
||||
},
|
||||
useLocalization: true,
|
||||
homeState: homeState,
|
||||
);
|
||||
}
|
||||
|
||||
// ignore: avoid_build_context_in_providers
|
||||
Future<void> goWebView(BuildContext context, String title, String url,
|
||||
{bool useLocalization = false,
|
||||
bool loginMode = false,
|
||||
RsiLoginCallback? rsiLoginCallback,
|
||||
required HomeUIModelState homeState}) async {
|
||||
Future<void> goWebView(
|
||||
BuildContext context,
|
||||
String title,
|
||||
String url, {
|
||||
bool useLocalization = false,
|
||||
bool loginMode = false,
|
||||
RsiLoginCallback? rsiLoginCallback,
|
||||
required HomeUIModelState homeState,
|
||||
}) async {
|
||||
if (useLocalization) {
|
||||
const tipVersion = 2;
|
||||
final box = await Hive.openBox("app_conf");
|
||||
@ -130,14 +133,11 @@ class HomeGameLoginUIModel extends _$HomeGameLoginUIModel {
|
||||
if (skip != tipVersion) {
|
||||
if (!context.mounted) return;
|
||||
final ok = await showConfirmDialogs(
|
||||
context,
|
||||
S.current.home_login_action_title_box_one_click_launch,
|
||||
Text(
|
||||
S.current.home_login_info_one_click_launch_description,
|
||||
style: const TextStyle(fontSize: 16),
|
||||
),
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: MediaQuery.of(context).size.width * .6));
|
||||
context,
|
||||
S.current.home_login_action_title_box_one_click_launch,
|
||||
Text(S.current.home_login_info_one_click_launch_description, style: const TextStyle(fontSize: 16)),
|
||||
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * .6),
|
||||
);
|
||||
if (!ok) {
|
||||
if (loginMode) {
|
||||
rsiLoginCallback?.call(null, false);
|
||||
@ -147,26 +147,17 @@ class HomeGameLoginUIModel extends _$HomeGameLoginUIModel {
|
||||
await box.put("skip_web_login_version", tipVersion);
|
||||
}
|
||||
}
|
||||
if (!await WebviewWindow.isWebviewAvailable()) {
|
||||
if (!context.mounted) return;
|
||||
await showToast(
|
||||
context, S.current.home_login_action_title_need_webview2_runtime);
|
||||
if (!context.mounted) return;
|
||||
await launchUrlString(
|
||||
"https://developer.microsoft.com/en-us/microsoft-edge/webview2/");
|
||||
if (!context.mounted) return;
|
||||
Navigator.pop(context);
|
||||
return;
|
||||
}
|
||||
// Rust WebView using wry + tao - no WebView2 runtime check needed as wry handles it internally
|
||||
if (!context.mounted) return;
|
||||
final webViewModel = WebViewModel(context,
|
||||
loginMode: loginMode,
|
||||
loginCallback: rsiLoginCallback,
|
||||
loginChannel: getChannelID(homeState.scInstalledPath!));
|
||||
final webViewModel = WebViewModel(
|
||||
context,
|
||||
loginMode: loginMode,
|
||||
loginCallback: rsiLoginCallback,
|
||||
loginChannel: getChannelID(homeState.scInstalledPath!),
|
||||
);
|
||||
if (useLocalization) {
|
||||
try {
|
||||
await webViewModel
|
||||
.initLocalization(homeState.webLocalizationVersionsData!);
|
||||
await webViewModel.initLocalization(homeState.webLocalizationVersionsData!);
|
||||
} catch (_) {}
|
||||
}
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
@ -179,9 +170,10 @@ class HomeGameLoginUIModel extends _$HomeGameLoginUIModel {
|
||||
}
|
||||
|
||||
Future<void> _readyForLaunch(
|
||||
HomeUIModelState homeState,
|
||||
// ignore: avoid_build_context_in_providers
|
||||
BuildContext context) async {
|
||||
HomeUIModelState homeState,
|
||||
// ignore: avoid_build_context_in_providers
|
||||
BuildContext context,
|
||||
) async {
|
||||
final userBox = await Hive.openBox("rsi_account_data");
|
||||
state = state.copyWith(loginStatus: 2);
|
||||
final launchData = {
|
||||
@ -211,11 +203,12 @@ class HomeGameLoginUIModel extends _$HomeGameLoginUIModel {
|
||||
final homeUIModel = ref.read(homeUIModelProvider.notifier);
|
||||
if (!context.mounted) return;
|
||||
homeUIModel.doLaunchGame(
|
||||
context,
|
||||
'${homeState.scInstalledPath}\\$executable',
|
||||
["-no_login_dialog", ...launchOptions.toString().split(" ")],
|
||||
homeState.scInstalledPath!,
|
||||
processorAffinity);
|
||||
context,
|
||||
'${homeState.scInstalledPath}\\$executable',
|
||||
["-no_login_dialog", ...launchOptions.toString().split(" ")],
|
||||
homeState.scInstalledPath!,
|
||||
processorAffinity,
|
||||
);
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
if (!context.mounted) return;
|
||||
Navigator.pop(context);
|
||||
|
||||
@ -42,7 +42,7 @@ final class HomeGameLoginUIModelProvider
|
||||
}
|
||||
|
||||
String _$homeGameLoginUIModelHash() =>
|
||||
r'c9e9ec2e85f2459b6bfc1518406b091ff4675a85';
|
||||
r'217a57f797b37f3467be2e7711f220610e9e67d8';
|
||||
|
||||
abstract class _$HomeGameLoginUIModel extends $Notifier<HomeGameLoginState> {
|
||||
HomeGameLoginState build();
|
||||
|
||||
@ -2,7 +2,6 @@ import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:desktop_webview_window/desktop_webview_window.dart';
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:hive_ce/hive.dart';
|
||||
@ -164,12 +163,7 @@ class HomeUIModel extends _$HomeUIModel {
|
||||
await box.put("skip_web_localization_tip_version", tipVersion);
|
||||
}
|
||||
}
|
||||
if (!await WebviewWindow.isWebviewAvailable()) {
|
||||
if (!context.mounted) return;
|
||||
showToast(context, S.current.home_login_action_title_need_webview2_runtime);
|
||||
launchUrlString("https://developer.microsoft.com/en-us/microsoft-edge/webview2/");
|
||||
return;
|
||||
}
|
||||
// Rust WebView using wry + tao - no WebView2 runtime check needed as wry handles it internally
|
||||
if (!context.mounted) return;
|
||||
final webViewModel = WebViewModel(context, loginMode: loginMode, loginCallback: rsiLoginCallback);
|
||||
if (useLocalization) {
|
||||
|
||||
@ -41,7 +41,7 @@ final class HomeUIModelProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$homeUIModelHash() => r'7dfe73383f7be2e520a42d176e199a8db208f008';
|
||||
String _$homeUIModelHash() => r'cc795e27213d02993459dd711de4a897c8491575';
|
||||
|
||||
abstract class _$HomeUIModel extends $Notifier<HomeUIModelState> {
|
||||
HomeUIModelState build();
|
||||
|
||||
@ -41,7 +41,7 @@ final class ToolsUIModelProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$toolsUIModelHash() => r'78732ff16e87cc9f92174bda43d0fafadba51146';
|
||||
String _$toolsUIModelHash() => r'ee1de3d555443f72b4fbb395a5728b2de1e8aaf4';
|
||||
|
||||
abstract class _$ToolsUIModel extends $Notifier<ToolsUIState> {
|
||||
ToolsUIState build();
|
||||
|
||||
@ -4,22 +4,24 @@ import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:desktop_webview_window/desktop_webview_window.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:hive_ce/hive.dart';
|
||||
import 'package:starcitizen_doctor/api/analytics.dart';
|
||||
import 'package:starcitizen_doctor/common/conf/url_conf.dart';
|
||||
import 'package:starcitizen_doctor/common/io/rs_http.dart';
|
||||
import 'package:starcitizen_doctor/common/rust/rust_webview_controller.dart';
|
||||
import 'package:starcitizen_doctor/common/utils/base_utils.dart';
|
||||
import 'package:starcitizen_doctor/common/utils/log.dart';
|
||||
import 'package:starcitizen_doctor/data/app_version_data.dart';
|
||||
import 'package:starcitizen_doctor/data/app_web_localization_versions_data.dart';
|
||||
|
||||
typedef RsiLoginCallback = void Function(Map? data, bool success);
|
||||
typedef OnWebMessageReceivedCallback = void Function(String message);
|
||||
|
||||
class WebViewModel {
|
||||
late Webview webview;
|
||||
late RustWebViewController webview;
|
||||
final BuildContext context;
|
||||
|
||||
bool _isClosed = false;
|
||||
@ -51,6 +53,8 @@ class WebViewModel {
|
||||
|
||||
final RsiLoginCallback? loginCallback;
|
||||
|
||||
late AppVersionData _appVersionData;
|
||||
|
||||
Future<void> initWebView({
|
||||
String title = "",
|
||||
required String applicationSupportDir,
|
||||
@ -59,114 +63,35 @@ class WebViewModel {
|
||||
try {
|
||||
final userBox = await Hive.openBox("app_conf");
|
||||
isEnableToolSiteMirrors = userBox.get("isEnableToolSiteMirrors", defaultValue: false);
|
||||
webview = await WebviewWindow.create(
|
||||
configuration: CreateConfiguration(
|
||||
windowWidth: loginMode ? 960 : 1920,
|
||||
windowHeight: loginMode ? 720 : 1080,
|
||||
userDataFolderWindows: "$applicationSupportDir/webview_data",
|
||||
title: Platform.isMacOS ? "" : title,
|
||||
),
|
||||
_appVersionData = appVersionData;
|
||||
|
||||
webview = await RustWebViewController.create(
|
||||
title: Platform.isMacOS ? "" : title,
|
||||
width: loginMode ? 960 : 1920,
|
||||
height: loginMode ? 720 : 1080,
|
||||
userDataFolder: "$applicationSupportDir/webview_data",
|
||||
enableDevtools: kDebugMode,
|
||||
);
|
||||
// webview.openDevToolsWindow();
|
||||
webview.isNavigating.addListener(() async {
|
||||
if (!webview.isNavigating.value && localizationResource.isNotEmpty) {
|
||||
dPrint("webview Navigating url === $url");
|
||||
if (url.contains("robertsspaceindustries.com")) {
|
||||
// SC 官网
|
||||
final replaceWords = _getLocalizationResource("zh-CN");
|
||||
const org = "https://robertsspaceindustries.com/orgs";
|
||||
const citizens = "https://robertsspaceindustries.com/citizens";
|
||||
const organization = "https://robertsspaceindustries.com/account/organization";
|
||||
const concierge = "https://robertsspaceindustries.com/account/concierge";
|
||||
const referral = "https://robertsspaceindustries.com/account/referral-program";
|
||||
const address = "https://robertsspaceindustries.com/account/addresses";
|
||||
|
||||
const hangar = "https://robertsspaceindustries.com/account/pledges";
|
||||
// 添加导航完成回调(用于注入脚本)
|
||||
webview.addOnNavigationCompletedCallback(_onNavigationCompleted);
|
||||
|
||||
const spectrum = "https://robertsspaceindustries.com/spectrum";
|
||||
// 跳过光谱论坛 https://github.com/StarCitizenToolBox/StarCitizenBoxBrowserEx/issues/1
|
||||
if (url.startsWith(spectrum)) {
|
||||
return;
|
||||
}
|
||||
// 添加关闭回调
|
||||
webview.addOnCloseCallback(dispose);
|
||||
|
||||
dPrint("load script");
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
await webview.evaluateJavaScript(localizationScript);
|
||||
|
||||
if (url.startsWith(org) || url.startsWith(citizens) || url.startsWith(organization)) {
|
||||
replaceWords.add({"word": 'members', "replacement": S.current.webview_localization_name_member});
|
||||
replaceWords.addAll(_getLocalizationResource("orgs"));
|
||||
}
|
||||
|
||||
if (address.startsWith(address)) {
|
||||
replaceWords.addAll(_getLocalizationResource("address"));
|
||||
}
|
||||
|
||||
if (url.startsWith(referral)) {
|
||||
replaceWords.addAll([
|
||||
{"word": 'Total recruits: ', "replacement": S.current.webview_localization_total_invitations},
|
||||
{"word": 'Prospects ', "replacement": S.current.webview_localization_unfinished_invitations},
|
||||
{"word": 'Recruits', "replacement": S.current.webview_localization_finished_invitations},
|
||||
]);
|
||||
}
|
||||
|
||||
if (url.startsWith(concierge)) {
|
||||
replaceWords.clear();
|
||||
replaceWords.addAll(_getLocalizationResource("concierge"));
|
||||
}
|
||||
if (url.startsWith(hangar)) {
|
||||
replaceWords.addAll(_getLocalizationResource("hangar"));
|
||||
}
|
||||
|
||||
_curReplaceWords = {};
|
||||
for (var element in replaceWords) {
|
||||
_curReplaceWords?[element["word"] ?? ""] = element["replacement"] ?? "";
|
||||
}
|
||||
await webview.evaluateJavaScript("InitWebLocalization()");
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
dPrint("update replaceWords");
|
||||
await webview.evaluateJavaScript(
|
||||
"WebLocalizationUpdateReplaceWords(${json.encode(replaceWords)},$enableCapture)",
|
||||
);
|
||||
|
||||
/// loginMode
|
||||
if (loginMode) {
|
||||
dPrint("--- do rsi login ---\n run === getRSILauncherToken(\"$loginChannel\");");
|
||||
await Future.delayed(const Duration(milliseconds: 200));
|
||||
webview.evaluateJavaScript("getRSILauncherToken(\"$loginChannel\");");
|
||||
}
|
||||
} else if (url.startsWith(await _handleMirrorsUrl("https://www.erkul.games", appVersionData))) {
|
||||
dPrint("load script");
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
await webview.evaluateJavaScript(localizationScript);
|
||||
dPrint("update replaceWords");
|
||||
final replaceWords = _getLocalizationResource("DPS");
|
||||
await webview.evaluateJavaScript(
|
||||
"WebLocalizationUpdateReplaceWords(${json.encode(replaceWords)},$enableCapture)",
|
||||
);
|
||||
} else if (url.startsWith(await _handleMirrorsUrl("https://uexcorp.space", appVersionData))) {
|
||||
dPrint("load script");
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
await webview.evaluateJavaScript(localizationScript);
|
||||
dPrint("update replaceWords");
|
||||
final replaceWords = _getLocalizationResource("UEX");
|
||||
await webview.evaluateJavaScript(
|
||||
"WebLocalizationUpdateReplaceWords(${json.encode(replaceWords)},$enableCapture)",
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
webview.addOnUrlRequestCallback(_onUrlRequest);
|
||||
webview.onClose.whenComplete(dispose);
|
||||
if (loginMode) {
|
||||
webview.addOnWebMessageReceivedCallback((messageString) {
|
||||
final message = json.decode(messageString);
|
||||
if (message["action"] == "webview_rsi_login_show_window") {
|
||||
webview.setWebviewWindowVisibility(true);
|
||||
} else if (message["action"] == "webview_rsi_login_success") {
|
||||
_loginModeSuccess = true;
|
||||
loginCallback?.call(message, true);
|
||||
webview.close();
|
||||
webview.addOnWebMessageCallback((messageString) {
|
||||
try {
|
||||
final message = json.decode(messageString);
|
||||
if (message["action"] == "webview_rsi_login_show_window") {
|
||||
webview.setVisible(true);
|
||||
} else if (message["action"] == "webview_rsi_login_success") {
|
||||
_loginModeSuccess = true;
|
||||
loginCallback?.call(message, true);
|
||||
webview.close();
|
||||
}
|
||||
} catch (e) {
|
||||
dPrint("Error parsing login message: $e");
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -175,6 +100,98 @@ class WebViewModel {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onNavigationCompleted(String newUrl) async {
|
||||
dPrint("Navigation completed: $newUrl");
|
||||
url = newUrl;
|
||||
|
||||
// 在页面加载时注入拦截器
|
||||
if (requestInterceptorScript.isNotEmpty) {
|
||||
dPrint("Injecting request interceptor for: $url");
|
||||
webview.executeScript(requestInterceptorScript);
|
||||
}
|
||||
|
||||
if (localizationResource.isEmpty) return;
|
||||
|
||||
dPrint("webview Navigating url === $url");
|
||||
if (url.contains("robertsspaceindustries.com")) {
|
||||
// SC 官网
|
||||
final replaceWords = _getLocalizationResource("zh-CN");
|
||||
const org = "https://robertsspaceindustries.com/orgs";
|
||||
const citizens = "https://robertsspaceindustries.com/citizens";
|
||||
const organization = "https://robertsspaceindustries.com/account/organization";
|
||||
const concierge = "https://robertsspaceindustries.com/account/concierge";
|
||||
const referral = "https://robertsspaceindustries.com/account/referral-program";
|
||||
const address = "https://robertsspaceindustries.com/account/addresses";
|
||||
|
||||
const hangar = "https://robertsspaceindustries.com/account/pledges";
|
||||
|
||||
const spectrum = "https://robertsspaceindustries.com/spectrum";
|
||||
// 跳过光谱论坛 https://github.com/StarCitizenToolBox/StarCitizenBoxBrowserEx/issues/1
|
||||
if (url.startsWith(spectrum)) {
|
||||
return;
|
||||
}
|
||||
|
||||
dPrint("load script");
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
webview.injectLocalizationScript();
|
||||
|
||||
if (url.startsWith(org) || url.startsWith(citizens) || url.startsWith(organization)) {
|
||||
replaceWords.add({"word": 'members', "replacement": S.current.webview_localization_name_member});
|
||||
replaceWords.addAll(_getLocalizationResource("orgs"));
|
||||
}
|
||||
|
||||
if (address.startsWith(address)) {
|
||||
replaceWords.addAll(_getLocalizationResource("address"));
|
||||
}
|
||||
|
||||
if (url.startsWith(referral)) {
|
||||
replaceWords.addAll([
|
||||
{"word": 'Total recruits: ', "replacement": S.current.webview_localization_total_invitations},
|
||||
{"word": 'Prospects ', "replacement": S.current.webview_localization_unfinished_invitations},
|
||||
{"word": 'Recruits', "replacement": S.current.webview_localization_finished_invitations},
|
||||
]);
|
||||
}
|
||||
|
||||
if (url.startsWith(concierge)) {
|
||||
replaceWords.clear();
|
||||
replaceWords.addAll(_getLocalizationResource("concierge"));
|
||||
}
|
||||
if (url.startsWith(hangar)) {
|
||||
replaceWords.addAll(_getLocalizationResource("hangar"));
|
||||
}
|
||||
|
||||
_curReplaceWords = {};
|
||||
for (var element in replaceWords) {
|
||||
_curReplaceWords?[element["word"] ?? ""] = element["replacement"] ?? "";
|
||||
}
|
||||
webview.initWebLocalization();
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
dPrint("update replaceWords");
|
||||
webview.updateReplaceWords(replaceWords, enableCapture);
|
||||
|
||||
/// loginMode
|
||||
if (loginMode) {
|
||||
dPrint("--- do rsi login ---\n run === getRSILauncherToken(\"$loginChannel\");");
|
||||
await Future.delayed(const Duration(milliseconds: 200));
|
||||
webview.executeRsiLogin(loginChannel);
|
||||
}
|
||||
} else if (url.startsWith(await _handleMirrorsUrl("https://www.erkul.games", _appVersionData))) {
|
||||
dPrint("load script");
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
webview.injectLocalizationScript();
|
||||
dPrint("update replaceWords");
|
||||
final replaceWords = _getLocalizationResource("DPS");
|
||||
webview.updateReplaceWords(replaceWords, enableCapture);
|
||||
} else if (url.startsWith(await _handleMirrorsUrl("https://uexcorp.space", _appVersionData))) {
|
||||
dPrint("load script");
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
webview.injectLocalizationScript();
|
||||
dPrint("update replaceWords");
|
||||
final replaceWords = _getLocalizationResource("UEX");
|
||||
webview.updateReplaceWords(replaceWords, enableCapture);
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> _handleMirrorsUrl(String url, AppVersionData appVersionData) async {
|
||||
var finalUrl = url;
|
||||
if (isEnableToolSiteMirrors) {
|
||||
@ -189,7 +206,7 @@ class WebViewModel {
|
||||
}
|
||||
|
||||
Future<void> launch(String url, AppVersionData appVersionData) async {
|
||||
webview.launch(await _handleMirrorsUrl(url, appVersionData));
|
||||
webview.navigate(await _handleMirrorsUrl(url, appVersionData));
|
||||
}
|
||||
|
||||
Future<void> initLocalization(AppWebLocalizationVersionsData v) async {
|
||||
@ -259,29 +276,19 @@ class WebViewModel {
|
||||
}
|
||||
|
||||
void addOnWebMessageReceivedCallback(OnWebMessageReceivedCallback callback) {
|
||||
webview.addOnWebMessageReceivedCallback(callback);
|
||||
webview.addOnWebMessageCallback(callback);
|
||||
}
|
||||
|
||||
void removeOnWebMessageReceivedCallback(OnWebMessageReceivedCallback callback) {
|
||||
webview.removeOnWebMessageReceivedCallback(callback);
|
||||
webview.removeOnWebMessageCallback(callback);
|
||||
}
|
||||
|
||||
FutureOr<void> dispose() {
|
||||
webview.removeOnUrlRequestCallback(_onUrlRequest);
|
||||
webview.removeOnNavigationCompletedCallback(_onNavigationCompleted);
|
||||
if (loginMode && !_loginModeSuccess) {
|
||||
loginCallback?.call(null, false);
|
||||
}
|
||||
_isClosed = true;
|
||||
}
|
||||
|
||||
void _onUrlRequest(String url) {
|
||||
dPrint("OnUrlRequestCallback === $url");
|
||||
this.url = url;
|
||||
|
||||
// 在页面开始加载时立即注入拦截器
|
||||
if (requestInterceptorScript.isNotEmpty) {
|
||||
dPrint("Injecting request interceptor for: $url");
|
||||
webview.evaluateJavaScript(requestInterceptorScript);
|
||||
}
|
||||
webview.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,7 +7,6 @@
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <desktop_multi_window/desktop_multi_window_plugin.h>
|
||||
#include <desktop_webview_window/desktop_webview_window_plugin.h>
|
||||
#include <flutter_acrylic/flutter_acrylic_plugin.h>
|
||||
#include <screen_retriever_linux/screen_retriever_linux_plugin.h>
|
||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||
@ -17,9 +16,6 @@ void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
g_autoptr(FlPluginRegistrar) desktop_multi_window_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "DesktopMultiWindowPlugin");
|
||||
desktop_multi_window_plugin_register_with_registrar(desktop_multi_window_registrar);
|
||||
g_autoptr(FlPluginRegistrar) desktop_webview_window_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "DesktopWebviewWindowPlugin");
|
||||
desktop_webview_window_plugin_register_with_registrar(desktop_webview_window_registrar);
|
||||
g_autoptr(FlPluginRegistrar) flutter_acrylic_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterAcrylicPlugin");
|
||||
flutter_acrylic_plugin_register_with_registrar(flutter_acrylic_registrar);
|
||||
|
||||
@ -4,7 +4,6 @@
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
desktop_multi_window
|
||||
desktop_webview_window
|
||||
flutter_acrylic
|
||||
screen_retriever_linux
|
||||
url_launcher_linux
|
||||
|
||||
@ -6,20 +6,20 @@ import FlutterMacOS
|
||||
import Foundation
|
||||
|
||||
import desktop_multi_window
|
||||
import desktop_webview_window
|
||||
import device_info_plus
|
||||
import file_picker
|
||||
import macos_window_utils
|
||||
import path_provider_foundation
|
||||
import screen_retriever_macos
|
||||
import url_launcher_macos
|
||||
import window_manager
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
FlutterMultiWindowPlugin.register(with: registry.registrar(forPlugin: "FlutterMultiWindowPlugin"))
|
||||
DesktopWebviewWindowPlugin.register(with: registry.registrar(forPlugin: "DesktopWebviewWindowPlugin"))
|
||||
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
|
||||
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
|
||||
MacOSWindowUtilsPlugin.register(with: registry.registrar(forPlugin: "MacOSWindowUtilsPlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
ScreenRetrieverMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverMacosPlugin"))
|
||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||
WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin"))
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
PODS:
|
||||
- desktop_multi_window (0.0.1):
|
||||
- FlutterMacOS
|
||||
- desktop_webview_window (0.0.1):
|
||||
- FlutterMacOS
|
||||
- device_info_plus (0.0.1):
|
||||
- FlutterMacOS
|
||||
- file_picker (0.0.1):
|
||||
@ -10,7 +8,8 @@ PODS:
|
||||
- FlutterMacOS (1.0.0)
|
||||
- macos_window_utils (1.0.0):
|
||||
- FlutterMacOS
|
||||
- objective_c (0.0.1):
|
||||
- path_provider_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- rust_builder (0.0.1):
|
||||
- FlutterMacOS
|
||||
@ -23,12 +22,11 @@ PODS:
|
||||
|
||||
DEPENDENCIES:
|
||||
- desktop_multi_window (from `Flutter/ephemeral/.symlinks/plugins/desktop_multi_window/macos`)
|
||||
- desktop_webview_window (from `Flutter/ephemeral/.symlinks/plugins/desktop_webview_window/macos`)
|
||||
- device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`)
|
||||
- file_picker (from `Flutter/ephemeral/.symlinks/plugins/file_picker/macos`)
|
||||
- FlutterMacOS (from `Flutter/ephemeral`)
|
||||
- macos_window_utils (from `Flutter/ephemeral/.symlinks/plugins/macos_window_utils/macos`)
|
||||
- objective_c (from `Flutter/ephemeral/.symlinks/plugins/objective_c/macos`)
|
||||
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- rust_builder (from `Flutter/ephemeral/.symlinks/plugins/rust_builder/macos`)
|
||||
- screen_retriever_macos (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos`)
|
||||
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
|
||||
@ -37,8 +35,6 @@ DEPENDENCIES:
|
||||
EXTERNAL SOURCES:
|
||||
desktop_multi_window:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/desktop_multi_window/macos
|
||||
desktop_webview_window:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/desktop_webview_window/macos
|
||||
device_info_plus:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos
|
||||
file_picker:
|
||||
@ -47,8 +43,8 @@ EXTERNAL SOURCES:
|
||||
:path: Flutter/ephemeral
|
||||
macos_window_utils:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/macos_window_utils/macos
|
||||
objective_c:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/objective_c/macos
|
||||
path_provider_foundation:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
|
||||
rust_builder:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/rust_builder/macos
|
||||
screen_retriever_macos:
|
||||
@ -60,12 +56,11 @@ EXTERNAL SOURCES:
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
desktop_multi_window: 93667594ccc4b88d91a97972fd3b1b89667fa80a
|
||||
desktop_webview_window: 7e37af677d6d19294cb433d9b1d878ef78dffa4d
|
||||
device_info_plus: 4fb280989f669696856f8b129e4a5e3cd6c48f76
|
||||
file_picker: 7584aae6fa07a041af2b36a2655122d42f578c1a
|
||||
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
|
||||
macos_window_utils: 23f54331a0fd51eea9e0ed347253bf48fd379d1d
|
||||
objective_c: ec13431e45ba099cb734eb2829a5c1cd37986cba
|
||||
path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
|
||||
rust_builder: f99e810dace35ac9783428b039d73d1caa5bfbc1
|
||||
screen_retriever_macos: 452e51764a9e1cdb74b3c541238795849f21557f
|
||||
url_launcher_macos: f87a979182d112f911de6820aefddaf56ee9fbfd
|
||||
|
||||
24
pubspec.lock
24
pubspec.lock
@ -322,14 +322,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.0"
|
||||
desktop_webview_window:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: desktop_webview_window
|
||||
sha256: "57cf20d81689d5cbb1adfd0017e96b669398a669d927906073b0e42fc64111c0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.3"
|
||||
device_info_plus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -934,14 +926,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
objective_c:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: objective_c
|
||||
sha256: "1f81ed9e41909d44162d7ec8663b2c647c202317cc0b56d3d56f6a13146a0b64"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.1.0"
|
||||
package_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -986,10 +970,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_foundation
|
||||
sha256: "6192e477f34018ef1ea790c56fffc7302e3bc3efede9e798b934c252c8c105ba"
|
||||
sha256: "6d13aece7b3f5c5a9731eaf553ff9dcbc2eff41087fd2df587fd0fed9a3eb0c4"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.0"
|
||||
version: "2.5.1"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1646,10 +1630,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: yaml_edit
|
||||
sha256: fb38626579fb345ad00e674e2af3a5c9b0cc4b9bfb8fd7f7ff322c7c9e62aef5
|
||||
sha256: ec709065bb2c911b336853b67f3732dd13e0336bd065cc2f1061d7610ddf45e3
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.2"
|
||||
version: "2.2.3"
|
||||
sdks:
|
||||
dart: ">=3.9.0 <4.0.0"
|
||||
flutter: ">=3.35.0"
|
||||
|
||||
@ -40,7 +40,6 @@ dependencies:
|
||||
device_info_plus: ^12.3.0
|
||||
file_picker: ^10.3.7
|
||||
file_sizes: ^1.0.6
|
||||
desktop_webview_window: ^0.2.3
|
||||
flutter_svg: ^2.2.3
|
||||
archive: ^4.0.7
|
||||
jwt_decode: ^0.3.1
|
||||
|
||||
1765
rust/Cargo.lock
generated
1765
rust/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -15,22 +15,32 @@ crate-type = ["cdylib", "staticlib"]
|
||||
|
||||
[dependencies]
|
||||
flutter_rust_bridge = "=2.11.1"
|
||||
tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros", "process"] }
|
||||
futures = { version = "0.3", default-features = false, features = ["executor"] }
|
||||
url = "2.5"
|
||||
once_cell = "1.21"
|
||||
reqwest = { version = "0.12", features = ["rustls-tls-webpki-roots", "cookies", "gzip", "json", "stream"] }
|
||||
hickory-resolver = { version = "0.25" }
|
||||
anyhow = "1.0"
|
||||
scopeguard = "1.2"
|
||||
notify-rust = "4"
|
||||
tokio = { version = "1.48.0", features = ["rt", "rt-multi-thread", "macros", "process", "sync"] }
|
||||
futures = { version = "0.3.31", default-features = false, features = ["executor"] }
|
||||
url = "2.5.7"
|
||||
once_cell = "1.21.3"
|
||||
reqwest = { version = "0.12.24", features = ["rustls-tls-webpki-roots", "cookies", "gzip", "json", "stream"] }
|
||||
hickory-resolver = { version = "0.25.2" }
|
||||
anyhow = "1.0.100"
|
||||
scopeguard = "1.0"
|
||||
notify-rust = "4.11.7"
|
||||
asar = "0.3.0"
|
||||
walkdir = "2.5.0"
|
||||
ort = { version = "2.0.0-rc.10", features = ["xnnpack", "download-binaries", "ndarray"] }
|
||||
tokenizers = { version = "0.22", default-features = false, features = ["onig"] }
|
||||
ndarray = "0.17"
|
||||
serde_json = "1.0"
|
||||
tokenizers = { version = "0.22.2", default-features = false, features = ["onig"] }
|
||||
ndarray = "0.17.1"
|
||||
serde_json = "1.0.145"
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
unp4k_rs = { git = "https://github.com/StarCitizenToolBox/unp4k_rs", tag = "V0.0.2" }
|
||||
uuid = { version = "1.19.0", features = ["v4"] }
|
||||
parking_lot = "0.12.5"
|
||||
crossbeam-channel = "0.5.15"
|
||||
|
||||
# WebView
|
||||
[target.'cfg(not(target_os = "macos"))'.dependencies]
|
||||
wry = "0.53.5"
|
||||
tao = { version = "0.34.5", features = ["serde"] }
|
||||
image = { version = "0.25.9", default-features = false, features = ["ico"] }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows = { version = "0.62.2", features = [
|
||||
@ -39,7 +49,7 @@ windows = { version = "0.62.2", features = [
|
||||
"Win32_System_Threading",
|
||||
"Win32_Foundation"
|
||||
] }
|
||||
win32job = "2"
|
||||
win32job = "2.0.3"
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(frb_expand)'] }
|
||||
|
||||
@ -7,3 +7,4 @@ pub mod win32_api;
|
||||
pub mod asar_api;
|
||||
pub mod ort_api;
|
||||
pub mod unp4k_api;
|
||||
pub mod webview_api;
|
||||
|
||||
209
rust/src/api/webview_api.rs
Normal file
209
rust/src/api/webview_api.rs
Normal file
@ -0,0 +1,209 @@
|
||||
// WebView API using wry + tao
|
||||
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use flutter_rust_bridge::frb;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::webview::{WEBVIEW_INSTANCES, send_command};
|
||||
|
||||
// ============ Data Structures ============
|
||||
|
||||
/// WebView window configuration
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[frb(dart_metadata = ("freezed"))]
|
||||
pub struct WebViewConfiguration {
|
||||
pub title: String,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub user_data_folder: Option<String>,
|
||||
pub enable_devtools: bool,
|
||||
pub transparent: bool,
|
||||
pub user_agent: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for WebViewConfiguration {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
title: "WebView".to_string(),
|
||||
width: 1280,
|
||||
height: 720,
|
||||
user_data_folder: None,
|
||||
enable_devtools: false,
|
||||
transparent: false,
|
||||
user_agent: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Navigation state of the WebView
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[frb(dart_metadata = ("freezed"))]
|
||||
pub struct WebViewNavigationState {
|
||||
pub url: String,
|
||||
pub title: String,
|
||||
pub can_go_back: bool,
|
||||
pub can_go_forward: bool,
|
||||
pub is_loading: bool,
|
||||
}
|
||||
|
||||
impl Default for WebViewNavigationState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
url: String::new(),
|
||||
title: String::new(),
|
||||
can_go_back: false,
|
||||
can_go_forward: false,
|
||||
is_loading: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Events from WebView to Dart
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[frb(dart_metadata = ("freezed"))]
|
||||
pub enum WebViewEvent {
|
||||
NavigationStarted { url: String },
|
||||
NavigationCompleted { url: String },
|
||||
TitleChanged { title: String },
|
||||
WebMessage { message: String },
|
||||
WindowClosed,
|
||||
Error { message: String },
|
||||
}
|
||||
|
||||
/// Commands from Dart to WebView
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum WebViewCommand {
|
||||
Navigate(String),
|
||||
GoBack,
|
||||
GoForward,
|
||||
Reload,
|
||||
Stop,
|
||||
ExecuteScript(String),
|
||||
SetVisibility(bool),
|
||||
Close,
|
||||
SetWindowSize(u32, u32),
|
||||
SetWindowPosition(i32, i32),
|
||||
}
|
||||
|
||||
// ============ Public API ============
|
||||
|
||||
/// Create a new WebView window and return its ID
|
||||
#[frb(sync)]
|
||||
pub fn webview_create(config: WebViewConfiguration) -> Result<String, String> {
|
||||
crate::webview::create_webview(config)
|
||||
}
|
||||
|
||||
/// Navigate to a URL
|
||||
#[frb(sync)]
|
||||
pub fn webview_navigate(id: String, url: String) -> Result<(), String> {
|
||||
send_command(&id, WebViewCommand::Navigate(url))
|
||||
}
|
||||
|
||||
/// Go back in history
|
||||
#[frb(sync)]
|
||||
pub fn webview_go_back(id: String) -> Result<(), String> {
|
||||
send_command(&id, WebViewCommand::GoBack)
|
||||
}
|
||||
|
||||
/// Go forward in history
|
||||
#[frb(sync)]
|
||||
pub fn webview_go_forward(id: String) -> Result<(), String> {
|
||||
send_command(&id, WebViewCommand::GoForward)
|
||||
}
|
||||
|
||||
/// Reload the current page
|
||||
#[frb(sync)]
|
||||
pub fn webview_reload(id: String) -> Result<(), String> {
|
||||
send_command(&id, WebViewCommand::Reload)
|
||||
}
|
||||
|
||||
/// Stop loading
|
||||
#[frb(sync)]
|
||||
pub fn webview_stop(id: String) -> Result<(), String> {
|
||||
send_command(&id, WebViewCommand::Stop)
|
||||
}
|
||||
|
||||
/// Execute JavaScript in the WebView
|
||||
#[frb(sync)]
|
||||
pub fn webview_execute_script(id: String, script: String) -> Result<(), String> {
|
||||
send_command(&id, WebViewCommand::ExecuteScript(script))
|
||||
}
|
||||
|
||||
/// Set window visibility
|
||||
#[frb(sync)]
|
||||
pub fn webview_set_visibility(id: String, visible: bool) -> Result<(), String> {
|
||||
send_command(&id, WebViewCommand::SetVisibility(visible))
|
||||
}
|
||||
|
||||
/// Close the WebView window
|
||||
#[frb(sync)]
|
||||
pub fn webview_close(id: String) -> Result<(), String> {
|
||||
send_command(&id, WebViewCommand::Close)?;
|
||||
// Remove from instances after a short delay
|
||||
std::thread::spawn(move || {
|
||||
std::thread::sleep(std::time::Duration::from_millis(200));
|
||||
WEBVIEW_INSTANCES.write().remove(&id);
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set window size
|
||||
#[frb(sync)]
|
||||
pub fn webview_set_window_size(id: String, width: u32, height: u32) -> Result<(), String> {
|
||||
send_command(&id, WebViewCommand::SetWindowSize(width, height))
|
||||
}
|
||||
|
||||
/// Set window position
|
||||
#[frb(sync)]
|
||||
pub fn webview_set_window_position(id: String, x: i32, y: i32) -> Result<(), String> {
|
||||
send_command(&id, WebViewCommand::SetWindowPosition(x, y))
|
||||
}
|
||||
|
||||
/// Get the current navigation state
|
||||
#[frb(sync)]
|
||||
pub fn webview_get_state(id: String) -> Result<WebViewNavigationState, String> {
|
||||
let instances = WEBVIEW_INSTANCES.read();
|
||||
let instance = instances
|
||||
.get(&id)
|
||||
.ok_or_else(|| format!("WebView instance not found: {}", id))?;
|
||||
let state = instance.state.read().clone();
|
||||
Ok(state)
|
||||
}
|
||||
|
||||
/// Check if the WebView is closed
|
||||
#[frb(sync)]
|
||||
pub fn webview_is_closed(id: String) -> bool {
|
||||
let instances = WEBVIEW_INSTANCES.read();
|
||||
let result = instances
|
||||
.get(&id)
|
||||
.map(|i| i.is_closed.load(Ordering::SeqCst))
|
||||
.unwrap_or(true);
|
||||
result
|
||||
}
|
||||
|
||||
/// Poll for events from the WebView (non-blocking)
|
||||
#[frb(sync)]
|
||||
pub fn webview_poll_events(id: String) -> Vec<WebViewEvent> {
|
||||
let instances = WEBVIEW_INSTANCES.read();
|
||||
if let Some(instance) = instances.get(&id) {
|
||||
let mut events = Vec::new();
|
||||
while let Ok(event) = instance.event_receiver.try_recv() {
|
||||
events.push(event);
|
||||
}
|
||||
drop(instances);
|
||||
events
|
||||
} else {
|
||||
drop(instances);
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a list of all active WebView IDs
|
||||
#[frb(sync)]
|
||||
pub fn webview_list_all() -> Vec<String> {
|
||||
let instances = WEBVIEW_INSTANCES.read();
|
||||
let keys: Vec<String> = instances.keys().cloned().collect();
|
||||
drop(instances);
|
||||
keys
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -2,3 +2,4 @@ pub mod api;
|
||||
mod frb_generated;
|
||||
pub mod http_package;
|
||||
pub mod ort_models;
|
||||
pub mod webview;
|
||||
|
||||
16
rust/src/webview/mod.rs
Normal file
16
rust/src/webview/mod.rs
Normal file
@ -0,0 +1,16 @@
|
||||
// WebView 独立模块
|
||||
// 此模块包含 wry + tao WebView 的完整实现
|
||||
// macOS 平台不支持 WebView,因为 tao 的 EventLoop 必须在主线程运行,与 Flutter 冲突
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
mod webview_impl;
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
pub use webview_impl::*;
|
||||
|
||||
// macOS 平台提供空实现
|
||||
#[cfg(target_os = "macos")]
|
||||
mod webview_stub;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub use webview_stub::*;
|
||||
529
rust/src/webview/webview_impl.rs
Normal file
529
rust/src/webview/webview_impl.rs
Normal file
@ -0,0 +1,529 @@
|
||||
// WebView 实现 (Windows/Linux)
|
||||
// 使用 wry + tao 实现跨平台 WebView
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use crossbeam_channel::{bounded, Receiver, Sender};
|
||||
use parking_lot::RwLock;
|
||||
use once_cell::sync::Lazy;
|
||||
use tao::dpi::{LogicalPosition, LogicalSize};
|
||||
use tao::event::{Event, WindowEvent};
|
||||
use tao::event_loop::{ControlFlow, EventLoop, EventLoopBuilder};
|
||||
use tao::platform::run_return::EventLoopExtRunReturn;
|
||||
use tao::window::{Icon, Window, WindowBuilder};
|
||||
use wry::{PageLoadEvent, WebView, WebViewBuilder};
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
use tao::platform::windows::EventLoopBuilderExtWindows;
|
||||
|
||||
use crate::api::webview_api::{
|
||||
WebViewConfiguration, WebViewNavigationState, WebViewEvent, WebViewCommand,
|
||||
};
|
||||
|
||||
// Embed the app icon at compile time
|
||||
static APP_ICON_DATA: &[u8] = include_bytes!("../../../windows/runner/resources/app_icon.ico");
|
||||
|
||||
// Embed the init script at compile time
|
||||
static INIT_SCRIPT: &str = include_str!("webview_init_script.js");
|
||||
|
||||
// ============ Types ============
|
||||
|
||||
pub type WebViewId = String;
|
||||
|
||||
/// Navigation history manager to track back/forward capability
|
||||
#[derive(Debug, Clone, Default)]
|
||||
struct NavigationHistory {
|
||||
urls: Vec<String>,
|
||||
current_index: i32,
|
||||
}
|
||||
|
||||
impl NavigationHistory {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
urls: Vec::new(),
|
||||
current_index: -1,
|
||||
}
|
||||
}
|
||||
|
||||
fn push(&mut self, url: &str) {
|
||||
if url == "about:blank" {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.current_index >= 0 && (self.current_index as usize) < self.urls.len().saturating_sub(1) {
|
||||
self.urls.truncate((self.current_index + 1) as usize);
|
||||
}
|
||||
|
||||
if self.urls.last().map(|s| s.as_str()) != Some(url) {
|
||||
self.urls.push(url.to_string());
|
||||
}
|
||||
self.current_index = (self.urls.len() as i32) - 1;
|
||||
}
|
||||
|
||||
fn can_go_back(&self) -> bool {
|
||||
self.current_index > 0
|
||||
}
|
||||
|
||||
fn can_go_forward(&self) -> bool {
|
||||
self.current_index >= 0 && (self.current_index as usize) < self.urls.len().saturating_sub(1)
|
||||
}
|
||||
|
||||
fn go_back(&mut self) -> bool {
|
||||
if self.can_go_back() {
|
||||
self.current_index -= 1;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn go_forward(&mut self) -> bool {
|
||||
if self.can_go_forward() {
|
||||
self.current_index += 1;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn current_url(&self) -> Option<&str> {
|
||||
if self.current_index >= 0 && (self.current_index as usize) < self.urls.len() {
|
||||
Some(&self.urls[self.current_index as usize])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WebViewInstance {
|
||||
pub command_sender: Sender<WebViewCommand>,
|
||||
pub event_receiver: Receiver<WebViewEvent>,
|
||||
pub state: Arc<RwLock<WebViewNavigationState>>,
|
||||
pub is_closed: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
pub static WEBVIEW_INSTANCES: Lazy<RwLock<HashMap<WebViewId, WebViewInstance>>> =
|
||||
Lazy::new(|| RwLock::new(HashMap::new()));
|
||||
|
||||
// Custom event type for the event loop
|
||||
#[derive(Debug, Clone)]
|
||||
#[allow(dead_code)]
|
||||
enum UserEvent {
|
||||
Command(WebViewCommand),
|
||||
Quit,
|
||||
}
|
||||
|
||||
// ============ Public Implementation ============
|
||||
|
||||
/// Create a new WebView window
|
||||
pub fn create_webview(config: WebViewConfiguration) -> Result<String, String> {
|
||||
let id = uuid::Uuid::new_v4().to_string();
|
||||
let id_clone = id.clone();
|
||||
|
||||
let (cmd_tx, cmd_rx) = bounded::<WebViewCommand>(100);
|
||||
let (event_tx, event_rx) = bounded::<WebViewEvent>(100);
|
||||
let state = Arc::new(RwLock::new(WebViewNavigationState::default()));
|
||||
let state_clone = Arc::clone(&state);
|
||||
let is_closed = Arc::new(AtomicBool::new(false));
|
||||
let is_closed_clone = Arc::clone(&is_closed);
|
||||
|
||||
std::thread::spawn(move || {
|
||||
run_webview_loop(id_clone, config, cmd_rx, event_tx, state_clone, is_closed_clone);
|
||||
});
|
||||
|
||||
// Wait a moment for the window to be created
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
|
||||
let instance = WebViewInstance {
|
||||
command_sender: cmd_tx,
|
||||
event_receiver: event_rx,
|
||||
state,
|
||||
is_closed,
|
||||
};
|
||||
|
||||
WEBVIEW_INSTANCES.write().insert(id.clone(), instance);
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
pub fn send_command(id: &str, command: WebViewCommand) -> Result<(), String> {
|
||||
let instances = WEBVIEW_INSTANCES.read();
|
||||
let instance = instances
|
||||
.get(id)
|
||||
.ok_or_else(|| format!("WebView instance not found: {}", id))?;
|
||||
|
||||
if instance.is_closed.load(Ordering::SeqCst) {
|
||||
return Err("WebView is closed".to_string());
|
||||
}
|
||||
|
||||
instance
|
||||
.command_sender
|
||||
.send(command)
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
// ============ Internal Implementation ============
|
||||
|
||||
fn run_webview_loop(
|
||||
_id: String,
|
||||
config: WebViewConfiguration,
|
||||
cmd_rx: Receiver<WebViewCommand>,
|
||||
event_tx: Sender<WebViewEvent>,
|
||||
state: Arc<RwLock<WebViewNavigationState>>,
|
||||
is_closed: Arc<AtomicBool>,
|
||||
) {
|
||||
// Create event loop with any_thread support for non-main thread execution
|
||||
#[cfg(target_os = "windows")]
|
||||
let mut event_loop: EventLoop<UserEvent> = EventLoopBuilder::with_user_event()
|
||||
.with_any_thread(true)
|
||||
.build();
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let mut event_loop: EventLoop<UserEvent> = EventLoopBuilder::with_user_event().build();
|
||||
|
||||
let proxy = event_loop.create_proxy();
|
||||
|
||||
// Load window icon from embedded ICO file
|
||||
let window_icon = load_app_icon();
|
||||
|
||||
// Build the window with decorations (title bar provided by OS)
|
||||
let mut window_builder = WindowBuilder::new()
|
||||
.with_title(&config.title)
|
||||
.with_inner_size(LogicalSize::new(config.width, config.height))
|
||||
.with_visible(true);
|
||||
|
||||
// Set window icon if loaded successfully
|
||||
if let Some(icon) = window_icon {
|
||||
window_builder = window_builder.with_window_icon(Some(icon));
|
||||
}
|
||||
|
||||
let window = window_builder.build(&event_loop)
|
||||
.expect("Failed to create window");
|
||||
|
||||
let window = Arc::new(window);
|
||||
|
||||
// Navigation history for tracking back/forward state
|
||||
let nav_history = Arc::new(RwLock::new(NavigationHistory::new()));
|
||||
|
||||
let event_tx_clone = event_tx.clone();
|
||||
let nav_history_ipc = Arc::clone(&nav_history);
|
||||
let state_ipc = Arc::clone(&state);
|
||||
|
||||
// Create web context with custom data directory if provided
|
||||
let mut web_context = config
|
||||
.user_data_folder
|
||||
.as_ref()
|
||||
.map(|path| wry::WebContext::new(Some(std::path::PathBuf::from(path))))
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut builder = WebViewBuilder::new_with_web_context(&mut web_context)
|
||||
.with_url("about:blank")
|
||||
.with_devtools(config.enable_devtools)
|
||||
.with_transparent(config.transparent)
|
||||
.with_background_color((26, 26, 26, 255)) // Dark background #1a1a1a
|
||||
.with_initialization_script(INIT_SCRIPT);
|
||||
|
||||
// Set user agent if provided
|
||||
if let Some(ref user_agent) = config.user_agent {
|
||||
builder = builder.with_user_agent(user_agent);
|
||||
}
|
||||
|
||||
// Store proxy for IPC commands
|
||||
let proxy_ipc = proxy.clone();
|
||||
|
||||
let webview = builder
|
||||
.with_ipc_handler(move |message| {
|
||||
let msg = message.body().to_string();
|
||||
|
||||
// Try to parse as navigation command from JS
|
||||
if let Ok(parsed) = serde_json::from_str::<serde_json::Value>(&msg) {
|
||||
if let Some(msg_type) = parsed.get("type").and_then(|v| v.as_str()) {
|
||||
match msg_type {
|
||||
"nav_back" => {
|
||||
let can_back = nav_history_ipc.read().can_go_back();
|
||||
if can_back {
|
||||
let _ = proxy_ipc.send_event(UserEvent::Command(WebViewCommand::GoBack));
|
||||
}
|
||||
return;
|
||||
}
|
||||
"nav_forward" => {
|
||||
let can_forward = nav_history_ipc.read().can_go_forward();
|
||||
if can_forward {
|
||||
let _ = proxy_ipc.send_event(UserEvent::Command(WebViewCommand::GoForward));
|
||||
}
|
||||
return;
|
||||
}
|
||||
"nav_reload" => {
|
||||
let _ = proxy_ipc.send_event(UserEvent::Command(WebViewCommand::Reload));
|
||||
return;
|
||||
}
|
||||
"get_nav_state" => {
|
||||
let state_guard = state_ipc.read();
|
||||
let history = nav_history_ipc.read();
|
||||
let state_json = serde_json::json!({
|
||||
"can_go_back": history.can_go_back(),
|
||||
"can_go_forward": history.can_go_forward(),
|
||||
"is_loading": state_guard.is_loading,
|
||||
"url": state_guard.url
|
||||
});
|
||||
let script = format!("window._sctUpdateNavState({})", state_json);
|
||||
let _ = proxy_ipc.send_event(UserEvent::Command(WebViewCommand::ExecuteScript(script)));
|
||||
return;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Forward other messages to Dart
|
||||
let _ = event_tx_clone.send(WebViewEvent::WebMessage { message: msg });
|
||||
})
|
||||
.with_navigation_handler({
|
||||
let event_tx = event_tx.clone();
|
||||
let state = Arc::clone(&state);
|
||||
let nav_history = Arc::clone(&nav_history);
|
||||
let proxy = proxy.clone();
|
||||
move |uri| {
|
||||
if uri == "about:blank" {
|
||||
return true;
|
||||
}
|
||||
|
||||
let can_back;
|
||||
let can_forward;
|
||||
{
|
||||
let mut state_guard = state.write();
|
||||
state_guard.url = uri.clone();
|
||||
state_guard.is_loading = true;
|
||||
let history = nav_history.read();
|
||||
can_back = history.can_go_back();
|
||||
can_forward = history.can_go_forward();
|
||||
state_guard.can_go_back = can_back;
|
||||
state_guard.can_go_forward = can_forward;
|
||||
}
|
||||
|
||||
// Send loading state to JS immediately when navigation starts
|
||||
// This makes the spinner appear on the current page before navigating away
|
||||
let state_json = serde_json::json!({
|
||||
"can_go_back": can_back,
|
||||
"can_go_forward": can_forward,
|
||||
"is_loading": true,
|
||||
"url": uri
|
||||
});
|
||||
let script = format!("window._sctUpdateNavState && window._sctUpdateNavState({})", state_json);
|
||||
let _ = proxy.send_event(UserEvent::Command(WebViewCommand::ExecuteScript(script)));
|
||||
|
||||
let _ = event_tx.send(WebViewEvent::NavigationStarted { url: uri });
|
||||
true
|
||||
}
|
||||
})
|
||||
.with_on_page_load_handler({
|
||||
let event_tx = event_tx.clone();
|
||||
let state = Arc::clone(&state);
|
||||
let nav_history = Arc::clone(&nav_history);
|
||||
let proxy = proxy.clone();
|
||||
move |event, url| {
|
||||
if url == "about:blank" {
|
||||
return;
|
||||
}
|
||||
|
||||
match event {
|
||||
PageLoadEvent::Started => {
|
||||
let can_back;
|
||||
let can_forward;
|
||||
{
|
||||
let mut state_guard = state.write();
|
||||
state_guard.url = url.clone();
|
||||
state_guard.is_loading = true;
|
||||
let history = nav_history.read();
|
||||
can_back = history.can_go_back();
|
||||
can_forward = history.can_go_forward();
|
||||
state_guard.can_go_back = can_back;
|
||||
state_guard.can_go_forward = can_forward;
|
||||
}
|
||||
let state_json = serde_json::json!({
|
||||
"can_go_back": can_back,
|
||||
"can_go_forward": can_forward,
|
||||
"is_loading": true,
|
||||
"url": url
|
||||
});
|
||||
let script = format!("window._sctUpdateNavState && window._sctUpdateNavState({})", state_json);
|
||||
let _ = proxy.send_event(UserEvent::Command(WebViewCommand::ExecuteScript(script)));
|
||||
}
|
||||
PageLoadEvent::Finished => {
|
||||
let can_back;
|
||||
let can_forward;
|
||||
{
|
||||
let mut history = nav_history.write();
|
||||
history.push(&url);
|
||||
can_back = history.can_go_back();
|
||||
can_forward = history.can_go_forward();
|
||||
}
|
||||
{
|
||||
let mut state_guard = state.write();
|
||||
state_guard.url = url.clone();
|
||||
state_guard.is_loading = false;
|
||||
state_guard.can_go_back = can_back;
|
||||
state_guard.can_go_forward = can_forward;
|
||||
}
|
||||
let _ = event_tx.send(WebViewEvent::NavigationCompleted { url: url.clone() });
|
||||
|
||||
let state_json = serde_json::json!({
|
||||
"can_go_back": can_back,
|
||||
"can_go_forward": can_forward,
|
||||
"is_loading": false,
|
||||
"url": url
|
||||
});
|
||||
let script = format!("window._sctUpdateNavState && window._sctUpdateNavState({})", state_json);
|
||||
let _ = proxy.send_event(UserEvent::Command(WebViewCommand::ExecuteScript(script)));
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.build(&window)
|
||||
.expect("Failed to create webview");
|
||||
|
||||
let webview = Arc::new(webview);
|
||||
let webview_cmd = Arc::clone(&webview);
|
||||
|
||||
// Spawn command handler thread
|
||||
let proxy_cmd = proxy.clone();
|
||||
std::thread::spawn(move || {
|
||||
while let Ok(cmd) = cmd_rx.recv() {
|
||||
if proxy_cmd.send_event(UserEvent::Command(cmd)).is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Run the event loop with run_return so we can properly cleanup
|
||||
event_loop.run_return(|event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => {
|
||||
is_closed.store(true, Ordering::SeqCst);
|
||||
let _ = event_tx.send(WebViewEvent::WindowClosed);
|
||||
*control_flow = ControlFlow::Exit;
|
||||
}
|
||||
Event::UserEvent(user_event) => match user_event {
|
||||
UserEvent::Command(cmd) => {
|
||||
handle_command(&webview_cmd, &window, cmd, &state, &nav_history, &event_tx, &is_closed, control_flow);
|
||||
}
|
||||
UserEvent::Quit => {
|
||||
is_closed.store(true, Ordering::SeqCst);
|
||||
*control_flow = ControlFlow::Exit;
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
});
|
||||
|
||||
// Explicitly drop in correct order
|
||||
drop(webview_cmd);
|
||||
drop(web_context);
|
||||
drop(window);
|
||||
}
|
||||
|
||||
fn handle_command(
|
||||
webview: &Arc<WebView>,
|
||||
window: &Arc<Window>,
|
||||
command: WebViewCommand,
|
||||
state: &Arc<RwLock<WebViewNavigationState>>,
|
||||
nav_history: &Arc<RwLock<NavigationHistory>>,
|
||||
event_tx: &Sender<WebViewEvent>,
|
||||
is_closed: &Arc<AtomicBool>,
|
||||
control_flow: &mut ControlFlow,
|
||||
) {
|
||||
match command {
|
||||
WebViewCommand::Navigate(url) => {
|
||||
{
|
||||
let mut s = state.write();
|
||||
s.url = url.clone();
|
||||
s.is_loading = true;
|
||||
}
|
||||
let _ = webview.load_url(&url);
|
||||
let _ = event_tx.send(WebViewEvent::NavigationStarted { url });
|
||||
}
|
||||
WebViewCommand::GoBack => {
|
||||
let can_go = {
|
||||
let mut history = nav_history.write();
|
||||
history.go_back()
|
||||
};
|
||||
if can_go {
|
||||
let _ = webview.evaluate_script("history.back()");
|
||||
}
|
||||
}
|
||||
WebViewCommand::GoForward => {
|
||||
let can_go = {
|
||||
let mut history = nav_history.write();
|
||||
history.go_forward()
|
||||
};
|
||||
if can_go {
|
||||
let _ = webview.evaluate_script("history.forward()");
|
||||
}
|
||||
}
|
||||
WebViewCommand::Reload => {
|
||||
let _ = webview.evaluate_script("location.reload()");
|
||||
}
|
||||
WebViewCommand::Stop => {
|
||||
let _ = webview.evaluate_script("window.stop()");
|
||||
}
|
||||
WebViewCommand::ExecuteScript(script) => {
|
||||
let _ = webview.evaluate_script(&script);
|
||||
}
|
||||
WebViewCommand::SetVisibility(visible) => {
|
||||
window.set_visible(visible);
|
||||
}
|
||||
WebViewCommand::Close => {
|
||||
is_closed.store(true, Ordering::SeqCst);
|
||||
let _ = event_tx.send(WebViewEvent::WindowClosed);
|
||||
*control_flow = ControlFlow::Exit;
|
||||
}
|
||||
WebViewCommand::SetWindowSize(width, height) => {
|
||||
let _ = window.set_inner_size(LogicalSize::new(width, height));
|
||||
}
|
||||
WebViewCommand::SetWindowPosition(x, y) => {
|
||||
window.set_outer_position(LogicalPosition::new(x, y));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Load the application icon from embedded ICO data
|
||||
fn load_app_icon() -> Option<Icon> {
|
||||
use std::io::Cursor;
|
||||
use image::ImageReader;
|
||||
|
||||
let cursor = Cursor::new(APP_ICON_DATA);
|
||||
let reader = match ImageReader::new(cursor).with_guessed_format() {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
eprintln!("[SCToolbox] Failed to create image reader: {}", e);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
let img = match reader.decode() {
|
||||
Ok(img) => img,
|
||||
Err(e) => {
|
||||
eprintln!("[SCToolbox] Failed to decode icon: {}", e);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
let rgba = img.to_rgba8();
|
||||
let (width, height) = rgba.dimensions();
|
||||
let raw_data = rgba.into_raw();
|
||||
|
||||
match Icon::from_rgba(raw_data, width, height) {
|
||||
Ok(icon) => Some(icon),
|
||||
Err(e) => {
|
||||
eprintln!("[SCToolbox] Failed to create icon: {:?}", e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
269
rust/src/webview/webview_init_script.js
Normal file
269
rust/src/webview/webview_init_script.js
Normal file
@ -0,0 +1,269 @@
|
||||
// SCToolbox WebView initialization script
|
||||
// Uses IPC (window.ipc.postMessage) to communicate with Rust backend
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
if (window._sctInitialized) return;
|
||||
window._sctInitialized = true;
|
||||
|
||||
// ========== IPC Communication ==========
|
||||
// Send message to Rust backend
|
||||
function sendToRust(type, payload) {
|
||||
if (window.ipc && typeof window.ipc.postMessage === 'function') {
|
||||
window.ipc.postMessage(JSON.stringify({ type, payload }));
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 导航栏 UI ==========
|
||||
const icons = {
|
||||
back: '<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M10 12L6 8L10 4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
||||
forward: '<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M6 4L10 8L6 12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
||||
reload: '<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M13.5 8C13.5 11.0376 11.0376 13.5 8 13.5C4.96243 13.5 2.5 11.0376 2.5 8C2.5 4.96243 4.96243 2.5 8 2.5C10.1012 2.5 11.9254 3.67022 12.8169 5.4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><path d="M10.5 5.5H13V3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>'
|
||||
};
|
||||
|
||||
// Global state from Rust
|
||||
window._sctNavState = {
|
||||
canGoBack: false,
|
||||
canGoForward: false,
|
||||
isLoading: true,
|
||||
url: window.location.href
|
||||
};
|
||||
|
||||
function createNavBar() {
|
||||
if (window.location.href === 'about:blank') return;
|
||||
if (document.getElementById('sct-navbar')) return;
|
||||
if (!document.body) {
|
||||
setTimeout(createNavBar, 50);
|
||||
return;
|
||||
}
|
||||
|
||||
const nav = document.createElement('div');
|
||||
nav.id = 'sct-navbar';
|
||||
nav.innerHTML = `
|
||||
<style>
|
||||
#sct-navbar {
|
||||
position: fixed;
|
||||
top: 0.5rem; /* 8px */
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
height: 2.25rem; /* 36px */
|
||||
background: rgba(19, 36, 49, 0.95);
|
||||
backdrop-filter: blur(0.75rem); /* 12px */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 0.5rem; /* 0 8px */
|
||||
z-index: 2147483647;
|
||||
font-family: 'Segoe UI', system-ui, sans-serif;
|
||||
box-shadow: 0 0.125rem 0.75rem rgba(0,0,0,0.4); /* 0 2px 12px */
|
||||
user-select: none;
|
||||
border-radius: 0.5rem; /* 8px */
|
||||
gap: 0.125rem; /* 2px */
|
||||
border: 0.0625rem solid rgba(10, 49, 66, 0.8); /* 1px */
|
||||
}
|
||||
#sct-navbar button {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: rgba(255,255,255,0.85);
|
||||
width: 2rem; /* 32px */
|
||||
height: 1.75rem; /* 28px */
|
||||
border-radius: 0.25rem; /* 4px */
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.15s ease;
|
||||
padding: 0;
|
||||
}
|
||||
#sct-navbar button:hover:not(:disabled) { background: rgba(10, 49, 66, 0.9); color: #fff; }
|
||||
#sct-navbar button:active:not(:disabled) { background: rgba(10, 49, 66, 1); transform: scale(0.95); }
|
||||
#sct-navbar button:disabled { opacity: 0.35; cursor: not-allowed; }
|
||||
#sct-navbar button svg { display: block; }
|
||||
#sct-navbar-url {
|
||||
width: 16.25rem; /* 260px */
|
||||
height: 1.625rem; /* 26px */
|
||||
padding: 0 0.625rem; /* 0 10px */
|
||||
border: 0.0625rem solid rgba(10, 49, 66, 0.6); /* 1px */
|
||||
border-radius: 0.25rem; /* 4px */
|
||||
background: rgba(0,0,0,0.25);
|
||||
color: rgba(255,255,255,0.9);
|
||||
font-size: 0.75rem; /* 12px */
|
||||
outline: none;
|
||||
text-overflow: ellipsis;
|
||||
margin-left: 0.25rem; /* 4px */
|
||||
}
|
||||
#sct-navbar-url:focus { border-color: rgba(10, 49, 66, 1); background: rgba(0,0,0,0.35); }
|
||||
/* ---------- Spinner & Favicon Slot ---------- */
|
||||
#sct-spinner {
|
||||
display: block; /* default: show spinner during loading */
|
||||
width: 1.125rem; /* 18px */
|
||||
height: 1.125rem; /* 18px */
|
||||
border: 0.125rem solid rgba(255, 255, 255, 0.16); /* ~2px */
|
||||
border-top-color: rgba(255, 255, 255, 0.92);
|
||||
border-radius: 50%;
|
||||
margin-left: 0.5rem;
|
||||
-webkit-animation: sct-spin 0.8s linear infinite;
|
||||
animation: sct-spin 0.8s linear infinite;
|
||||
pointer-events: none;
|
||||
align-self: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
#sct-favicon-slot {
|
||||
display: none; /* hidden until page ready */
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 1.125rem; /* 18px - same as spinner */
|
||||
height: 1.125rem; /* 18px - same as spinner */
|
||||
margin-left: 0.5rem;
|
||||
pointer-events: none;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
#sct-favicon {
|
||||
width: 1.125rem; /* 18px */
|
||||
height: 1.125rem; /* 18px */
|
||||
object-fit: cover;
|
||||
border-radius: 0.25rem; /* 4px */
|
||||
}
|
||||
@-webkit-keyframes sct-spin { to { -webkit-transform: rotate(360deg); } }
|
||||
@keyframes sct-spin { to { transform: rotate(360deg); } }
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
#sct-spinner { -webkit-animation: none; animation: none; }
|
||||
}
|
||||
</style>
|
||||
<button id="sct-back" title="Back" disabled>${icons.back}</button>
|
||||
<button id="sct-forward" title="Forward" disabled>${icons.forward}</button>
|
||||
<button id="sct-reload" title="Reload">${icons.reload}</button>
|
||||
<div id="sct-spinner" role="progressbar" aria-hidden="false" aria-valuetext="Loading" title="Loading"></div>
|
||||
<div id="sct-favicon-slot"><img id="sct-favicon" src="" alt="Page icon" /></div>
|
||||
<input type="text" id="sct-navbar-url" readonly value="${window.location.href}" />
|
||||
`;
|
||||
document.body.insertBefore(nav, document.body.firstChild);
|
||||
|
||||
// Navigation buttons - send commands to Rust
|
||||
document.getElementById('sct-back').onclick = () => {
|
||||
sendToRust('nav_back', {});
|
||||
};
|
||||
document.getElementById('sct-forward').onclick = () => {
|
||||
sendToRust('nav_forward', {});
|
||||
};
|
||||
document.getElementById('sct-reload').onclick = () => {
|
||||
sendToRust('nav_reload', {});
|
||||
};
|
||||
|
||||
// Apply initial state from Rust
|
||||
updateNavBarFromState();
|
||||
|
||||
// Request initial state from Rust
|
||||
sendToRust('get_nav_state', {});
|
||||
}
|
||||
|
||||
// Update navbar UI based on state from Rust
|
||||
function updateNavBarFromState() {
|
||||
const state = window._sctNavState;
|
||||
const backBtn = document.getElementById('sct-back');
|
||||
const forwardBtn = document.getElementById('sct-forward');
|
||||
const urlEl = document.getElementById('sct-navbar-url');
|
||||
const spinner = document.getElementById('sct-spinner');
|
||||
const faviconSlot = document.getElementById('sct-favicon-slot');
|
||||
|
||||
if (backBtn) {
|
||||
backBtn.disabled = !state.canGoBack;
|
||||
}
|
||||
if (forwardBtn) {
|
||||
forwardBtn.disabled = !state.canGoForward;
|
||||
}
|
||||
if (urlEl && state.url) {
|
||||
urlEl.value = state.url;
|
||||
}
|
||||
|
||||
// Show spinner when loading, show favicon when complete
|
||||
if (state.isLoading) {
|
||||
if (spinner) {
|
||||
spinner.style.display = 'block';
|
||||
spinner.setAttribute('aria-hidden', 'false');
|
||||
spinner.setAttribute('aria-busy', 'true');
|
||||
}
|
||||
if (faviconSlot) {
|
||||
faviconSlot.style.display = 'none';
|
||||
}
|
||||
} else {
|
||||
if (spinner) {
|
||||
spinner.style.display = 'none';
|
||||
spinner.setAttribute('aria-hidden', 'true');
|
||||
spinner.setAttribute('aria-busy', 'false');
|
||||
}
|
||||
// Show favicon when page is loaded
|
||||
showFaviconIfAvailable();
|
||||
}
|
||||
}
|
||||
|
||||
// Extract and show favicon from page
|
||||
function showFaviconIfAvailable() {
|
||||
const faviconSlot = document.getElementById('sct-favicon-slot');
|
||||
const faviconImg = document.getElementById('sct-favicon');
|
||||
|
||||
if (!faviconSlot || !faviconImg) return;
|
||||
|
||||
// Try to find favicon from page
|
||||
let faviconUrl = null;
|
||||
|
||||
// 1. Look for link[rel="icon"] or link[rel="shortcut icon"]
|
||||
const linkIcon = document.querySelector('link[rel="icon"], link[rel="shortcut icon"], link[rel="apple-touch-icon"]');
|
||||
if (linkIcon && linkIcon.href) {
|
||||
faviconUrl = linkIcon.href;
|
||||
}
|
||||
|
||||
// 2. Look for og:image in meta tags (fallback)
|
||||
if (!faviconUrl) {
|
||||
const ogImage = document.querySelector('meta[property="og:image"]');
|
||||
if (ogImage && ogImage.content) {
|
||||
faviconUrl = ogImage.content;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Try default favicon.ico
|
||||
if (!faviconUrl) {
|
||||
try {
|
||||
const origin = window.location.origin;
|
||||
if (origin && origin !== 'null') {
|
||||
faviconUrl = origin + '/favicon.ico';
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
// Display favicon if found
|
||||
if (faviconUrl) {
|
||||
faviconImg.src = faviconUrl;
|
||||
faviconImg.onerror = () => {
|
||||
faviconSlot.style.display = 'none';
|
||||
};
|
||||
faviconImg.onload = () => {
|
||||
faviconSlot.style.display = 'flex';
|
||||
};
|
||||
} else {
|
||||
faviconSlot.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Rust -> JS Message Handler ==========
|
||||
// Rust will call this function to update navigation state
|
||||
window._sctUpdateNavState = function(state) {
|
||||
if (state) {
|
||||
window._sctNavState = {
|
||||
canGoBack: !!state.can_go_back,
|
||||
canGoForward: !!state.can_go_forward,
|
||||
isLoading: !!state.is_loading,
|
||||
url: state.url || window.location.href
|
||||
};
|
||||
updateNavBarFromState();
|
||||
}
|
||||
};
|
||||
|
||||
// 在 DOM 准备好时创建导航栏
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', createNavBar);
|
||||
} else {
|
||||
createNavBar();
|
||||
}
|
||||
})();
|
||||
39
rust/src/webview/webview_stub.rs
Normal file
39
rust/src/webview/webview_stub.rs
Normal file
@ -0,0 +1,39 @@
|
||||
// macOS WebView 存根实现
|
||||
// macOS 平台不支持 WebView,因为 tao 的 EventLoop 必须在主线程运行,与 Flutter 冲突
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
|
||||
use crossbeam_channel::{Receiver, Sender};
|
||||
use parking_lot::RwLock;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use crate::api::webview_api::{
|
||||
WebViewConfiguration, WebViewNavigationState, WebViewEvent, WebViewCommand,
|
||||
};
|
||||
|
||||
// ============ Types ============
|
||||
|
||||
pub type WebViewId = String;
|
||||
|
||||
pub struct WebViewInstance {
|
||||
pub command_sender: Sender<WebViewCommand>,
|
||||
pub event_receiver: Receiver<WebViewEvent>,
|
||||
pub state: Arc<RwLock<WebViewNavigationState>>,
|
||||
pub is_closed: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
pub static WEBVIEW_INSTANCES: Lazy<RwLock<HashMap<WebViewId, WebViewInstance>>> =
|
||||
Lazy::new(|| RwLock::new(HashMap::new()));
|
||||
|
||||
// ============ macOS 存根实现 ============
|
||||
|
||||
/// macOS 上 WebView 不可用
|
||||
pub fn create_webview(_config: WebViewConfiguration) -> Result<String, String> {
|
||||
Err("WebView is not supported on macOS".to_string())
|
||||
}
|
||||
|
||||
pub fn send_command(id: &str, _command: WebViewCommand) -> Result<(), String> {
|
||||
Err(format!("WebView instance not found: {} (WebView not supported on macOS)", id))
|
||||
}
|
||||
@ -37,18 +37,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: async
|
||||
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
|
||||
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.11.0"
|
||||
version: "2.13.0"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: boolean_selector
|
||||
sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
|
||||
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
version: "2.1.2"
|
||||
cli_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cli_config
|
||||
sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
collection:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -69,10 +77,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: coverage
|
||||
sha256: "8acabb8306b57a409bf4c83522065672ee13179297a6bb0cb9ead73948df7c76"
|
||||
sha256: "802bd084fb82e55df091ec8ad1553a7331b61c08251eef19a508b6f3f3a9858d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.7.2"
|
||||
version: "1.13.1"
|
||||
crypto:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -101,18 +109,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fixnum
|
||||
sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1"
|
||||
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
version: "1.1.1"
|
||||
frontend_server_client:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: frontend_server_client
|
||||
sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612"
|
||||
sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.0"
|
||||
version: "4.0.0"
|
||||
github:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -125,10 +133,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: glob
|
||||
sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63"
|
||||
sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
version: "2.1.3"
|
||||
hex:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -149,10 +157,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_multi_server
|
||||
sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b"
|
||||
sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.1"
|
||||
version: "3.2.2"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -165,26 +173,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: io
|
||||
sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e"
|
||||
sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
version: "1.0.5"
|
||||
js:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: js
|
||||
sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf
|
||||
sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.1"
|
||||
version: "0.7.2"
|
||||
json_annotation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: json_annotation
|
||||
sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467
|
||||
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.8.1"
|
||||
version: "4.9.0"
|
||||
lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
@ -205,26 +213,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
|
||||
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.16+1"
|
||||
version: "0.12.17"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
|
||||
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.12.0"
|
||||
version: "1.17.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: mime
|
||||
sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2"
|
||||
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.5"
|
||||
version: "2.0.0"
|
||||
node_preamble:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -237,10 +245,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_config
|
||||
sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd"
|
||||
sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
version: "2.2.0"
|
||||
path:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -261,18 +269,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pool
|
||||
sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
|
||||
sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.1"
|
||||
version: "1.5.2"
|
||||
pub_semver:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pub_semver
|
||||
sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c"
|
||||
sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
version: "2.2.0"
|
||||
shelf:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -293,34 +301,34 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf_static
|
||||
sha256: a41d3f53c4adf0f57480578c1d61d90342cd617de7fc8077b1304643c2d85c1e
|
||||
sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
version: "1.1.3"
|
||||
shelf_web_socket:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf_web_socket
|
||||
sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1"
|
||||
sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
version: "3.0.0"
|
||||
source_map_stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_map_stack_trace
|
||||
sha256: "84cf769ad83aa6bb61e0aa5a18e53aea683395f196a6f39c4c881fb90ed4f7ae"
|
||||
sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
version: "2.1.2"
|
||||
source_maps:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_maps
|
||||
sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703"
|
||||
sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.10.12"
|
||||
version: "0.10.13"
|
||||
source_span:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -333,58 +341,58 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
|
||||
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.11.1"
|
||||
version: "1.12.1"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
|
||||
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
version: "2.1.4"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: string_scanner
|
||||
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
|
||||
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
version: "1.4.1"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: term_glyph
|
||||
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
|
||||
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
version: "1.2.2"
|
||||
test:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: test
|
||||
sha256: "7ee446762c2c50b3bd4ea96fe13ffac69919352bd3b4b17bac3f3465edc58073"
|
||||
sha256: "75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.25.2"
|
||||
version: "1.26.3"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f"
|
||||
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.0"
|
||||
version: "0.7.7"
|
||||
test_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_core
|
||||
sha256: "2bc4b4ecddd75309300d8096f781c0e3280ca1ef85beda558d33fcbedc2eead4"
|
||||
sha256: "0cc24b5ff94b38d2ae73e1eb43cc302b77964fbf67abad1e296025b78deb53d0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.0"
|
||||
version: "0.6.12"
|
||||
toml:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -397,10 +405,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: typed_data
|
||||
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
|
||||
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.2"
|
||||
version: "1.4.0"
|
||||
version:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -413,34 +421,42 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: e7d5ecd604e499358c5fe35ee828c0298a320d54455e791e9dcf73486bc8d9f0
|
||||
sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "14.1.0"
|
||||
version: "15.0.2"
|
||||
watcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: watcher
|
||||
sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
|
||||
sha256: "592ab6e2892f67760543fb712ff0177f4ec76c031f02f5b4ff8d3fc5eb9fb61a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
version: "1.1.4"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
|
||||
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.1"
|
||||
version: "1.1.1"
|
||||
web_socket:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web_socket
|
||||
sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
web_socket_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web_socket_channel
|
||||
sha256: "1d8e795e2a8b3730c41b8a98a2dff2e0fb57ae6f0764a1c46ec5915387d257b2"
|
||||
sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.4"
|
||||
version: "3.0.3"
|
||||
webkit_inspection_protocol:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -458,4 +474,4 @@ packages:
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
sdks:
|
||||
dart: ">=3.3.0 <4.0.0"
|
||||
dart: ">=3.7.0-0 <4.0.0"
|
||||
|
||||
@ -7,7 +7,6 @@
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <desktop_multi_window/desktop_multi_window_plugin.h>
|
||||
#include <desktop_webview_window/desktop_webview_window_plugin.h>
|
||||
#include <flutter_acrylic/flutter_acrylic_plugin.h>
|
||||
#include <screen_retriever_windows/screen_retriever_windows_plugin_c_api.h>
|
||||
#include <url_launcher_windows/url_launcher_windows.h>
|
||||
@ -16,8 +15,6 @@
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
DesktopMultiWindowPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("DesktopMultiWindowPlugin"));
|
||||
DesktopWebviewWindowPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("DesktopWebviewWindowPlugin"));
|
||||
FlutterAcrylicPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FlutterAcrylicPlugin"));
|
||||
ScreenRetrieverWindowsPluginCApiRegisterWithRegistrar(
|
||||
|
||||
@ -4,7 +4,6 @@
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
desktop_multi_window
|
||||
desktop_webview_window
|
||||
flutter_acrylic
|
||||
screen_retriever_windows
|
||||
url_launcher_windows
|
||||
|
||||
Loading…
Reference in New Issue
Block a user