Fix terminal page safe-area layout
This commit is contained in:
parent
20553d88c7
commit
dd211e0949
@ -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) {
|
||||
|
||||
@ -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();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user