From 0d52b840a2636d5f0d371deee244cdb67a571e4a Mon Sep 17 00:00:00 2001 From: Levi Date: Sat, 12 Sep 2026 18:09:36 +0800 Subject: [PATCH] feat(ui): emphasize task creation and add empty-list actions (#1505) --- .../home/presentation/pages/home_page.dart | 31 ++++++++++-- .../presentation/widgets/tasks_top_bar.dart | 25 +--------- .../widgets/task_create_button.dart | 42 ++++++++++++++++ .../widgets/task_empty_create_hint.dart | 49 +++++++++++++++++++ .../widgets/task_empty_state.dart | 48 +++++++++++------- ui/flutter/lib/l10n/app_ar.arb | 3 +- ui/flutter/lib/l10n/app_az.arb | 3 +- ui/flutter/lib/l10n/app_ca.arb | 3 +- ui/flutter/lib/l10n/app_de.arb | 3 +- ui/flutter/lib/l10n/app_en.arb | 11 ++++- ui/flutter/lib/l10n/app_es.arb | 3 +- ui/flutter/lib/l10n/app_fa.arb | 3 +- ui/flutter/lib/l10n/app_fr.arb | 3 +- ui/flutter/lib/l10n/app_hu.arb | 3 +- ui/flutter/lib/l10n/app_id.arb | 3 +- ui/flutter/lib/l10n/app_it.arb | 3 +- ui/flutter/lib/l10n/app_ja.arb | 3 +- ui/flutter/lib/l10n/app_ko.arb | 3 +- ui/flutter/lib/l10n/app_pl.arb | 3 +- ui/flutter/lib/l10n/app_pt.arb | 3 +- ui/flutter/lib/l10n/app_ru.arb | 3 +- ui/flutter/lib/l10n/app_ta.arb | 3 +- ui/flutter/lib/l10n/app_tr.arb | 3 +- ui/flutter/lib/l10n/app_uk.arb | 3 +- ui/flutter/lib/l10n/app_vi.arb | 3 +- ui/flutter/lib/l10n/app_zh.arb | 3 +- ui/flutter/lib/l10n/app_zh_TW.arb | 3 +- ui/flutter/test/widget_test.dart | 15 ++++++ 28 files changed, 216 insertions(+), 68 deletions(-) create mode 100644 ui/flutter/lib/features/tasks/presentation/widgets/task_create_button.dart create mode 100644 ui/flutter/lib/features/tasks/presentation/widgets/task_empty_create_hint.dart diff --git a/ui/flutter/lib/features/home/presentation/pages/home_page.dart b/ui/flutter/lib/features/home/presentation/pages/home_page.dart index f23af3ee..a4b3b2aa 100644 --- a/ui/flutter/lib/features/home/presentation/pages/home_page.dart +++ b/ui/flutter/lib/features/home/presentation/pages/home_page.dart @@ -30,6 +30,7 @@ import '../../../tasks/presentation/widgets/task_drawer.dart'; import '../../../tasks/presentation/widgets/task_context_menu.dart'; import '../../../tasks/presentation/widgets/task_delete_dialog.dart'; import '../../../tasks/presentation/widgets/task_empty_state.dart'; +import '../../../tasks/presentation/widgets/task_create_button.dart'; import '../../../tasks/presentation/widgets/task_file_browser_dialog.dart'; import '../../../tasks/presentation/widgets/task_update_url_dialog.dart'; import '../widgets/primary_rail.dart'; @@ -172,7 +173,10 @@ class _HomePageState extends ConsumerState { loading: () => const Center(child: shad.CircularProgressIndicator()), error: (error, _) => const TaskEmptyState(), data: (_) => filteredTasks.isEmpty - ? TaskEmptyState(message: emptyStateMessage) + ? TaskEmptyState( + message: emptyStateMessage, + onCreateTask: _searchController.text.trim().isEmpty ? _handleAddTask : null, + ) : ListView.separated( padding: const EdgeInsets.only(bottom: 16), itemCount: filteredTasks.length, @@ -260,6 +264,7 @@ class _HomePageState extends ConsumerState { data: (_) => _MobileTasksView( tasks: filteredTasks, emptyStateMessage: emptyStateMessage, + onCreateTask: _searchController.text.trim().isEmpty ? _handleAddTask : null, batchMode: _batchMode, batchSelection: _batchSelection, onToggleSelectAll: _toggleSelectAll, @@ -685,6 +690,7 @@ class _MobileTasksView extends StatelessWidget { required this.contextActionsForTask, required this.pendingUpdateTaskId, this.emptyStateMessage, + this.onCreateTask, this.loading = false, }); @@ -705,6 +711,7 @@ class _MobileTasksView extends StatelessWidget { final TaskContextActions Function(TaskRecord task, bool selected, bool allSelected) contextActionsForTask; final String? pendingUpdateTaskId; final String? emptyStateMessage; + final VoidCallback? onCreateTask; final bool loading; @override @@ -745,7 +752,7 @@ class _MobileTasksView extends StatelessWidget { child: loading ? const Center(child: shad.CircularProgressIndicator()) : tasks.isEmpty - ? TaskEmptyState(message: emptyStateMessage) + ? TaskEmptyState(message: emptyStateMessage, onCreateTask: onCreateTask) : ListView.separated( padding: const EdgeInsets.fromLTRB(16, 8, 16, 12), itemCount: tasks.length, @@ -814,9 +821,19 @@ class _MobileTasksHeader extends StatelessWidget { style: TextStyle(color: palette.textPrimary, fontSize: 22, fontWeight: FontWeight.w800), ), ), - _MobileHeaderIconButton(icon: Icons.add_rounded, onPressed: onAddTask), + const SizedBox(width: AppDesignTokens.space8), + ConstrainedBox( + constraints: BoxConstraints(maxWidth: MediaQuery.sizeOf(context).width * 0.5), + child: TaskCreateButton( + key: const ValueKey('tasks-mobile-create-button'), + onPressed: onAddTask, + minHeight: 34, + compact: true, + ), + ), const SizedBox(width: 8), _MobileHeaderIconButton( + key: const ValueKey('tasks-mobile-batch-button'), icon: Icons.checklist_rtl_outlined, active: batchMode, badge: batchMode && selectedCount > 0 ? selectedCount.toString() : null, @@ -903,7 +920,13 @@ class _MobileBatchToolbar extends StatelessWidget { } class _MobileHeaderIconButton extends StatelessWidget { - const _MobileHeaderIconButton({required this.icon, required this.onPressed, this.active = false, this.badge}); + const _MobileHeaderIconButton({ + super.key, + required this.icon, + required this.onPressed, + this.active = false, + this.badge, + }); final IconData icon; final VoidCallback onPressed; diff --git a/ui/flutter/lib/features/home/presentation/widgets/tasks_top_bar.dart b/ui/flutter/lib/features/home/presentation/widgets/tasks_top_bar.dart index 595d78b4..5805d4bf 100644 --- a/ui/flutter/lib/features/home/presentation/widgets/tasks_top_bar.dart +++ b/ui/flutter/lib/features/home/presentation/widgets/tasks_top_bar.dart @@ -4,7 +4,7 @@ import 'package:shadcn_flutter/shadcn_flutter.dart' as shad; import '../../../../shared/theme/app_design_tokens.dart'; import '../../../../shared/theme/app_palette.dart'; -import '../../../../shared/widgets/app_primary_button.dart'; +import '../../../tasks/presentation/widgets/task_create_button.dart'; import '../../../../shared/widgets/app_text_field.dart'; import '../../../../l10n/l10n.dart'; @@ -62,7 +62,7 @@ class TasksTopBar extends StatelessWidget { ConstrainedBox( key: const ValueKey('tasks-create-button-container'), constraints: const BoxConstraints(minWidth: 120, maxWidth: 190), - child: _AddTaskButton(onPressed: onAddTask), + child: TaskCreateButton(onPressed: onAddTask), ), const SizedBox(width: 16), Row( @@ -131,27 +131,6 @@ class _SearchField extends StatelessWidget { } } -class _AddTaskButton extends StatelessWidget { - const _AddTaskButton({required this.onPressed}); - - final VoidCallback onPressed; - - @override - Widget build(BuildContext context) { - return AppPrimaryButton( - onPressed: onPressed, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.add), - const SizedBox(width: AppDesignTokens.space8), - Flexible(child: Text(context.l10n.create, maxLines: 1, overflow: TextOverflow.ellipsis)), - ], - ), - ); - } -} - class _ActionIconButton extends StatelessWidget { const _ActionIconButton({required this.icon, required this.onPressed}); diff --git a/ui/flutter/lib/features/tasks/presentation/widgets/task_create_button.dart b/ui/flutter/lib/features/tasks/presentation/widgets/task_create_button.dart new file mode 100644 index 00000000..465b7c2c --- /dev/null +++ b/ui/flutter/lib/features/tasks/presentation/widgets/task_create_button.dart @@ -0,0 +1,42 @@ +import 'package:shadcn_flutter/shadcn_flutter.dart' as shad; +import 'package:flutter/material.dart' show Icons; +import 'package:flutter/widgets.dart'; + +import '../../../../l10n/l10n.dart'; +import '../../../../shared/theme/app_design_tokens.dart'; +import '../../../../shared/widgets/app_primary_button.dart'; + +/// Shared creation action for task headers and empty-list guidance. +class TaskCreateButton extends StatelessWidget { + const TaskCreateButton({super.key, required this.onPressed, this.minHeight = 0, this.compact = false}); + + final VoidCallback onPressed; + final double minHeight; + final bool compact; + + @override + Widget build(BuildContext context) { + return ConstrainedBox( + constraints: BoxConstraints(minHeight: minHeight), + child: AppPrimaryButton( + onPressed: onPressed, + size: compact ? shad.ButtonSize.small : shad.ButtonSize.normal, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.add, size: compact ? 16 : null), + SizedBox(width: compact ? AppDesignTokens.space4 : AppDesignTokens.space8), + Flexible( + child: Text( + context.l10n.create, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: compact ? const TextStyle(fontSize: 13) : null, + ), + ), + ], + ), + ), + ); + } +} diff --git a/ui/flutter/lib/features/tasks/presentation/widgets/task_empty_create_hint.dart b/ui/flutter/lib/features/tasks/presentation/widgets/task_empty_create_hint.dart new file mode 100644 index 00000000..0ddd8189 --- /dev/null +++ b/ui/flutter/lib/features/tasks/presentation/widgets/task_empty_create_hint.dart @@ -0,0 +1,49 @@ +import 'package:shadcn_flutter/shadcn_flutter.dart'; + +import '../../../../l10n/l10n.dart'; +import '../../../../shared/theme/app_palette.dart'; +import '../../../../shared/widgets/app_tooltip.dart'; + +/// Keeps the action inside the localized sentence, including RTL word order. +class TaskEmptyCreateHint extends StatelessWidget { + const TaskEmptyCreateHint({super.key, required this.onCreateTask}); + + final VoidCallback onCreateTask; + + @override + Widget build(BuildContext context) { + const actionMarker = '\uFFFC'; + final parts = context.l10n.emptyTaskCreateHint(actionMarker).split(actionMarker); + return Text.rich( + TextSpan( + children: [ + TextSpan(text: parts.first.trimRight()), + WidgetSpan( + alignment: PlaceholderAlignment.middle, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: AppTooltip( + message: context.l10n.create, + child: Semantics( + label: context.l10n.create, + child: SizedBox.square( + dimension: 30, + child: IconButton.outline( + key: const ValueKey('tasks-empty-create-button'), + density: ButtonDensity.compact, + onPressed: onCreateTask, + icon: const Icon(Icons.add, size: 16), + ), + ), + ), + ), + ), + ), + TextSpan(text: parts.last.trimLeft()), + ], + ), + textAlign: TextAlign.center, + style: TextStyle(color: AppPalette.of(context).textMuted, fontSize: 13), + ); + } +} diff --git a/ui/flutter/lib/features/tasks/presentation/widgets/task_empty_state.dart b/ui/flutter/lib/features/tasks/presentation/widgets/task_empty_state.dart index 73b7f816..be086dd6 100644 --- a/ui/flutter/lib/features/tasks/presentation/widgets/task_empty_state.dart +++ b/ui/flutter/lib/features/tasks/presentation/widgets/task_empty_state.dart @@ -1,35 +1,45 @@ import 'package:flutter/widgets.dart'; import '../../../../shared/theme/app_palette.dart'; +import '../../../../shared/theme/app_design_tokens.dart'; +import 'task_empty_create_hint.dart'; import '../../../../l10n/l10n.dart'; class TaskEmptyState extends StatelessWidget { - const TaskEmptyState({super.key, this.message}); + const TaskEmptyState({super.key, this.message, this.onCreateTask}); final String? message; + final VoidCallback? onCreateTask; @override Widget build(BuildContext context) { final palette = AppPalette.of(context); return Center( - child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 360), - child: Padding( - padding: const EdgeInsets.all(24), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - CustomPaint( - key: const ValueKey('task-empty-illustration'), - size: const Size(148, 108), - painter: _TaskEmptyIllustrationPainter(palette: palette), - ), - const SizedBox(height: 18), - Text( - message ?? context.l10n.emptyTaskList, - style: TextStyle(color: palette.textPrimary, fontSize: 17, fontWeight: FontWeight.w700), - ), - ], + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 360), + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + CustomPaint( + key: const ValueKey('task-empty-illustration'), + size: const Size(148, 108), + painter: _TaskEmptyIllustrationPainter(palette: palette), + ), + const SizedBox(height: 18), + Text( + message ?? context.l10n.emptyTaskList, + textAlign: TextAlign.center, + style: TextStyle(color: palette.textPrimary, fontSize: 17, fontWeight: FontWeight.w700), + ), + if (onCreateTask != null) ...[ + const SizedBox(height: AppDesignTokens.space24), + TaskEmptyCreateHint(onCreateTask: onCreateTask!), + ], + ], + ), ), ), ), diff --git a/ui/flutter/lib/l10n/app_ar.arb b/ui/flutter/lib/l10n/app_ar.arb index 3b7c407d..174cb2d1 100644 --- a/ui/flutter/lib/l10n/app_ar.arb +++ b/ui/flutter/lib/l10n/app_ar.arb @@ -442,5 +442,6 @@ "webApiConfigReadOnlyHelp": "لا يدعم إصدار الويب تعديل إعدادات API مباشرة. عدّل خيارات بدء تشغيل الخادم أو ملف الإعدادات، ثم أعد تشغيل الخدمة لتطبيق التغييرات.", "webMcpApiTokenHint": "إذا قمت بتكوين رمز API، فيجب تمرير معلومات المصادقة عند ربط الوكيل", "useMcpApiTokenTemplate": "التبديل إلى إعدادات المصادقة", - "mcpRequiresTcp": "تتوفر إرشادات ربط الوكيل عندما يكون بروتوكول API هو TCP." + "mcpRequiresTcp": "تتوفر إرشادات ربط الوكيل عندما يكون بروتوكول API هو TCP.", + "emptyTaskCreateHint": "اضغط على {action} لبدء التنزيل" } diff --git a/ui/flutter/lib/l10n/app_az.arb b/ui/flutter/lib/l10n/app_az.arb index 4fccb3de..4e9ba6ec 100644 --- a/ui/flutter/lib/l10n/app_az.arb +++ b/ui/flutter/lib/l10n/app_az.arb @@ -626,5 +626,6 @@ "webMcpApiTokenHint": "Əgər API açarı konfiqurasiya etmisinizsə, agenti qoşarkən doğrulama məlumatı tələb olunur", "useMcpApiTokenTemplate": "Doğrulamalı konfiqurasiyaya keç", "webApiConfigReadOnlyHelp": "Veb versiyası API parametrlərinin onlayn dəyişdirilməsini dəstəkləmir. Server başlanğıc seçimlərini və ya konfiqurasiya faylını yeniləyin, sonra dəyişikliklərin qüvvəyə minməsi üçün xidməti yenidən başladın.", - "mcpRequiresTcp": "Agent əlaqə təlimatları API protokolu TCP olduqda mövcuddur." + "mcpRequiresTcp": "Agent əlaqə təlimatları API protokolu TCP olduqda mövcuddur.", + "emptyTaskCreateHint": "Endirməyə başlamaq üçün {action} düyməsinə klikləyin" } diff --git a/ui/flutter/lib/l10n/app_ca.arb b/ui/flutter/lib/l10n/app_ca.arb index bb030cf5..dc3f81f7 100644 --- a/ui/flutter/lib/l10n/app_ca.arb +++ b/ui/flutter/lib/l10n/app_ca.arb @@ -442,5 +442,6 @@ "webApiConfigReadOnlyHelp": "La versió web no permet modificar la configuració de l’API en línia. Actualitza les opcions d’inici del servidor o el fitxer de configuració i reinicia el servei perquè els canvis tinguin efecte.", "webMcpApiTokenHint": "Si heu configurat un token d’API, cal proporcionar la informació d’autenticació en connectar l’agent", "useMcpApiTokenTemplate": "Canvia a la configuració autenticada", - "mcpRequiresTcp": "Les instruccions de connexió d’agents estan disponibles quan el protocol de l’API és TCP." + "mcpRequiresTcp": "Les instruccions de connexió d’agents estan disponibles quan el protocol de l’API és TCP.", + "emptyTaskCreateHint": "Fes clic a {action} per començar a baixar" } diff --git a/ui/flutter/lib/l10n/app_de.arb b/ui/flutter/lib/l10n/app_de.arb index 64d7683c..a27d800c 100644 --- a/ui/flutter/lib/l10n/app_de.arb +++ b/ui/flutter/lib/l10n/app_de.arb @@ -442,5 +442,6 @@ "webApiConfigReadOnlyHelp": "In der Web-Version können API-Einstellungen nicht online geändert werden. Ändere die Server-Startoptionen oder die Konfigurationsdatei und starte den Dienst neu, damit die Änderungen wirksam werden.", "webMcpApiTokenHint": "Wenn du ein API-Token konfiguriert hast, müssen beim Verbinden des Agents Authentifizierungsinformationen übermittelt werden", "useMcpApiTokenTemplate": "Zur authentifizierten Konfiguration wechseln", - "mcpRequiresTcp": "Anweisungen zur Agent-Verbindung sind verfügbar, wenn das API-Protokoll TCP ist." + "mcpRequiresTcp": "Anweisungen zur Agent-Verbindung sind verfügbar, wenn das API-Protokoll TCP ist.", + "emptyTaskCreateHint": "Klicke auf {action}, um den Download zu starten" } diff --git a/ui/flutter/lib/l10n/app_en.arb b/ui/flutter/lib/l10n/app_en.arb index 5aab7de5..d1897ce8 100644 --- a/ui/flutter/lib/l10n/app_en.arb +++ b/ui/flutter/lib/l10n/app_en.arb @@ -626,5 +626,14 @@ "webMcpApiTokenHint": "If you configured an API token, authentication information is required when connecting the agent", "useMcpApiTokenTemplate": "Switch to authenticated configuration", "webApiConfigReadOnlyHelp": "The web version cannot modify API settings online. Update the server startup options or configuration file, then restart the service for changes to take effect.", - "mcpRequiresTcp": "Agent connection instructions are available when the API protocol is TCP." + "mcpRequiresTcp": "Agent connection instructions are available when the API protocol is TCP.", + "emptyTaskCreateHint": "Click {action} to start downloading", + "@emptyTaskCreateHint": { + "description": "Empty-list guidance; action is replaced with an inline create button.", + "placeholders": { + "action": { + "type": "String" + } + } + } } diff --git a/ui/flutter/lib/l10n/app_es.arb b/ui/flutter/lib/l10n/app_es.arb index 89ed2ad0..34d2343c 100644 --- a/ui/flutter/lib/l10n/app_es.arb +++ b/ui/flutter/lib/l10n/app_es.arb @@ -442,5 +442,6 @@ "webApiConfigReadOnlyHelp": "La versión web no permite modificar la configuración de la API en línea. Actualiza las opciones de inicio del servidor o el archivo de configuración y reinicia el servicio para aplicar los cambios.", "webMcpApiTokenHint": "Si has configurado un token de API, debes proporcionar la información de autenticación al conectar el agente", "useMcpApiTokenTemplate": "Cambiar a la configuración autenticada", - "mcpRequiresTcp": "Las instrucciones de conexión de agentes están disponibles cuando el protocolo de la API es TCP." + "mcpRequiresTcp": "Las instrucciones de conexión de agentes están disponibles cuando el protocolo de la API es TCP.", + "emptyTaskCreateHint": "Haz clic en {action} para empezar a descargar" } diff --git a/ui/flutter/lib/l10n/app_fa.arb b/ui/flutter/lib/l10n/app_fa.arb index 6486d00d..bafb1bb3 100644 --- a/ui/flutter/lib/l10n/app_fa.arb +++ b/ui/flutter/lib/l10n/app_fa.arb @@ -442,5 +442,6 @@ "webApiConfigReadOnlyHelp": "نسخه وب از ویرایش آنلاین تنظیمات API پشتیبانی نمی‌کند. گزینه‌های راه‌اندازی سرور یا فایل پیکربندی را تغییر دهید و برای اعمال تغییرات سرویس را دوباره راه‌اندازی کنید.", "webMcpApiTokenHint": "اگر توکن API را پیکربندی کرده‌اید، هنگام اتصال عامل باید اطلاعات احراز هویت را ارسال کنید", "useMcpApiTokenTemplate": "تغییر به پیکربندی احراز هویت", - "mcpRequiresTcp": "راهنمای اتصال عامل زمانی در دسترس است که پروتکل API روی TCP باشد." + "mcpRequiresTcp": "راهنمای اتصال عامل زمانی در دسترس است که پروتکل API روی TCP باشد.", + "emptyTaskCreateHint": "برای شروع دانلود روی {action} کلیک کنید" } diff --git a/ui/flutter/lib/l10n/app_fr.arb b/ui/flutter/lib/l10n/app_fr.arb index cd764419..e708aca4 100644 --- a/ui/flutter/lib/l10n/app_fr.arb +++ b/ui/flutter/lib/l10n/app_fr.arb @@ -442,5 +442,6 @@ "webApiConfigReadOnlyHelp": "La version web ne permet pas de modifier les paramètres de l’API en ligne. Modifiez les options de démarrage du serveur ou le fichier de configuration, puis redémarrez le service pour appliquer les changements.", "webMcpApiTokenHint": "Si vous avez configuré un jeton API, les informations d’authentification doivent être transmises lors de la connexion de l’agent", "useMcpApiTokenTemplate": "Passer à la configuration authentifiée", - "mcpRequiresTcp": "Les instructions de connexion des agents sont disponibles lorsque le protocole de l’API est TCP." + "mcpRequiresTcp": "Les instructions de connexion des agents sont disponibles lorsque le protocole de l’API est TCP.", + "emptyTaskCreateHint": "Cliquez sur {action} pour commencer le téléchargement" } diff --git a/ui/flutter/lib/l10n/app_hu.arb b/ui/flutter/lib/l10n/app_hu.arb index 034eff81..2823ae6d 100644 --- a/ui/flutter/lib/l10n/app_hu.arb +++ b/ui/flutter/lib/l10n/app_hu.arb @@ -442,5 +442,6 @@ "webApiConfigReadOnlyHelp": "A webes verzióban az API-beállítások nem módosíthatók online. Módosítsd a kiszolgáló indítási beállításait vagy a konfigurációs fájlt, majd indítsd újra a szolgáltatást.", "webMcpApiTokenHint": "Ha API-tokent állítottál be, az ügynök csatlakoztatásakor meg kell adni a hitelesítési adatokat", "useMcpApiTokenTemplate": "Váltás hitelesített konfigurációra", - "mcpRequiresTcp": "Az ügynökcsatlakoztatási útmutató akkor érhető el, ha az API protokollja TCP." + "mcpRequiresTcp": "Az ügynökcsatlakoztatási útmutató akkor érhető el, ha az API protokollja TCP.", + "emptyTaskCreateHint": "Kattints a {action} gombra a letöltés indításához" } diff --git a/ui/flutter/lib/l10n/app_id.arb b/ui/flutter/lib/l10n/app_id.arb index 40acc714..43a57bdc 100644 --- a/ui/flutter/lib/l10n/app_id.arb +++ b/ui/flutter/lib/l10n/app_id.arb @@ -442,5 +442,6 @@ "webApiConfigReadOnlyHelp": "Versi web tidak dapat mengubah pengaturan API secara online. Perbarui opsi startup server atau file konfigurasi, lalu mulai ulang layanan agar perubahan diterapkan.", "webMcpApiTokenHint": "Jika Anda telah mengatur token API, informasi autentikasi harus disertakan saat menghubungkan agen", "useMcpApiTokenTemplate": "Beralih ke konfigurasi terautentikasi", - "mcpRequiresTcp": "Petunjuk koneksi agen tersedia saat protokol API menggunakan TCP." + "mcpRequiresTcp": "Petunjuk koneksi agen tersedia saat protokol API menggunakan TCP.", + "emptyTaskCreateHint": "Klik {action} untuk mulai mengunduh" } diff --git a/ui/flutter/lib/l10n/app_it.arb b/ui/flutter/lib/l10n/app_it.arb index aba9620f..8d0f62b9 100644 --- a/ui/flutter/lib/l10n/app_it.arb +++ b/ui/flutter/lib/l10n/app_it.arb @@ -442,5 +442,6 @@ "webApiConfigReadOnlyHelp": "La versione web non consente di modificare online le impostazioni API. Aggiorna le opzioni di avvio del server o il file di configurazione, quindi riavvia il servizio per applicare le modifiche.", "webMcpApiTokenHint": "Se hai configurato un token API, devi fornire le informazioni di autenticazione quando colleghi l’agente", "useMcpApiTokenTemplate": "Passa alla configurazione autenticata", - "mcpRequiresTcp": "Le istruzioni per connettere gli agenti sono disponibili quando il protocollo API è TCP." + "mcpRequiresTcp": "Le istruzioni per connettere gli agenti sono disponibili quando il protocollo API è TCP.", + "emptyTaskCreateHint": "Fai clic su {action} per iniziare il download" } diff --git a/ui/flutter/lib/l10n/app_ja.arb b/ui/flutter/lib/l10n/app_ja.arb index 8f15b2d1..097488ef 100644 --- a/ui/flutter/lib/l10n/app_ja.arb +++ b/ui/flutter/lib/l10n/app_ja.arb @@ -442,5 +442,6 @@ "webApiConfigReadOnlyHelp": "Web 版では API 設定をオンラインで変更できません。サーバーの起動オプションまたは設定ファイルを変更し、サービスを再起動して反映してください。", "webMcpApiTokenHint": "API トークンを設定している場合、エージェント接続時に認証情報を渡す必要があります", "useMcpApiTokenTemplate": "認証付き設定に切り替える", - "mcpRequiresTcp": "API プロトコルが TCP の場合にエージェント接続手順を利用できます。" + "mcpRequiresTcp": "API プロトコルが TCP の場合にエージェント接続手順を利用できます。", + "emptyTaskCreateHint": "{action}をクリックしてダウンロードを開始" } diff --git a/ui/flutter/lib/l10n/app_ko.arb b/ui/flutter/lib/l10n/app_ko.arb index 23c42a68..b7c66da1 100644 --- a/ui/flutter/lib/l10n/app_ko.arb +++ b/ui/flutter/lib/l10n/app_ko.arb @@ -442,5 +442,6 @@ "webApiConfigReadOnlyHelp": "웹 버전에서는 API 설정을 온라인으로 수정할 수 없습니다. 서버 시작 옵션이나 구성 파일을 변경한 후 서비스를 다시 시작해야 적용됩니다.", "webMcpApiTokenHint": "API 토큰을 설정한 경우 에이전트 연결 시 인증 정보를 전달해야 합니다", "useMcpApiTokenTemplate": "인증 구성으로 전환", - "mcpRequiresTcp": "API 프로토콜이 TCP일 때 에이전트 연결 안내를 사용할 수 있습니다." + "mcpRequiresTcp": "API 프로토콜이 TCP일 때 에이전트 연결 안내를 사용할 수 있습니다.", + "emptyTaskCreateHint": "{action}을 눌러 다운로드 시작" } diff --git a/ui/flutter/lib/l10n/app_pl.arb b/ui/flutter/lib/l10n/app_pl.arb index 85b61144..ff542d50 100644 --- a/ui/flutter/lib/l10n/app_pl.arb +++ b/ui/flutter/lib/l10n/app_pl.arb @@ -442,5 +442,6 @@ "webApiConfigReadOnlyHelp": "Wersja webowa nie obsługuje edycji ustawień API online. Zmień opcje uruchamiania serwera lub plik konfiguracyjny, a następnie uruchom usługę ponownie.", "webMcpApiTokenHint": "Jeśli skonfigurowano token API, podczas łączenia agenta należy przekazać dane uwierzytelniające", "useMcpApiTokenTemplate": "Przełącz na konfigurację z uwierzytelnianiem", - "mcpRequiresTcp": "Instrukcje łączenia agentów są dostępne, gdy protokół API to TCP." + "mcpRequiresTcp": "Instrukcje łączenia agentów są dostępne, gdy protokół API to TCP.", + "emptyTaskCreateHint": "Kliknij {action}, aby rozpocząć pobieranie" } diff --git a/ui/flutter/lib/l10n/app_pt.arb b/ui/flutter/lib/l10n/app_pt.arb index 46674d2c..c0d66de9 100644 --- a/ui/flutter/lib/l10n/app_pt.arb +++ b/ui/flutter/lib/l10n/app_pt.arb @@ -442,5 +442,6 @@ "webApiConfigReadOnlyHelp": "A versão web não permite alterar as definições da API online. Atualize as opções de arranque do servidor ou o ficheiro de configuração e reinicie o serviço para aplicar as alterações.", "webMcpApiTokenHint": "Se configurou um token de API, é necessário fornecer as informações de autenticação ao ligar o agente", "useMcpApiTokenTemplate": "Mudar para a configuração autenticada", - "mcpRequiresTcp": "As instruções de conexão de agentes estão disponíveis quando o protocolo da API é TCP." + "mcpRequiresTcp": "As instruções de conexão de agentes estão disponíveis quando o protocolo da API é TCP.", + "emptyTaskCreateHint": "Clique em {action} para começar a baixar" } diff --git a/ui/flutter/lib/l10n/app_ru.arb b/ui/flutter/lib/l10n/app_ru.arb index 53f5f2f6..67e42710 100644 --- a/ui/flutter/lib/l10n/app_ru.arb +++ b/ui/flutter/lib/l10n/app_ru.arb @@ -442,5 +442,6 @@ "webApiConfigReadOnlyHelp": "В веб-версии нельзя изменить настройки API онлайн. Измените параметры запуска сервера или файл конфигурации, затем перезапустите службу.", "webMcpApiTokenHint": "Если настроен API-токен, при подключении агента необходимо передать данные аутентификации", "useMcpApiTokenTemplate": "Переключиться на конфигурацию с аутентификацией", - "mcpRequiresTcp": "Инструкции по подключению агентов доступны, когда протокол API — TCP." + "mcpRequiresTcp": "Инструкции по подключению агентов доступны, когда протокол API — TCP.", + "emptyTaskCreateHint": "Нажмите {action}, чтобы начать загрузку" } diff --git a/ui/flutter/lib/l10n/app_ta.arb b/ui/flutter/lib/l10n/app_ta.arb index bccefc16..51ee4819 100644 --- a/ui/flutter/lib/l10n/app_ta.arb +++ b/ui/flutter/lib/l10n/app_ta.arb @@ -442,5 +442,6 @@ "webApiConfigReadOnlyHelp": "இணையப் பதிப்பில் API அமைப்புகளை ஆன்லைனில் மாற்ற முடியாது. சேவையக தொடக்க விருப்பங்கள் அல்லது உள்ளமைவு கோப்பை மாற்றி, மாற்றங்கள் செயல்பட சேவையை மறுதொடக்கம் செய்யவும்.", "webMcpApiTokenHint": "API டோக்கனை அமைத்திருந்தால், முகவரை இணைக்கும்போது அங்கீகாரத் தகவலை அனுப்ப வேண்டும்", "useMcpApiTokenTemplate": "அங்கீகார உள்ளமைவுக்கு மாறு", - "mcpRequiresTcp": "API நெறிமுறை TCP ஆக இருக்கும்போது முகவர் இணைப்பு வழிமுறைகள் கிடைக்கும்." + "mcpRequiresTcp": "API நெறிமுறை TCP ஆக இருக்கும்போது முகவர் இணைப்பு வழிமுறைகள் கிடைக்கும்.", + "emptyTaskCreateHint": "பதிவிறக்கத்தைத் தொடங்க {action} ஐ அழுத்தவும்" } diff --git a/ui/flutter/lib/l10n/app_tr.arb b/ui/flutter/lib/l10n/app_tr.arb index 9b3b4954..0b160d9b 100644 --- a/ui/flutter/lib/l10n/app_tr.arb +++ b/ui/flutter/lib/l10n/app_tr.arb @@ -442,5 +442,6 @@ "webApiConfigReadOnlyHelp": "Web sürümü API ayarlarının çevrimiçi değiştirilmesini desteklemez. Sunucu başlatma seçeneklerini veya yapılandırma dosyasını güncelleyin ve değişikliklerin etkili olması için hizmeti yeniden başlatın.", "webMcpApiTokenHint": "Bir API anahtarı yapılandırdıysanız, ajan bağlanırken kimlik doğrulama bilgileri iletilmelidir", "useMcpApiTokenTemplate": "Kimlik doğrulamalı yapılandırmaya geç", - "mcpRequiresTcp": "Ajan bağlantı yönergeleri API protokolü TCP olduğunda kullanılabilir." + "mcpRequiresTcp": "Ajan bağlantı yönergeleri API protokolü TCP olduğunda kullanılabilir.", + "emptyTaskCreateHint": "İndirmeye başlamak için {action} düğmesine tıklayın" } diff --git a/ui/flutter/lib/l10n/app_uk.arb b/ui/flutter/lib/l10n/app_uk.arb index e4b3e923..8632668f 100644 --- a/ui/flutter/lib/l10n/app_uk.arb +++ b/ui/flutter/lib/l10n/app_uk.arb @@ -442,5 +442,6 @@ "webApiConfigReadOnlyHelp": "У вебверсії не можна змінювати налаштування API онлайн. Змініть параметри запуску сервера або файл конфігурації, а потім перезапустіть службу.", "webMcpApiTokenHint": "Якщо налаштовано API-токен, під час підключення агента потрібно передати дані автентифікації", "useMcpApiTokenTemplate": "Перейти до конфігурації з автентифікацією", - "mcpRequiresTcp": "Інструкції з підключення агентів доступні, коли протокол API — TCP." + "mcpRequiresTcp": "Інструкції з підключення агентів доступні, коли протокол API — TCP.", + "emptyTaskCreateHint": "Натисніть {action}, щоб почати завантаження" } diff --git a/ui/flutter/lib/l10n/app_vi.arb b/ui/flutter/lib/l10n/app_vi.arb index f7a6db52..ab4171ab 100644 --- a/ui/flutter/lib/l10n/app_vi.arb +++ b/ui/flutter/lib/l10n/app_vi.arb @@ -442,5 +442,6 @@ "webApiConfigReadOnlyHelp": "Phiên bản web không hỗ trợ sửa cấu hình API trực tuyến. Hãy cập nhật tùy chọn khởi động máy chủ hoặc tệp cấu hình rồi khởi động lại dịch vụ để áp dụng thay đổi.", "webMcpApiTokenHint": "Nếu đã cấu hình API Token, bạn cần truyền thông tin xác thực khi kết nối tác nhân", "useMcpApiTokenTemplate": "Chuyển sang cấu hình có xác thực", - "mcpRequiresTcp": "Hướng dẫn kết nối tác nhân khả dụng khi giao thức API là TCP." + "mcpRequiresTcp": "Hướng dẫn kết nối tác nhân khả dụng khi giao thức API là TCP.", + "emptyTaskCreateHint": "Nhấn {action} để bắt đầu tải xuống" } diff --git a/ui/flutter/lib/l10n/app_zh.arb b/ui/flutter/lib/l10n/app_zh.arb index 0bd33ecd..0b801d25 100644 --- a/ui/flutter/lib/l10n/app_zh.arb +++ b/ui/flutter/lib/l10n/app_zh.arb @@ -442,5 +442,6 @@ "webMcpApiTokenHint": "若已配置 API Token,接入 Agent 时需要传递认证信息", "useMcpApiTokenTemplate": "切换为认证配置", "webApiConfigReadOnlyHelp": "Web 版本不支持在线修改 API 配置。请修改服务端启动参数或配置文件,并重启服务后使配置生效。", - "mcpRequiresTcp": "API 通讯协议为 TCP 时才能使用 Agent 接入指引。" + "mcpRequiresTcp": "API 通讯协议为 TCP 时才能使用 Agent 接入指引。", + "emptyTaskCreateHint": "点击{action}开始下载" } diff --git a/ui/flutter/lib/l10n/app_zh_TW.arb b/ui/flutter/lib/l10n/app_zh_TW.arb index 8220df52..21a2bc72 100644 --- a/ui/flutter/lib/l10n/app_zh_TW.arb +++ b/ui/flutter/lib/l10n/app_zh_TW.arb @@ -442,5 +442,6 @@ "webApiConfigReadOnlyHelp": "Web 版本不支援線上修改 API 設定。請修改伺服器啟動參數或設定檔,並重新啟動服務後使設定生效。", "webMcpApiTokenHint": "若已設定 API Token,接入 Agent 時需要傳遞認證資訊", "useMcpApiTokenTemplate": "切換為認證設定", - "mcpRequiresTcp": "API 通訊協定為 TCP 時才能使用 Agent 接入指引。" + "mcpRequiresTcp": "API 通訊協定為 TCP 時才能使用 Agent 接入指引。", + "emptyTaskCreateHint": "點擊{action}開始下載" } diff --git a/ui/flutter/test/widget_test.dart b/ui/flutter/test/widget_test.dart index cf6098e4..7d95ab0e 100644 --- a/ui/flutter/test/widget_test.dart +++ b/ui/flutter/test/widget_test.dart @@ -411,6 +411,7 @@ void main() { await tester.pump(); expect(find.text('No matching tasks found'), findsOneWidget); + expect(find.byKey(const ValueKey('tasks-empty-create-button')), findsNothing); expect(find.text('No tasks in this list'), findsNothing); }); @@ -437,6 +438,20 @@ void main() { expect(find.text('Downloading 0'), findsOneWidget); expect(find.text('Completed 0'), findsOneWidget); expect(find.text('Failed 0'), findsOneWidget); + expect(find.text('Create Task'), findsOneWidget); + final headerCreate = find.byKey(const ValueKey('tasks-mobile-create-button')); + expect(tester.getSize(headerCreate).height, 34); + final batchButton = find.byKey(const ValueKey('tasks-mobile-batch-button')); + expect(tester.getRect(headerCreate).height, tester.getRect(batchButton).height); + expect(tester.getRect(headerCreate).top, tester.getRect(batchButton).top); + expect(tester.getRect(headerCreate).bottom, tester.getRect(batchButton).bottom); + expect(tester.getBottomLeft(headerCreate).dy, lessThan(tester.getTopLeft(find.text('Downloading 0')).dy)); + for (final size in [const Size(320, 568), const Size(768, 1024)]) { + tester.view.physicalSize = size; + await tester.pumpAndSettle(); + expect(tester.takeException(), isNull); + expect(find.byKey(const ValueKey('tasks-empty-create-button')), findsOneWidget); + } }); testWidgets('extensions grid adds columns from a minimum card width and keeps desktop toolbar aligned', (