feat: 添加登录页面 URL 检测功能;更新 H5ShellPage 状态管理以支持路由变化
This commit is contained in:
@@ -83,6 +83,19 @@ class AppConfig {
|
|||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool isLoginPageUrl(String? url) {
|
||||||
|
if (url == null || url.isEmpty) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final uri = Uri.tryParse(url);
|
||||||
|
if (uri == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _isLoginPath(uri.path) || _isLoginPath(uri.fragment);
|
||||||
|
}
|
||||||
|
|
||||||
static int? lineIndexForUrl(String url) {
|
static int? lineIndexForUrl(String url) {
|
||||||
final uri = Uri.tryParse(url);
|
final uri = Uri.tryParse(url);
|
||||||
if (uri == null) {
|
if (uri == null) {
|
||||||
@@ -123,6 +136,21 @@ class AppConfig {
|
|||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool _isLoginPath(String path) {
|
||||||
|
final normalized = path.trim();
|
||||||
|
if (normalized.isEmpty) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final pathOnly = normalized.split('?').first.split('#').first;
|
||||||
|
final segments = pathOnly.split('/').where((segment) => segment.isNotEmpty);
|
||||||
|
if (segments.isEmpty) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return segments.last == 'login';
|
||||||
|
}
|
||||||
|
|
||||||
static bool _isSameLine(Uri lineUri, Uri uri) {
|
static bool _isSameLine(Uri lineUri, Uri uri) {
|
||||||
if (lineUri.host.toLowerCase() != uri.host.toLowerCase()) {
|
if (lineUri.host.toLowerCase() != uri.host.toLowerCase()) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -133,11 +133,12 @@ class _H5ShellPageState extends State<H5ShellPage> {
|
|||||||
setState(() => _lineSlots[lineIndex].progress = progress);
|
setState(() => _lineSlots[lineIndex].progress = progress);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onPageStarted: (_) {
|
onPageStarted: (url) {
|
||||||
final slot = _lineSlots[lineIndex];
|
final slot = _lineSlots[lineIndex];
|
||||||
slot.shellCoverFallbackTimer?.cancel();
|
slot.shellCoverFallbackTimer?.cancel();
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
slot.setCurrentUrl(url);
|
||||||
slot.loadError = null;
|
slot.loadError = null;
|
||||||
slot.progress = 0;
|
slot.progress = 0;
|
||||||
slot.showShellCover = true;
|
slot.showShellCover = true;
|
||||||
@@ -147,6 +148,9 @@ class _H5ShellPageState extends State<H5ShellPage> {
|
|||||||
onPageFinished: (_) {
|
onPageFinished: (_) {
|
||||||
unawaited(_handlePageFinished(lineIndex));
|
unawaited(_handlePageFinished(lineIndex));
|
||||||
},
|
},
|
||||||
|
onUrlChange: (change) {
|
||||||
|
_updateSlotUrl(lineIndex, change.url);
|
||||||
|
},
|
||||||
onWebResourceError: (error) {
|
onWebResourceError: (error) {
|
||||||
if (error.isForMainFrame ?? true) {
|
if (error.isForMainFrame ?? true) {
|
||||||
final slot = _lineSlots[lineIndex];
|
final slot = _lineSlots[lineIndex];
|
||||||
@@ -183,6 +187,7 @@ class _H5ShellPageState extends State<H5ShellPage> {
|
|||||||
Future<void> _handlePageFinished(int lineIndex) async {
|
Future<void> _handlePageFinished(int lineIndex) async {
|
||||||
await _loadShellBrandingIfNeeded();
|
await _loadShellBrandingIfNeeded();
|
||||||
await _syncShellBranding(lineIndex);
|
await _syncShellBranding(lineIndex);
|
||||||
|
await _installRouteObserver(lineIndex);
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_lineSlots[lineIndex].progress = 100;
|
_lineSlots[lineIndex].progress = 100;
|
||||||
@@ -196,6 +201,8 @@ class _H5ShellPageState extends State<H5ShellPage> {
|
|||||||
final decoded = jsonDecode(message.message);
|
final decoded = jsonDecode(message.message);
|
||||||
if (decoded is Map && decoded['type'] == 'first-screen-ready') {
|
if (decoded is Map && decoded['type'] == 'first-screen-ready') {
|
||||||
_hideShellCover(lineIndex);
|
_hideShellCover(lineIndex);
|
||||||
|
} else if (decoded is Map && decoded['type'] == 'route-changed') {
|
||||||
|
_updateSlotUrl(lineIndex, decoded['url']?.toString());
|
||||||
}
|
}
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
if (message.message == 'first-screen-ready') {
|
if (message.message == 'first-screen-ready') {
|
||||||
@@ -260,6 +267,60 @@ class _H5ShellPageState extends State<H5ShellPage> {
|
|||||||
return _runJavaScriptSafely(lineIndex, script);
|
return _runJavaScriptSafely(lineIndex, script);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _installRouteObserver(int lineIndex) {
|
||||||
|
const script = '''
|
||||||
|
(() => {
|
||||||
|
try {
|
||||||
|
const notify = () => {
|
||||||
|
try {
|
||||||
|
window.OpenIMShell.postMessage(JSON.stringify({
|
||||||
|
type: 'route-changed',
|
||||||
|
url: window.location.href
|
||||||
|
}));
|
||||||
|
} catch (_) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (window.__OPENIM_FLUTTER_ROUTE_OBSERVER__) {
|
||||||
|
window.__OPENIM_FLUTTER_ROUTE_OBSERVER__.notify();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const originalPushState = window.history.pushState;
|
||||||
|
const originalReplaceState = window.history.replaceState;
|
||||||
|
|
||||||
|
window.history.pushState = function() {
|
||||||
|
const result = originalPushState.apply(this, arguments);
|
||||||
|
notify();
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
window.history.replaceState = function() {
|
||||||
|
const result = originalReplaceState.apply(this, arguments);
|
||||||
|
notify();
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('popstate', notify);
|
||||||
|
window.__OPENIM_FLUTTER_ROUTE_OBSERVER__ = { notify };
|
||||||
|
notify();
|
||||||
|
} catch (_) {}
|
||||||
|
})();
|
||||||
|
''';
|
||||||
|
return _runJavaScriptSafely(lineIndex, script);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _updateSlotUrl(int lineIndex, String? url) {
|
||||||
|
if (url == null || lineIndex < 0 || lineIndex >= _lineSlots.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final slot = _lineSlots[lineIndex];
|
||||||
|
final changed = slot.setCurrentUrl(url);
|
||||||
|
if (changed && mounted) {
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _ensureLineLoaded(int lineIndex) async {
|
Future<void> _ensureLineLoaded(int lineIndex) async {
|
||||||
final slot = _lineSlots[lineIndex];
|
final slot = _lineSlots[lineIndex];
|
||||||
if (slot.hasLoadedInitialRequest) {
|
if (slot.hasLoadedInitialRequest) {
|
||||||
@@ -270,6 +331,7 @@ class _H5ShellPageState extends State<H5ShellPage> {
|
|||||||
if (mounted) {
|
if (mounted) {
|
||||||
slot.shellCoverFallbackTimer?.cancel();
|
slot.shellCoverFallbackTimer?.cancel();
|
||||||
setState(() {
|
setState(() {
|
||||||
|
slot.setCurrentUrl(slot.line.url);
|
||||||
slot.loadError = null;
|
slot.loadError = null;
|
||||||
slot.progress = 0;
|
slot.progress = 0;
|
||||||
slot.showShellCover = true;
|
slot.showShellCover = true;
|
||||||
@@ -369,6 +431,7 @@ class _H5ShellPageState extends State<H5ShellPage> {
|
|||||||
final bottomInset = MediaQuery.viewInsetsOf(context).bottom;
|
final bottomInset = MediaQuery.viewInsetsOf(context).bottom;
|
||||||
final showLineSwitch = !currentSlot.showShellCover &&
|
final showLineSwitch = !currentSlot.showShellCover &&
|
||||||
currentSlot.loadError == null &&
|
currentSlot.loadError == null &&
|
||||||
|
currentSlot.isLoginPage &&
|
||||||
bottomInset == 0;
|
bottomInset == 0;
|
||||||
|
|
||||||
return PopScope(
|
return PopScope(
|
||||||
@@ -448,8 +511,21 @@ class _H5LineWebViewSlot {
|
|||||||
String? loadError;
|
String? loadError;
|
||||||
bool showShellCover = true;
|
bool showShellCover = true;
|
||||||
bool hasLoadedInitialRequest = false;
|
bool hasLoadedInitialRequest = false;
|
||||||
|
bool isLoginPage = false;
|
||||||
|
String? currentUrl;
|
||||||
Timer? shellCoverFallbackTimer;
|
Timer? shellCoverFallbackTimer;
|
||||||
|
|
||||||
|
bool setCurrentUrl(String url) {
|
||||||
|
final nextIsLoginPage = AppConfig.isLoginPageUrl(url);
|
||||||
|
if (currentUrl == url && isLoginPage == nextIsLoginPage) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentUrl = url;
|
||||||
|
isLoginPage = nextIsLoginPage;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void dispose() {
|
void dispose() {
|
||||||
shellCoverFallbackTimer?.cancel();
|
shellCoverFallbackTimer?.cancel();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,20 @@ void main() {
|
|||||||
expect(AppConfig.lineIndexForUrl('${lineUrl}login'), 0);
|
expect(AppConfig.lineIndexForUrl('${lineUrl}login'), 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('detects only H5 login routes for shell line switch display', () {
|
||||||
|
expect(
|
||||||
|
AppConfig.isLoginPageUrl('https://h5-test.imharry.work/login'), isTrue);
|
||||||
|
expect(AppConfig.isLoginPageUrl('https://h5-test.imharry.work/app/login'),
|
||||||
|
isTrue);
|
||||||
|
expect(AppConfig.isLoginPageUrl('https://h5-test.imharry.work/#/login'),
|
||||||
|
isTrue);
|
||||||
|
expect(AppConfig.isLoginPageUrl('https://h5-test.imharry.work/'), isFalse);
|
||||||
|
expect(AppConfig.isLoginPageUrl('https://h5-test.imharry.work/contact'),
|
||||||
|
isFalse);
|
||||||
|
expect(AppConfig.isLoginPageUrl('https://h5-test.imharry.work/getCode'),
|
||||||
|
isFalse);
|
||||||
|
});
|
||||||
|
|
||||||
test('refreshes an H5 route URL without adding branding to the URL', () {
|
test('refreshes an H5 route URL without adding branding to the URL', () {
|
||||||
final uri = Uri.parse(
|
final uri = Uri.parse(
|
||||||
AppConfig.withFreshShellParams(
|
AppConfig.withFreshShellParams(
|
||||||
|
|||||||
Reference in New Issue
Block a user