Fix terminal page safe-area layout

This commit is contained in:
sladro 2026-04-02 17:36:11 +08:00
parent 20553d88c7
commit dd211e0949
2 changed files with 95 additions and 23 deletions

View File

@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:xterm/xterm.dart';
import '../../app/app_theme.dart';
import '../../app/ui_shell.dart';
import '../../core/network/agent_connection_providers.dart';
import '../../core/network/agent_error_formatter.dart';
@ -393,6 +394,7 @@ class _TerminalPageState extends ConsumerState<TerminalPage>
Widget build(BuildContext context) {
final width = MediaQuery.sizeOf(context).width;
final isCompact = width < 420;
final isTight = width < 400;
final workingDirectory =
widget.project?.workingDirectory ??
widget.session.workingDirectory ??
@ -415,12 +417,13 @@ class _TerminalPageState extends ConsumerState<TerminalPage>
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.titleSmall,
),
Text(
workingDirectory,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodySmall,
),
if (!isTight)
Text(
workingDirectory,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodySmall,
),
],
),
),
@ -431,7 +434,12 @@ class _TerminalPageState extends ConsumerState<TerminalPage>
final mode = controller.isFollowingLiveOutput
? 'Live'
: 'Scrollback';
final modeLabel = '$mode | ${controller.liveLines.length} lines';
final modeLabel = isCompact
? mode
: '$mode | ${controller.liveLines.length} lines';
final statusLabel = isCompact
? _compactStatusLabel
: _statusLabel;
return Row(
key: const Key('terminal_status_summary'),
@ -450,12 +458,12 @@ class _TerminalPageState extends ConsumerState<TerminalPage>
),
child: Text(modeLabel),
),
const SizedBox(width: 4),
SizedBox(width: isCompact ? 2 : 4),
Padding(
padding: const EdgeInsets.only(right: 8),
padding: EdgeInsets.only(right: isCompact ? 4 : 8),
child: Center(
child: StatusPill(
label: _statusLabel,
label: statusLabel,
icon: _statusIcon,
color: _statusColor(context),
),
@ -467,8 +475,9 @@ class _TerminalPageState extends ConsumerState<TerminalPage>
),
],
),
body: Padding(
padding: const EdgeInsets.fromLTRB(16, 6, 16, 10),
body: SafeArea(
top: false,
minimum: AppTheme.pagePadding,
child: TextFieldTapRegion(
child: Column(
children: [
@ -491,11 +500,17 @@ class _TerminalPageState extends ConsumerState<TerminalPage>
borderRadius: BorderRadius.zero,
border: Border.all(color: const Color(0xFF332B22)),
),
child: TerminalView(
terminal,
focusNode: _terminalFocusNode,
autofocus: false,
scrollController: _terminalScrollController,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 8,
),
child: TerminalView(
terminal,
focusNode: _terminalFocusNode,
autofocus: false,
scrollController: _terminalScrollController,
),
),
),
),
@ -975,6 +990,13 @@ class _TerminalPageState extends ConsumerState<TerminalPage>
TerminalConnectionState.disconnected => 'Offline',
};
String get _compactStatusLabel => switch (_connectionState) {
TerminalConnectionState.connecting => 'Sync',
TerminalConnectionState.connected => 'On',
TerminalConnectionState.reconnecting => 'Sync',
TerminalConnectionState.disconnected => 'Off',
};
bool get _canSendInput => controller.canSendInput;
IconData get _statusIcon => switch (_connectionState) {

View File

@ -210,6 +210,52 @@ void main() {
expect(find.text('Recent sessions'), findsOneWidget);
});
testWidgets(
'terminal page keeps the command deck above the bottom safe area',
(tester) async {
tester.view.devicePixelRatio = 1;
tester.view.physicalSize = const Size(393, 852);
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
final mediaQueryData = MediaQueryData.fromView(tester.view).copyWith(
padding: const EdgeInsets.only(bottom: 34),
viewPadding: const EdgeInsets.only(bottom: 34),
);
await _pumpTerminalPage(
tester,
session: _session('session-1', 'codex-main'),
mediaQueryData: mediaQueryData,
);
final commandDeckRect = tester.getRect(
find.byKey(const Key('terminal_command_deck')),
);
expect(commandDeckRect.bottom, lessThanOrEqualTo(852 - 34));
},
);
testWidgets('terminal surface keeps terminal content inset from the border', (
tester,
) async {
await _pumpTerminalPage(
tester,
session: _session('session-1', 'codex-main'),
);
final surfaceRect = tester.getRect(
find.byKey(const Key('terminal_surface_panel')),
);
final terminalViewRect = tester.getRect(find.byType(TerminalView));
expect(terminalViewRect.left, greaterThan(surfaceRect.left));
expect(terminalViewRect.top, greaterThan(surfaceRect.top));
expect(terminalViewRect.right, lessThan(surfaceRect.right));
expect(terminalViewRect.bottom, lessThan(surfaceRect.bottom));
});
testWidgets(
'terminal page keeps tools hidden until the user opens the tools sheet',
(tester) async {
@ -810,7 +856,16 @@ Future<void> _pumpTerminalPage(
required Session session,
AgentApiClient? apiClient,
TerminalSocketSessionFactory? socketFactory,
MediaQueryData? mediaQueryData,
}) async {
Widget page = TerminalPage(
session: session,
agentBaseUri: Uri.parse('http://100.81.30.82:5067'),
);
if (mediaQueryData != null) {
page = MediaQuery(data: mediaQueryData, child: page);
}
await tester.pumpWidget(
ProviderScope(
overrides: [
@ -825,12 +880,7 @@ Future<void> _pumpTerminalPage(
),
),
],
child: MaterialApp(
home: TerminalPage(
session: session,
agentBaseUri: Uri.parse('http://100.81.30.82:5067'),
),
),
child: MaterialApp(home: page),
),
);
await tester.pumpAndSettle();