diff --git a/ui/flutter/lib/features/tasks/presentation/pages/create_task_window_page.dart b/ui/flutter/lib/features/tasks/presentation/pages/create_task_window_page.dart index bce0c31b..e4cbb8bc 100644 --- a/ui/flutter/lib/features/tasks/presentation/pages/create_task_window_page.dart +++ b/ui/flutter/lib/features/tasks/presentation/pages/create_task_window_page.dart @@ -356,18 +356,12 @@ class _CreateTaskWindowPageState extends ConsumerState { const SizedBox(height: 16), _FormRow( label: context.l10n.connections, - child: Align( - alignment: Alignment.centerLeft, - child: SizedBox( - width: AppDesignTokens.settingsNumberControlWidth, - child: AppNumberInput( - fieldKey: const ValueKey('create-task-connections-input'), - controller: _connectionsController, - min: 1, - max: 256, - hintText: context.l10n.enterCount, - ), - ), + child: AppNumberInput( + fieldKey: const ValueKey('create-task-connections-input'), + controller: _connectionsController, + min: 1, + max: 256, + hintText: context.l10n.enterCount, ), ), const SizedBox(height: 16), @@ -382,10 +376,6 @@ class _CreateTaskWindowPageState extends ConsumerState { pickerKey: const ValueKey('create-task-directory-picker'), controller: _directoryController, hintText: context.l10n.chooseDownloadDirectory, - filled: true, - border: Border.all(color: palette.border), - borderRadius: BorderRadius.circular(AppDesignTokens.controlRadius), - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), onChanged: (_) { if (_asDefaultPath) { setState(() => _asDefaultPath = false); @@ -1654,17 +1644,12 @@ class _WindowTextField extends StatelessWidget { @override Widget build(BuildContext context) { - final palette = AppPalette.of(context); return AppTextField( controller: controller, hintText: hintText, keyboardType: keyboardType, inputFormatters: inputFormatters, obscureText: obscureText, - filled: true, - border: Border.all(color: palette.border), - borderRadius: BorderRadius.circular(4), - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), ); } } diff --git a/ui/flutter/lib/shared/theme/app_component_themes.dart b/ui/flutter/lib/shared/theme/app_component_themes.dart index 5b0915a0..bc6611db 100644 --- a/ui/flutter/lib/shared/theme/app_component_themes.dart +++ b/ui/flutter/lib/shared/theme/app_component_themes.dart @@ -18,42 +18,50 @@ class AppComponentThemes extends StatelessWidget { align: 1, border: Border.all(color: theme.colorScheme.ring.withValues(alpha: 0.65), width: 1), ), - child: shad.ComponentTheme( - data: shad.SecondaryButtonTheme( - decoration: (context, states, value) { - final decoration = switch (value) { - final BoxDecoration decoration => decoration, - _ => BoxDecoration(borderRadius: BorderRadius.circular(theme.radiusMd)), - }; - if (states.contains(WidgetState.disabled)) { - return decoration.copyWith(color: palette.surfaceSoft); - } - if (theme.brightness == Brightness.light) { - final background = Color.alphaBlend(palette.textPrimary.withValues(alpha: 0.08), palette.surfaceSoft); - final hoverBackground = Color.alphaBlend( - palette.textPrimary.withValues(alpha: 0.13), - palette.surfaceSoft, - ); - return decoration.copyWith(color: states.contains(WidgetState.hovered) ? hoverBackground : background); - } - return value; - }, - textStyle: (context, states, value) => states.contains(WidgetState.disabled) - ? value.copyWith(color: palette.textMuted.withValues(alpha: 0.62)) - : value, - iconTheme: (context, states, value) => states.contains(WidgetState.disabled) - ? value.copyWith(color: palette.textMuted.withValues(alpha: 0.62)) - : value, + child: shad.ComponentTheme( + data: shad.TextFieldTheme( + filled: true, + border: Border.all(color: palette.border), + borderRadius: BorderRadius.circular(AppDesignTokens.controlRadius), + padding: AppDesignTokens.textFieldPadding, ), - child: shad.ComponentTheme( - data: shad.CheckboxTheme( - size: AppDesignTokens.checkboxSize, - gap: AppDesignTokens.checkboxLabelGap, - borderColor: theme.brightness == Brightness.dark ? palette.textMuted : palette.border, + child: shad.ComponentTheme( + data: shad.SecondaryButtonTheme( + decoration: (context, states, value) { + final decoration = switch (value) { + final BoxDecoration decoration => decoration, + _ => BoxDecoration(borderRadius: BorderRadius.circular(theme.radiusMd)), + }; + if (states.contains(WidgetState.disabled)) { + return decoration.copyWith(color: palette.surfaceSoft); + } + if (theme.brightness == Brightness.light) { + final background = Color.alphaBlend(palette.textPrimary.withValues(alpha: 0.08), palette.surfaceSoft); + final hoverBackground = Color.alphaBlend( + palette.textPrimary.withValues(alpha: 0.13), + palette.surfaceSoft, + ); + return decoration.copyWith(color: states.contains(WidgetState.hovered) ? hoverBackground : background); + } + return value; + }, + textStyle: (context, states, value) => states.contains(WidgetState.disabled) + ? value.copyWith(color: palette.textMuted.withValues(alpha: 0.62)) + : value, + iconTheme: (context, states, value) => states.contains(WidgetState.disabled) + ? value.copyWith(color: palette.textMuted.withValues(alpha: 0.62)) + : value, ), - child: shad.ComponentTheme( - data: const shad.ToastTheme(toastConstraints: BoxConstraints(maxWidth: 420)), - child: child, + child: shad.ComponentTheme( + data: shad.CheckboxTheme( + size: AppDesignTokens.checkboxSize, + gap: AppDesignTokens.checkboxLabelGap, + borderColor: theme.brightness == Brightness.dark ? palette.textMuted : palette.border, + ), + child: shad.ComponentTheme( + data: const shad.ToastTheme(toastConstraints: BoxConstraints(maxWidth: 420)), + child: child, + ), ), ), ), diff --git a/ui/flutter/lib/shared/theme/app_design_tokens.dart b/ui/flutter/lib/shared/theme/app_design_tokens.dart index 7847316d..e7d1f8ff 100644 --- a/ui/flutter/lib/shared/theme/app_design_tokens.dart +++ b/ui/flutter/lib/shared/theme/app_design_tokens.dart @@ -21,6 +21,8 @@ class AppDesignTokens { static const double settingsFormControlWidth = 420; static const double settingsNumberControlWidth = 168; static const double settingsItemMinLabelWidth = 160; + static const EdgeInsets textFieldPadding = EdgeInsets.symmetric(horizontal: 12, vertical: 11); + static const EdgeInsets numberTextFieldPadding = EdgeInsets.symmetric(horizontal: 12, vertical: 10); static const EdgeInsets pagePadding = EdgeInsets.symmetric(horizontal: 32); static const EdgeInsets sidebarPadding = EdgeInsets.symmetric(horizontal: 16); diff --git a/ui/flutter/lib/shared/widgets/app_number_input.dart b/ui/flutter/lib/shared/widgets/app_number_input.dart index cda9545c..70d4bb52 100644 --- a/ui/flutter/lib/shared/widgets/app_number_input.dart +++ b/ui/flutter/lib/shared/widgets/app_number_input.dart @@ -2,6 +2,7 @@ import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart' as shad; +import '../theme/app_design_tokens.dart'; import 'app_text_field.dart'; /// A bounded numeric text field with the app's standard spinner controls. @@ -91,6 +92,7 @@ class _AppNumberInputState extends State { : TextInputType.number, inputFormatters: formatters, enabled: widget.enabled, + padding: AppDesignTokens.numberTextFieldPadding, features: [ shad.InputFeature.spinner( step: widget.step, diff --git a/ui/flutter/test/features/tasks/presentation/create_task_window_page_test.dart b/ui/flutter/test/features/tasks/presentation/create_task_window_page_test.dart index 59254019..66d5dfb7 100644 --- a/ui/flutter/test/features/tasks/presentation/create_task_window_page_test.dart +++ b/ui/flutter/test/features/tasks/presentation/create_task_window_page_test.dart @@ -158,7 +158,10 @@ void main() { final connectionsInput = find.byKey(const ValueKey('create-task-connections-input')); expect(connectionsInput, findsOneWidget); - expect(tester.getSize(connectionsInput).width, AppDesignTokens.settingsNumberControlWidth); + expect( + tester.getSize(connectionsInput).width, + tester.getSize(find.byKey(const ValueKey('create-task-rename-input'))).width, + ); final connectionsField = tester.widget(connectionsInput); expect(connectionsField.controller!.text, '12'); expect(connectionsField.features.single, isA()); diff --git a/ui/flutter/test/shared/theme/app_component_themes_test.dart b/ui/flutter/test/shared/theme/app_component_themes_test.dart index 9080b1e2..b78b0235 100644 --- a/ui/flutter/test/shared/theme/app_component_themes_test.dart +++ b/ui/flutter/test/shared/theme/app_component_themes_test.dart @@ -7,6 +7,30 @@ import 'package:gopeed/shared/theme/app_theme.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart' as shad; void main() { + testWidgets('provides the standard text field appearance', (tester) async { + late shad.TextFieldTheme textFieldTheme; + + await tester.pumpWidget( + shad.ShadcnApp( + theme: AppTheme.light(), + materialTheme: AppTheme.materialLight(), + home: AppComponentThemes( + child: Builder( + builder: (context) { + textFieldTheme = shad.ComponentTheme.of(context); + return const SizedBox(); + }, + ), + ), + ), + ); + + expect(textFieldTheme.filled, isTrue); + expect(textFieldTheme.border, Border.all(color: AppPalette.light.border)); + expect(textFieldTheme.borderRadius, BorderRadius.circular(AppDesignTokens.controlRadius)); + expect(textFieldTheme.padding, AppDesignTokens.textFieldPadding); + }); + testWidgets('provides the standard checkbox metrics', (tester) async { late shad.CheckboxTheme checkboxTheme; diff --git a/ui/flutter/test/widget_test.dart b/ui/flutter/test/widget_test.dart index f11f9e50..5932f4ef 100644 --- a/ui/flutter/test/widget_test.dart +++ b/ui/flutter/test/widget_test.dart @@ -2336,7 +2336,9 @@ void main() { await tester.pumpAndSettle(); final createDirectoryInput = find.byKey(const ValueKey('create-task-directory-input')); final createRenameInput = find.byKey(const ValueKey('create-task-rename-input')); + final createConnectionsInput = find.byKey(const ValueKey('create-task-connections-input')); expect(tester.getSize(createDirectoryInput).height, tester.getSize(createRenameInput).height); + expect(tester.getSize(createConnectionsInput), tester.getSize(createRenameInput)); final createDirectoryField = tester.widget(createDirectoryInput); final createRenameField = tester.widget( find.descendant(of: createRenameInput, matching: find.byType(AppTextField)), @@ -4875,7 +4877,7 @@ class _CreateTaskPageHarness extends StatelessWidget { return shad.ShadcnApp( theme: AppTheme.light(), materialTheme: AppTheme.materialLight(), - home: const CreateTaskWindowPage(), + home: const AppComponentThemes(child: CreateTaskWindowPage()), ); } }