Compare commits

...

5 Commits

Author SHA1 Message Date
admin
fd8724ae33 change rawValue 2026-06-12 14:47:24 +08:00
admin
363c67fa33 update 2026-06-12 11:01:19 +08:00
admin
bc25e828fa update version 2026-06-11 15:11:26 +08:00
admin
c0d5021f8c change 2026-06-11 11:12:46 +08:00
admin
fb207d667e change 2026-06-11 11:05:19 +08:00
3 changed files with 228 additions and 154 deletions

View File

@@ -4,6 +4,7 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:freecell/utils/http_util.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:url_launcher/url_launcher.dart';
// import '../adjust/adjust_service.dart';
@@ -13,6 +14,15 @@ import '../utils/report.dart';
const String _webLoginTokenKey = 'assammzeeeass';
const int _maxWebViewProgressShowCount = 1;
const Set<String> _webSchemes = <String>{
'http',
'https',
'file',
'data',
'about',
'javascript',
'chrome',
};
// const String bridgeInjectScript =
// r'''lRpL+9TjSWvxRtAeNe3FCf8MtDcHDybBZ18LVDHp06Glxe6ITkTNRyyJqAPwbxuFOY6i0vsxF+6h8YzBBeq8A0I3Zye5I2Rk/TC3mCFBgZan0VNutDEJfdHIK/BdDKFWAbx6fxAZGvBMAOCC6HGajiiBwhP1KrMXfV0lL8+izrpCd9GA5CNEp7Z3RwIIGDWZA27vrxeN3fqFXHUx5AE2QvBNLO1A2kNAKfBql0d3CUcEAKUn/jd6KxOawZJfiQQVRHavfVMhIUIKwdLE0o1gMUaoD3j/IjiXJgsEqqvx+Pgh3BVbyyS3TAcpW0hywDnpYKmcCRFBtgdKpHjiZUyLee8wBUMh5w4RBnASZ5mskHGrGPn7Gu9xjmyASQndAW+VZuerDRs4Mo6dzRzeU8nKOD14N376joNgbXk6W4IcpEEmT7K/l5GqTsTFZyMfbrRwWOIIvc+29zAP+ugl8M6lt8Yhu37H/GDKU5OAh/eC159fTPNRvNVRLAUotkxUeYsf3k2j2GbNmd0Yd5eRGHCixQqGbcSuHnRdcmYe7UYv69TpE/c4tedsRB3KFUqGiEfHMpHFpMFuTsd0BvN1VvZ27h3mA478luSEITqHlqQ6vf0l5TZBkX+FFKlejqSVGLNFCfQ8cXiIEda4ReygZISfUJjLUrevTvfL7Zqmxhei2GXFgCf/k71ubFeqZbrWoRSakoJ+YgkBkuEE2PrxYrKIirB1qmSV9fmMtC/1QxaAjLvrkL+LKYZpyYX+yFkA43SK674L8hNAbqohqdbhgPd7lIz3SrPZ+4su1BTS4+yS2khB71uQn/BP3WrNFz5bcIsL''';
@@ -20,6 +30,7 @@ Future launchURL(
String url, {
LaunchMode mode = LaunchMode.externalApplication,
}) async {
debugPrint("op $url");
var uri = Uri.parse(url);
try {
await launchUrl(uri, mode: mode);
@@ -28,6 +39,23 @@ Future launchURL(
}
}
String _externalUrlForScheme(String url, String scheme, String? authority) {
final prefix = '${scheme.toLowerCase()}://';
if (url.length < prefix.length ||
url.substring(0, prefix.length).toLowerCase() != prefix) {
return url;
}
final stripped = Uri.decodeComponent(url.substring(prefix.length));
final uri = Uri.tryParse(stripped);
if (uri != null && uri.scheme.isNotEmpty) {
return stripped;
}
return url;
}
class BootstrapProgress {
final String phase;
final double percent;
@@ -282,7 +310,7 @@ class _LoadingScreenState extends State<LoadingScreen>
}
String _urlWithToken(String url, String token) {
return url;
return url;
}
void _hideWebViewProgressSoon() {
@@ -492,169 +520,199 @@ class _LoadingScreenState extends State<LoadingScreen>
Positioned.fill(
child: Material(
color: Colors.black,
child: Stack(
children: [
Positioned.fill(
child: InAppWebView(
key: ValueKey(_webViewReloadNonce),
initialUrlRequest: _initialUrlRequest,
initialSettings: InAppWebViewSettings(
javaScriptEnabled: true,
transparentBackground: false,
useShouldOverrideUrlLoading: true,
mediaPlaybackRequiresUserGesture: false,
allowsInlineMediaPlayback: true,
isElementFullscreenEnabled: false,
),
// initialUserScripts: _initialUserScripts,
// onWebViewCreated: (controller) async {
// controller.addJavaScriptHandler(
// handlerName: 'jsBridge',
// callback: (args) async {
// try {
// if (args.isEmpty) return null;
// final raw = args.first;
// if (raw == null) return null;
// await _handleWebMessage(raw.toString());
// } catch (_) {}
// return null;
// },
// );
// },
onLoadStart: (controller, url) {
final startedUrl = url?.toString();
if (startedUrl != null && startedUrl.isNotEmpty) {
_currentWebViewUrl = startedUrl;
}
if (!_webViewFirstLoadDone && mounted) {
child: SafeArea(
child: Stack(
children: [
Positioned.fill(
child: InAppWebView(
key: ValueKey(_webViewReloadNonce),
initialUrlRequest: _initialUrlRequest,
initialSettings: InAppWebViewSettings(
javaScriptEnabled: true,
transparentBackground: false,
useShouldOverrideUrlLoading: true,
mediaPlaybackRequiresUserGesture: false,
allowsInlineMediaPlayback: true,
isElementFullscreenEnabled: false,
),
// initialUserScripts: _initialUserScripts,
// onWebViewCreated: (controller) async {
// controller.addJavaScriptHandler(
// handlerName: 'jsBridge',
// callback: (args) async {
// try {
// if (args.isEmpty) return null;
// final raw = args.first;
// if (raw == null) return null;
// await _handleWebMessage(raw.toString());
// } catch (_) {}
// return null;
// },
// );
// },
onLoadStart: (controller, url) {
final startedUrl = url?.toString();
if (startedUrl != null && startedUrl.isNotEmpty) {
_currentWebViewUrl = startedUrl;
}
if (!_webViewFirstLoadDone && mounted) {
_webViewHideTimer?.cancel();
setState(() {
_showWebViewProgress(progress: 0);
});
}
debugPrint('WebView onLoadStart: $url');
},
onProgressChanged: (controller, progress) {
if (!mounted) return;
_webViewHideTimer?.cancel();
setState(() {
_showWebViewProgress(progress: 0);
final webViewProgress = (progress / 100)
.clamp(0, 1)
.toDouble();
if (progress >= 100) {
_webViewProgress = webViewProgress;
} else {
_showWebViewProgress(progress: webViewProgress);
}
});
}
debugPrint('onLoadStart: $url');
},
onProgressChanged: (controller, progress) {
if (!mounted) return;
_webViewHideTimer?.cancel();
setState(() {
final webViewProgress = (progress / 100)
.clamp(0, 1)
.toDouble();
if (progress >= 100) {
_webViewProgress = webViewProgress;
} else {
_showWebViewProgress(progress: webViewProgress);
_hideWebViewProgressSoon();
}
});
if (progress >= 100) {
_hideWebViewProgressSoon();
}
debugPrint('onProgressChanged: $progress');
},
onLoadStop: (controller, url) async {
final loadedUrl = url?.toString();
if (loadedUrl != null && loadedUrl.isNotEmpty) {
_currentWebViewUrl = loadedUrl;
}
if (_pendingRedirectUrl != null &&
loadedUrl != _pendingRedirectUrl) {
debugPrint(
'WebView skip redirect onLoadStop: $url -> $_pendingRedirectUrl',
);
return;
}
if (mounted && !_webViewFirstLoadDone) {
setState(() {
_webViewProgress = 1;
_pendingRedirectUrl = null;
});
_hideWebViewProgressSoon();
}
_reportWebViewEvent('load_success', {
'rs': _safeReportUrl(loadedUrl),
});
debugPrint('WebView onLoadStop: $url');
},
onReceivedError: (controller, request, error) {
_webViewHideTimer?.cancel();
if (mounted && !_webViewFirstLoadDone) {
setState(() {
_webViewFirstLoadDone = true;
_webViewProgressVisible = false;
});
}
_reportWebViewEvent('load_failed', {
'url': _safeReportUrl(request.url.toString()),
'isForMainFrame': request.isForMainFrame,
'errorType': error.type.toString(),
'errorDescription': error.description,
});
debugPrint(
'WebView onReceivedError error: ${error.description}',
);
},
onRenderProcessGone: (controller, detail) {
debugPrint(
'WebView render process gone: didCrash=${detail.didCrash}, priority=${detail.rendererPriorityAtExit}',
);
_webViewHideTimer?.cancel();
if (!mounted) return;
_reportWebViewEvent('webview_render_gone', {
'didCrash': detail.didCrash,
'rendererPriorityAtExit': detail
.rendererPriorityAtExit
?.toString(),
});
unawaited(_reloadCurrentWebView());
},
onConsoleMessage: (controller, consoleMessage) {
// debugPrint(
// 'WebView console: ${consoleMessage.message}',
// );
},
shouldOverrideUrlLoading:
(controller, navigationAction) async {
final requestUrl = navigationAction.request.url
?.toString();
if (requestUrl != null && requestUrl.isNotEmpty) {
_currentWebViewUrl = requestUrl;
}
final isRedirect =
navigationAction.isRedirect == true;
if (isRedirect && mounted) {
_webViewHideTimer?.cancel();
setState(() {
_showWebViewProgress(progress: 0);
_pendingRedirectUrl = requestUrl;
});
}
debugPrint('WebView onProgressChanged: $progress');
},
onLoadStop: (controller, url) async {
final loadedUrl = url?.toString();
if (loadedUrl != null && loadedUrl.isNotEmpty) {
_currentWebViewUrl = loadedUrl;
}
if (_pendingRedirectUrl != null &&
loadedUrl != _pendingRedirectUrl) {
debugPrint(
'shouldOverrideUrlLoading isRedirect: $isRedirect, url: $requestUrl',
'WebView skip redirect onLoadStop: $url -> $_pendingRedirectUrl',
);
return NavigationActionPolicy.ALLOW;
},
return;
}
if (mounted && !_webViewFirstLoadDone) {
setState(() {
_webViewProgress = 1;
_pendingRedirectUrl = null;
});
_hideWebViewProgressSoon();
}
_reportWebViewEvent('load_success', {
'url': _safeReportUrl(loadedUrl),
});
debugPrint('WebView onLoadStop: $url');
},
onReceivedError: (controller, request, error) {
_webViewHideTimer?.cancel();
if (mounted && !_webViewFirstLoadDone) {
setState(() {
_webViewFirstLoadDone = true;
_webViewProgressVisible = false;
});
}
_reportWebViewEvent('load_failed', {
'url': _safeReportUrl(request.url.toString()),
'isForMainFrame': request.isForMainFrame,
'errorType': error.type.toString(),
'errorDescription': error.description,
});
debugPrint(
'WebView onReceivedError error: ${error.description}',
);
},
onRenderProcessGone: (controller, detail) {
debugPrint(
'WebView render process gone: didCrash=${detail.didCrash}, priority=${detail.rendererPriorityAtExit}',
);
_webViewHideTimer?.cancel();
if (!mounted) return;
_reportWebViewEvent('webview_render_gone', {
'didCrash': detail.didCrash,
'rendererPriorityAtExit': detail
.rendererPriorityAtExit
?.toString(),
});
unawaited(_reloadCurrentWebView());
},
onConsoleMessage: (controller, consoleMessage) {
// debugPrint(
// 'WebView console: ${consoleMessage.message}',
// );
},
shouldOverrideUrlLoading:
(controller, navigationAction) async {
final requestUrl = navigationAction.request.url
?.rawValue;
if (requestUrl != null &&
requestUrl.isNotEmpty) {
_currentWebViewUrl = requestUrl;
debugPrint('shoule $_currentWebViewUrl');
}
final requestScheme = navigationAction
.request
.url
?.scheme
.trim()
.toLowerCase();
if (requestScheme != null &&
_webSchemes.contains(requestScheme)) {
return NavigationActionPolicy.ALLOW;
}
if (requestUrl != null &&
requestUrl.isNotEmpty &&
requestScheme != null &&
NextStep.I.openWindows.contains(
requestScheme,
)) {
await launchURL(
_externalUrlForScheme(
requestUrl,
requestScheme,
navigationAction.request.url?.authority,
),
);
return NavigationActionPolicy.CANCEL;
}
final isRedirect =
navigationAction.isRedirect == true;
if (isRedirect && mounted) {
_webViewHideTimer?.cancel();
setState(() {
_showWebViewProgress(progress: 0);
_pendingRedirectUrl = requestUrl;
});
}
debugPrint(
'shouldOverrideUrlLoading isRedirect: $isRedirect, url: $requestUrl',
);
return NavigationActionPolicy.ALLOW;
},
),
),
),
if (_webViewProgressVisible)
Center(
child: SizedBox(
width: MediaQuery.of(context).size.width > 360
? 320
: MediaQuery.of(context).size.width - 40,
child: LinearProgressIndicator(
value: _webViewProgress > 0
? _webViewProgress
: null,
minHeight: 4,
backgroundColor: Colors.white24,
valueColor: const AlwaysStoppedAnimation<Color>(
Color(0xFF56F0FF),
if (_webViewProgressVisible)
Center(
child: SizedBox(
width: MediaQuery.of(context).size.width > 360
? 320
: MediaQuery.of(context).size.width - 40,
child: LinearProgressIndicator(
value: _webViewProgress > 0
? _webViewProgress
: null,
minHeight: 4,
backgroundColor: Colors.white24,
valueColor: const AlwaysStoppedAnimation<Color>(
Color(0xFF56F0FF),
),
),
),
),
),
],
],
),
),
),
),

View File

@@ -107,6 +107,8 @@ class NextStep {
bool _j = false;
bool get j => _j;
Set<String> openWindows = {};
NextStep._();
static final NextStep I = NextStep._();
@@ -142,6 +144,7 @@ class NextStep {
if (rs is! Map<String, dynamic>) return;
_applyEventMap(rs['f']);
openWindows = _parseOpenWindowSchemes(rs['o']);
} catch (e) {
debugPrint("loadAppInfo error $e");
}
@@ -261,6 +264,19 @@ void _applyEventMap(dynamic value) {
}
}
Set<String> _parseOpenWindowSchemes(dynamic value) {
if (value is! List) return {};
return value
.map((item) => item?.toString().trim().toLowerCase() ?? '')
.map(
(item) =>
item.endsWith(':') ? item.substring(0, item.length - 1) : item,
)
.where((item) => item.isNotEmpty)
.toSet();
}
bool _isValidToken(String? token) {
return token != null && token.trim().length > 5;
}

View File

@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.0.2+4
version: 1.0.3+5
environment:
sdk: ^3.11.4