feat: 添加键盘状态同步功能,优化 H5ShellPage 的用户体验
This commit is contained in:
@@ -19,6 +19,7 @@ const _shellBrandingChannel =
|
|||||||
MethodChannel('io.openim.flutter.im_webview_app/shell_branding');
|
MethodChannel('io.openim.flutter.im_webview_app/shell_branding');
|
||||||
const _androidFilePickerChannel =
|
const _androidFilePickerChannel =
|
||||||
MethodChannel('io.openim.flutter.openim/file_picker');
|
MethodChannel('io.openim.flutter.openim/file_picker');
|
||||||
|
const _keyboardAnimationDuration = Duration(milliseconds: 250);
|
||||||
|
|
||||||
Future<void> main() async {
|
Future<void> main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
@@ -104,6 +105,9 @@ class _H5ShellPageState extends State<H5ShellPage> {
|
|||||||
|
|
||||||
bool _shellBrandingLoaded = false;
|
bool _shellBrandingLoaded = false;
|
||||||
int _currentLineIndex = 0;
|
int _currentLineIndex = 0;
|
||||||
|
double _lastKeyboardInset = 0;
|
||||||
|
bool _lastKeyboardVisible = false;
|
||||||
|
int _keyboardSyncToken = 0;
|
||||||
late ShellBranding _shellBranding;
|
late ShellBranding _shellBranding;
|
||||||
|
|
||||||
H5Line get _currentLine => _h5Lines[_currentLineIndex];
|
H5Line get _currentLine => _h5Lines[_currentLineIndex];
|
||||||
@@ -244,6 +248,7 @@ class _H5ShellPageState extends State<H5ShellPage> {
|
|||||||
await _loadShellBrandingIfNeeded();
|
await _loadShellBrandingIfNeeded();
|
||||||
await _syncShellBranding(lineIndex);
|
await _syncShellBranding(lineIndex);
|
||||||
await _installRouteObserver(lineIndex);
|
await _installRouteObserver(lineIndex);
|
||||||
|
await _syncKeyboardState(lineIndex);
|
||||||
final slot = _lineSlots[lineIndex];
|
final slot = _lineSlots[lineIndex];
|
||||||
slot.progress = 100;
|
slot.progress = 100;
|
||||||
if (mounted &&
|
if (mounted &&
|
||||||
@@ -265,6 +270,8 @@ class _H5ShellPageState extends State<H5ShellPage> {
|
|||||||
_hideShellCover(lineIndex);
|
_hideShellCover(lineIndex);
|
||||||
} else if (decoded is Map && decoded['type'] == 'route-changed') {
|
} else if (decoded is Map && decoded['type'] == 'route-changed') {
|
||||||
_updateSlotUrl(lineIndex, decoded['url']?.toString());
|
_updateSlotUrl(lineIndex, decoded['url']?.toString());
|
||||||
|
} else if (decoded is Map && decoded['type'] == 'keyboard-bridge-ready') {
|
||||||
|
unawaited(_syncKeyboardState(lineIndex));
|
||||||
}
|
}
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
if (message.message == 'first-screen-ready') {
|
if (message.message == 'first-screen-ready') {
|
||||||
@@ -296,6 +303,8 @@ class _H5ShellPageState extends State<H5ShellPage> {
|
|||||||
);
|
);
|
||||||
case 'openAppSettings':
|
case 'openAppSettings':
|
||||||
unawaited(openAppSettings());
|
unawaited(openAppSettings());
|
||||||
|
case 'keyboard-bridge-ready':
|
||||||
|
unawaited(_syncKeyboardState(lineIndex));
|
||||||
}
|
}
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
// Ignore malformed shell messages from web content.
|
// Ignore malformed shell messages from web content.
|
||||||
@@ -366,6 +375,53 @@ class _H5ShellPageState extends State<H5ShellPage> {
|
|||||||
return _runJavaScriptSafely(lineIndex, script);
|
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) {
|
Future<void> _installRouteObserver(int lineIndex) {
|
||||||
const script = '''
|
const script = '''
|
||||||
(() => {
|
(() => {
|
||||||
@@ -551,6 +607,7 @@ class _H5ShellPageState extends State<H5ShellPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await _ensureLineLoaded(safeIndex);
|
await _ensureLineLoaded(safeIndex);
|
||||||
|
await _syncKeyboardState(safeIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildWebViewWidget(int index) {
|
Widget _buildWebViewWidget(int index) {
|
||||||
@@ -635,6 +692,7 @@ class _H5ShellPageState extends State<H5ShellPage> {
|
|||||||
final currentSlot = _currentSlot;
|
final currentSlot = _currentSlot;
|
||||||
final topInset = MediaQuery.paddingOf(context).top;
|
final topInset = MediaQuery.paddingOf(context).top;
|
||||||
final bottomInset = MediaQuery.viewInsetsOf(context).bottom;
|
final bottomInset = MediaQuery.viewInsetsOf(context).bottom;
|
||||||
|
_scheduleKeyboardStateSync(bottomInset);
|
||||||
final showLineSwitch = !currentSlot.showShellCover &&
|
final showLineSwitch = !currentSlot.showShellCover &&
|
||||||
currentSlot.loadError == null &&
|
currentSlot.loadError == null &&
|
||||||
currentSlot.isLoginPage &&
|
currentSlot.isLoginPage &&
|
||||||
|
|||||||
Reference in New Issue
Block a user