From 2d4a8cddc1c96fe64133a5f135fe58708b07815e Mon Sep 17 00:00:00 2001 From: Levi Date: Sat, 26 Sep 2026 11:05:43 +0800 Subject: [PATCH] fix(ui): keep extension update chip within card header --- .../presentation/pages/extensions_page.dart | 1 + .../widgets/extension_update_status.dart | 7 +++--- ui/flutter/test/widget_test.dart | 25 +++++++++++++++++++ 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/ui/flutter/lib/features/extensions/presentation/pages/extensions_page.dart b/ui/flutter/lib/features/extensions/presentation/pages/extensions_page.dart index 9f5d3317..9d720605 100644 --- a/ui/flutter/lib/features/extensions/presentation/pages/extensions_page.dart +++ b/ui/flutter/lib/features/extensions/presentation/pages/extensions_page.dart @@ -994,6 +994,7 @@ class _ExtensionCard extends ConsumerWidget { key: ValueKey('extension-card-update-status-${item.id}'), label: context.l10n.extensionCanUpdate, onTap: busy ? null : () => showExtensionUpdateDialog(context, installed), + compact: true, ), ), ], diff --git a/ui/flutter/lib/features/extensions/presentation/widgets/extension_update_status.dart b/ui/flutter/lib/features/extensions/presentation/widgets/extension_update_status.dart index 3d9e7812..f1382b12 100644 --- a/ui/flutter/lib/features/extensions/presentation/widgets/extension_update_status.dart +++ b/ui/flutter/lib/features/extensions/presentation/widgets/extension_update_status.dart @@ -5,16 +5,17 @@ import '../../../../shared/theme/app_palette.dart'; /// Green status dot with an optional label, marking an available update. /// Pass [onTap] to make it open the update prompt. class ExtensionUpdateStatus extends StatelessWidget { - const ExtensionUpdateStatus({super.key, this.label, this.onTap}); + const ExtensionUpdateStatus({super.key, this.label, this.onTap, this.compact = false}); final String? label; final VoidCallback? onTap; + final bool compact; @override Widget build(BuildContext context) { final palette = AppPalette.of(context); final content = Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), + padding: EdgeInsets.symmetric(horizontal: 8, vertical: compact ? 2 : 3), decoration: BoxDecoration( color: palette.brandSoft, borderRadius: BorderRadius.circular(999), @@ -49,7 +50,7 @@ class ExtensionUpdateStatus extends StatelessWidget { behavior: HitTestBehavior.opaque, onTap: onTap, child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 2, vertical: 4), + padding: EdgeInsets.symmetric(horizontal: 2, vertical: compact ? 2 : 4), child: content, ), ), diff --git a/ui/flutter/test/widget_test.dart b/ui/flutter/test/widget_test.dart index 0d2c3fee..c6fa7f17 100644 --- a/ui/flutter/test/widget_test.dart +++ b/ui/flutter/test/widget_test.dart @@ -50,6 +50,7 @@ import 'package:gopeed/features/home/presentation/widgets/tasks_top_bar.dart'; import 'package:gopeed/features/extensions/application/extensions_controller.dart'; import 'package:gopeed/features/extensions/presentation/pages/extension_details_page.dart'; import 'package:gopeed/features/extensions/presentation/pages/extensions_page.dart'; +import 'package:gopeed/features/extensions/presentation/widgets/extension_icon.dart'; import 'package:gopeed/features/home/presentation/widgets/primary_rail.dart'; import 'package:gopeed/features/tasks/application/pending_update_task.dart'; import 'package:gopeed/features/tasks/application/task_batch_selection_controller.dart'; @@ -688,6 +689,30 @@ void main() { expect(tester.takeException(), isNull); }); + for (final width in [320.0, 1100.0]) { + testWidgets('extension update status keeps the card header at icon height at $width', (tester) async { + await _setTestSize(tester, Size(width, 900)); + await tester.pumpWidget( + ProviderScope( + overrides: [extensionsControllerProvider.overrideWith(UpdateableExtensionsController.new)], + child: shad.ShadcnApp( + theme: AppTheme.light(), + materialTheme: AppTheme.materialLight(), + home: const ExtensionsPage(), + ), + ), + ); + await tester.pumpAndSettle(); + + final card = find.byKey(const ValueKey('extension-card-extension-0')); + final icon = find.descendant(of: card, matching: find.byType(ExtensionIcon)); + final description = find.descendant(of: card, matching: find.text('Installed extension')); + expect(find.byKey(const ValueKey('extension-card-update-status-extension-0')), findsOneWidget); + expect(tester.getRect(description).top - tester.getRect(icon).bottom, closeTo(10, 0.01)); + expect(tester.takeException(), isNull); + }); + } + testWidgets('extension details reuse the desktop detail drawer', (WidgetTester tester) async { await _setTestSize(tester, const Size(1100, 900)); final controller = FakeExtensionsController();