feat: 添加键盘状态同步功能,优化 H5ShellPage 的用户体验
This commit is contained in:
@@ -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<void> main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
@@ -104,6 +105,9 @@ class _H5ShellPageState extends State<H5ShellPage> {
|
||||
|
||||
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<H5ShellPage> {
|
||||
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<H5ShellPage> {
|
||||
_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<H5ShellPage> {
|
||||
);
|
||||
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<H5ShellPage> {
|
||||
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<void> _syncKeyboardState(int lineIndex) {
|
||||
if (lineIndex < 0 || lineIndex >= _lineSlots.length) {
|
||||
return Future<void>.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<void> _installRouteObserver(int lineIndex) {
|
||||
const script = '''
|
||||
(() => {
|
||||
@@ -551,6 +607,7 @@ class _H5ShellPageState extends State<H5ShellPage> {
|
||||
}
|
||||
|
||||
await _ensureLineLoaded(safeIndex);
|
||||
await _syncKeyboardState(safeIndex);
|
||||
}
|
||||
|
||||
Widget _buildWebViewWidget(int index) {
|
||||
@@ -635,6 +692,7 @@ class _H5ShellPageState extends State<H5ShellPage> {
|
||||
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 &&
|
||||
|
||||
Reference in New Issue
Block a user