diff --git a/ui/flutter/lib/features/extensions/application/extensions_controller.dart b/ui/flutter/lib/features/extensions/application/extensions_controller.dart index 74e439d3..110af4ce 100644 --- a/ui/flutter/lib/features/extensions/application/extensions_controller.dart +++ b/ui/flutter/lib/features/extensions/application/extensions_controller.dart @@ -185,12 +185,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 { @@ -240,6 +239,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); @@ -354,24 +356,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/extension_details_page.dart b/ui/flutter/lib/features/extensions/presentation/pages/extension_details_page.dart index e719fdfd..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,8 @@ 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 { const ExtensionDetailsPage({super.key, required this.extensionId, this.initialItem}); @@ -24,6 +26,15 @@ 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( + 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( child: stateAsync.isLoading @@ -33,4 +44,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 f2bd73b9..9f5d3317 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; @@ -485,25 +486,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); } } @@ -994,11 +977,27 @@ class _ExtensionCard extends ConsumerWidget { 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 && installed != null) ...[ + const SizedBox(width: 4), + Flexible( + child: ExtensionUpdateStatus( + key: ValueKey('extension-card-update-status-${item.id}'), + label: context.l10n.extensionCanUpdate, + onTap: busy ? null : () => showExtensionUpdateDialog(context, installed), + ), + ), + ], + ], ), const SizedBox(height: 2), Text( @@ -1067,13 +1066,6 @@ class _ExtensionCard extends ConsumerWidget { key: ValueKey('extension-card-actions-${item.id}'), mainAxisSize: MainAxisSize.min, children: [ - if (canUpdate && installed != null) - shad.GhostButton( - density: shad.ButtonDensity.icon, - key: ValueKey('update-extension-${installed.identity}'), - onPressed: busy ? null : () => showExtensionUpdateDialog(context, installed), - child: const Icon(Icons.refresh), - ), if (installed == null && item.store != null) shad.GhostButton( key: ValueKey('install-store-extension-${item.store!.id}'), @@ -1127,21 +1119,17 @@ class _ExtensionCard extends ConsumerWidget { ], ), ); - if (item.store == null) return card; - 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_detail_view.dart b/ui/flutter/lib/features/extensions/presentation/widgets/extension_detail_view.dart index 87837560..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 @@ -21,20 +21,33 @@ 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); + 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: updateChip, child: item == null ? const SizedBox.shrink() : ExtensionDetailView(item: item!), ); } @@ -104,8 +117,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), ], ), ], @@ -130,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.refresh, size: 16), - onPressed: busy ? null : () => showExtensionUpdateDialog(context, installed), - child: Text(context.l10n.newVersionUpdate), - ), if (installed != null && !canUpdate) AppLoadingButton( key: const ValueKey('extension-details-installed'), @@ -276,35 +278,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_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, 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..3d9e7812 --- /dev/null +++ b/ui/flutter/lib/features/extensions/presentation/widgets/extension_update_status.dart @@ -0,0 +1,58 @@ +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 = 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( + cursor: SystemMouseCursors.click, + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: onTap, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 2, vertical: 4), + child: content, + ), + ), + ); + } +} 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_en.arb b/ui/flutter/lib/l10n/app_en.arb index a241bbfd..5ad3aacc 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", @@ -137,7 +137,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/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.arb b/ui/flutter/lib/l10n/app_zh.arb index 0269904d..c67677e0 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/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", 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..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 @@ -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,25 @@ 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), + Flexible(child: 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..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 @@ -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,22 @@ 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), + Flexible(child: titleTrailing!), + ], + ], ), ), const SizedBox(width: 44), diff --git a/ui/flutter/test/widget_test.dart b/ui/flutter/test/widget_test.dart index 4be35271..191a020f 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')); @@ -2327,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();