From 6b35e80d4741432bc1963b5b1e4ae147c3b81519 Mon Sep 17 00:00:00 2001 From: Booker Date: Fri, 29 May 2026 11:47:26 +0700 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E9=94=AE=E7=9B=98?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E5=90=8C=E6=AD=A5=E5=8A=9F=E8=83=BD=EF=BC=8C?= =?UTF-8?q?=E4=BC=98=E5=8C=96=20H5ShellPage=20=E7=9A=84=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E4=BD=93=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/main.dart | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/lib/main.dart b/lib/main.dart index 6ac7d8a..e4f9116 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -19,6 +19,7 @@ const _shellBrandingChannel = MethodChannel('io.openim.flutter.im_webview_app/shell_branding'); const _androidFilePickerChannel = MethodChannel('io.openim.flutter.openim/file_picker'); +const _keyboardAnimationDuration = Duration(milliseconds: 250); Future main() async { WidgetsFlutterBinding.ensureInitialized(); @@ -104,6 +105,9 @@ class _H5ShellPageState extends State { bool _shellBrandingLoaded = false; int _currentLineIndex = 0; + double _lastKeyboardInset = 0; + bool _lastKeyboardVisible = false; + int _keyboardSyncToken = 0; late ShellBranding _shellBranding; H5Line get _currentLine => _h5Lines[_currentLineIndex]; @@ -244,6 +248,7 @@ class _H5ShellPageState extends State { await _loadShellBrandingIfNeeded(); await _syncShellBranding(lineIndex); await _installRouteObserver(lineIndex); + await _syncKeyboardState(lineIndex); final slot = _lineSlots[lineIndex]; slot.progress = 100; if (mounted && @@ -265,6 +270,8 @@ class _H5ShellPageState extends State { _hideShellCover(lineIndex); } else if (decoded is Map && decoded['type'] == 'route-changed') { _updateSlotUrl(lineIndex, decoded['url']?.toString()); + } else if (decoded is Map && decoded['type'] == 'keyboard-bridge-ready') { + unawaited(_syncKeyboardState(lineIndex)); } } catch (_) { if (message.message == 'first-screen-ready') { @@ -296,6 +303,8 @@ class _H5ShellPageState extends State { ); case 'openAppSettings': unawaited(openAppSettings()); + case 'keyboard-bridge-ready': + unawaited(_syncKeyboardState(lineIndex)); } } catch (_) { // Ignore malformed shell messages from web content. @@ -366,6 +375,53 @@ class _H5ShellPageState extends State { return _runJavaScriptSafely(lineIndex, script); } + void _scheduleKeyboardStateSync(double bottomInset) { + final nextInset = bottomInset < 1 ? 0.0 : bottomInset; + final nextVisible = nextInset > 0; + if ((nextInset - _lastKeyboardInset).abs() < 1 && + nextVisible == _lastKeyboardVisible) { + return; + } + + _lastKeyboardInset = nextInset; + _lastKeyboardVisible = nextVisible; + final token = ++_keyboardSyncToken; + + WidgetsBinding.instance.addPostFrameCallback((_) { + if (!mounted || token != _keyboardSyncToken) { + return; + } + unawaited(_syncKeyboardState(_currentLineIndex)); + }); + } + + Future _syncKeyboardState(int lineIndex) { + if (lineIndex < 0 || lineIndex >= _lineSlots.length) { + return Future.value(); + } + + final payload = jsonEncode({ + 'height': _lastKeyboardInset.round(), + 'visible': _lastKeyboardVisible, + 'duration': _keyboardAnimationDuration.inMilliseconds, + }); + final script = ''' +(() => { + try { + const keyboard = $payload; + if (typeof window.__OPENIM_KEYBOARD_UPDATE__ === 'function') { + window.__OPENIM_KEYBOARD_UPDATE__(keyboard); + return; + } + if (typeof window.openIMKeyboardUpdate === 'function') { + window.openIMKeyboardUpdate(keyboard); + } + } catch (_) {} +})(); +'''; + return _runJavaScriptSafely(lineIndex, script); + } + Future _installRouteObserver(int lineIndex) { const script = ''' (() => { @@ -551,6 +607,7 @@ class _H5ShellPageState extends State { } await _ensureLineLoaded(safeIndex); + await _syncKeyboardState(safeIndex); } Widget _buildWebViewWidget(int index) { @@ -635,6 +692,7 @@ class _H5ShellPageState extends State { final currentSlot = _currentSlot; final topInset = MediaQuery.paddingOf(context).top; final bottomInset = MediaQuery.viewInsetsOf(context).bottom; + _scheduleKeyboardStateSync(bottomInset); final showLineSwitch = !currentSlot.showShellCover && currentSlot.loadError == null && currentSlot.isLoginPage &&