diff --git a/ui/flutter_v2/.github/copilot-instructions.md b/ui/flutter_v2/.github/copilot-instructions.md new file mode 100644 index 00000000..174abd62 --- /dev/null +++ b/ui/flutter_v2/.github/copilot-instructions.md @@ -0,0 +1,169 @@ +# Gopeed Flutter V2 UI - AI Coding Instructions + +## Response Rules + +Respond in the same language as user's question + +## Project Overview + +This is the Flutter V2 UI for **Gopeed** (Go Speed), a high-speed download manager built with Golang + Flutter. This Flutter app is a **desktop-focused UI redesign** with cross-platform support (macOS, Windows, Linux, iOS, Android, Web). + +**Critical Context**: This Flutter UI communicates with a Go backend REST API (not in this workspace). The backend handles download operations, task management, and file I/O. The UI is purely presentational with future API integration planned. + +## Architecture & Structure + +### Module Organization (Feature-First) + +``` +lib/ +├── main.dart # App entry, window config, router setup +├── routes/ # Centralized routing +│ ├── app_routes.dart # go_router configuration +│ └── route_names.dart # Route path constants +├── modules/ # Feature modules (screens + widgets) +│ ├── home/ # Shell layout with sidebar +│ ├── task/ # Download task management UI +│ └── settings/ # Settings screen +└── components/ # Shared UI components (gp_* prefix) +``` + +**Key Pattern**: Features are organized in `modules/` with `screens/` and `widgets/` subdirectories. Shared components use `gp_` prefix (e.g., `GpGradientButton`, `GpIconButton`). + +### Routing Architecture + +- Uses `go_router` with `ShellRoute` for persistent sidebar navigation +- `HomeScreen` wraps all routes and provides the sidebar layout +- Route paths defined in `RouteNames` class (prevents magic strings) +- Current route detection via `GoRouterState.of(context).matchedLocation` + +## Critical Patterns & Conventions + +### Component Design System + +1. **Gradient Buttons** (`GpGradientButton`): Primary actions, teal-to-green gradient (Colors: `0xFF3ACBBE` → `0xFF2CDB90`) +2. **Outline Buttons** (`GpOutlineButton`): Secondary actions, border-only with teal accent (`0xFF39CDB9`) +3. **Icon Buttons** (`GpIconButton`): Tertiary actions with hover effects (`0xFF4883A2` on hover) + +### Design Implementation + +- **Glass Morphism**: `TaskItem` widget uses `BackdropFilter` with blur (24px) and layered gradients +- **Color Palette**: Dark blue theme (`0xFF131956` background, `0xFF000E4B` sidebar, teal/green accents) +- **Comments Reference Design Specs**: Many widgets have detailed comments like "Rectangle 82: 主容器" mapping to Figma/design files + +### Widget Documentation Style + +Components use triple-slash (`///`) documentation: + +```dart +/// TaskItem - 任务项组件 +/// +/// 一个带有毛玻璃效果和渐变边框的任务项容器 +class TaskItem extends StatelessWidget { + /// 左侧图标 + final dynamic icon; +``` + +**Convention**: Document widget purpose, parameters, and design intent in Chinese. Keep inline comments for implementation details. + +### State Management + +- Currently uses `StatefulWidget` with local state (e.g., `TaskScreen._selectedTabIndex`) +- **Future Integration**: Will integrate with backend API - prepare for state management library (GetX/Riverpod likely based on Gopeed ecosystem) + +### Window Management + +Desktop apps use `window_manager` package: + +- Initial size: 1024x768, minimum: 800x600 +- Hidden title bar (`TitleBarStyle.hidden`) for custom UI +- Configured in `main.dart` before `runApp()` + +## Development Workflows + +### Running the App + +```bash +# Desktop (macOS/Windows/Linux) +flutter run -d macos # or windows/linux + +# Mobile +flutter run -d ios # requires Xcode +flutter run -d android + +# Web +flutter run -d chrome +``` + +### Building + +```bash +flutter build macos --release +flutter build windows --release +flutter build linux --release +``` + +### Dependencies + +Key packages in `pubspec.yaml`: + +- `window_manager: ^0.4.3` - Desktop window control +- `flutter_svg: ^2.0.17` - SVG asset support +- `go_router: ^14.8.1` - Declarative routing + +Assets in `assets/icons/` (e.g., `gopeed_sidebar.svg`, `pause.png`) + +## Backend Integration (Future) + +**Not Yet Implemented**: Based on parent project structure, this UI will connect to Go REST API: + +- API endpoints: `/api/task`, `/api/resolve`, `/api/config` (reference: `pkg/rest/api.go` in parent) +- Task operations: create, pause, continue, delete +- Real-time progress updates via events/WebSocket + +**When Implementing API**: + +1. Create `lib/api/` or `lib/services/` directory +2. Use `http` or `dio` package for REST calls +3. Models should match Go backend structs (e.g., `Task`, `Progress`) +4. Replace mock data in `TaskScreen._buildTaskList()` with API calls + +## Testing & Quality + +- Lints: Uses `package:flutter_lints/flutter.yaml` (standard Flutter best practices) +- No tests currently - **add tests when backend integration begins** +- Desktop focus: Prioritize macOS/Windows, mobile is secondary + +## Common Tasks + +### Adding a New Screen + +1. Create `lib/modules/[feature]/screens/[screen_name].dart` +2. Add route to `RouteNames` constant +3. Register route in `AppRoutes.getRouter()` under `ShellRoute.routes` + +### Adding Shared Component + +1. Create `lib/components/gp_[component_name].dart` +2. Follow naming: `Gp` prefix + PascalCase +3. Document with `///` comments +4. Support flexible sizing/theming via parameters + +### Design-to-Code Workflow + +When implementing from design files: + +- Preserve design layer names in comments (e.g., "Rectangle 82") +- Use exact hex colors from specs +- Document gradient stops and blur sigma values +- Stack layers match design hierarchy (bottom-to-top) + +## Important Notes + +- **Language**: UI text is in Chinese (e.g., "下载中", "已完成", "任务失败") +- **Branch Context**: Current branch is `feat/new_ui` - this is a redesign +- **Desktop First**: Window management, mouse hover states prioritized +- **Parent Project**: Part of larger Gopeed monorepo with Go backend (outside this workspace) + +--- + +**When uncertain**: Check `lib/modules/task/widgets/task_item.dart` for complex widget patterns, `lib/routes/app_routes.dart` for routing examples, or `lib/main.dart` for app initialization. diff --git a/ui/flutter_v2/assets/icons/delete.svg b/ui/flutter_v2/assets/icons/delete.svg new file mode 100644 index 00000000..27490950 --- /dev/null +++ b/ui/flutter_v2/assets/icons/delete.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ui/flutter_v2/assets/icons/item_delete.svg b/ui/flutter_v2/assets/icons/item_delete.svg new file mode 100644 index 00000000..9a905ddf --- /dev/null +++ b/ui/flutter_v2/assets/icons/item_delete.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ui/flutter_v2/assets/icons/item_info.svg b/ui/flutter_v2/assets/icons/item_info.svg new file mode 100644 index 00000000..4420f34b --- /dev/null +++ b/ui/flutter_v2/assets/icons/item_info.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ui/flutter_v2/assets/icons/item_more.svg b/ui/flutter_v2/assets/icons/item_more.svg new file mode 100644 index 00000000..6da67da4 --- /dev/null +++ b/ui/flutter_v2/assets/icons/item_more.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ui/flutter_v2/assets/icons/item_pause.svg b/ui/flutter_v2/assets/icons/item_pause.svg new file mode 100644 index 00000000..b9eac269 --- /dev/null +++ b/ui/flutter_v2/assets/icons/item_pause.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ui/flutter_v2/assets/icons/item_resume.svg b/ui/flutter_v2/assets/icons/item_resume.svg new file mode 100644 index 00000000..3cd475e8 --- /dev/null +++ b/ui/flutter_v2/assets/icons/item_resume.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ui/flutter_v2/assets/icons/item_reveal.svg b/ui/flutter_v2/assets/icons/item_reveal.svg new file mode 100644 index 00000000..370772dc --- /dev/null +++ b/ui/flutter_v2/assets/icons/item_reveal.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ui/flutter_v2/assets/icons/item_speed.svg b/ui/flutter_v2/assets/icons/item_speed.svg new file mode 100644 index 00000000..26018742 --- /dev/null +++ b/ui/flutter_v2/assets/icons/item_speed.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ui/flutter_v2/assets/icons/pause.png b/ui/flutter_v2/assets/icons/pause.png deleted file mode 100644 index caaa71b4..00000000 Binary files a/ui/flutter_v2/assets/icons/pause.png and /dev/null differ diff --git a/ui/flutter_v2/assets/icons/pause.svg b/ui/flutter_v2/assets/icons/pause.svg new file mode 100644 index 00000000..6aaa98a3 --- /dev/null +++ b/ui/flutter_v2/assets/icons/pause.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ui/flutter_v2/assets/icons/resume.svg b/ui/flutter_v2/assets/icons/resume.svg new file mode 100644 index 00000000..acf32e6b --- /dev/null +++ b/ui/flutter_v2/assets/icons/resume.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ui/flutter_v2/lib/components/gp_icon_button.dart b/ui/flutter_v2/lib/components/gp_icon_button.dart index 8dd46bf3..972a04bd 100644 --- a/ui/flutter_v2/lib/components/gp_icon_button.dart +++ b/ui/flutter_v2/lib/components/gp_icon_button.dart @@ -1,11 +1,12 @@ import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; /// GpIconButton - 简单的图标按钮组件 /// /// 用于TaskItem的操作按钮,支持鼠标悬浮效果 class GpIconButton extends StatefulWidget { - /// 图标 - final IconData icon; + /// 图标(支持 IconData 或 String 路径) + final dynamic icon; /// 点击回调 final VoidCallback? onTap; @@ -53,13 +54,57 @@ class _GpIconButtonState extends State { ? const Color(0xFF4883A2) : Colors.transparent, // 悬浮背景色 ), - child: Icon( - widget.icon, - color: widget.color ?? Colors.white, - size: widget.size ?? 20.0, - ), + child: _buildIcon(), ), ), ); } + + Widget _buildIcon() { + // 如果是字符串,认为是资源路径 + if (widget.icon is String) { + final String path = widget.icon as String; + // 支持 svg 资源 + if (path.toLowerCase().endsWith('.svg')) { + return Center( + child: SvgPicture.asset( + path, + width: widget.size ?? 20.0, + height: widget.size ?? 20.0, + fit: BoxFit.contain, + colorFilter: ColorFilter.mode( + widget.color ?? Colors.white, + BlendMode.srcIn, + ), + ), + ); + } + + return Center( + child: Image.asset( + path, + width: widget.size ?? 20.0, + height: widget.size ?? 20.0, + color: widget.color, + fit: BoxFit.contain, + ), + ); + } + + // 如果是IconData,使用Icon组件 + if (widget.icon is IconData) { + return Icon( + widget.icon as IconData, + color: widget.color ?? Colors.white, + size: widget.size ?? 20.0, + ); + } + + // 如果是Widget,直接使用 + if (widget.icon is Widget) { + return Center(child: widget.icon as Widget); + } + + return const SizedBox.shrink(); + } } diff --git a/ui/flutter_v2/lib/components/gp_outline_button.dart b/ui/flutter_v2/lib/components/gp_outline_button.dart index ae294ea6..f9079542 100644 --- a/ui/flutter_v2/lib/components/gp_outline_button.dart +++ b/ui/flutter_v2/lib/components/gp_outline_button.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; /// GpOutlineButton - 线框按钮组件 /// @@ -74,9 +75,26 @@ class GpOutlineButton extends StatelessWidget { // 如果是字符串,认为是资源路径 if (icon is String) { + final String path = icon as String; + // 支持 svg 资源 + if (path.toLowerCase().endsWith('.svg')) { + return Center( + child: SvgPicture.asset( + path, + width: iconSize, + height: iconSize, + fit: BoxFit.contain, + colorFilter: + iconColor != null + ? ColorFilter.mode(iconColor!, BlendMode.srcIn) + : null, + ), + ); + } + return Center( child: Image.asset( - icon as String, + path, width: iconSize, height: iconSize, color: iconColor, diff --git a/ui/flutter_v2/lib/modules/task/screens/task.dart b/ui/flutter_v2/lib/modules/task/screens/task.dart index 0af5c871..37b3c13e 100644 --- a/ui/flutter_v2/lib/modules/task/screens/task.dart +++ b/ui/flutter_v2/lib/modules/task/screens/task.dart @@ -60,21 +60,21 @@ class _TaskScreenState extends State { mainAxisAlignment: MainAxisAlignment.end, children: [ GpOutlineButton( - icon: 'assets/icons/pause.png', + icon: 'assets/icons/resume.svg', onTap: () { // 处理开始按钮点击事件 }, ), const SizedBox(width: 12), GpOutlineButton( - icon: 'assets/icons/pause.png', + icon: 'assets/icons/pause.svg', onTap: () { // 处理暂停按钮点击事件 }, ), const SizedBox(width: 12), GpOutlineButton( - icon: 'assets/icons/pause.png', + icon: 'assets/icons/delete.svg', onTap: () { // 处理删除按钮点击事件 }, @@ -109,23 +109,23 @@ class _TaskScreenState extends State { bottomRightStatusText: '下载中', // 状态文本 actionButtons: [ GpIconButton( - icon: Icons.pause, + icon: 'assets/icons/item_pause.svg', onTap: () => print('暂停任务 $index'), ), GpIconButton( - icon: Icons.delete, + icon: 'assets/icons/item_delete.svg', onTap: () => print('删除任务 $index'), ), GpIconButton( - icon: Icons.folder_open, - onTap: () => print('打开所在位置 $index'), + icon: 'assets/icons/item_reveal.svg', + onTap: () => print('打开所在·位置 $index'), ), GpIconButton( - icon: Icons.info, + icon: 'assets/icons/item_info.svg', onTap: () => print('查看信息 $index'), ), GpIconButton( - icon: Icons.more_vert, + icon: 'assets/icons/item_more.svg', onTap: () => print('更多操作 $index'), ), ], @@ -149,23 +149,23 @@ class _TaskScreenState extends State { bottomRightStatusText: '已完成', // 状态文本 actionButtons: [ GpIconButton( - icon: Icons.pause, - onTap: () => print('暂停任务 $index'), + icon: 'assets/icons/item_resume.svg', + onTap: () => print('重新下载任务 $index'), ), GpIconButton( - icon: Icons.delete, + icon: 'assets/icons/item_delete.svg', onTap: () => print('删除任务 $index'), ), GpIconButton( - icon: Icons.folder_open, + icon: 'assets/icons/item_reveal.svg', onTap: () => print('打开所在位置 $index'), ), GpIconButton( - icon: Icons.info, + icon: 'assets/icons/item_info.svg', onTap: () => print('查看信息 $index'), ), GpIconButton( - icon: Icons.more_vert, + icon: 'assets/icons/item_more.svg', onTap: () => print('更多操作 $index'), ), ], @@ -189,23 +189,23 @@ class _TaskScreenState extends State { bottomRightStatusText: '失败', // 状态文本 actionButtons: [ GpIconButton( - icon: Icons.pause, - onTap: () => print('暂停任务 $index'), + icon: 'assets/icons/item_resume.svg', + onTap: () => print('重试任务 $index'), ), GpIconButton( - icon: Icons.delete, + icon: 'assets/icons/item_delete.svg', onTap: () => print('删除任务 $index'), ), GpIconButton( - icon: Icons.folder_open, + icon: 'assets/icons/item_reveal.svg', onTap: () => print('打开所在位置 $index'), ), GpIconButton( - icon: Icons.info, + icon: 'assets/icons/item_info.svg', onTap: () => print('查看信息 $index'), ), GpIconButton( - icon: Icons.more_vert, + icon: 'assets/icons/item_more.svg', onTap: () => print('更多操作 $index'), ), ], diff --git a/ui/flutter_v2/macos/Podfile.lock b/ui/flutter_v2/macos/Podfile.lock index f6f0ae1b..7b42b4e6 100644 --- a/ui/flutter_v2/macos/Podfile.lock +++ b/ui/flutter_v2/macos/Podfile.lock @@ -19,7 +19,7 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/window_manager/macos SPEC CHECKSUMS: - FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 + FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 screen_retriever_macos: 776e0fa5d42c6163d2bf772d22478df4b302b161 window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8 diff --git a/ui/flutter_v2/pubspec.lock b/ui/flutter_v2/pubspec.lock index a1e69f4b..42f9b1a5 100644 --- a/ui/flutter_v2/pubspec.lock +++ b/ui/flutter_v2/pubspec.lock @@ -13,10 +13,10 @@ packages: dependency: transitive description: name: async - sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 url: "https://pub.dev" source: hosted - version: "2.13.0" + version: "2.12.0" boolean_selector: dependency: transitive description: @@ -61,10 +61,10 @@ packages: dependency: transitive description: name: fake_async - sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" url: "https://pub.dev" source: hosted - version: "1.3.3" + version: "1.3.2" flutter: dependency: "direct main" description: flutter @@ -132,26 +132,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec url: "https://pub.dev" source: hosted - version: "11.0.2" + version: "10.0.8" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 url: "https://pub.dev" source: hosted - version: "3.0.10" + version: "3.0.9" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.1" lints: dependency: transitive description: @@ -212,10 +212,10 @@ packages: dependency: transitive description: name: petitparser - sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1" + sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646" url: "https://pub.dev" source: hosted - version: "7.0.1" + version: "6.1.0" plugin_platform_interface: dependency: transitive description: @@ -313,10 +313,10 @@ packages: dependency: transitive description: name: test_api - sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd url: "https://pub.dev" source: hosted - version: "0.7.6" + version: "0.7.4" typed_data: dependency: transitive description: @@ -353,18 +353,18 @@ packages: dependency: transitive description: name: vector_math - sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.1.4" vm_service: dependency: transitive description: name: vm_service - sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" + sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" url: "https://pub.dev" source: hosted - version: "15.0.2" + version: "14.3.1" web: dependency: transitive description: @@ -385,10 +385,10 @@ packages: dependency: transitive description: name: xml - sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 url: "https://pub.dev" source: hosted - version: "6.6.1" + version: "6.5.0" sdks: - dart: ">=3.8.0 <4.0.0" + dart: ">=3.7.2 <4.0.0" flutter: ">=3.29.0"