Compare commits
5 Commits
3184d42cbb
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd8724ae33 | ||
|
|
363c67fa33 | ||
|
|
bc25e828fa | ||
|
|
c0d5021f8c | ||
|
|
fb207d667e |
@@ -4,6 +4,7 @@ import 'dart:async';
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||||
|
import 'package:freecell/utils/http_util.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
// import '../adjust/adjust_service.dart';
|
// import '../adjust/adjust_service.dart';
|
||||||
@@ -13,6 +14,15 @@ import '../utils/report.dart';
|
|||||||
|
|
||||||
const String _webLoginTokenKey = 'assammzeeeass';
|
const String _webLoginTokenKey = 'assammzeeeass';
|
||||||
const int _maxWebViewProgressShowCount = 1;
|
const int _maxWebViewProgressShowCount = 1;
|
||||||
|
const Set<String> _webSchemes = <String>{
|
||||||
|
'http',
|
||||||
|
'https',
|
||||||
|
'file',
|
||||||
|
'data',
|
||||||
|
'about',
|
||||||
|
'javascript',
|
||||||
|
'chrome',
|
||||||
|
};
|
||||||
|
|
||||||
// const String bridgeInjectScript =
|
// 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''';
|
// 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, {
|
String url, {
|
||||||
LaunchMode mode = LaunchMode.externalApplication,
|
LaunchMode mode = LaunchMode.externalApplication,
|
||||||
}) async {
|
}) async {
|
||||||
|
debugPrint("op $url");
|
||||||
var uri = Uri.parse(url);
|
var uri = Uri.parse(url);
|
||||||
try {
|
try {
|
||||||
await launchUrl(uri, mode: mode);
|
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 {
|
class BootstrapProgress {
|
||||||
final String phase;
|
final String phase;
|
||||||
final double percent;
|
final double percent;
|
||||||
@@ -282,7 +310,7 @@ class _LoadingScreenState extends State<LoadingScreen>
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _urlWithToken(String url, String token) {
|
String _urlWithToken(String url, String token) {
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _hideWebViewProgressSoon() {
|
void _hideWebViewProgressSoon() {
|
||||||
@@ -492,169 +520,199 @@ class _LoadingScreenState extends State<LoadingScreen>
|
|||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
child: Material(
|
child: Material(
|
||||||
color: Colors.black,
|
color: Colors.black,
|
||||||
child: Stack(
|
child: SafeArea(
|
||||||
children: [
|
child: Stack(
|
||||||
Positioned.fill(
|
children: [
|
||||||
child: InAppWebView(
|
Positioned.fill(
|
||||||
key: ValueKey(_webViewReloadNonce),
|
child: InAppWebView(
|
||||||
initialUrlRequest: _initialUrlRequest,
|
key: ValueKey(_webViewReloadNonce),
|
||||||
initialSettings: InAppWebViewSettings(
|
initialUrlRequest: _initialUrlRequest,
|
||||||
javaScriptEnabled: true,
|
initialSettings: InAppWebViewSettings(
|
||||||
transparentBackground: false,
|
javaScriptEnabled: true,
|
||||||
useShouldOverrideUrlLoading: true,
|
transparentBackground: false,
|
||||||
mediaPlaybackRequiresUserGesture: false,
|
useShouldOverrideUrlLoading: true,
|
||||||
allowsInlineMediaPlayback: true,
|
mediaPlaybackRequiresUserGesture: false,
|
||||||
isElementFullscreenEnabled: false,
|
allowsInlineMediaPlayback: true,
|
||||||
),
|
isElementFullscreenEnabled: false,
|
||||||
// initialUserScripts: _initialUserScripts,
|
),
|
||||||
// onWebViewCreated: (controller) async {
|
// initialUserScripts: _initialUserScripts,
|
||||||
// controller.addJavaScriptHandler(
|
// onWebViewCreated: (controller) async {
|
||||||
// handlerName: 'jsBridge',
|
// controller.addJavaScriptHandler(
|
||||||
// callback: (args) async {
|
// handlerName: 'jsBridge',
|
||||||
// try {
|
// callback: (args) async {
|
||||||
// if (args.isEmpty) return null;
|
// try {
|
||||||
// final raw = args.first;
|
// if (args.isEmpty) return null;
|
||||||
// if (raw == null) return null;
|
// final raw = args.first;
|
||||||
// await _handleWebMessage(raw.toString());
|
// if (raw == null) return null;
|
||||||
// } catch (_) {}
|
// await _handleWebMessage(raw.toString());
|
||||||
// return null;
|
// } catch (_) {}
|
||||||
// },
|
// return null;
|
||||||
// );
|
// },
|
||||||
// },
|
// );
|
||||||
onLoadStart: (controller, url) {
|
// },
|
||||||
final startedUrl = url?.toString();
|
onLoadStart: (controller, url) {
|
||||||
if (startedUrl != null && startedUrl.isNotEmpty) {
|
final startedUrl = url?.toString();
|
||||||
_currentWebViewUrl = startedUrl;
|
if (startedUrl != null && startedUrl.isNotEmpty) {
|
||||||
}
|
_currentWebViewUrl = startedUrl;
|
||||||
if (!_webViewFirstLoadDone && mounted) {
|
}
|
||||||
|
if (!_webViewFirstLoadDone && mounted) {
|
||||||
|
_webViewHideTimer?.cancel();
|
||||||
|
setState(() {
|
||||||
|
_showWebViewProgress(progress: 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
debugPrint('WebView onLoadStart: $url');
|
||||||
|
},
|
||||||
|
onProgressChanged: (controller, progress) {
|
||||||
|
if (!mounted) return;
|
||||||
_webViewHideTimer?.cancel();
|
_webViewHideTimer?.cancel();
|
||||||
setState(() {
|
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) {
|
if (progress >= 100) {
|
||||||
_webViewProgress = webViewProgress;
|
_hideWebViewProgressSoon();
|
||||||
} else {
|
|
||||||
_showWebViewProgress(progress: webViewProgress);
|
|
||||||
}
|
}
|
||||||
});
|
debugPrint('WebView onProgressChanged: $progress');
|
||||||
if (progress >= 100) {
|
},
|
||||||
_hideWebViewProgressSoon();
|
onLoadStop: (controller, url) async {
|
||||||
}
|
final loadedUrl = url?.toString();
|
||||||
debugPrint('onProgressChanged: $progress');
|
if (loadedUrl != null && loadedUrl.isNotEmpty) {
|
||||||
},
|
_currentWebViewUrl = loadedUrl;
|
||||||
onLoadStop: (controller, url) async {
|
}
|
||||||
final loadedUrl = url?.toString();
|
if (_pendingRedirectUrl != null &&
|
||||||
if (loadedUrl != null && loadedUrl.isNotEmpty) {
|
loadedUrl != _pendingRedirectUrl) {
|
||||||
_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(
|
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)
|
||||||
if (_webViewProgressVisible)
|
Center(
|
||||||
Center(
|
child: SizedBox(
|
||||||
child: SizedBox(
|
width: MediaQuery.of(context).size.width > 360
|
||||||
width: MediaQuery.of(context).size.width > 360
|
? 320
|
||||||
? 320
|
: MediaQuery.of(context).size.width - 40,
|
||||||
: MediaQuery.of(context).size.width - 40,
|
child: LinearProgressIndicator(
|
||||||
child: LinearProgressIndicator(
|
value: _webViewProgress > 0
|
||||||
value: _webViewProgress > 0
|
? _webViewProgress
|
||||||
? _webViewProgress
|
: null,
|
||||||
: null,
|
minHeight: 4,
|
||||||
minHeight: 4,
|
backgroundColor: Colors.white24,
|
||||||
backgroundColor: Colors.white24,
|
valueColor: const AlwaysStoppedAnimation<Color>(
|
||||||
valueColor: const AlwaysStoppedAnimation<Color>(
|
Color(0xFF56F0FF),
|
||||||
Color(0xFF56F0FF),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -107,6 +107,8 @@ class NextStep {
|
|||||||
bool _j = false;
|
bool _j = false;
|
||||||
bool get j => _j;
|
bool get j => _j;
|
||||||
|
|
||||||
|
Set<String> openWindows = {};
|
||||||
|
|
||||||
NextStep._();
|
NextStep._();
|
||||||
static final NextStep I = NextStep._();
|
static final NextStep I = NextStep._();
|
||||||
|
|
||||||
@@ -142,6 +144,7 @@ class NextStep {
|
|||||||
if (rs is! Map<String, dynamic>) return;
|
if (rs is! Map<String, dynamic>) return;
|
||||||
|
|
||||||
_applyEventMap(rs['f']);
|
_applyEventMap(rs['f']);
|
||||||
|
openWindows = _parseOpenWindowSchemes(rs['o']);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint("loadAppInfo error $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) {
|
bool _isValidToken(String? token) {
|
||||||
return token != null && token.trim().length > 5;
|
return token != null && token.trim().length > 5;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
# 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
|
# 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.
|
# 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:
|
environment:
|
||||||
sdk: ^3.11.4
|
sdk: ^3.11.4
|
||||||
|
|||||||
Reference in New Issue
Block a user