feat(ui): emphasize task creation and add empty-list actions (#1505)

This commit is contained in:
Levi authored and GitHub committed 2026-09-12 18:09:36 +08:00
1 parent 091e7b8936
commit 0d52b840a2
28 files changed
+216 -68

No files matched your search

@@ -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<HomePage> {
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<HomePage> {
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;
@@ -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});
@@ -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,
),
),
],
),
),
);
}
}
@@ -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),
);
}
}
@@ -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!),
],
],
),
),
),
),
+2 -1
View File
@@ -442,5 +442,6 @@
"webApiConfigReadOnlyHelp": "لا يدعم إصدار الويب تعديل إعدادات API مباشرة. عدّل خيارات بدء تشغيل الخادم أو ملف الإعدادات، ثم أعد تشغيل الخدمة لتطبيق التغييرات.",
"webMcpApiTokenHint": "إذا قمت بتكوين رمز API، فيجب تمرير معلومات المصادقة عند ربط الوكيل",
"useMcpApiTokenTemplate": "التبديل إلى إعدادات المصادقة",
"mcpRequiresTcp": "تتوفر إرشادات ربط الوكيل عندما يكون بروتوكول API هو TCP."
"mcpRequiresTcp": "تتوفر إرشادات ربط الوكيل عندما يكون بروتوكول API هو TCP.",
"emptyTaskCreateHint": "اضغط على {action} لبدء التنزيل"
}
+2 -1
View File
@@ -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"
}
+2 -1
View File
@@ -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"
}
+2 -1
View File
@@ -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"
}
+10 -1
View File
@@ -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"
}
}
}
}
+2 -1
View File
@@ -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"
}
+2 -1
View File
@@ -442,5 +442,6 @@
"webApiConfigReadOnlyHelp": "نسخه وب از ویرایش آنلاین تنظیمات API پشتیبانی نمی‌کند. گزینه‌های راه‌اندازی سرور یا فایل پیکربندی را تغییر دهید و برای اعمال تغییرات سرویس را دوباره راه‌اندازی کنید.",
"webMcpApiTokenHint": "اگر توکن API را پیکربندی کرده‌اید، هنگام اتصال عامل باید اطلاعات احراز هویت را ارسال کنید",
"useMcpApiTokenTemplate": "تغییر به پیکربندی احراز هویت",
"mcpRequiresTcp": "راهنمای اتصال عامل زمانی در دسترس است که پروتکل API روی TCP باشد."
"mcpRequiresTcp": "راهنمای اتصال عامل زمانی در دسترس است که پروتکل API روی TCP باشد.",
"emptyTaskCreateHint": "برای شروع دانلود روی {action} کلیک کنید"
}
+2 -1
View File
@@ -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"
}
+2 -1
View File
@@ -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"
}
+2 -1
View File
@@ -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"
}
+2 -1
View File
@@ -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"
}
+2 -1
View File
@@ -442,5 +442,6 @@
"webApiConfigReadOnlyHelp": "Web 版では API 設定をオンラインで変更できません。サーバーの起動オプションまたは設定ファイルを変更し、サービスを再起動して反映してください。",
"webMcpApiTokenHint": "API トークンを設定している場合、エージェント接続時に認証情報を渡す必要があります",
"useMcpApiTokenTemplate": "認証付き設定に切り替える",
"mcpRequiresTcp": "API プロトコルが TCP の場合にエージェント接続手順を利用できます。"
"mcpRequiresTcp": "API プロトコルが TCP の場合にエージェント接続手順を利用できます。",
"emptyTaskCreateHint": "{action}をクリックしてダウンロードを開始"
}
+2 -1
View File
@@ -442,5 +442,6 @@
"webApiConfigReadOnlyHelp": "웹 버전에서는 API 설정을 온라인으로 수정할 수 없습니다. 서버 시작 옵션이나 구성 파일을 변경한 후 서비스를 다시 시작해야 적용됩니다.",
"webMcpApiTokenHint": "API 토큰을 설정한 경우 에이전트 연결 시 인증 정보를 전달해야 합니다",
"useMcpApiTokenTemplate": "인증 구성으로 전환",
"mcpRequiresTcp": "API 프로토콜이 TCP일 때 에이전트 연결 안내를 사용할 수 있습니다."
"mcpRequiresTcp": "API 프로토콜이 TCP일 때 에이전트 연결 안내를 사용할 수 있습니다.",
"emptyTaskCreateHint": "{action}을 눌러 다운로드 시작"
}
+2 -1
View File
@@ -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"
}
+2 -1
View File
@@ -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"
}
+2 -1
View File
@@ -442,5 +442,6 @@
"webApiConfigReadOnlyHelp": "В веб-версии нельзя изменить настройки API онлайн. Измените параметры запуска сервера или файл конфигурации, затем перезапустите службу.",
"webMcpApiTokenHint": "Если настроен API-токен, при подключении агента необходимо передать данные аутентификации",
"useMcpApiTokenTemplate": "Переключиться на конфигурацию с аутентификацией",
"mcpRequiresTcp": "Инструкции по подключению агентов доступны, когда протокол API — TCP."
"mcpRequiresTcp": "Инструкции по подключению агентов доступны, когда протокол API — TCP.",
"emptyTaskCreateHint": "Нажмите {action}, чтобы начать загрузку"
}
+2 -1
View File
@@ -442,5 +442,6 @@
"webApiConfigReadOnlyHelp": "இணையப் பதிப்பில் API அமைப்புகளை ஆன்லைனில் மாற்ற முடியாது. சேவையக தொடக்க விருப்பங்கள் அல்லது உள்ளமைவு கோப்பை மாற்றி, மாற்றங்கள் செயல்பட சேவையை மறுதொடக்கம் செய்யவும்.",
"webMcpApiTokenHint": "API டோக்கனை அமைத்திருந்தால், முகவரை இணைக்கும்போது அங்கீகாரத் தகவலை அனுப்ப வேண்டும்",
"useMcpApiTokenTemplate": "அங்கீகார உள்ளமைவுக்கு மாறு",
"mcpRequiresTcp": "API நெறிமுறை TCP ஆக இருக்கும்போது முகவர் இணைப்பு வழிமுறைகள் கிடைக்கும்."
"mcpRequiresTcp": "API நெறிமுறை TCP ஆக இருக்கும்போது முகவர் இணைப்பு வழிமுறைகள் கிடைக்கும்.",
"emptyTaskCreateHint": "பதிவிறக்கத்தைத் தொடங்க {action} ஐ அழுத்தவும்"
}
+2 -1
View File
@@ -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"
}
+2 -1
View File
@@ -442,5 +442,6 @@
"webApiConfigReadOnlyHelp": "У вебверсії не можна змінювати налаштування API онлайн. Змініть параметри запуску сервера або файл конфігурації, а потім перезапустіть службу.",
"webMcpApiTokenHint": "Якщо налаштовано API-токен, під час підключення агента потрібно передати дані автентифікації",
"useMcpApiTokenTemplate": "Перейти до конфігурації з автентифікацією",
"mcpRequiresTcp": "Інструкції з підключення агентів доступні, коли протокол API — TCP."
"mcpRequiresTcp": "Інструкції з підключення агентів доступні, коли протокол API — TCP.",
"emptyTaskCreateHint": "Натисніть {action}, щоб почати завантаження"
}
+2 -1
View File
@@ -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"
}
+2 -1
View File
@@ -442,5 +442,6 @@
"webMcpApiTokenHint": "若已配置 API Token,接入 Agent 时需要传递认证信息",
"useMcpApiTokenTemplate": "切换为认证配置",
"webApiConfigReadOnlyHelp": "Web 版本不支持在线修改 API 配置。请修改服务端启动参数或配置文件,并重启服务后使配置生效。",
"mcpRequiresTcp": "API 通讯协议为 TCP 时才能使用 Agent 接入指引。"
"mcpRequiresTcp": "API 通讯协议为 TCP 时才能使用 Agent 接入指引。",
"emptyTaskCreateHint": "点击{action}开始下载"
}
+2 -1
View File
@@ -442,5 +442,6 @@
"webApiConfigReadOnlyHelp": "Web 版本不支援線上修改 API 設定。請修改伺服器啟動參數或設定檔,並重新啟動服務後使設定生效。",
"webMcpApiTokenHint": "若已設定 API Token,接入 Agent 時需要傳遞認證資訊",
"useMcpApiTokenTemplate": "切換為認證設定",
"mcpRequiresTcp": "API 通訊協定為 TCP 時才能使用 Agent 接入指引。"
"mcpRequiresTcp": "API 通訊協定為 TCP 時才能使用 Agent 接入指引。",
"emptyTaskCreateHint": "點擊{action}開始下載"
}
+15
View File
@@ -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', (