Fix Terminal top content overlapping with notch (SafeArea) (#13724)

This commit is contained in:
alonginwind
2025-12-22 21:08:38 +08:00
committed by GitHub
parent b80eb2dc6c
commit eba847e62e

View File

@@ -1,3 +1,5 @@
import 'dart:async';
import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_hbb/common.dart'; import 'package:flutter_hbb/common.dart';
@@ -29,9 +31,12 @@ class TerminalPage extends StatefulWidget {
} }
class _TerminalPageState extends State<TerminalPage> class _TerminalPageState extends State<TerminalPage>
with AutomaticKeepAliveClientMixin { with AutomaticKeepAliveClientMixin, WidgetsBindingObserver {
late FFI _ffi; late FFI _ffi;
late TerminalModel _terminalModel; late TerminalModel _terminalModel;
double? _cellHeight;
double _sysKeyboardHeight = 0;
Timer? _keyboardDebounce;
// For web only. // For web only.
// 'monospace' does not work on web, use Google Fonts, `??` is only for null safety. // 'monospace' does not work on web, use Google Fonts, `??` is only for null safety.
@@ -44,6 +49,7 @@ class _TerminalPageState extends State<TerminalPage>
@override @override
void initState() { void initState() {
super.initState(); super.initState();
WidgetsBinding.instance.addObserver(this);
debugPrint( debugPrint(
'[TerminalPage] Initializing terminal ${widget.terminalId} for peer ${widget.id}'); '[TerminalPage] Initializing terminal ${widget.terminalId} for peer ${widget.id}');
@@ -62,6 +68,10 @@ class _TerminalPageState extends State<TerminalPage>
debugPrint( debugPrint(
'[TerminalPage] Terminal model created for terminal ${widget.terminalId}'); '[TerminalPage] Terminal model created for terminal ${widget.terminalId}');
_terminalModel.onResizeExternal = (w, h, pw, ph) {
_cellHeight = ph * 1.0;
};
// Register this terminal model with FFI for event routing // Register this terminal model with FFI for event routing
_ffi.registerTerminalModel(widget.terminalId, _terminalModel); _ffi.registerTerminalModel(widget.terminalId, _terminalModel);
@@ -78,10 +88,36 @@ class _TerminalPageState extends State<TerminalPage>
// Unregister terminal model from FFI // Unregister terminal model from FFI
_ffi.unregisterTerminalModel(widget.terminalId); _ffi.unregisterTerminalModel(widget.terminalId);
_terminalModel.dispose(); _terminalModel.dispose();
_keyboardDebounce?.cancel();
WidgetsBinding.instance.removeObserver(this);
super.dispose(); super.dispose();
TerminalConnectionManager.releaseConnection(widget.id); TerminalConnectionManager.releaseConnection(widget.id);
} }
@override
void didChangeMetrics() {
super.didChangeMetrics();
_keyboardDebounce?.cancel();
_keyboardDebounce = Timer(const Duration(milliseconds: 20), () {
final bottomInset = MediaQuery.of(context).viewInsets.bottom;
setState(() {
_sysKeyboardHeight = bottomInset;
});
});
}
EdgeInsets _calculatePadding(double heightPx) {
if (_cellHeight == null) {
return const EdgeInsets.symmetric(horizontal: 5.0, vertical: 2.0);
}
final realHeight = heightPx - _sysKeyboardHeight;
final rows = (realHeight / _cellHeight!).floor();
final extraSpace = realHeight - rows * _cellHeight!;
final topBottom = max(0.0, extraSpace / 2.0);
return EdgeInsets.only(left: 5.0, right: 5.0, top: topBottom, bottom: topBottom + _sysKeyboardHeight);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context); super.build(context);
@@ -96,14 +132,20 @@ class _TerminalPageState extends State<TerminalPage>
Widget buildBody() { Widget buildBody() {
return Scaffold( return Scaffold(
resizeToAvoidBottomInset: false, // Disable automatic layout adjustment; manually control UI updates to prevent flickering when the keyboard shows/hides
backgroundColor: Theme.of(context).scaffoldBackgroundColor, backgroundColor: Theme.of(context).scaffoldBackgroundColor,
body: TerminalView( body: SafeArea(
top: true,
child: LayoutBuilder(
builder: (context, constraints) {
final heightPx = constraints.maxHeight;
return TerminalView(
_terminalModel.terminal, _terminalModel.terminal,
controller: _terminalModel.terminalController, controller: _terminalModel.terminalController,
autofocus: true, autofocus: true,
textStyle: _getTerminalStyle(), textStyle: _getTerminalStyle(),
backgroundOpacity: 0.7, backgroundOpacity: 0.7,
padding: const EdgeInsets.symmetric(horizontal: 5.0, vertical: 2.0), padding: _calculatePadding(heightPx),
onSecondaryTapDown: (details, offset) async { onSecondaryTapDown: (details, offset) async {
final selection = _terminalModel.terminalController.selection; final selection = _terminalModel.terminalController.selection;
if (selection != null) { if (selection != null) {
@@ -118,6 +160,9 @@ class _TerminalPageState extends State<TerminalPage>
} }
} }
}, },
);
},
),
), ),
); );
} }