From 172f4a3650cc810514fe84ad63bba906bff114e1 Mon Sep 17 00:00:00 2001 From: Levi Date: Sat, 19 Sep 2026 17:32:44 +0800 Subject: [PATCH 01/15] fix(ui): unify extension update check and improve update affordance - Update availability is now decided solely by the backend live GitHub check (updateFlags); the store index may lag and previously masked new versions in the market view. - Replace the ambiguous refresh icon on extension cards with a labeled update button, and add a red dot badge on extension icons (card and detail view) when an update is available. - Surface a toast when the update check fails instead of failing silently. --- .../application/extensions_controller.dart | 47 +++++++--------- .../presentation/pages/extensions_page.dart | 54 +++++++++++-------- .../widgets/extension_detail_view.dart | 34 +++++++++--- ui/flutter/lib/l10n/app_en.arb | 2 + ui/flutter/lib/l10n/app_zh.arb | 2 + 5 files changed, 81 insertions(+), 58 deletions(-) diff --git a/ui/flutter/lib/features/extensions/application/extensions_controller.dart b/ui/flutter/lib/features/extensions/application/extensions_controller.dart index 74e439d3..b63b27f4 100644 --- a/ui/flutter/lib/features/extensions/application/extensions_controller.dart +++ b/ui/flutter/lib/features/extensions/application/extensions_controller.dart @@ -49,6 +49,7 @@ class ExtensionsState { this.loadingMoreStore = false, this.busyExtensionIds = const {}, this.devMode = false, + this.updateCheckFailed = false, }); final List installedExtensions; @@ -63,6 +64,7 @@ class ExtensionsState { final bool loadingMoreStore; final Set busyExtensionIds; final bool devMode; + final bool updateCheckFailed; UnmodifiableMapView get installedMap => UnmodifiableMapView({for (final ext in installedExtensions) ext.identity: ext}); @@ -100,6 +102,7 @@ class ExtensionsState { bool? loadingMoreStore, Set? busyExtensionIds, bool? devMode, + bool? updateCheckFailed, }) { return ExtensionsState( installedExtensions: installedExtensions ?? this.installedExtensions, @@ -114,6 +117,7 @@ class ExtensionsState { loadingMoreStore: loadingMoreStore ?? this.loadingMoreStore, busyExtensionIds: busyExtensionIds ?? this.busyExtensionIds, devMode: devMode ?? this.devMode, + updateCheckFailed: updateCheckFailed ?? this.updateCheckFailed, ); } } @@ -185,12 +189,11 @@ class ExtensionsController extends AsyncNotifier { } bool canUpdateItem(ExtensionListItem item) { - final current = _current; - if (item.installed == null) return false; - if (current.listFilter == ExtensionListFilter.market && item.store != null) { - return _compareVersion(item.store!.version, item.installed!.version) > 0; - } - return current.updateFlags.containsKey(item.installed!.identity); + final installed = item.installed; + if (installed == null) return false; + // The backend checks GitHub live for every installed extension; the store + // index may lag behind and must not decide update visibility. + return _current.updateFlags.containsKey(installed.identity); } Future installFromStore(StoreExtension extension) async { @@ -257,15 +260,23 @@ class ExtensionsController extends AsyncNotifier { Future checkUpdate() async { final flags = {}; + var failed = false; for (final ext in _current.installedExtensions) { try { final resp = await ref.read(gopeedServiceProvider).upgradeCheckExtension(ext.identity); if (resp.newVersion.isNotEmpty) { flags[ext.identity] = resp.newVersion; } - } catch (_) {} + } catch (_) { + failed = true; + } } - state = AsyncValue.data(_current.copyWith(updateFlags: flags)); + state = AsyncValue.data( + _current.copyWith( + updateFlags: flags, + updateCheckFailed: failed && _current.installedExtensions.isNotEmpty, + ), + ); } void tryOpenDevMode() { @@ -354,24 +365,4 @@ class ExtensionsController extends AsyncNotifier { }()); } - static int _compareVersion(String a, String b) { - final aNums = _toVersionNumbers(a); - final bNums = _toVersionNumbers(b); - final maxLen = aNums.length > bNums.length ? aNums.length : bNums.length; - for (var i = 0; i < maxLen; i++) { - final left = i < aNums.length ? aNums[i] : 0; - final right = i < bNums.length ? bNums[i] : 0; - if (left > right) return 1; - if (left < right) return -1; - } - return 0; - } - - static List _toVersionNumbers(String version) { - return version - .split(RegExp(r'[^0-9]+')) - .where((part) => part.isNotEmpty) - .map((part) => int.tryParse(part) ?? 0) - .toList(); - } } 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 f2bd73b9..7d4db1ee 100644 --- a/ui/flutter/lib/features/extensions/presentation/pages/extensions_page.dart +++ b/ui/flutter/lib/features/extensions/presentation/pages/extensions_page.dart @@ -106,6 +106,12 @@ class _ExtensionsPageState extends ConsumerState { }); } final stateAsync = ref.watch(extensionsControllerProvider); + ref.listen(extensionsControllerProvider, (previous, next) { + final failed = next.value?.updateCheckFailed ?? false; + if (failed && previous?.value?.updateCheckFailed != true) { + showAppToast(context, context.l10n.extensionUpdateCheckFailed); + } + }); final body = Stack( children: [ ColoredBox( @@ -485,25 +491,7 @@ class _Content extends StatelessWidget { } bool _canUpdate(ExtensionsState state, ExtensionListItem item) { - if (item.installed == null) return false; - if (state.listFilter == ExtensionListFilter.market && item.store != null) { - return _compareVersion(item.store!.version, item.installed!.version) > 0; - } - return state.updateFlags.containsKey(item.installed!.identity); - } - - int _compareVersion(String a, String b) { - final aNums = a.split(RegExp(r'[^0-9]+')).where((part) => part.isNotEmpty).map((part) => int.tryParse(part) ?? 0); - final bNums = b.split(RegExp(r'[^0-9]+')).where((part) => part.isNotEmpty).map((part) => int.tryParse(part) ?? 0); - final left = aNums.toList(); - final right = bNums.toList(); - final maxLen = left.length > right.length ? left.length : right.length; - for (var i = 0; i < maxLen; i++) { - final l = i < left.length ? left[i] : 0; - final r = i < right.length ? right[i] : 0; - if (l != r) return l.compareTo(r); - } - return 0; + return item.installed != null && state.updateFlags.containsKey(item.installed!.identity); } } @@ -988,7 +976,27 @@ class _ExtensionCard extends ConsumerWidget { children: [ Row( children: [ - ExtensionIcon(item: item, size: 40, borderRadius: BorderRadius.circular(6), fallbackFit: BoxFit.cover), + Stack( + clipBehavior: Clip.none, + children: [ + ExtensionIcon(item: item, size: 40, borderRadius: BorderRadius.circular(6), fallbackFit: BoxFit.cover), + if (canUpdate) + Positioned( + top: -3, + right: -3, + child: Container( + key: ValueKey('extension-card-update-dot-${item.id}'), + width: 11, + height: 11, + decoration: BoxDecoration( + color: palette.error, + shape: BoxShape.circle, + border: Border.all(color: palette.cardBg, width: 2), + ), + ), + ), + ], + ), const SizedBox(width: 10), Expanded( child: Column( @@ -1068,11 +1076,11 @@ class _ExtensionCard extends ConsumerWidget { mainAxisSize: MainAxisSize.min, children: [ if (canUpdate && installed != null) - shad.GhostButton( - density: shad.ButtonDensity.icon, + shad.PrimaryButton( key: ValueKey('update-extension-${installed.identity}'), + density: shad.ButtonDensity.dense, onPressed: busy ? null : () => showExtensionUpdateDialog(context, installed), - child: const Icon(Icons.refresh), + child: Text(context.l10n.extensionUpdateAction), ), if (installed == null && item.store != null) shad.GhostButton( diff --git a/ui/flutter/lib/features/extensions/presentation/widgets/extension_detail_view.dart b/ui/flutter/lib/features/extensions/presentation/widgets/extension_detail_view.dart index 87837560..26204eef 100644 --- a/ui/flutter/lib/features/extensions/presentation/widgets/extension_detail_view.dart +++ b/ui/flutter/lib/features/extensions/presentation/widgets/extension_detail_view.dart @@ -65,12 +65,32 @@ class ExtensionDetailView extends ConsumerWidget { key: const ValueKey('extension-details-hero'), crossAxisAlignment: CrossAxisAlignment.start, children: [ - ExtensionIcon( - item: current, - size: mobile ? 60 : 68, - borderRadius: BorderRadius.circular(14), - fallbackPadding: EdgeInsets.all((mobile ? 60 : 68) * 0.16), - fallbackBackgroundColor: palette.surfaceSoft, + Stack( + clipBehavior: Clip.none, + children: [ + ExtensionIcon( + item: current, + size: mobile ? 60 : 68, + borderRadius: BorderRadius.circular(14), + fallbackPadding: EdgeInsets.all((mobile ? 60 : 68) * 0.16), + fallbackBackgroundColor: palette.surfaceSoft, + ), + if (canUpdate) + Positioned( + top: -2, + right: -2, + child: Container( + key: const ValueKey('extension-details-update-dot'), + width: 13, + height: 13, + decoration: BoxDecoration( + color: palette.error, + shape: BoxShape.circle, + border: Border.all(color: palette.bg, width: 2.5), + ), + ), + ), + ], ), SizedBox(width: mobile ? 16 : 20), Expanded( @@ -135,7 +155,7 @@ class ExtensionDetailView extends ConsumerWidget { key: const ValueKey('extension-details-update'), loading: false, variant: AppLoadingButtonVariant.primary, - icon: const Icon(Icons.refresh, size: 16), + icon: const Icon(Icons.upgrade, size: 16), onPressed: busy ? null : () => showExtensionUpdateDialog(context, installed), child: Text(context.l10n.newVersionUpdate), ), diff --git a/ui/flutter/lib/l10n/app_en.arb b/ui/flutter/lib/l10n/app_en.arb index a241bbfd..97fb6f36 100644 --- a/ui/flutter/lib/l10n/app_en.arb +++ b/ui/flutter/lib/l10n/app_en.arb @@ -120,6 +120,8 @@ } }, "extensionUpdateSuccess": "Updated successfully", + "extensionUpdateAction": "Update", + "extensionUpdateCheckFailed": "Failed to check for updates. Please check your network", "extensionDelete": "Delete Extension", "extensionAlreadyLatest": "It's already the latest version", "extensionFind": "Find Extensions", diff --git a/ui/flutter/lib/l10n/app_zh.arb b/ui/flutter/lib/l10n/app_zh.arb index 0269904d..2cbd5aa7 100644 --- a/ui/flutter/lib/l10n/app_zh.arb +++ b/ui/flutter/lib/l10n/app_zh.arb @@ -106,6 +106,8 @@ "extensionInstallSuccess": "安装成功", "extensionUpdateConfirm": "确定将“{name}”更新到最新版本吗?", "extensionUpdateSuccess": "更新成功", + "extensionUpdateAction": "更新", + "extensionUpdateCheckFailed": "检查更新失败,请检查网络后重试", "extensionDelete": "删除扩展", "extensionAlreadyLatest": "已经是最新版本", "extensionFind": "获取扩展", From f34cb9c971310c82150a006f5d7feb2d422be4e5 Mon Sep 17 00:00:00 2001 From: Levi Date: Sat, 19 Sep 2026 18:07:28 +0800 Subject: [PATCH 02/15] refactor(ui): declutter extension card actions Drop homepage/GitHub buttons from store extension cards (they remain on the detail page); keep them only for unlisted extensions whose cards do not open a detail view. With the labeled update button this caps card actions at update/settings/delete. --- .../extensions/presentation/pages/extensions_page.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 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 7d4db1ee..a4148313 100644 --- a/ui/flutter/lib/features/extensions/presentation/pages/extensions_page.dart +++ b/ui/flutter/lib/features/extensions/presentation/pages/extensions_page.dart @@ -1095,13 +1095,15 @@ class _ExtensionCard extends ConsumerWidget { ? const SizedBox.square(dimension: 14, child: shad.CircularProgressIndicator()) : const Icon(Icons.download), ), - if ((item.homepage ?? '').isNotEmpty) + // Unlisted extensions have no detail page to open, so keep + // their links reachable directly on the card. + if (item.store == null && (item.homepage ?? '').isNotEmpty) shad.GhostButton( density: shad.ButtonDensity.icon, onPressed: () => unawaited(launchUrl(Uri.parse(item.homepage!))), child: const Icon(Icons.home_outlined), ), - if ((item.repoUrl ?? '').isNotEmpty) + if (item.store == null && (item.repoUrl ?? '').isNotEmpty) shad.GhostButton( density: shad.ButtonDensity.icon, onPressed: () => unawaited(launchUrl(Uri.parse(item.repoUrl!))), From d94b59b165a85079c12c3ecc44b8772179410475 Mon Sep 17 00:00:00 2001 From: Levi Date: Sat, 19 Sep 2026 18:43:57 +0800 Subject: [PATCH 03/15] refactor(ui): open extension details from every card Installed extensions missing from the store index previously had no tap handler on their card, so they kept on-card homepage/GitHub buttons as the only metadata entry. Make all cards open the detail view and drop the homepage/GitHub buttons entirely; details remain the single place for links. --- .../presentation/pages/extensions_page.dart | 15 --------------- 1 file changed, 15 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 a4148313..21fe8c56 100644 --- a/ui/flutter/lib/features/extensions/presentation/pages/extensions_page.dart +++ b/ui/flutter/lib/features/extensions/presentation/pages/extensions_page.dart @@ -1095,20 +1095,6 @@ class _ExtensionCard extends ConsumerWidget { ? const SizedBox.square(dimension: 14, child: shad.CircularProgressIndicator()) : const Icon(Icons.download), ), - // Unlisted extensions have no detail page to open, so keep - // their links reachable directly on the card. - if (item.store == null && (item.homepage ?? '').isNotEmpty) - shad.GhostButton( - density: shad.ButtonDensity.icon, - onPressed: () => unawaited(launchUrl(Uri.parse(item.homepage!))), - child: const Icon(Icons.home_outlined), - ), - if (item.store == null && (item.repoUrl ?? '').isNotEmpty) - shad.GhostButton( - density: shad.ButtonDensity.icon, - onPressed: () => unawaited(launchUrl(Uri.parse(item.repoUrl!))), - child: const Icon(Icons.code), - ), if (installed?.settings?.isNotEmpty == true) shad.GhostButton( density: shad.ButtonDensity.icon, @@ -1137,7 +1123,6 @@ class _ExtensionCard extends ConsumerWidget { ], ), ); - if (item.store == null) return card; return MouseRegion( cursor: SystemMouseCursors.click, child: GestureDetector( From d8758267c45381e26ddb64f77a2808f152abb4cb Mon Sep 17 00:00:00 2001 From: Levi Date: Sat, 19 Sep 2026 19:04:58 +0800 Subject: [PATCH 04/15] refactor(ui): polish extension update affordance per review - Move the update dot from the icon corner to beside the card title - Restyle the card update button to match the row's ghost buttons - Drop the detail-view dot; the existing update status chip covers it - Rename the detail update button label to just Update --- .../presentation/pages/extensions_page.dart | 54 +++++++++---------- .../widgets/extension_detail_view.dart | 34 +++--------- 2 files changed, 33 insertions(+), 55 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 21fe8c56..dc8fe431 100644 --- a/ui/flutter/lib/features/extensions/presentation/pages/extensions_page.dart +++ b/ui/flutter/lib/features/extensions/presentation/pages/extensions_page.dart @@ -976,37 +976,32 @@ class _ExtensionCard extends ConsumerWidget { children: [ Row( children: [ - Stack( - clipBehavior: Clip.none, - children: [ - ExtensionIcon(item: item, size: 40, borderRadius: BorderRadius.circular(6), fallbackFit: BoxFit.cover), - if (canUpdate) - Positioned( - top: -3, - right: -3, - child: Container( - key: ValueKey('extension-card-update-dot-${item.id}'), - width: 11, - height: 11, - decoration: BoxDecoration( - color: palette.error, - shape: BoxShape.circle, - border: Border.all(color: palette.cardBg, width: 2), - ), - ), - ), - ], - ), + ExtensionIcon(item: item, size: 40, borderRadius: BorderRadius.circular(6), fallbackFit: BoxFit.cover), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - item.title, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: TextStyle(color: palette.textPrimary, fontSize: 14, fontWeight: FontWeight.w700), + Row( + children: [ + Flexible( + child: Text( + item.title, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle(color: palette.textPrimary, fontSize: 14, fontWeight: FontWeight.w700), + ), + ), + if (canUpdate) ...[ + const SizedBox(width: 6), + Container( + key: ValueKey('extension-card-update-dot-${item.id}'), + width: 8, + height: 8, + decoration: BoxDecoration(color: palette.error, shape: BoxShape.circle), + ), + ], + ], ), const SizedBox(height: 2), Text( @@ -1076,11 +1071,14 @@ class _ExtensionCard extends ConsumerWidget { mainAxisSize: MainAxisSize.min, children: [ if (canUpdate && installed != null) - shad.PrimaryButton( + shad.GhostButton( key: ValueKey('update-extension-${installed.identity}'), density: shad.ButtonDensity.dense, onPressed: busy ? null : () => showExtensionUpdateDialog(context, installed), - child: Text(context.l10n.extensionUpdateAction), + child: Text( + context.l10n.extensionUpdateAction, + style: TextStyle(color: palette.error, fontWeight: FontWeight.w600), + ), ), if (installed == null && item.store != null) shad.GhostButton( diff --git a/ui/flutter/lib/features/extensions/presentation/widgets/extension_detail_view.dart b/ui/flutter/lib/features/extensions/presentation/widgets/extension_detail_view.dart index 26204eef..f7337291 100644 --- a/ui/flutter/lib/features/extensions/presentation/widgets/extension_detail_view.dart +++ b/ui/flutter/lib/features/extensions/presentation/widgets/extension_detail_view.dart @@ -65,32 +65,12 @@ class ExtensionDetailView extends ConsumerWidget { key: const ValueKey('extension-details-hero'), crossAxisAlignment: CrossAxisAlignment.start, children: [ - Stack( - clipBehavior: Clip.none, - children: [ - ExtensionIcon( - item: current, - size: mobile ? 60 : 68, - borderRadius: BorderRadius.circular(14), - fallbackPadding: EdgeInsets.all((mobile ? 60 : 68) * 0.16), - fallbackBackgroundColor: palette.surfaceSoft, - ), - if (canUpdate) - Positioned( - top: -2, - right: -2, - child: Container( - key: const ValueKey('extension-details-update-dot'), - width: 13, - height: 13, - decoration: BoxDecoration( - color: palette.error, - shape: BoxShape.circle, - border: Border.all(color: palette.bg, width: 2.5), - ), - ), - ), - ], + ExtensionIcon( + item: current, + size: mobile ? 60 : 68, + borderRadius: BorderRadius.circular(14), + fallbackPadding: EdgeInsets.all((mobile ? 60 : 68) * 0.16), + fallbackBackgroundColor: palette.surfaceSoft, ), SizedBox(width: mobile ? 16 : 20), Expanded( @@ -157,7 +137,7 @@ class ExtensionDetailView extends ConsumerWidget { variant: AppLoadingButtonVariant.primary, icon: const Icon(Icons.upgrade, size: 16), onPressed: busy ? null : () => showExtensionUpdateDialog(context, installed), - child: Text(context.l10n.newVersionUpdate), + child: Text(context.l10n.extensionUpdateAction), ), if (installed != null && !canUpdate) AppLoadingButton( From 51b7abed487de8ebba083973440664b8b02d4d0a Mon Sep 17 00:00:00 2001 From: Levi Date: Sat, 19 Sep 2026 19:47:02 +0800 Subject: [PATCH 05/15] refactor(ui): replace update dot with tappable status chip Card and detail headers now show a green update chip next to the extension title; on cards it opens the update dialog directly. The red dot and the red card button are gone, and the status chip no longer duplicates inside the detail content. Shorten the English label so the chip fits narrow cards. --- .../pages/extension_details_page.dart | 9 ++++ .../presentation/pages/extensions_page.dart | 24 +++-------- .../widgets/extension_detail_view.dart | 41 ++++-------------- .../widgets/extension_update_status.dart | 43 +++++++++++++++++++ ui/flutter/lib/l10n/app_en.arb | 2 +- .../detail/app_detail_surface_desktop.dart | 27 ++++++++---- .../detail/app_detail_surface_mobile.dart | 21 ++++++--- ui/flutter/test/widget_test.dart | 4 +- 8 files changed, 103 insertions(+), 68 deletions(-) create mode 100644 ui/flutter/lib/features/extensions/presentation/widgets/extension_update_status.dart diff --git a/ui/flutter/lib/features/extensions/presentation/pages/extension_details_page.dart b/ui/flutter/lib/features/extensions/presentation/pages/extension_details_page.dart index e719fdfd..3ae0b6ec 100644 --- a/ui/flutter/lib/features/extensions/presentation/pages/extension_details_page.dart +++ b/ui/flutter/lib/features/extensions/presentation/pages/extension_details_page.dart @@ -8,6 +8,7 @@ import '../../../../shared/theme/app_palette.dart'; import '../../../../shared/widgets/detail/app_detail_surface.dart'; import '../../application/extensions_controller.dart'; import '../widgets/extension_detail_view.dart'; +import '../widgets/extension_update_status.dart'; class ExtensionDetailsPage extends ConsumerWidget { const ExtensionDetailsPage({super.key, required this.extensionId, this.initialItem}); @@ -24,6 +25,9 @@ class ExtensionDetailsPage extends ConsumerWidget { return AppDetailPage( title: item?.title ?? context.l10n.extensions, onBack: () => context.canPop() ? context.pop() : context.go('/extensions'), + titleTrailing: _canUpdate(stateAsync.value, item) + ? ExtensionUpdateStatus(label: context.l10n.extensionCanUpdate) + : null, child: item == null ? Center( child: stateAsync.isLoading @@ -33,4 +37,9 @@ class ExtensionDetailsPage extends ConsumerWidget { : ExtensionDetailView(key: ValueKey(item.id), item: item, mobile: true), ); } + + bool _canUpdate(ExtensionsState? state, ExtensionListItem? item) { + final installed = item?.installed; + return item != null && installed != null && (state?.updateFlags.containsKey(installed.identity) ?? false); + } } 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 dc8fe431..437b4971 100644 --- a/ui/flutter/lib/features/extensions/presentation/pages/extensions_page.dart +++ b/ui/flutter/lib/features/extensions/presentation/pages/extensions_page.dart @@ -28,6 +28,7 @@ import '../widgets/extension_detail_view.dart'; import '../widgets/extension_icon.dart'; import '../widgets/extension_setting_field.dart'; import '../widgets/extension_update_dialog.dart'; +import '../widgets/extension_update_status.dart'; const _extensionCardMinWidth = 290.0; const _extensionGridSpacing = 10.0; @@ -992,15 +993,12 @@ class _ExtensionCard extends ConsumerWidget { style: TextStyle(color: palette.textPrimary, fontSize: 14, fontWeight: FontWeight.w700), ), ), - if (canUpdate) ...[ - const SizedBox(width: 6), - Container( - key: ValueKey('extension-card-update-dot-${item.id}'), - width: 8, - height: 8, - decoration: BoxDecoration(color: palette.error, shape: BoxShape.circle), + if (canUpdate && installed != null) + ExtensionUpdateStatus( + key: ValueKey('extension-card-update-status-${item.id}'), + label: context.l10n.extensionCanUpdate, + onTap: busy ? null : () => showExtensionUpdateDialog(context, installed), ), - ], ], ), const SizedBox(height: 2), @@ -1070,16 +1068,6 @@ class _ExtensionCard extends ConsumerWidget { key: ValueKey('extension-card-actions-${item.id}'), mainAxisSize: MainAxisSize.min, children: [ - if (canUpdate && installed != null) - shad.GhostButton( - key: ValueKey('update-extension-${installed.identity}'), - density: shad.ButtonDensity.dense, - onPressed: busy ? null : () => showExtensionUpdateDialog(context, installed), - child: Text( - context.l10n.extensionUpdateAction, - style: TextStyle(color: palette.error, fontWeight: FontWeight.w600), - ), - ), if (installed == null && item.store != null) shad.GhostButton( key: ValueKey('install-store-extension-${item.store!.id}'), diff --git a/ui/flutter/lib/features/extensions/presentation/widgets/extension_detail_view.dart b/ui/flutter/lib/features/extensions/presentation/widgets/extension_detail_view.dart index f7337291..ad159938 100644 --- a/ui/flutter/lib/features/extensions/presentation/widgets/extension_detail_view.dart +++ b/ui/flutter/lib/features/extensions/presentation/widgets/extension_detail_view.dart @@ -21,20 +21,26 @@ import '../../../../util/util.dart'; import '../../application/extensions_controller.dart'; import 'extension_icon.dart'; import 'extension_update_dialog.dart'; +import 'extension_update_status.dart'; -class ExtensionDetailDrawer extends StatelessWidget { +class ExtensionDetailDrawer extends ConsumerWidget { const ExtensionDetailDrawer({super.key, required this.item, required this.onClose}); final ExtensionListItem? item; final VoidCallback onClose; @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { + final state = ref.watch(extensionsControllerProvider).value; + final installed = item?.installed; + final canUpdate = + item != null && installed != null && (state?.updateFlags.containsKey(installed.identity) ?? false); return AppDetailDrawer( open: item != null, title: item?.title ?? '', onClose: onClose, drawerKey: const ValueKey('extension-details-drawer'), + titleTrailing: canUpdate ? ExtensionUpdateStatus(label: context.l10n.extensionCanUpdate) : null, child: item == null ? const SizedBox.shrink() : ExtensionDetailView(item: item!), ); } @@ -104,8 +110,6 @@ class ExtensionDetailView extends ConsumerWidget { style: TextStyle(color: palette.textMuted, fontSize: 11.5, fontWeight: FontWeight.w600), ), if (store != null) _ExtensionStats(store: store), - if (installed != null && canUpdate) - _ExtensionStatus(label: context.l10n.extensionCanUpdate, emphasized: true), ], ), ], @@ -276,35 +280,6 @@ class _ExtensionReadmeState extends State<_ExtensionReadme> { } } -class _ExtensionStatus extends StatelessWidget { - const _ExtensionStatus({required this.label, required this.emphasized}); - - final String label; - final bool emphasized; - - @override - Widget build(BuildContext context) { - final palette = AppPalette.of(context); - final dotColor = emphasized ? palette.brand : palette.success; - final textColor = emphasized ? palette.textPrimary : palette.success; - return Row( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - width: 5, - height: 5, - decoration: BoxDecoration(color: dotColor, shape: BoxShape.circle), - ), - const SizedBox(width: 5), - Text( - label, - style: TextStyle(color: textColor, fontSize: 11.5, fontWeight: FontWeight.w600), - ), - ], - ); - } -} - class _LinkButtonContent extends StatelessWidget { const _LinkButtonContent({required this.icon, required this.label}); 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 new file mode 100644 index 00000000..046b5a21 --- /dev/null +++ b/ui/flutter/lib/features/extensions/presentation/widgets/extension_update_status.dart @@ -0,0 +1,43 @@ +import 'package:flutter/widgets.dart'; + +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}); + + final String? label; + final VoidCallback? onTap; + + @override + Widget build(BuildContext context) { + final palette = AppPalette.of(context); + final content = Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 6, + height: 6, + decoration: BoxDecoration(color: palette.brand, shape: BoxShape.circle), + ), + if (label != null) ...[ + const SizedBox(width: 5), + Text( + label!, + style: TextStyle(color: palette.textPrimary, fontSize: 11.5, fontWeight: FontWeight.w600), + ), + ], + ], + ); + if (onTap == null) return content; + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: onTap, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 6), + child: content, + ), + ); + } +} diff --git a/ui/flutter/lib/l10n/app_en.arb b/ui/flutter/lib/l10n/app_en.arb index 97fb6f36..48059151 100644 --- a/ui/flutter/lib/l10n/app_en.arb +++ b/ui/flutter/lib/l10n/app_en.arb @@ -139,7 +139,7 @@ "extensionLoadMore": "Load More", "extensionNoMore": "No more extensions", "extensionInstalled": "Installed", - "extensionCanUpdate": "Update Available", + "extensionCanUpdate": "Update", "extensionInstall": "Install", "extensionLoadLocal": "Install Local Folder", "extensionFilterAll": "All", diff --git a/ui/flutter/lib/shared/widgets/detail/app_detail_surface_desktop.dart b/ui/flutter/lib/shared/widgets/detail/app_detail_surface_desktop.dart index 75f722f5..3c9294ad 100644 --- a/ui/flutter/lib/shared/widgets/detail/app_detail_surface_desktop.dart +++ b/ui/flutter/lib/shared/widgets/detail/app_detail_surface_desktop.dart @@ -16,6 +16,7 @@ class AppDetailDrawer extends StatelessWidget { required this.child, this.drawerKey, this.width, + this.titleTrailing, }); final bool open; @@ -24,6 +25,7 @@ class AppDetailDrawer extends StatelessWidget { final Widget child; final Key? drawerKey; final double? width; + final Widget? titleTrailing; @override Widget build(BuildContext context) { @@ -82,15 +84,22 @@ class AppDetailDrawer extends StatelessWidget { child: Row( children: [ Expanded( - child: Text( - title, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: TextStyle( - color: palette.textPrimary, - fontSize: 16, - fontWeight: FontWeight.w700, - ), + child: Row( + children: [ + Flexible( + child: Text( + title, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + color: palette.textPrimary, + fontSize: 16, + fontWeight: FontWeight.w700, + ), + ), + ), + if (titleTrailing != null) ...[const SizedBox(width: 8), titleTrailing!], + ], ), ), const SizedBox(width: 12), diff --git a/ui/flutter/lib/shared/widgets/detail/app_detail_surface_mobile.dart b/ui/flutter/lib/shared/widgets/detail/app_detail_surface_mobile.dart index 71f340ff..1b191e85 100644 --- a/ui/flutter/lib/shared/widgets/detail/app_detail_surface_mobile.dart +++ b/ui/flutter/lib/shared/widgets/detail/app_detail_surface_mobile.dart @@ -13,12 +13,14 @@ class AppDetailPage extends StatelessWidget { required this.onBack, required this.child, this.backgroundColor, + this.titleTrailing, }); final String title; final VoidCallback onBack; final Widget child; final Color? backgroundColor; + final Widget? titleTrailing; @override Widget build(BuildContext context) { @@ -53,12 +55,19 @@ class AppDetailPage extends StatelessWidget { ), ), Expanded( - child: Text( - title, - textAlign: TextAlign.center, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: TextStyle(color: palette.textPrimary, fontSize: 17, fontWeight: FontWeight.w700), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Flexible( + child: Text( + title, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle(color: palette.textPrimary, fontSize: 17, fontWeight: FontWeight.w700), + ), + ), + if (titleTrailing != null) ...[const SizedBox(width: 8), titleTrailing!], + ], ), ), const SizedBox(width: 44), diff --git a/ui/flutter/test/widget_test.dart b/ui/flutter/test/widget_test.dart index 7269398e..9b8ec608 100644 --- a/ui/flutter/test/widget_test.dart +++ b/ui/flutter/test/widget_test.dart @@ -817,7 +817,9 @@ void main() { await tester.tap(find.byKey(const ValueKey('extension-card-extension-0'))); await tester.pumpAndSettle(); } - final entry = find.byKey(ValueKey(details ? 'extension-details-update' : 'update-extension-extension-0')); + final entry = find.byKey( + ValueKey(details ? 'extension-details-update' : 'extension-card-update-status-extension-0'), + ); final dialog = find.byKey(const ValueKey('extension-update-dialog')); final confirm = find.byKey(const ValueKey('confirm-update-extension-button')); final cancel = find.byKey(const ValueKey('cancel-update-extension-button')); From 948f6d54875010cfde73584e7cbadacc43dfc867 Mon Sep 17 00:00:00 2001 From: Levi Date: Sat, 19 Sep 2026 20:02:28 +0800 Subject: [PATCH 06/15] fix(ui): let the update status chip shrink instead of overflow Long translations (e.g. Italian, Tamil) could exceed the card title row on minimum-width cards. The chip is now flex-sized and its label ellipsizes, so the title and chip degrade gracefully at any width and locale. --- .../extensions/presentation/pages/extensions_page.dart | 10 ++++++---- .../presentation/widgets/extension_update_status.dart | 10 +++++++--- .../widgets/detail/app_detail_surface_desktop.dart | 5 ++++- .../widgets/detail/app_detail_surface_mobile.dart | 5 ++++- 4 files changed, 21 insertions(+), 9 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 437b4971..e1635150 100644 --- a/ui/flutter/lib/features/extensions/presentation/pages/extensions_page.dart +++ b/ui/flutter/lib/features/extensions/presentation/pages/extensions_page.dart @@ -994,10 +994,12 @@ class _ExtensionCard extends ConsumerWidget { ), ), if (canUpdate && installed != null) - ExtensionUpdateStatus( - key: ValueKey('extension-card-update-status-${item.id}'), - label: context.l10n.extensionCanUpdate, - onTap: busy ? null : () => showExtensionUpdateDialog(context, installed), + Flexible( + child: ExtensionUpdateStatus( + key: ValueKey('extension-card-update-status-${item.id}'), + label: context.l10n.extensionCanUpdate, + onTap: busy ? null : () => showExtensionUpdateDialog(context, installed), + ), ), ], ), 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 046b5a21..755e363a 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 @@ -23,9 +23,13 @@ class ExtensionUpdateStatus extends StatelessWidget { ), if (label != null) ...[ const SizedBox(width: 5), - Text( - label!, - style: TextStyle(color: palette.textPrimary, fontSize: 11.5, fontWeight: FontWeight.w600), + Flexible( + child: Text( + label!, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle(color: palette.textPrimary, fontSize: 11.5, fontWeight: FontWeight.w600), + ), ), ], ], diff --git a/ui/flutter/lib/shared/widgets/detail/app_detail_surface_desktop.dart b/ui/flutter/lib/shared/widgets/detail/app_detail_surface_desktop.dart index 3c9294ad..509248d8 100644 --- a/ui/flutter/lib/shared/widgets/detail/app_detail_surface_desktop.dart +++ b/ui/flutter/lib/shared/widgets/detail/app_detail_surface_desktop.dart @@ -98,7 +98,10 @@ class AppDetailDrawer extends StatelessWidget { ), ), ), - if (titleTrailing != null) ...[const SizedBox(width: 8), titleTrailing!], + if (titleTrailing != null) ...[ + const SizedBox(width: 8), + Flexible(child: titleTrailing!), + ], ], ), ), diff --git a/ui/flutter/lib/shared/widgets/detail/app_detail_surface_mobile.dart b/ui/flutter/lib/shared/widgets/detail/app_detail_surface_mobile.dart index 1b191e85..cc807e68 100644 --- a/ui/flutter/lib/shared/widgets/detail/app_detail_surface_mobile.dart +++ b/ui/flutter/lib/shared/widgets/detail/app_detail_surface_mobile.dart @@ -66,7 +66,10 @@ class AppDetailPage extends StatelessWidget { style: TextStyle(color: palette.textPrimary, fontSize: 17, fontWeight: FontWeight.w700), ), ), - if (titleTrailing != null) ...[const SizedBox(width: 8), titleTrailing!], + if (titleTrailing != null) ...[ + const SizedBox(width: 8), + Flexible(child: titleTrailing!), + ], ], ), ), From 04ebca60cf98312665210d5fd56478c21ba27e11 Mon Sep 17 00:00:00 2001 From: Levi Date: Sat, 19 Sep 2026 20:49:20 +0800 Subject: [PATCH 07/15] refactor(ui): make the status chip the only update entry Remove the update button from the detail actions; the green chip next to the extension title opens the update dialog on cards, the desktop drawer and the mobile details page alike. --- .../pages/extension_details_page.dart | 9 ++++++++- .../widgets/extension_detail_view.dart | 18 ++++++++---------- ui/flutter/lib/l10n/app_en.arb | 1 - ui/flutter/lib/l10n/app_zh.arb | 1 - 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/ui/flutter/lib/features/extensions/presentation/pages/extension_details_page.dart b/ui/flutter/lib/features/extensions/presentation/pages/extension_details_page.dart index 3ae0b6ec..8c81eca5 100644 --- a/ui/flutter/lib/features/extensions/presentation/pages/extension_details_page.dart +++ b/ui/flutter/lib/features/extensions/presentation/pages/extension_details_page.dart @@ -8,6 +8,7 @@ import '../../../../shared/theme/app_palette.dart'; import '../../../../shared/widgets/detail/app_detail_surface.dart'; import '../../application/extensions_controller.dart'; import '../widgets/extension_detail_view.dart'; +import '../widgets/extension_update_dialog.dart'; import '../widgets/extension_update_status.dart'; class ExtensionDetailsPage extends ConsumerWidget { @@ -26,7 +27,13 @@ class ExtensionDetailsPage extends ConsumerWidget { title: item?.title ?? context.l10n.extensions, onBack: () => context.canPop() ? context.pop() : context.go('/extensions'), titleTrailing: _canUpdate(stateAsync.value, item) - ? ExtensionUpdateStatus(label: context.l10n.extensionCanUpdate) + ? ExtensionUpdateStatus( + key: const ValueKey('extension-details-update-mobile'), + label: context.l10n.extensionCanUpdate, + onTap: item?.installed == null + ? null + : () => showExtensionUpdateDialog(context, item!.installed!), + ) : null, child: item == null ? Center( diff --git a/ui/flutter/lib/features/extensions/presentation/widgets/extension_detail_view.dart b/ui/flutter/lib/features/extensions/presentation/widgets/extension_detail_view.dart index ad159938..73431ea7 100644 --- a/ui/flutter/lib/features/extensions/presentation/widgets/extension_detail_view.dart +++ b/ui/flutter/lib/features/extensions/presentation/widgets/extension_detail_view.dart @@ -35,12 +35,19 @@ class ExtensionDetailDrawer extends ConsumerWidget { final installed = item?.installed; final canUpdate = item != null && installed != null && (state?.updateFlags.containsKey(installed.identity) ?? false); + final updateChip = canUpdate + ? ExtensionUpdateStatus( + key: const ValueKey('extension-details-update'), + label: context.l10n.extensionCanUpdate, + onTap: () => showExtensionUpdateDialog(context, installed), + ) + : null; return AppDetailDrawer( open: item != null, title: item?.title ?? '', onClose: onClose, drawerKey: const ValueKey('extension-details-drawer'), - titleTrailing: canUpdate ? ExtensionUpdateStatus(label: context.l10n.extensionCanUpdate) : null, + titleTrailing: updateChip, child: item == null ? const SizedBox.shrink() : ExtensionDetailView(item: item!), ); } @@ -134,15 +141,6 @@ class ExtensionDetailView extends ConsumerWidget { _runAction(context, () => ref.read(extensionsControllerProvider.notifier).installFromStore(store)), child: Text(context.l10n.extensionInstall), ), - if (installed != null && canUpdate) - AppLoadingButton( - key: const ValueKey('extension-details-update'), - loading: false, - variant: AppLoadingButtonVariant.primary, - icon: const Icon(Icons.upgrade, size: 16), - onPressed: busy ? null : () => showExtensionUpdateDialog(context, installed), - child: Text(context.l10n.extensionUpdateAction), - ), if (installed != null && !canUpdate) AppLoadingButton( key: const ValueKey('extension-details-installed'), diff --git a/ui/flutter/lib/l10n/app_en.arb b/ui/flutter/lib/l10n/app_en.arb index 48059151..ba46fd44 100644 --- a/ui/flutter/lib/l10n/app_en.arb +++ b/ui/flutter/lib/l10n/app_en.arb @@ -120,7 +120,6 @@ } }, "extensionUpdateSuccess": "Updated successfully", - "extensionUpdateAction": "Update", "extensionUpdateCheckFailed": "Failed to check for updates. Please check your network", "extensionDelete": "Delete Extension", "extensionAlreadyLatest": "It's already the latest version", diff --git a/ui/flutter/lib/l10n/app_zh.arb b/ui/flutter/lib/l10n/app_zh.arb index 2cbd5aa7..6e724adc 100644 --- a/ui/flutter/lib/l10n/app_zh.arb +++ b/ui/flutter/lib/l10n/app_zh.arb @@ -106,7 +106,6 @@ "extensionInstallSuccess": "安装成功", "extensionUpdateConfirm": "确定将“{name}”更新到最新版本吗?", "extensionUpdateSuccess": "更新成功", - "extensionUpdateAction": "更新", "extensionUpdateCheckFailed": "检查更新失败,请检查网络后重试", "extensionDelete": "删除扩展", "extensionAlreadyLatest": "已经是最新版本", From 2e5f8948beebc2fd8c54635e59548d8252d8c8cf Mon Sep 17 00:00:00 2001 From: Levi Date: Sat, 19 Sep 2026 20:55:22 +0800 Subject: [PATCH 08/15] fix(ui): restore homepage and GitHub buttons on extension cards --- .../presentation/pages/extensions_page.dart | 12 ++++++++++++ 1 file changed, 12 insertions(+) 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 e1635150..23384352 100644 --- a/ui/flutter/lib/features/extensions/presentation/pages/extensions_page.dart +++ b/ui/flutter/lib/features/extensions/presentation/pages/extensions_page.dart @@ -1083,6 +1083,18 @@ class _ExtensionCard extends ConsumerWidget { ? const SizedBox.square(dimension: 14, child: shad.CircularProgressIndicator()) : const Icon(Icons.download), ), + if ((item.homepage ?? '').isNotEmpty) + shad.GhostButton( + density: shad.ButtonDensity.icon, + onPressed: () => unawaited(launchUrl(Uri.parse(item.homepage!))), + child: const Icon(Icons.home_outlined), + ), + if ((item.repoUrl ?? '').isNotEmpty) + shad.GhostButton( + density: shad.ButtonDensity.icon, + onPressed: () => unawaited(launchUrl(Uri.parse(item.repoUrl!))), + child: const Icon(Icons.code), + ), if (installed?.settings?.isNotEmpty == true) shad.GhostButton( density: shad.ButtonDensity.icon, From 1b4956b0ec186506edb04e646d2aeacbdaeccdd6 Mon Sep 17 00:00:00 2001 From: Levi Date: Sat, 19 Sep 2026 21:18:57 +0800 Subject: [PATCH 09/15] fix(ui): reserve the pointer cursor for the update chip Extension cards no longer show a pointer cursor on hover; the tappable update status chip shows it instead, on cards and detail headers alike. --- .../presentation/pages/extensions_page.dart | 25 ++++++++----------- .../widgets/extension_update_status.dart | 15 ++++++----- 2 files changed, 20 insertions(+), 20 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 23384352..dcb4221e 100644 --- a/ui/flutter/lib/features/extensions/presentation/pages/extensions_page.dart +++ b/ui/flutter/lib/features/extensions/presentation/pages/extensions_page.dart @@ -1123,20 +1123,17 @@ class _ExtensionCard extends ConsumerWidget { ], ), ); - return MouseRegion( - cursor: SystemMouseCursors.click, - child: GestureDetector( - key: ValueKey('open-extension-details-${item.id}'), - behavior: HitTestBehavior.opaque, - onTap: () { - if (MediaQuery.sizeOf(context).width < Breakpoints.mobile) { - context.push('/extensions/${Uri.encodeComponent(item.id)}', extra: item); - return; - } - onOpenDetails(item); - }, - child: card, - ), + return GestureDetector( + key: ValueKey('open-extension-details-${item.id}'), + behavior: HitTestBehavior.opaque, + onTap: () { + if (MediaQuery.sizeOf(context).width < Breakpoints.mobile) { + context.push('/extensions/${Uri.encodeComponent(item.id)}', extra: item); + return; + } + onOpenDetails(item); + }, + child: card, ); } } 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 755e363a..c27933b8 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 @@ -35,12 +35,15 @@ class ExtensionUpdateStatus extends StatelessWidget { ], ); if (onTap == null) return content; - return GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: onTap, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 6), - child: content, + return MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: onTap, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 6), + child: content, + ), ), ); } From 8c728ffda788e1502e6ffdd5f78d9d969718e3ef Mon Sep 17 00:00:00 2001 From: Levi Date: Sat, 19 Sep 2026 21:41:56 +0800 Subject: [PATCH 10/15] style(ui): render the update status as a bordered chip Pill shape with a brand-green tint, outline and dot so the status reads as a badge next to the title instead of plain text. --- .../widgets/extension_update_status.dart | 46 +++++++++++-------- 1 file changed, 27 insertions(+), 19 deletions(-) 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 c27933b8..3d9e7812 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 @@ -13,26 +13,34 @@ class ExtensionUpdateStatus extends StatelessWidget { @override Widget build(BuildContext context) { final palette = AppPalette.of(context); - final content = Row( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - width: 6, - height: 6, - decoration: BoxDecoration(color: palette.brand, shape: BoxShape.circle), - ), - if (label != null) ...[ - const SizedBox(width: 5), - Flexible( - child: Text( - label!, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: TextStyle(color: palette.textPrimary, fontSize: 11.5, fontWeight: FontWeight.w600), - ), + final content = Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), + decoration: BoxDecoration( + color: palette.brandSoft, + borderRadius: BorderRadius.circular(999), + border: Border.all(color: palette.brand.withValues(alpha: 0.35)), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 6, + height: 6, + decoration: BoxDecoration(color: palette.brand, shape: BoxShape.circle), ), + if (label != null) ...[ + const SizedBox(width: 5), + Flexible( + child: Text( + label!, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle(color: palette.textPrimary, fontSize: 11.5, fontWeight: FontWeight.w600), + ), + ), + ], ], - ], + ), ); if (onTap == null) return content; return MouseRegion( @@ -41,7 +49,7 @@ class ExtensionUpdateStatus extends StatelessWidget { behavior: HitTestBehavior.opaque, onTap: onTap, child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 6), + padding: const EdgeInsets.symmetric(horizontal: 2, vertical: 4), child: content, ), ), From fac17b0ccc600c3447bedafeaabe94da55eac10a Mon Sep 17 00:00:00 2001 From: Levi Date: Sat, 19 Sep 2026 21:47:44 +0800 Subject: [PATCH 11/15] style(ui): shorten update button label to Update The shared newVersionUpdate label now reads Update on the app upgrade dialog, the tracker update button and the extension update dialog, so the extension flow matches the host dialog wording. --- ui/flutter/lib/l10n/app_en.arb | 2 +- ui/flutter/lib/l10n/app_zh.arb | 2 +- ui/flutter/test/widget_test.dart | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/flutter/lib/l10n/app_en.arb b/ui/flutter/lib/l10n/app_en.arb index ba46fd44..0cb61f11 100644 --- a/ui/flutter/lib/l10n/app_en.arb +++ b/ui/flutter/lib/l10n/app_en.arb @@ -106,7 +106,7 @@ "add": "Add", "edit": "Edit", "newVersionTitle": "Discover new version @version", - "newVersionUpdate": "Update Now", + "newVersionUpdate": "Update", "newVersionLater": "Later", "extensions": "Extensions", "extensionInstallUrl": "Install URL", diff --git a/ui/flutter/lib/l10n/app_zh.arb b/ui/flutter/lib/l10n/app_zh.arb index 6e724adc..6d4e91fc 100644 --- a/ui/flutter/lib/l10n/app_zh.arb +++ b/ui/flutter/lib/l10n/app_zh.arb @@ -99,7 +99,7 @@ "add": "添加", "edit": "编辑", "newVersionTitle": "发现新版本 @version", - "newVersionUpdate": "立即更新", + "newVersionUpdate": "更新", "newVersionLater": "稍后再说", "extensions": "扩展", "extensionInstallUrl": "安装链接", diff --git a/ui/flutter/test/widget_test.dart b/ui/flutter/test/widget_test.dart index 9b8ec608..0d2c3fee 100644 --- a/ui/flutter/test/widget_test.dart +++ b/ui/flutter/test/widget_test.dart @@ -2329,7 +2329,7 @@ void main() { final progress = find.descendant(of: updateButton, matching: find.byType(shad.CircularProgressIndicator)); expect(progress, findsOneWidget); expect(tester.getSize(progress), const Size.square(14)); - expect(find.descendant(of: updateButton, matching: find.text('Update Now')), findsOneWidget); + expect(find.descendant(of: updateButton, matching: find.text('Update')), findsOneWidget); runtimeController.completeTrackerUpdate(); await tester.pumpAndSettle(); From e779de285d56023bbfb7ba99602cc76bcd22a879 Mon Sep 17 00:00:00 2001 From: Levi Date: Sat, 19 Sep 2026 21:56:46 +0800 Subject: [PATCH 12/15] fix(ui): card chip spacing, instant flag clearing, locale-wide label - Space the update chip off the card title to match the detail header - Clear the extension's update flag right after a successful upgrade so the chip disappears without waiting for the background re-check - Use the concise Update label across all locales --- .../extensions/application/extensions_controller.dart | 3 +++ .../extensions/presentation/pages/extensions_page.dart | 4 +++- ui/flutter/lib/l10n/app_ar.arb | 2 +- ui/flutter/lib/l10n/app_az.arb | 2 +- ui/flutter/lib/l10n/app_ca.arb | 2 +- ui/flutter/lib/l10n/app_de.arb | 2 +- ui/flutter/lib/l10n/app_es.arb | 2 +- ui/flutter/lib/l10n/app_fa.arb | 2 +- ui/flutter/lib/l10n/app_fr.arb | 2 +- ui/flutter/lib/l10n/app_hu.arb | 2 +- ui/flutter/lib/l10n/app_id.arb | 2 +- ui/flutter/lib/l10n/app_it.arb | 2 +- ui/flutter/lib/l10n/app_ja.arb | 2 +- ui/flutter/lib/l10n/app_ko.arb | 2 +- ui/flutter/lib/l10n/app_pl.arb | 2 +- ui/flutter/lib/l10n/app_pt.arb | 2 +- ui/flutter/lib/l10n/app_ta.arb | 2 +- ui/flutter/lib/l10n/app_tr.arb | 2 +- ui/flutter/lib/l10n/app_uk.arb | 2 +- ui/flutter/lib/l10n/app_vi.arb | 2 +- ui/flutter/lib/l10n/app_zh_TW.arb | 2 +- 21 files changed, 25 insertions(+), 20 deletions(-) diff --git a/ui/flutter/lib/features/extensions/application/extensions_controller.dart b/ui/flutter/lib/features/extensions/application/extensions_controller.dart index b63b27f4..263b2c4c 100644 --- a/ui/flutter/lib/features/extensions/application/extensions_controller.dart +++ b/ui/flutter/lib/features/extensions/application/extensions_controller.dart @@ -243,6 +243,9 @@ class ExtensionsController extends AsyncNotifier { await _runBusy(extension.identity, () async { await ref.read(gopeedServiceProvider).updateExtension(extension.identity); await loadInstalled(refreshUpdates: false); + // Drop the stale flag immediately; the background re-check confirms it. + final flags = Map.of(_current.updateFlags)..remove(extension.identity); + state = AsyncValue.data(_current.copyWith(updateFlags: flags)); unawaited(checkUpdate()); _bumpStoreInstallCount(extension.identity); _reportInstallSafe(extension.identity); 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 dcb4221e..330b2869 100644 --- a/ui/flutter/lib/features/extensions/presentation/pages/extensions_page.dart +++ b/ui/flutter/lib/features/extensions/presentation/pages/extensions_page.dart @@ -993,7 +993,8 @@ class _ExtensionCard extends ConsumerWidget { style: TextStyle(color: palette.textPrimary, fontSize: 14, fontWeight: FontWeight.w700), ), ), - if (canUpdate && installed != null) + if (canUpdate && installed != null) ...[ + const SizedBox(width: 8), Flexible( child: ExtensionUpdateStatus( key: ValueKey('extension-card-update-status-${item.id}'), @@ -1001,6 +1002,7 @@ class _ExtensionCard extends ConsumerWidget { onTap: busy ? null : () => showExtensionUpdateDialog(context, installed), ), ), + ], ], ), const SizedBox(height: 2), diff --git a/ui/flutter/lib/l10n/app_ar.arb b/ui/flutter/lib/l10n/app_ar.arb index a450ab56..1a1847e4 100644 --- a/ui/flutter/lib/l10n/app_ar.arb +++ b/ui/flutter/lib/l10n/app_ar.arb @@ -99,7 +99,7 @@ "add": "إضافة", "edit": "تعديل", "newVersionTitle": "يتوفر إصدار جديد @version", - "newVersionUpdate": "تحديث الآن", + "newVersionUpdate": "تحديث", "newVersionLater": "لاحقاً", "extensions": "الإضافات", "extensionInstallUrl": "عنوان التثبيت", diff --git a/ui/flutter/lib/l10n/app_az.arb b/ui/flutter/lib/l10n/app_az.arb index d4f297d5..e7428576 100644 --- a/ui/flutter/lib/l10n/app_az.arb +++ b/ui/flutter/lib/l10n/app_az.arb @@ -106,7 +106,7 @@ "add": "Əlavə et", "edit": "Redaktə et", "newVersionTitle": "Yeni versiya @version tapıldı", - "newVersionUpdate": "İndi Yenilə", + "newVersionUpdate": "Yenilə", "newVersionLater": "Sonra", "extensions": "Uzantılar", "extensionInstallUrl": "Quraşdırma URL-i", diff --git a/ui/flutter/lib/l10n/app_ca.arb b/ui/flutter/lib/l10n/app_ca.arb index 86185834..698c0839 100644 --- a/ui/flutter/lib/l10n/app_ca.arb +++ b/ui/flutter/lib/l10n/app_ca.arb @@ -99,7 +99,7 @@ "add": "Afegeix", "edit": "Edita", "newVersionTitle": "Descobriu la nova versió @version", - "newVersionUpdate": "Actualitza ara", + "newVersionUpdate": "Actualitza", "newVersionLater": "Més tard", "extensions": "Extensions", "extensionInstallUrl": "URL d'instal·lació", diff --git a/ui/flutter/lib/l10n/app_de.arb b/ui/flutter/lib/l10n/app_de.arb index c0c97ad8..05c6152f 100644 --- a/ui/flutter/lib/l10n/app_de.arb +++ b/ui/flutter/lib/l10n/app_de.arb @@ -99,7 +99,7 @@ "add": "Hinzugefügt", "edit": "Bearbeiten", "newVersionTitle": "Entdecken Sie die neue Version @version", - "newVersionUpdate": "Jetzt aktualisieren", + "newVersionUpdate": "Aktualisieren", "newVersionLater": "Später", "extensions": "Erweiterungen", "extensionInstallUrl": "Installations-URL", diff --git a/ui/flutter/lib/l10n/app_es.arb b/ui/flutter/lib/l10n/app_es.arb index 3beff20c..f2b990b2 100644 --- a/ui/flutter/lib/l10n/app_es.arb +++ b/ui/flutter/lib/l10n/app_es.arb @@ -99,7 +99,7 @@ "add": "Añadir", "edit": "Editar", "newVersionTitle": "Nueva versión @version disponible", - "newVersionUpdate": "Actualizar Ahora", + "newVersionUpdate": "Actualizar", "newVersionLater": "Más tarde", "extensions": "Extensiones", "extensionInstallUrl": "URL de instalación", diff --git a/ui/flutter/lib/l10n/app_fa.arb b/ui/flutter/lib/l10n/app_fa.arb index 08664277..0f34b487 100644 --- a/ui/flutter/lib/l10n/app_fa.arb +++ b/ui/flutter/lib/l10n/app_fa.arb @@ -99,7 +99,7 @@ "add": "افزودن", "edit": "ویرایش", "newVersionTitle": "@version عنوان: کشف نسخه جدید", - "newVersionUpdate": "بروزرسانی", + "newVersionUpdate": "به‌روزرسانی", "newVersionLater": "بعداً", "extensions": "افزونه‌ها", "extensionInstallUrl": "نشانی نصب", diff --git a/ui/flutter/lib/l10n/app_fr.arb b/ui/flutter/lib/l10n/app_fr.arb index 4c667ae7..2a81e907 100644 --- a/ui/flutter/lib/l10n/app_fr.arb +++ b/ui/flutter/lib/l10n/app_fr.arb @@ -99,7 +99,7 @@ "add": "Ajouter", "edit": "Modifier", "newVersionTitle": "Nouvelle version @version disponible", - "newVersionUpdate": "Mettre à jour maintenant", + "newVersionUpdate": "Mettre à jour", "newVersionLater": "Plus tard", "extensions": "Extensions", "extensionInstallUrl": "URL d'installation", diff --git a/ui/flutter/lib/l10n/app_hu.arb b/ui/flutter/lib/l10n/app_hu.arb index 7330fe85..b6c706dc 100644 --- a/ui/flutter/lib/l10n/app_hu.arb +++ b/ui/flutter/lib/l10n/app_hu.arb @@ -99,7 +99,7 @@ "add": "Hozzáad", "edit": "Szerk", "newVersionTitle": "Új verzió felfedezése @version", - "newVersionUpdate": "Frissítés most", + "newVersionUpdate": "Frissítés", "newVersionLater": "Később", "extensions": "Bővítmények", "extensionInstallUrl": "Telepítési URL", diff --git a/ui/flutter/lib/l10n/app_id.arb b/ui/flutter/lib/l10n/app_id.arb index c28ee0a7..cd22a906 100644 --- a/ui/flutter/lib/l10n/app_id.arb +++ b/ui/flutter/lib/l10n/app_id.arb @@ -99,7 +99,7 @@ "add": "Tambah", "edit": "Sunting", "newVersionTitle": "Temukan versi batu @version", - "newVersionUpdate": "Perbarui Sekarang", + "newVersionUpdate": "Perbarui", "newVersionLater": "Nanti", "extensions": "Ekstensi", "extensionInstallUrl": "URL Instal", diff --git a/ui/flutter/lib/l10n/app_it.arb b/ui/flutter/lib/l10n/app_it.arb index 0ae8fc65..5c97ba09 100644 --- a/ui/flutter/lib/l10n/app_it.arb +++ b/ui/flutter/lib/l10n/app_it.arb @@ -99,7 +99,7 @@ "add": "Aggiungi", "edit": "Modifica", "newVersionTitle": "Scopri la nuova versione @version", - "newVersionUpdate": "Aggiorna ora", + "newVersionUpdate": "Aggiorna", "newVersionLater": "Dopo", "extensions": "Estensioni", "extensionInstallUrl": "Installa URL", diff --git a/ui/flutter/lib/l10n/app_ja.arb b/ui/flutter/lib/l10n/app_ja.arb index d85cb2bc..9aa5b685 100644 --- a/ui/flutter/lib/l10n/app_ja.arb +++ b/ui/flutter/lib/l10n/app_ja.arb @@ -99,7 +99,7 @@ "add": "追加", "edit": "編集", "newVersionTitle": "新しいバージョン @version を発見する", - "newVersionUpdate": "アップデート", + "newVersionUpdate": "更新", "newVersionLater": "後で", "extensions": "拡張機能", "extensionInstallUrl": "インストールURL", diff --git a/ui/flutter/lib/l10n/app_ko.arb b/ui/flutter/lib/l10n/app_ko.arb index 051a42f4..ce226871 100644 --- a/ui/flutter/lib/l10n/app_ko.arb +++ b/ui/flutter/lib/l10n/app_ko.arb @@ -99,7 +99,7 @@ "add": "추가", "edit": "편집", "newVersionTitle": "새 버전 @version 발견", - "newVersionUpdate": "지금 업데이트", + "newVersionUpdate": "업데이트", "newVersionLater": "나중에", "extensions": "확장 프로그램", "extensionInstallUrl": "설치 URL", diff --git a/ui/flutter/lib/l10n/app_pl.arb b/ui/flutter/lib/l10n/app_pl.arb index bf86c584..9c0ee557 100644 --- a/ui/flutter/lib/l10n/app_pl.arb +++ b/ui/flutter/lib/l10n/app_pl.arb @@ -99,7 +99,7 @@ "add": "Dodaj", "edit": "Edycja", "newVersionTitle": "Sprawdź aktualizację @version", - "newVersionUpdate": "Aktualizuj teraz", + "newVersionUpdate": "Aktualizuj", "newVersionLater": "Później", "extensions": "Wtyczki", "extensionInstallUrl": "Instaluj z url", diff --git a/ui/flutter/lib/l10n/app_pt.arb b/ui/flutter/lib/l10n/app_pt.arb index b7f581e4..674a0bd1 100644 --- a/ui/flutter/lib/l10n/app_pt.arb +++ b/ui/flutter/lib/l10n/app_pt.arb @@ -99,7 +99,7 @@ "add": "Adicionar", "edit": "Editar", "newVersionTitle": "Nova Versão Disponível @version", - "newVersionUpdate": "Atualizar Agora", + "newVersionUpdate": "Atualizar", "newVersionLater": "Mais Tarde", "extensions": "Extensões", "extensionInstallUrl": "Link de Instalação", diff --git a/ui/flutter/lib/l10n/app_ta.arb b/ui/flutter/lib/l10n/app_ta.arb index a967ea8c..27816377 100644 --- a/ui/flutter/lib/l10n/app_ta.arb +++ b/ui/flutter/lib/l10n/app_ta.arb @@ -99,7 +99,7 @@ "add": "சேர்", "edit": "திருத்துக", "newVersionTitle": "புதிய பதிப்பைக் கண்டறியவும் @version", - "newVersionUpdate": "இப்பொழுது புதுப்பி", + "newVersionUpdate": "புதுப்பிப்பு", "newVersionLater": "பின்னர்", "extensions": "நீட்டிப்புகள்", "extensionInstallUrl": "நிறுவப்படும் URL", diff --git a/ui/flutter/lib/l10n/app_tr.arb b/ui/flutter/lib/l10n/app_tr.arb index 89be13ea..0ac0f4da 100644 --- a/ui/flutter/lib/l10n/app_tr.arb +++ b/ui/flutter/lib/l10n/app_tr.arb @@ -99,7 +99,7 @@ "add": "Ekle", "edit": "Düzenle", "newVersionTitle": "Yeni sürüm @version keşfedildi", - "newVersionUpdate": "Şimdi Güncelle", + "newVersionUpdate": "Güncelle", "newVersionLater": "Daha Sonra", "extensions": "Eklentiler", "extensionInstallUrl": "Kurulum URL'si", diff --git a/ui/flutter/lib/l10n/app_uk.arb b/ui/flutter/lib/l10n/app_uk.arb index d32d2701..9156a9f7 100644 --- a/ui/flutter/lib/l10n/app_uk.arb +++ b/ui/flutter/lib/l10n/app_uk.arb @@ -99,7 +99,7 @@ "add": "Додати", "edit": "Редагування", "newVersionTitle": "Доступна нова версія @Version", - "newVersionUpdate": "Оновити зараз", + "newVersionUpdate": "Оновити", "newVersionLater": "Пізніше", "extensions": "Розширення", "extensionInstallUrl": "URL для встановлення", diff --git a/ui/flutter/lib/l10n/app_vi.arb b/ui/flutter/lib/l10n/app_vi.arb index db3e0c37..4ec15246 100644 --- a/ui/flutter/lib/l10n/app_vi.arb +++ b/ui/flutter/lib/l10n/app_vi.arb @@ -99,7 +99,7 @@ "add": "Thêm", "edit": "Sửa", "newVersionTitle": "Khám phá phiên bản mới @version", - "newVersionUpdate": "Cập nhật ngay", + "newVersionUpdate": "Cập nhật", "newVersionLater": "Sau", "extensions": "Tiện ích mở rộng", "extensionInstallUrl": "Liên kết cài đặt", diff --git a/ui/flutter/lib/l10n/app_zh_TW.arb b/ui/flutter/lib/l10n/app_zh_TW.arb index 5ad3f7f7..d413d343 100644 --- a/ui/flutter/lib/l10n/app_zh_TW.arb +++ b/ui/flutter/lib/l10n/app_zh_TW.arb @@ -99,7 +99,7 @@ "add": "新增", "edit": "編輯", "newVersionTitle": "發現新版本 @version", - "newVersionUpdate": "立即更新", + "newVersionUpdate": "更新", "newVersionLater": "稍後", "extensions": "擴充功能", "extensionInstallUrl": "安裝 URL", From a480c5bd149fee1013d99ab6124f94e80dff9a80 Mon Sep 17 00:00:00 2001 From: Levi Date: Sat, 19 Sep 2026 22:16:37 +0800 Subject: [PATCH 13/15] fix(ui): align extension update dialog title with app upgrade dialog --- .../presentation/widgets/extension_update_dialog.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/flutter/lib/features/extensions/presentation/widgets/extension_update_dialog.dart b/ui/flutter/lib/features/extensions/presentation/widgets/extension_update_dialog.dart index 107a46bc..605f5445 100644 --- a/ui/flutter/lib/features/extensions/presentation/widgets/extension_update_dialog.dart +++ b/ui/flutter/lib/features/extensions/presentation/widgets/extension_update_dialog.dart @@ -58,7 +58,7 @@ class _ExtensionUpdateDialogState extends ConsumerState<_ExtensionUpdateDialog> canPop: !_updating, child: AlertDialog( key: const ValueKey('extension-update-dialog'), - title: Text(context.l10n.extensionCanUpdate), + title: Text(context.l10n.newVersionAvailable), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, From 68402c85d3941a73799649b7980c39d4ca3bf7c1 Mon Sep 17 00:00:00 2001 From: Levi Date: Sun, 20 Sep 2026 08:02:22 +0800 Subject: [PATCH 14/15] revert(ui): keep extension update check silent on failure A failed check simply means no update chip; the failure toast and its state field are removed. --- .../application/extensions_controller.dart | 16 ++-------------- .../presentation/pages/extensions_page.dart | 6 ------ ui/flutter/lib/l10n/app_en.arb | 1 - ui/flutter/lib/l10n/app_zh.arb | 1 - 4 files changed, 2 insertions(+), 22 deletions(-) diff --git a/ui/flutter/lib/features/extensions/application/extensions_controller.dart b/ui/flutter/lib/features/extensions/application/extensions_controller.dart index 263b2c4c..110af4ce 100644 --- a/ui/flutter/lib/features/extensions/application/extensions_controller.dart +++ b/ui/flutter/lib/features/extensions/application/extensions_controller.dart @@ -49,7 +49,6 @@ class ExtensionsState { this.loadingMoreStore = false, this.busyExtensionIds = const {}, this.devMode = false, - this.updateCheckFailed = false, }); final List installedExtensions; @@ -64,7 +63,6 @@ class ExtensionsState { final bool loadingMoreStore; final Set busyExtensionIds; final bool devMode; - final bool updateCheckFailed; UnmodifiableMapView get installedMap => UnmodifiableMapView({for (final ext in installedExtensions) ext.identity: ext}); @@ -102,7 +100,6 @@ class ExtensionsState { bool? loadingMoreStore, Set? busyExtensionIds, bool? devMode, - bool? updateCheckFailed, }) { return ExtensionsState( installedExtensions: installedExtensions ?? this.installedExtensions, @@ -117,7 +114,6 @@ class ExtensionsState { loadingMoreStore: loadingMoreStore ?? this.loadingMoreStore, busyExtensionIds: busyExtensionIds ?? this.busyExtensionIds, devMode: devMode ?? this.devMode, - updateCheckFailed: updateCheckFailed ?? this.updateCheckFailed, ); } } @@ -263,23 +259,15 @@ class ExtensionsController extends AsyncNotifier { Future checkUpdate() async { final flags = {}; - var failed = false; for (final ext in _current.installedExtensions) { try { final resp = await ref.read(gopeedServiceProvider).upgradeCheckExtension(ext.identity); if (resp.newVersion.isNotEmpty) { flags[ext.identity] = resp.newVersion; } - } catch (_) { - failed = true; - } + } catch (_) {} } - state = AsyncValue.data( - _current.copyWith( - updateFlags: flags, - updateCheckFailed: failed && _current.installedExtensions.isNotEmpty, - ), - ); + state = AsyncValue.data(_current.copyWith(updateFlags: flags)); } void tryOpenDevMode() { 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 330b2869..379056af 100644 --- a/ui/flutter/lib/features/extensions/presentation/pages/extensions_page.dart +++ b/ui/flutter/lib/features/extensions/presentation/pages/extensions_page.dart @@ -107,12 +107,6 @@ class _ExtensionsPageState extends ConsumerState { }); } final stateAsync = ref.watch(extensionsControllerProvider); - ref.listen(extensionsControllerProvider, (previous, next) { - final failed = next.value?.updateCheckFailed ?? false; - if (failed && previous?.value?.updateCheckFailed != true) { - showAppToast(context, context.l10n.extensionUpdateCheckFailed); - } - }); final body = Stack( children: [ ColoredBox( diff --git a/ui/flutter/lib/l10n/app_en.arb b/ui/flutter/lib/l10n/app_en.arb index 0cb61f11..5ad3aacc 100644 --- a/ui/flutter/lib/l10n/app_en.arb +++ b/ui/flutter/lib/l10n/app_en.arb @@ -120,7 +120,6 @@ } }, "extensionUpdateSuccess": "Updated successfully", - "extensionUpdateCheckFailed": "Failed to check for updates. Please check your network", "extensionDelete": "Delete Extension", "extensionAlreadyLatest": "It's already the latest version", "extensionFind": "Find Extensions", diff --git a/ui/flutter/lib/l10n/app_zh.arb b/ui/flutter/lib/l10n/app_zh.arb index 6d4e91fc..c67677e0 100644 --- a/ui/flutter/lib/l10n/app_zh.arb +++ b/ui/flutter/lib/l10n/app_zh.arb @@ -106,7 +106,6 @@ "extensionInstallSuccess": "安装成功", "extensionUpdateConfirm": "确定将“{name}”更新到最新版本吗?", "extensionUpdateSuccess": "更新成功", - "extensionUpdateCheckFailed": "检查更新失败,请检查网络后重试", "extensionDelete": "删除扩展", "extensionAlreadyLatest": "已经是最新版本", "extensionFind": "获取扩展", From e956bd1ce583b813b7deed263fa193e83044af64 Mon Sep 17 00:00:00 2001 From: Levi Date: Sun, 20 Sep 2026 08:05:00 +0800 Subject: [PATCH 15/15] style(ui): tighten card update chip spacing to 4px --- .../features/extensions/presentation/pages/extensions_page.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 379056af..9f5d3317 100644 --- a/ui/flutter/lib/features/extensions/presentation/pages/extensions_page.dart +++ b/ui/flutter/lib/features/extensions/presentation/pages/extensions_page.dart @@ -988,7 +988,7 @@ class _ExtensionCard extends ConsumerWidget { ), ), if (canUpdate && installed != null) ...[ - const SizedBox(width: 8), + const SizedBox(width: 4), Flexible( child: ExtensionUpdateStatus( key: ValueKey('extension-card-update-status-${item.id}'),