diff --git a/.agents/skills/flutter-expert/SKILL.md b/.agents/skills/flutter-expert/SKILL.md new file mode 100644 index 0000000..d32358f --- /dev/null +++ b/.agents/skills/flutter-expert/SKILL.md @@ -0,0 +1,82 @@ +--- +name: flutter-expert +description: Use when building cross-platform applications with Flutter 3+ and Dart. Invoke for widget development, Riverpod/Bloc state management, GoRouter navigation, platform-specific implementations, performance optimization. +license: MIT +metadata: + author: https://github.com/Jeffallan + version: "1.0.0" + domain: frontend + triggers: Flutter, Dart, widget, Riverpod, Bloc, GoRouter, cross-platform + role: specialist + scope: implementation + output-format: code + related-skills: react-native-expert, test-master, fullstack-guardian +--- + +# Flutter Expert + +Senior mobile engineer building high-performance cross-platform applications with Flutter 3 and Dart. + +## Role Definition + +You are a senior Flutter developer with 6+ years of experience. You specialize in Flutter 3.19+, Riverpod 2.0, GoRouter, and building apps for iOS, Android, Web, and Desktop. You write performant, maintainable Dart code with proper state management. + +## When to Use This Skill + +- Building cross-platform Flutter applications +- Implementing state management (Riverpod, Bloc) +- Setting up navigation with GoRouter +- Creating custom widgets and animations +- Optimizing Flutter performance +- Platform-specific implementations + +## Core Workflow + +1. **Setup** - Project structure, dependencies, routing +2. **State** - Riverpod providers or Bloc setup +3. **Widgets** - Reusable, const-optimized components +4. **Test** - Widget tests, integration tests +5. **Optimize** - Profile, reduce rebuilds + +## Reference Guide + +Load detailed guidance based on context: + +| Topic | Reference | Load When | +|-------|-----------|-----------| +| Riverpod | `references/riverpod-state.md` | State management, providers, notifiers | +| Bloc | `references/bloc-state.md` | Bloc, Cubit, event-driven state, complex business logic | +| GoRouter | `references/gorouter-navigation.md` | Navigation, routing, deep linking | +| Widgets | `references/widget-patterns.md` | Building UI components, const optimization | +| Structure | `references/project-structure.md` | Setting up project, architecture | +| Performance | `references/performance.md` | Optimization, profiling, jank fixes | + +## Constraints + +### MUST DO +- Use const constructors wherever possible +- Implement proper keys for lists +- Use Consumer/ConsumerWidget for state (not StatefulWidget) +- Follow Material/Cupertino design guidelines +- Profile with DevTools, fix jank +- Test widgets with flutter_test + +### MUST NOT DO +- Build widgets inside build() method +- Mutate state directly (always create new instances) +- Use setState for app-wide state +- Skip const on static widgets +- Ignore platform-specific behavior +- Block UI thread with heavy computation (use compute()) + +## Output Templates + +When implementing Flutter features, provide: +1. Widget code with proper const usage +2. Provider/Bloc definitions +3. Route configuration if needed +4. Test file structure + +## Knowledge Reference + +Flutter 3.19+, Dart 3.3+, Riverpod 2.0, Bloc 8.x, GoRouter, freezed, json_serializable, Dio, flutter_hooks diff --git a/.agents/skills/flutter-expert/references/bloc-state.md b/.agents/skills/flutter-expert/references/bloc-state.md new file mode 100644 index 0000000..2c659e9 --- /dev/null +++ b/.agents/skills/flutter-expert/references/bloc-state.md @@ -0,0 +1,259 @@ +# Bloc State Management + +## When to Use Bloc + +Use **Bloc/Cubit** when you need: + +* Explicit event → state transitions +* Complex business logic +* Predictable, testable flows +* Clear separation between UI and logic + +| Use Case | Recommended | +| ---------------------- | ----------- | +| Simple mutable state | Riverpod | +| Event-driven workflows | Bloc | +| Forms, auth, wizards | Bloc | +| Feature modules | Bloc | + +--- + +## Core Concepts + +| Concept | Description | +| ------- | ---------------------- | +| Event | User/system input | +| State | Immutable UI state | +| Bloc | Event → State mapper | +| Cubit | State-only (no events) | + +--- + +## Basic Bloc Setup + +### Event + +```dart +sealed class CounterEvent {} + +final class CounterIncremented extends CounterEvent {} + +final class CounterDecremented extends CounterEvent {} +``` + +### State + +```dart +class CounterState { + final int value; + + const CounterState({required this.value}); + + CounterState copyWith({int? value}) { + return CounterState(value: value ?? this.value); + } +} +``` + +### Bloc + +```dart +import 'package:flutter_bloc/flutter_bloc.dart'; + +class CounterBloc extends Bloc { + CounterBloc() : super(const CounterState(value: 0)) { + on((event, emit) { + emit(state.copyWith(value: state.value + 1)); + }); + + on((event, emit) { + emit(state.copyWith(value: state.value - 1)); + }); + } +} +``` + +--- + +## Cubit (Recommended for Simpler Logic) + +```dart +class CounterCubit extends Cubit { + CounterCubit() : super(0); + + void increment() => emit(state + 1); + void decrement() => emit(state - 1); +} +``` + +--- + +## Providing Bloc to the Widget Tree + +```dart +BlocProvider( + create: (_) => CounterBloc(), + child: const CounterScreen(), +); +``` + +Multiple blocs: + +```dart +MultiBlocProvider( + providers: [ + BlocProvider(create: (_) => AuthBloc()), + BlocProvider(create: (_) => ProfileBloc()), + ], + child: const AppRoot(), +); +``` + +--- + +## Using Bloc in Widgets + +### BlocBuilder (UI rebuilds) + +```dart +class CounterScreen extends StatelessWidget { + const CounterScreen({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + buildWhen: (prev, curr) => prev.value != curr.value, + builder: (context, state) { + return Text( + state.value.toString(), + style: Theme.of(context).textTheme.displayLarge, + ); + }, + ); + } +} +``` + +--- + +### BlocListener (Side Effects) + +```dart +BlocListener( + listenWhen: (prev, curr) => curr is AuthFailure, + listener: (context, state) { + if (state is AuthFailure) { + ScaffoldMessenger.of(context) + .showSnackBar(SnackBar(content: Text(state.message))); + } + }, + child: const LoginForm(), +); +``` + +--- + +### BlocConsumer (Builder + Listener) + +```dart +BlocConsumer( + listener: (context, state) { + if (state.status == FormStatus.success) { + context.pop(); + } + }, + builder: (context, state) { + return ElevatedButton( + onPressed: state.isValid + ? () => context.read().add(FormSubmitted()) + : null, + child: const Text('Submit'), + ); + }, +); +``` + +--- + +## Accessing Bloc Without Rebuilds + +```dart +context.read().add(CounterIncremented()); +``` + +⚠️ **Never use `watch` inside callbacks** + +--- + +## Async Bloc Pattern (API Calls) + +```dart +on((event, emit) async { + emit(const UserState.loading()); + + try { + final user = await repository.fetchUser(); + emit(UserState.success(user)); + } catch (e) { + emit(UserState.failure(e.toString())); + } +}); +``` + +--- + +## Bloc + GoRouter (Auth Guard Example) + +```dart +redirect: (context, state) { + final authState = context.read().state; + + if (authState is Unauthenticated) { + return '/login'; + } + return null; +} +``` + +--- + +## Testing Bloc + +```dart +blocTest( + 'emits incremented value', + build: () => CounterBloc(), + act: (bloc) => bloc.add(CounterIncremented()), + expect: () => [ + const CounterState(value: 1), + ], +); +``` + +--- + +## Best Practices (MUST FOLLOW) + +✅ Immutable states +✅ Small, focused blocs +✅ One feature = one bloc +✅ Use Cubit when possible +✅ Test all blocs + +❌ No UI logic inside blocs +❌ No context usage inside blocs +❌ No mutable state +❌ No massive “god blocs” + +--- + +## Quick Reference + +| Widget | Purpose | +| ----------------- | -------------------- | +| BlocBuilder | UI rebuild | +| BlocListener | Side effects | +| BlocConsumer | Both | +| BlocProvider | Dependency injection | +| MultiBlocProvider | Multiple blocs | + diff --git a/.agents/skills/flutter-expert/references/gorouter-navigation.md b/.agents/skills/flutter-expert/references/gorouter-navigation.md new file mode 100644 index 0000000..82812db --- /dev/null +++ b/.agents/skills/flutter-expert/references/gorouter-navigation.md @@ -0,0 +1,119 @@ +# GoRouter Navigation + +## Basic Setup + +```dart +import 'package:go_router/go_router.dart'; + +final goRouter = GoRouter( + initialLocation: '/', + redirect: (context, state) { + final isLoggedIn = /* check auth */; + if (!isLoggedIn && !state.matchedLocation.startsWith('/auth')) { + return '/auth/login'; + } + return null; + }, + routes: [ + GoRoute( + path: '/', + builder: (context, state) => const HomeScreen(), + routes: [ + GoRoute( + path: 'details/:id', + builder: (context, state) { + final id = state.pathParameters['id']!; + return DetailsScreen(id: id); + }, + ), + ], + ), + GoRoute( + path: '/auth/login', + builder: (context, state) => const LoginScreen(), + ), + ], +); + +// In app.dart +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp.router( + routerConfig: goRouter, + theme: AppTheme.light, + darkTheme: AppTheme.dark, + ); + } +} +``` + +## Navigation Methods + +```dart +// Navigate and replace history +context.go('/details/123'); + +// Navigate and add to stack +context.push('/details/123'); + +// Go back +context.pop(); + +// Replace current route +context.pushReplacement('/home'); + +// Navigate with extra data +context.push('/details/123', extra: {'title': 'Item'}); + +// Access extra in destination +final extra = GoRouterState.of(context).extra as Map?; +``` + +## Shell Routes (Persistent UI) + +```dart +final goRouter = GoRouter( + routes: [ + ShellRoute( + builder: (context, state, child) { + return ScaffoldWithNavBar(child: child); + }, + routes: [ + GoRoute(path: '/home', builder: (_, __) => const HomeScreen()), + GoRoute(path: '/profile', builder: (_, __) => const ProfileScreen()), + GoRoute(path: '/settings', builder: (_, __) => const SettingsScreen()), + ], + ), + ], +); +``` + +## Query Parameters + +```dart +GoRoute( + path: '/search', + builder: (context, state) { + final query = state.uri.queryParameters['q'] ?? ''; + final page = int.tryParse(state.uri.queryParameters['page'] ?? '1') ?? 1; + return SearchScreen(query: query, page: page); + }, +), + +// Navigate with query params +context.go('/search?q=flutter&page=2'); +``` + +## Quick Reference + +| Method | Behavior | +|--------|----------| +| `context.go()` | Navigate, replace stack | +| `context.push()` | Navigate, add to stack | +| `context.pop()` | Go back | +| `context.pushReplacement()` | Replace current | +| `:param` | Path parameter | +| `?key=value` | Query parameter | diff --git a/.agents/skills/flutter-expert/references/performance.md b/.agents/skills/flutter-expert/references/performance.md new file mode 100644 index 0000000..9ed2c5c --- /dev/null +++ b/.agents/skills/flutter-expert/references/performance.md @@ -0,0 +1,99 @@ +# Performance Optimization + +## Profiling Commands + +```bash +# Run in profile mode +flutter run --profile + +# Analyze performance +flutter analyze + +# DevTools +flutter pub global activate devtools +flutter pub global run devtools +``` + +## Common Optimizations + +### Const Widgets +```dart +// ❌ Rebuilds every time +Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.all(16), // Creates new object + child: Text('Hello'), + ); +} + +// ✅ Const prevents rebuilds +Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(16), + child: const Text('Hello'), + ); +} +``` + +### Selective Provider Watching +```dart +// ❌ Rebuilds on any user change +final user = ref.watch(userProvider); +return Text(user.name); + +// ✅ Only rebuilds when name changes +final name = ref.watch(userProvider.select((u) => u.name)); +return Text(name); +``` + +### RepaintBoundary +```dart +// Isolate expensive widgets +RepaintBoundary( + child: ComplexAnimatedWidget(), +) +``` + +### Image Optimization +```dart +// Use cached_network_image +CachedNetworkImage( + imageUrl: url, + placeholder: (_, __) => const CircularProgressIndicator(), + errorWidget: (_, __, ___) => const Icon(Icons.error), +) + +// Resize images +Image.network( + url, + cacheWidth: 200, // Resize in memory + cacheHeight: 200, +) +``` + +### Compute for Heavy Operations +```dart +// ❌ Blocks UI thread +final result = heavyComputation(data); + +// ✅ Runs in isolate +final result = await compute(heavyComputation, data); +``` + +## Performance Checklist + +| Check | Solution | +|-------|----------| +| Unnecessary rebuilds | Add `const`, use `select()` | +| Large lists | Use `ListView.builder` | +| Image loading | Use `cached_network_image` | +| Heavy computation | Use `compute()` | +| Jank in animations | Use `RepaintBoundary` | +| Memory leaks | Dispose controllers | + +## DevTools Metrics + +- **Frame rendering time**: < 16ms for 60fps +- **Widget rebuilds**: Minimize unnecessary rebuilds +- **Memory usage**: Watch for leaks +- **CPU profiler**: Identify bottlenecks diff --git a/.agents/skills/flutter-expert/references/project-structure.md b/.agents/skills/flutter-expert/references/project-structure.md new file mode 100644 index 0000000..ebd30cc --- /dev/null +++ b/.agents/skills/flutter-expert/references/project-structure.md @@ -0,0 +1,118 @@ +# Project Structure + +## Feature-Based Structure + +``` +lib/ +├── main.dart +├── app.dart +├── core/ +│ ├── constants/ +│ │ ├── colors.dart +│ │ └── strings.dart +│ ├── theme/ +│ │ ├── app_theme.dart +│ │ └── text_styles.dart +│ ├── utils/ +│ │ ├── extensions.dart +│ │ └── validators.dart +│ └── errors/ +│ └── failures.dart +├── features/ +│ ├── auth/ +│ │ ├── data/ +│ │ │ ├── repositories/ +│ │ │ └── datasources/ +│ │ ├── domain/ +│ │ │ ├── entities/ +│ │ │ └── usecases/ +│ │ ├── presentation/ +│ │ │ ├── screens/ +│ │ │ └── widgets/ +│ │ └── providers/ +│ │ └── auth_provider.dart +│ └── home/ +│ ├── data/ +│ ├── domain/ +│ ├── presentation/ +│ └── providers/ +├── shared/ +│ ├── widgets/ +│ │ ├── buttons/ +│ │ ├── inputs/ +│ │ └── cards/ +│ ├── services/ +│ │ ├── api_service.dart +│ │ └── storage_service.dart +│ └── models/ +│ └── user.dart +└── routes/ + └── app_router.dart +``` + +## pubspec.yaml Essentials + +```yaml +dependencies: + flutter: + sdk: flutter + # State Management + flutter_riverpod: ^2.5.0 + riverpod_annotation: ^2.3.0 + # Navigation + go_router: ^14.0.0 + # Networking + dio: ^5.4.0 + # Code Generation + freezed_annotation: ^2.4.0 + json_annotation: ^4.8.0 + # Storage + shared_preferences: ^2.2.0 + hive_flutter: ^1.1.0 + +dev_dependencies: + flutter_test: + sdk: flutter + build_runner: ^2.4.0 + riverpod_generator: ^2.4.0 + freezed: ^2.5.0 + json_serializable: ^6.8.0 + flutter_lints: ^4.0.0 +``` + +## Feature Layer Responsibilities + +| Layer | Responsibility | +|-------|----------------| +| **data/** | API calls, local storage, DTOs | +| **domain/** | Business logic, entities, use cases | +| **presentation/** | UI screens, widgets | +| **providers/** | Riverpod providers for feature | + +## Main Entry Point + +```dart +// main.dart +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + await Hive.initFlutter(); + runApp(const ProviderScope(child: MyApp())); +} + +// app.dart +class MyApp extends ConsumerWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final router = ref.watch(routerProvider); + + return MaterialApp.router( + routerConfig: router, + theme: AppTheme.light, + darkTheme: AppTheme.dark, + themeMode: ThemeMode.system, + ); + } +} +``` diff --git a/.agents/skills/flutter-expert/references/riverpod-state.md b/.agents/skills/flutter-expert/references/riverpod-state.md new file mode 100644 index 0000000..7cef036 --- /dev/null +++ b/.agents/skills/flutter-expert/references/riverpod-state.md @@ -0,0 +1,130 @@ +# Riverpod State Management + +## Provider Types + +```dart +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +// Simple state +final counterProvider = StateProvider((ref) => 0); + +// Async state (API calls) +final usersProvider = FutureProvider>((ref) async { + final api = ref.read(apiProvider); + return api.getUsers(); +}); + +// Stream state (real-time) +final messagesProvider = StreamProvider>((ref) { + return ref.read(chatServiceProvider).messagesStream; +}); +``` + +## Notifier Pattern (Riverpod 2.0) + +```dart +@riverpod +class TodoList extends _$TodoList { + @override + List build() => []; + + void add(Todo todo) { + state = [...state, todo]; + } + + void toggle(String id) { + state = [ + for (final todo in state) + if (todo.id == id) todo.copyWith(completed: !todo.completed) else todo, + ]; + } + + void remove(String id) { + state = state.where((t) => t.id != id).toList(); + } +} + +// Async Notifier +@riverpod +class UserProfile extends _$UserProfile { + @override + Future build() async { + return ref.read(apiProvider).getCurrentUser(); + } + + Future updateName(String name) async { + state = const AsyncValue.loading(); + state = await AsyncValue.guard(() async { + final updated = await ref.read(apiProvider).updateUser(name: name); + return updated; + }); + } +} +``` + +## Usage in Widgets + +```dart +// ConsumerWidget (recommended) +class TodoScreen extends ConsumerWidget { + const TodoScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final todos = ref.watch(todoListProvider); + + return ListView.builder( + itemCount: todos.length, + itemBuilder: (context, index) { + final todo = todos[index]; + return ListTile( + title: Text(todo.title), + leading: Checkbox( + value: todo.completed, + onChanged: (_) => ref.read(todoListProvider.notifier).toggle(todo.id), + ), + ); + }, + ); + } +} + +// Selective rebuilds with select +class UserAvatar extends ConsumerWidget { + const UserAvatar({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final avatarUrl = ref.watch(userProvider.select((u) => u?.avatarUrl)); + + return CircleAvatar( + backgroundImage: avatarUrl != null ? NetworkImage(avatarUrl) : null, + ); + } +} + +// Async state handling +class UserProfileScreen extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + final userAsync = ref.watch(userProfileProvider); + + return userAsync.when( + data: (user) => Text(user.name), + loading: () => const CircularProgressIndicator(), + error: (err, stack) => Text('Error: $err'), + ); + } +} +``` + +## Quick Reference + +| Provider | Use Case | +|----------|----------| +| `Provider` | Computed/derived values | +| `StateProvider` | Simple mutable state | +| `FutureProvider` | Async operations (one-time) | +| `StreamProvider` | Real-time data streams | +| `NotifierProvider` | Complex state with methods | +| `AsyncNotifierProvider` | Async state with methods | diff --git a/.agents/skills/flutter-expert/references/widget-patterns.md b/.agents/skills/flutter-expert/references/widget-patterns.md new file mode 100644 index 0000000..304a451 --- /dev/null +++ b/.agents/skills/flutter-expert/references/widget-patterns.md @@ -0,0 +1,123 @@ +# Widget Patterns + +## Optimized Widget Pattern + +```dart +// Use const constructors +class OptimizedCard extends StatelessWidget { + final String title; + final VoidCallback onTap; + + const OptimizedCard({ + super.key, + required this.title, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + return Card( + child: InkWell( + onTap: onTap, + child: Padding( + padding: const EdgeInsets.all(16), + child: Text(title, style: Theme.of(context).textTheme.titleMedium), + ), + ), + ); + } +} +``` + +## Responsive Layout + +```dart +class ResponsiveLayout extends StatelessWidget { + final Widget mobile; + final Widget? tablet; + final Widget desktop; + + const ResponsiveLayout({ + super.key, + required this.mobile, + this.tablet, + required this.desktop, + }); + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (context, constraints) { + if (constraints.maxWidth >= 1100) return desktop; + if (constraints.maxWidth >= 650) return tablet ?? mobile; + return mobile; + }, + ); + } +} +``` + +## Custom Hooks (flutter_hooks) + +```dart +import 'package:flutter_hooks/flutter_hooks.dart'; + +class CounterWidget extends HookWidget { + @override + Widget build(BuildContext context) { + final counter = useState(0); + final controller = useTextEditingController(); + + useEffect(() { + // Setup + return () { + // Cleanup + }; + }, []); + + return Column( + children: [ + Text('Count: ${counter.value}'), + ElevatedButton( + onPressed: () => counter.value++, + child: const Text('Increment'), + ), + ], + ); + } +} +``` + +## Sliver Patterns + +```dart +CustomScrollView( + slivers: [ + SliverAppBar( + expandedHeight: 200, + pinned: true, + flexibleSpace: FlexibleSpaceBar( + title: const Text('Title'), + background: Image.network(imageUrl, fit: BoxFit.cover), + ), + ), + SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) => ListTile(title: Text('Item $index')), + childCount: 100, + ), + ), + ], +) +``` + +## Key Optimization Patterns + +| Pattern | Implementation | +|---------|----------------| +| **const widgets** | Add `const` to static widgets | +| **keys** | Use `Key` for list items | +| **select** | `ref.watch(provider.select(...))` | +| **RepaintBoundary** | Isolate expensive repaints | +| **ListView.builder** | Lazy loading for lists | +| **const constructors** | Always use when possible | diff --git a/.cursor/rules/dart-coding-standards.mdc b/.cursor/rules/dart-coding-standards.mdc new file mode 100644 index 0000000..4c2dafd --- /dev/null +++ b/.cursor/rules/dart-coding-standards.mdc @@ -0,0 +1,70 @@ +--- +description: Dart language coding standards and conventions. Apply to ALL Dart files. +globs: "**/*.dart" +--- + +# Dart Coding Standards + +## Naming Conventions +- PascalCase for classes, enums, typedefs, extensions. +- camelCase for variables, functions, methods, parameters. +- snake_case for file and directory names. +- SCREAMING_SNAKE_CASE for compile-time constants only when conventional. +- Start functions with a verb: fetchUser(), saveSettings(), isValid(). +- Use boolean prefixes: isLoading, hasError, canDelete, shouldRefresh. +- Use complete words, avoid abbreviations (except API, URL, i/j for loops). + +## Functions +- Short, single purpose, < 20 lines. +- Arrow syntax for simple one-line functions. +- Named parameters for > 2 parameters. +- Default parameter values instead of null checks. +- Reduce parameters using parameter objects (RO-RO pattern). +- Single level of abstraction per function. +- Early return to avoid deep nesting. + +## Classes +- Follow SOLID principles. +- Prefer composition over inheritance. +- Small classes: < 200 lines, < 10 public methods, < 10 properties. +- Declare interfaces (abstract classes) for contracts/repositories. +- Use factory constructors for complex initialization. + +## Data & Immutability +- Prefer immutable data structures. +- Use final for fields that don't change. +- Use const for compile-time constants. +- Encapsulate data in composite types (avoid primitive obsession). +- Use Freezed for complex immutable state classes. + +## Null Safety +- Write soundly null-safe code. +- Avoid ! operator unless value is guaranteed non-null. +- Use ?. and ?? operators appropriately. +- Prefer required named parameters over nullable ones. + +## Error Handling +- Use try-catch for expected exceptions. +- Create custom exception classes for domain-specific errors. +- Never catch generic Exception without rethrowing or logging. +- Use Either for functional error handling in repositories. + +## Async +- Use async/await (not .then() chains). +- Use Future for single async operations. +- Use Stream for sequences of async events. +- Handle errors in every async operation. +- Cancel subscriptions in dispose(). + +## Code Organization +- One primary export per file. +- Group related libraries in the same folder. +- Use part/part of sparingly; prefer separate files. +- Import order: dart:, package:, relative imports. + +## Documentation +- /// for all public API doc comments. +- First line: concise summary ending with period. +- Explain WHY, not WHAT. +- Use backtick fences for code samples. +- Place doc comments before annotations. diff --git a/.cursor/rules/flutter-clean-architecture.mdc b/.cursor/rules/flutter-clean-architecture.mdc new file mode 100644 index 0000000..8827445 --- /dev/null +++ b/.cursor/rules/flutter-clean-architecture.mdc @@ -0,0 +1,73 @@ +--- +description: Clean Architecture + Feature-first + BLoC/Riverpod patterns for Flutter features. Apply to feature modules. +globs: "lib/features/**/*.dart" +--- + +# Flutter Clean Architecture — Feature-First + +## Architecture Layers (Dependency Rule: always inward) +- **Presentation** → Widgets, Screens, BLoC/Cubit, ViewModels +- **Domain** → Entities, Repository interfaces, Use Cases +- **Data** → Repository implementations, Data Sources, DTOs/Models + +## Feature Directory Structure +``` +lib/features/feature_name/ +├── data/ +│ ├── datasources/ # Remote + Local data sources +│ ├── models/ # DTOs, data models (with fromJson/toJson) +│ └── repositories/ # Repository implementations +├── domain/ +│ ├── entities/ # Pure business objects +│ ├── repositories/ # Abstract repository interfaces +│ └── usecases/ # Single-purpose business logic +└── presentation/ + ├── bloc/ # BLoC/Cubit state management + ├── pages/ # Screen widgets + └── widgets/ # Feature-specific widgets +``` + +## Use Case Pattern +```dart +abstract class UseCase { + Future> call(Params params); +} +``` + +## State Management +- Use Freezed for immutable state classes and union types. +- States: initial, loading, loaded(data), error(failure). +- Use BlocBuilder with buildWhen for optimized rebuilds. +- Use BlocListener for side effects (navigation, dialogs). +- Avoid business logic in UI components. + +## Error Handling +- Use Either from Dartz for functional error handling. +- Create custom Failure classes per domain. +- Proper error mapping between layers. +- User-friendly error messages in presentation layer. + +## Dependency Injection +- Use GetIt as service locator. +- Register dependencies by feature in separate files. +- Lazy initialization; factories for transient, singletons for services. + +## Repository Pattern +- Single source of truth for data. +- Handle network/cache fallback gracefully. +- Map data models to domain entities at repository boundary. + +## Constraints +### MUST DO +- Use const constructors wherever possible. +- Implement proper keys for lists. +- Follow Material/Cupertino design guidelines. +- Profile with DevTools, fix jank. +- Test widgets with flutter_test. + +### MUST NOT DO +- Build widgets inside build() method. +- Mutate state directly (always create new instances). +- Use setState for app-wide state. +- Block UI thread with heavy computation (use compute()). +- Skip const on static widgets. diff --git a/.cursor/rules/flutter-official.mdc b/.cursor/rules/flutter-official.mdc new file mode 100644 index 0000000..50ed499 --- /dev/null +++ b/.cursor/rules/flutter-official.mdc @@ -0,0 +1,80 @@ +--- +description: Flutter & Dart official best practices from Flutter team. Apply to ALL Flutter/Dart files. +globs: "**/*.dart" +--- + +# AI Rules for Flutter (Official — from docs.flutter.dev) + +You are an expert in Flutter and Dart development. Build beautiful, performant, and maintainable applications following modern best practices. + +## Flutter Style Guide +- **SOLID Principles:** Apply throughout the codebase. +- **Concise and Declarative:** Write modern, technical Dart code. Prefer functional and declarative patterns. +- **Composition over Inheritance:** Favor composition for building complex widgets and logic. +- **Immutability:** Prefer immutable data structures. Widgets (especially StatelessWidget) should be immutable. +- **State Management:** Separate ephemeral state and app state. +- **Navigation:** Use GoRouter or auto_route for routing and deep linking. + +## Code Quality +- Adhere to maintainable code structure and separation of concerns. +- Avoid abbreviations; use meaningful, descriptive names. +- Line length: 80 characters or fewer. +- Use PascalCase for classes, camelCase for members/variables, snake_case for files. +- Functions: short, single purpose, strive for less than 20 lines. +- Use `logging` package instead of `print`. + +## Dart Best Practices +- Follow official Effective Dart guidelines. +- Add doc comments to all public APIs. +- Use async/await for asynchronous operations with robust error handling. +- Write code that is soundly null-safe. Avoid `!` unless guaranteed non-null. +- Use pattern matching features where they simplify code. +- Use records to return multiple types. +- Prefer exhaustive switch statements. +- Use arrow syntax for simple one-line functions. + +## Flutter Best Practices +- Use `const` constructors whenever possible to reduce rebuilds. +- Use small, private Widget classes instead of helper methods returning Widget. +- Break down large build() methods into smaller, reusable private Widget classes. +- Use ListView.builder or SliverList for long lists (lazy loading). +- Use compute() for expensive calculations in separate isolate. +- Avoid performing expensive operations in build() methods. + +## Application Architecture +- Separation of Concerns: MVC/MVVM with defined Model, View, ViewModel/Controller. +- Logical Layers: Presentation → Domain → Data → Core. +- Feature-based Organization for larger projects. + +## State Management (Built-in preferred) +- Streams + StreamBuilder for sequences of async events. +- Futures + FutureBuilder for single async operations. +- ValueNotifier + ValueListenableBuilder for simple local state. +- ChangeNotifier for complex/shared state. +- MVVM when robust solution needed. +- Manual constructor dependency injection to keep dependencies explicit. + +## Theming +- Define centralized ThemeData; implement light + dark themes. +- Use ColorScheme.fromSeed() for harmonious palettes. +- Use ThemeExtension for custom design tokens. +- Use google_fonts package for custom fonts. +- Follow 60-30-10 rule for color distribution. + +## Testing +- Unit tests (package:test), Widget tests (flutter_test), Integration tests (integration_test). +- Follow Arrange-Act-Assert pattern. +- Prefer fakes/stubs over mocks; use mockito/mocktail if necessary. +- Aim for high test coverage. + +## Accessibility +- Text contrast ratio ≥ 4.5:1. +- Test with dynamic text scaling. +- Use Semantics widget for descriptive labels. +- Test with TalkBack (Android) and VoiceOver (iOS). + +## Documentation +- Use `///` for doc comments. +- Start with single-sentence summary. +- Comment WHY, not WHAT. +- Include code samples where appropriate. diff --git a/.cursor/rules/flutter-testing.mdc b/.cursor/rules/flutter-testing.mdc new file mode 100644 index 0000000..28da8e0 --- /dev/null +++ b/.cursor/rules/flutter-testing.mdc @@ -0,0 +1,43 @@ +--- +description: Flutter testing guidelines covering unit, widget, and integration tests. +globs: "test/**/*.dart" +--- + +# Flutter Testing Rules + +## Test Structure +- Follow Arrange-Act-Assert (Given-When-Then) pattern. +- Name test variables clearly: inputX, mockX, actualX, expectedX. +- Mirror lib/ directory structure in test/. +- One test file per source file. + +## Unit Tests +- Write unit tests for all domain logic and use cases. +- Test repository implementations with mock data sources. +- Test BLoC/Cubit state transitions thoroughly. +- Use package:test for pure Dart unit tests. + +## Widget Tests +- Use package:flutter_test for widget tests. +- Test rendering, interaction, and state changes. +- Use pumpWidget() and pump() for async rendering. +- Use find.byType, find.byKey, find.text for assertions. +- Verify widget rebuilds correctly with different states. + +## Integration Tests +- Use package:integration_test for end-to-end flows. +- Test critical user journeys (login, navigation, data flow). +- Add integration_test as dev_dependency with sdk: flutter. + +## Mocking +- Prefer fakes and stubs over mocks. +- Use mockito or mocktail when mocks are necessary. +- Avoid code generation for mocks when possible. +- Mock external services (APIs, databases) at repository boundary. + +## Best Practices +- Aim for high test coverage on domain and data layers. +- Test error states and edge cases. +- Keep tests fast and independent. +- Use setUp() and tearDown() for common setup. +- Run tests in CI/CD pipeline. diff --git a/.cursor/rules/flutter-ui-performance.mdc b/.cursor/rules/flutter-ui-performance.mdc new file mode 100644 index 0000000..d8d3d2d --- /dev/null +++ b/.cursor/rules/flutter-ui-performance.mdc @@ -0,0 +1,52 @@ +--- +description: Flutter UI patterns, widget best practices, and performance optimization. Apply to all presentation-layer Dart files. +globs: "lib/**/{widgets,pages,screens,components}/**/*.dart" +--- + +# Flutter UI & Performance Rules + +## Widget Best Practices +- Create small, private widget classes instead of methods like `Widget _buildXxx()`. +- Use const constructors for all immutable widgets. +- Implement proper widget keys for lists and conditional widgets. +- Keep widget tree flat — avoid nesting deeper than necessary. +- Use composition: combine small widgets into complex ones. + +## Performance Optimization +- Use ListView.builder / GridView.builder for long scrollable lists. +- Use const wherever possible to skip unnecessary rebuilds. +- Avoid expensive operations (network calls, parsing) in build(). +- Use compute() to run heavy work in a background isolate. +- Use RepaintBoundary to isolate expensive paint operations. +- Cache images: AssetImage for local, cached_network_image for remote. +- Profile regularly with Flutter DevTools; target 60fps. + +## Responsive Design +- Use LayoutBuilder or MediaQuery to adapt to screen sizes. +- Use Expanded/Flexible in Row/Column to prevent overflow. +- Use Wrap for content that may overflow horizontally. +- Use FittedBox to scale a child within its parent. + +## Animation Guidelines +- Use AnimationController + AnimatedBuilder for custom animations. +- Prefer implicit animations (AnimatedContainer, AnimatedOpacity) for simple cases. +- Use physics-based animations (SpringSimulation) for natural feel. +- Always dispose AnimationControllers in dispose(). + +## Theming in Widgets +- Access colors via Theme.of(context).colorScheme. +- Access text styles via Theme.of(context).textTheme. +- Never hardcode colors or font sizes — always reference theme. +- Use Theme.of(context).extension() for custom design tokens. + +## Forms & Input +- Set appropriate textCapitalization, keyboardType, textInputAction. +- Always include errorBuilder when using Image.network. +- Implement RefreshIndicator for pull-to-refresh. +- Use Form + GlobalKey for validation. + +## Accessibility +- Use Semantics widget for all interactive elements. +- Ensure touch targets ≥ 48x48 dp. +- Test with screen readers (TalkBack / VoiceOver). +- Support dynamic type scaling. diff --git a/.cursor/rules/user-preferences.mdc b/.cursor/rules/user-preferences.mdc new file mode 100644 index 0000000..7681a2f --- /dev/null +++ b/.cursor/rules/user-preferences.mdc @@ -0,0 +1,17 @@ +--- +description: User preferences for communication and workflow +globs: +alwaysApply: true +--- + +# 用户偏好(必须遵守) + +## 语言 +- 永远使用中文回复,无论用户用什么语言输入(用户使用语音输入时系统可能自动翻译为英文) + +## 沟通方式 +- 用户不懂代码,不需要看代码修改过程 +- 不要打开代码文件让用户审阅 +- 直接完成修改,只展示最终结果 +- 用产品语言描述变更("改好了,刷新看效果"),而非技术语言 +- 用户擅长产品思维和创造力,用这个层面和他沟通 diff --git a/.cursor/skills/flutter-expert/SKILL.md b/.cursor/skills/flutter-expert/SKILL.md new file mode 100644 index 0000000..d32358f --- /dev/null +++ b/.cursor/skills/flutter-expert/SKILL.md @@ -0,0 +1,82 @@ +--- +name: flutter-expert +description: Use when building cross-platform applications with Flutter 3+ and Dart. Invoke for widget development, Riverpod/Bloc state management, GoRouter navigation, platform-specific implementations, performance optimization. +license: MIT +metadata: + author: https://github.com/Jeffallan + version: "1.0.0" + domain: frontend + triggers: Flutter, Dart, widget, Riverpod, Bloc, GoRouter, cross-platform + role: specialist + scope: implementation + output-format: code + related-skills: react-native-expert, test-master, fullstack-guardian +--- + +# Flutter Expert + +Senior mobile engineer building high-performance cross-platform applications with Flutter 3 and Dart. + +## Role Definition + +You are a senior Flutter developer with 6+ years of experience. You specialize in Flutter 3.19+, Riverpod 2.0, GoRouter, and building apps for iOS, Android, Web, and Desktop. You write performant, maintainable Dart code with proper state management. + +## When to Use This Skill + +- Building cross-platform Flutter applications +- Implementing state management (Riverpod, Bloc) +- Setting up navigation with GoRouter +- Creating custom widgets and animations +- Optimizing Flutter performance +- Platform-specific implementations + +## Core Workflow + +1. **Setup** - Project structure, dependencies, routing +2. **State** - Riverpod providers or Bloc setup +3. **Widgets** - Reusable, const-optimized components +4. **Test** - Widget tests, integration tests +5. **Optimize** - Profile, reduce rebuilds + +## Reference Guide + +Load detailed guidance based on context: + +| Topic | Reference | Load When | +|-------|-----------|-----------| +| Riverpod | `references/riverpod-state.md` | State management, providers, notifiers | +| Bloc | `references/bloc-state.md` | Bloc, Cubit, event-driven state, complex business logic | +| GoRouter | `references/gorouter-navigation.md` | Navigation, routing, deep linking | +| Widgets | `references/widget-patterns.md` | Building UI components, const optimization | +| Structure | `references/project-structure.md` | Setting up project, architecture | +| Performance | `references/performance.md` | Optimization, profiling, jank fixes | + +## Constraints + +### MUST DO +- Use const constructors wherever possible +- Implement proper keys for lists +- Use Consumer/ConsumerWidget for state (not StatefulWidget) +- Follow Material/Cupertino design guidelines +- Profile with DevTools, fix jank +- Test widgets with flutter_test + +### MUST NOT DO +- Build widgets inside build() method +- Mutate state directly (always create new instances) +- Use setState for app-wide state +- Skip const on static widgets +- Ignore platform-specific behavior +- Block UI thread with heavy computation (use compute()) + +## Output Templates + +When implementing Flutter features, provide: +1. Widget code with proper const usage +2. Provider/Bloc definitions +3. Route configuration if needed +4. Test file structure + +## Knowledge Reference + +Flutter 3.19+, Dart 3.3+, Riverpod 2.0, Bloc 8.x, GoRouter, freezed, json_serializable, Dio, flutter_hooks diff --git a/.cursor/skills/flutter-expert/references/bloc-state.md b/.cursor/skills/flutter-expert/references/bloc-state.md new file mode 100644 index 0000000..2c659e9 --- /dev/null +++ b/.cursor/skills/flutter-expert/references/bloc-state.md @@ -0,0 +1,259 @@ +# Bloc State Management + +## When to Use Bloc + +Use **Bloc/Cubit** when you need: + +* Explicit event → state transitions +* Complex business logic +* Predictable, testable flows +* Clear separation between UI and logic + +| Use Case | Recommended | +| ---------------------- | ----------- | +| Simple mutable state | Riverpod | +| Event-driven workflows | Bloc | +| Forms, auth, wizards | Bloc | +| Feature modules | Bloc | + +--- + +## Core Concepts + +| Concept | Description | +| ------- | ---------------------- | +| Event | User/system input | +| State | Immutable UI state | +| Bloc | Event → State mapper | +| Cubit | State-only (no events) | + +--- + +## Basic Bloc Setup + +### Event + +```dart +sealed class CounterEvent {} + +final class CounterIncremented extends CounterEvent {} + +final class CounterDecremented extends CounterEvent {} +``` + +### State + +```dart +class CounterState { + final int value; + + const CounterState({required this.value}); + + CounterState copyWith({int? value}) { + return CounterState(value: value ?? this.value); + } +} +``` + +### Bloc + +```dart +import 'package:flutter_bloc/flutter_bloc.dart'; + +class CounterBloc extends Bloc { + CounterBloc() : super(const CounterState(value: 0)) { + on((event, emit) { + emit(state.copyWith(value: state.value + 1)); + }); + + on((event, emit) { + emit(state.copyWith(value: state.value - 1)); + }); + } +} +``` + +--- + +## Cubit (Recommended for Simpler Logic) + +```dart +class CounterCubit extends Cubit { + CounterCubit() : super(0); + + void increment() => emit(state + 1); + void decrement() => emit(state - 1); +} +``` + +--- + +## Providing Bloc to the Widget Tree + +```dart +BlocProvider( + create: (_) => CounterBloc(), + child: const CounterScreen(), +); +``` + +Multiple blocs: + +```dart +MultiBlocProvider( + providers: [ + BlocProvider(create: (_) => AuthBloc()), + BlocProvider(create: (_) => ProfileBloc()), + ], + child: const AppRoot(), +); +``` + +--- + +## Using Bloc in Widgets + +### BlocBuilder (UI rebuilds) + +```dart +class CounterScreen extends StatelessWidget { + const CounterScreen({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + buildWhen: (prev, curr) => prev.value != curr.value, + builder: (context, state) { + return Text( + state.value.toString(), + style: Theme.of(context).textTheme.displayLarge, + ); + }, + ); + } +} +``` + +--- + +### BlocListener (Side Effects) + +```dart +BlocListener( + listenWhen: (prev, curr) => curr is AuthFailure, + listener: (context, state) { + if (state is AuthFailure) { + ScaffoldMessenger.of(context) + .showSnackBar(SnackBar(content: Text(state.message))); + } + }, + child: const LoginForm(), +); +``` + +--- + +### BlocConsumer (Builder + Listener) + +```dart +BlocConsumer( + listener: (context, state) { + if (state.status == FormStatus.success) { + context.pop(); + } + }, + builder: (context, state) { + return ElevatedButton( + onPressed: state.isValid + ? () => context.read().add(FormSubmitted()) + : null, + child: const Text('Submit'), + ); + }, +); +``` + +--- + +## Accessing Bloc Without Rebuilds + +```dart +context.read().add(CounterIncremented()); +``` + +⚠️ **Never use `watch` inside callbacks** + +--- + +## Async Bloc Pattern (API Calls) + +```dart +on((event, emit) async { + emit(const UserState.loading()); + + try { + final user = await repository.fetchUser(); + emit(UserState.success(user)); + } catch (e) { + emit(UserState.failure(e.toString())); + } +}); +``` + +--- + +## Bloc + GoRouter (Auth Guard Example) + +```dart +redirect: (context, state) { + final authState = context.read().state; + + if (authState is Unauthenticated) { + return '/login'; + } + return null; +} +``` + +--- + +## Testing Bloc + +```dart +blocTest( + 'emits incremented value', + build: () => CounterBloc(), + act: (bloc) => bloc.add(CounterIncremented()), + expect: () => [ + const CounterState(value: 1), + ], +); +``` + +--- + +## Best Practices (MUST FOLLOW) + +✅ Immutable states +✅ Small, focused blocs +✅ One feature = one bloc +✅ Use Cubit when possible +✅ Test all blocs + +❌ No UI logic inside blocs +❌ No context usage inside blocs +❌ No mutable state +❌ No massive “god blocs” + +--- + +## Quick Reference + +| Widget | Purpose | +| ----------------- | -------------------- | +| BlocBuilder | UI rebuild | +| BlocListener | Side effects | +| BlocConsumer | Both | +| BlocProvider | Dependency injection | +| MultiBlocProvider | Multiple blocs | + diff --git a/.cursor/skills/flutter-expert/references/gorouter-navigation.md b/.cursor/skills/flutter-expert/references/gorouter-navigation.md new file mode 100644 index 0000000..82812db --- /dev/null +++ b/.cursor/skills/flutter-expert/references/gorouter-navigation.md @@ -0,0 +1,119 @@ +# GoRouter Navigation + +## Basic Setup + +```dart +import 'package:go_router/go_router.dart'; + +final goRouter = GoRouter( + initialLocation: '/', + redirect: (context, state) { + final isLoggedIn = /* check auth */; + if (!isLoggedIn && !state.matchedLocation.startsWith('/auth')) { + return '/auth/login'; + } + return null; + }, + routes: [ + GoRoute( + path: '/', + builder: (context, state) => const HomeScreen(), + routes: [ + GoRoute( + path: 'details/:id', + builder: (context, state) { + final id = state.pathParameters['id']!; + return DetailsScreen(id: id); + }, + ), + ], + ), + GoRoute( + path: '/auth/login', + builder: (context, state) => const LoginScreen(), + ), + ], +); + +// In app.dart +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp.router( + routerConfig: goRouter, + theme: AppTheme.light, + darkTheme: AppTheme.dark, + ); + } +} +``` + +## Navigation Methods + +```dart +// Navigate and replace history +context.go('/details/123'); + +// Navigate and add to stack +context.push('/details/123'); + +// Go back +context.pop(); + +// Replace current route +context.pushReplacement('/home'); + +// Navigate with extra data +context.push('/details/123', extra: {'title': 'Item'}); + +// Access extra in destination +final extra = GoRouterState.of(context).extra as Map?; +``` + +## Shell Routes (Persistent UI) + +```dart +final goRouter = GoRouter( + routes: [ + ShellRoute( + builder: (context, state, child) { + return ScaffoldWithNavBar(child: child); + }, + routes: [ + GoRoute(path: '/home', builder: (_, __) => const HomeScreen()), + GoRoute(path: '/profile', builder: (_, __) => const ProfileScreen()), + GoRoute(path: '/settings', builder: (_, __) => const SettingsScreen()), + ], + ), + ], +); +``` + +## Query Parameters + +```dart +GoRoute( + path: '/search', + builder: (context, state) { + final query = state.uri.queryParameters['q'] ?? ''; + final page = int.tryParse(state.uri.queryParameters['page'] ?? '1') ?? 1; + return SearchScreen(query: query, page: page); + }, +), + +// Navigate with query params +context.go('/search?q=flutter&page=2'); +``` + +## Quick Reference + +| Method | Behavior | +|--------|----------| +| `context.go()` | Navigate, replace stack | +| `context.push()` | Navigate, add to stack | +| `context.pop()` | Go back | +| `context.pushReplacement()` | Replace current | +| `:param` | Path parameter | +| `?key=value` | Query parameter | diff --git a/.cursor/skills/flutter-expert/references/performance.md b/.cursor/skills/flutter-expert/references/performance.md new file mode 100644 index 0000000..9ed2c5c --- /dev/null +++ b/.cursor/skills/flutter-expert/references/performance.md @@ -0,0 +1,99 @@ +# Performance Optimization + +## Profiling Commands + +```bash +# Run in profile mode +flutter run --profile + +# Analyze performance +flutter analyze + +# DevTools +flutter pub global activate devtools +flutter pub global run devtools +``` + +## Common Optimizations + +### Const Widgets +```dart +// ❌ Rebuilds every time +Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.all(16), // Creates new object + child: Text('Hello'), + ); +} + +// ✅ Const prevents rebuilds +Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(16), + child: const Text('Hello'), + ); +} +``` + +### Selective Provider Watching +```dart +// ❌ Rebuilds on any user change +final user = ref.watch(userProvider); +return Text(user.name); + +// ✅ Only rebuilds when name changes +final name = ref.watch(userProvider.select((u) => u.name)); +return Text(name); +``` + +### RepaintBoundary +```dart +// Isolate expensive widgets +RepaintBoundary( + child: ComplexAnimatedWidget(), +) +``` + +### Image Optimization +```dart +// Use cached_network_image +CachedNetworkImage( + imageUrl: url, + placeholder: (_, __) => const CircularProgressIndicator(), + errorWidget: (_, __, ___) => const Icon(Icons.error), +) + +// Resize images +Image.network( + url, + cacheWidth: 200, // Resize in memory + cacheHeight: 200, +) +``` + +### Compute for Heavy Operations +```dart +// ❌ Blocks UI thread +final result = heavyComputation(data); + +// ✅ Runs in isolate +final result = await compute(heavyComputation, data); +``` + +## Performance Checklist + +| Check | Solution | +|-------|----------| +| Unnecessary rebuilds | Add `const`, use `select()` | +| Large lists | Use `ListView.builder` | +| Image loading | Use `cached_network_image` | +| Heavy computation | Use `compute()` | +| Jank in animations | Use `RepaintBoundary` | +| Memory leaks | Dispose controllers | + +## DevTools Metrics + +- **Frame rendering time**: < 16ms for 60fps +- **Widget rebuilds**: Minimize unnecessary rebuilds +- **Memory usage**: Watch for leaks +- **CPU profiler**: Identify bottlenecks diff --git a/.cursor/skills/flutter-expert/references/project-structure.md b/.cursor/skills/flutter-expert/references/project-structure.md new file mode 100644 index 0000000..ebd30cc --- /dev/null +++ b/.cursor/skills/flutter-expert/references/project-structure.md @@ -0,0 +1,118 @@ +# Project Structure + +## Feature-Based Structure + +``` +lib/ +├── main.dart +├── app.dart +├── core/ +│ ├── constants/ +│ │ ├── colors.dart +│ │ └── strings.dart +│ ├── theme/ +│ │ ├── app_theme.dart +│ │ └── text_styles.dart +│ ├── utils/ +│ │ ├── extensions.dart +│ │ └── validators.dart +│ └── errors/ +│ └── failures.dart +├── features/ +│ ├── auth/ +│ │ ├── data/ +│ │ │ ├── repositories/ +│ │ │ └── datasources/ +│ │ ├── domain/ +│ │ │ ├── entities/ +│ │ │ └── usecases/ +│ │ ├── presentation/ +│ │ │ ├── screens/ +│ │ │ └── widgets/ +│ │ └── providers/ +│ │ └── auth_provider.dart +│ └── home/ +│ ├── data/ +│ ├── domain/ +│ ├── presentation/ +│ └── providers/ +├── shared/ +│ ├── widgets/ +│ │ ├── buttons/ +│ │ ├── inputs/ +│ │ └── cards/ +│ ├── services/ +│ │ ├── api_service.dart +│ │ └── storage_service.dart +│ └── models/ +│ └── user.dart +└── routes/ + └── app_router.dart +``` + +## pubspec.yaml Essentials + +```yaml +dependencies: + flutter: + sdk: flutter + # State Management + flutter_riverpod: ^2.5.0 + riverpod_annotation: ^2.3.0 + # Navigation + go_router: ^14.0.0 + # Networking + dio: ^5.4.0 + # Code Generation + freezed_annotation: ^2.4.0 + json_annotation: ^4.8.0 + # Storage + shared_preferences: ^2.2.0 + hive_flutter: ^1.1.0 + +dev_dependencies: + flutter_test: + sdk: flutter + build_runner: ^2.4.0 + riverpod_generator: ^2.4.0 + freezed: ^2.5.0 + json_serializable: ^6.8.0 + flutter_lints: ^4.0.0 +``` + +## Feature Layer Responsibilities + +| Layer | Responsibility | +|-------|----------------| +| **data/** | API calls, local storage, DTOs | +| **domain/** | Business logic, entities, use cases | +| **presentation/** | UI screens, widgets | +| **providers/** | Riverpod providers for feature | + +## Main Entry Point + +```dart +// main.dart +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + await Hive.initFlutter(); + runApp(const ProviderScope(child: MyApp())); +} + +// app.dart +class MyApp extends ConsumerWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final router = ref.watch(routerProvider); + + return MaterialApp.router( + routerConfig: router, + theme: AppTheme.light, + darkTheme: AppTheme.dark, + themeMode: ThemeMode.system, + ); + } +} +``` diff --git a/.cursor/skills/flutter-expert/references/riverpod-state.md b/.cursor/skills/flutter-expert/references/riverpod-state.md new file mode 100644 index 0000000..7cef036 --- /dev/null +++ b/.cursor/skills/flutter-expert/references/riverpod-state.md @@ -0,0 +1,130 @@ +# Riverpod State Management + +## Provider Types + +```dart +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +// Simple state +final counterProvider = StateProvider((ref) => 0); + +// Async state (API calls) +final usersProvider = FutureProvider>((ref) async { + final api = ref.read(apiProvider); + return api.getUsers(); +}); + +// Stream state (real-time) +final messagesProvider = StreamProvider>((ref) { + return ref.read(chatServiceProvider).messagesStream; +}); +``` + +## Notifier Pattern (Riverpod 2.0) + +```dart +@riverpod +class TodoList extends _$TodoList { + @override + List build() => []; + + void add(Todo todo) { + state = [...state, todo]; + } + + void toggle(String id) { + state = [ + for (final todo in state) + if (todo.id == id) todo.copyWith(completed: !todo.completed) else todo, + ]; + } + + void remove(String id) { + state = state.where((t) => t.id != id).toList(); + } +} + +// Async Notifier +@riverpod +class UserProfile extends _$UserProfile { + @override + Future build() async { + return ref.read(apiProvider).getCurrentUser(); + } + + Future updateName(String name) async { + state = const AsyncValue.loading(); + state = await AsyncValue.guard(() async { + final updated = await ref.read(apiProvider).updateUser(name: name); + return updated; + }); + } +} +``` + +## Usage in Widgets + +```dart +// ConsumerWidget (recommended) +class TodoScreen extends ConsumerWidget { + const TodoScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final todos = ref.watch(todoListProvider); + + return ListView.builder( + itemCount: todos.length, + itemBuilder: (context, index) { + final todo = todos[index]; + return ListTile( + title: Text(todo.title), + leading: Checkbox( + value: todo.completed, + onChanged: (_) => ref.read(todoListProvider.notifier).toggle(todo.id), + ), + ); + }, + ); + } +} + +// Selective rebuilds with select +class UserAvatar extends ConsumerWidget { + const UserAvatar({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final avatarUrl = ref.watch(userProvider.select((u) => u?.avatarUrl)); + + return CircleAvatar( + backgroundImage: avatarUrl != null ? NetworkImage(avatarUrl) : null, + ); + } +} + +// Async state handling +class UserProfileScreen extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + final userAsync = ref.watch(userProfileProvider); + + return userAsync.when( + data: (user) => Text(user.name), + loading: () => const CircularProgressIndicator(), + error: (err, stack) => Text('Error: $err'), + ); + } +} +``` + +## Quick Reference + +| Provider | Use Case | +|----------|----------| +| `Provider` | Computed/derived values | +| `StateProvider` | Simple mutable state | +| `FutureProvider` | Async operations (one-time) | +| `StreamProvider` | Real-time data streams | +| `NotifierProvider` | Complex state with methods | +| `AsyncNotifierProvider` | Async state with methods | diff --git a/.cursor/skills/flutter-expert/references/widget-patterns.md b/.cursor/skills/flutter-expert/references/widget-patterns.md new file mode 100644 index 0000000..304a451 --- /dev/null +++ b/.cursor/skills/flutter-expert/references/widget-patterns.md @@ -0,0 +1,123 @@ +# Widget Patterns + +## Optimized Widget Pattern + +```dart +// Use const constructors +class OptimizedCard extends StatelessWidget { + final String title; + final VoidCallback onTap; + + const OptimizedCard({ + super.key, + required this.title, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + return Card( + child: InkWell( + onTap: onTap, + child: Padding( + padding: const EdgeInsets.all(16), + child: Text(title, style: Theme.of(context).textTheme.titleMedium), + ), + ), + ); + } +} +``` + +## Responsive Layout + +```dart +class ResponsiveLayout extends StatelessWidget { + final Widget mobile; + final Widget? tablet; + final Widget desktop; + + const ResponsiveLayout({ + super.key, + required this.mobile, + this.tablet, + required this.desktop, + }); + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (context, constraints) { + if (constraints.maxWidth >= 1100) return desktop; + if (constraints.maxWidth >= 650) return tablet ?? mobile; + return mobile; + }, + ); + } +} +``` + +## Custom Hooks (flutter_hooks) + +```dart +import 'package:flutter_hooks/flutter_hooks.dart'; + +class CounterWidget extends HookWidget { + @override + Widget build(BuildContext context) { + final counter = useState(0); + final controller = useTextEditingController(); + + useEffect(() { + // Setup + return () { + // Cleanup + }; + }, []); + + return Column( + children: [ + Text('Count: ${counter.value}'), + ElevatedButton( + onPressed: () => counter.value++, + child: const Text('Increment'), + ), + ], + ); + } +} +``` + +## Sliver Patterns + +```dart +CustomScrollView( + slivers: [ + SliverAppBar( + expandedHeight: 200, + pinned: true, + flexibleSpace: FlexibleSpaceBar( + title: const Text('Title'), + background: Image.network(imageUrl, fit: BoxFit.cover), + ), + ), + SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) => ListTile(title: Text('Item $index')), + childCount: 100, + ), + ), + ], +) +``` + +## Key Optimization Patterns + +| Pattern | Implementation | +|---------|----------------| +| **const widgets** | Add `const` to static widgets | +| **keys** | Use `Key` for list items | +| **select** | `ref.watch(provider.select(...))` | +| **RepaintBoundary** | Isolate expensive repaints | +| **ListView.builder** | Lazy loading for lists | +| **const constructors** | Always use when possible | diff --git a/.gitignore b/.gitignore index 9191781..64b9cd2 100644 --- a/.gitignore +++ b/.gitignore @@ -14,7 +14,14 @@ dist/ # Project Specific .agent/ .gemini/ -tmp/ +.trae/ + +# Skills recursive symlinks (npx skills installer bug) +.agents/skills/**/flutter-expert/flutter-expert/ +.cursor/skills/**/flutter-expert/flutter-expert/ + +# Environment Variables +.env # Large Media Directories # (Uncomment below if push fails due to 413 error) diff --git a/API相关/minimax-歌词生成 (Lyrics Generation).md b/API相关/minimax-歌词生成 (Lyrics Generation).md new file mode 100644 index 0000000..67dfcc9 --- /dev/null +++ b/API相关/minimax-歌词生成 (Lyrics Generation).md @@ -0,0 +1,177 @@ +> ## Documentation Index +> Fetch the complete documentation index at: https://platform.minimaxi.com/docs/llms.txt +> Use this file to discover all available pages before exploring further. + +# 歌词生成 (Lyrics Generation) + +> 使用本接口生成歌词,支持完整歌曲创作和歌词编辑/续写。 + + + +## OpenAPI + +````yaml api-reference/music/lyrics/api/openapi.json post /v1/lyrics_generation +openapi: 3.1.0 +info: + title: MiniMax Lyrics Generation API + description: MiniMax 歌词生成 API,支持完整歌曲创作和歌词编辑/续写 + license: + name: MIT + version: 1.0.0 +servers: + - url: https://api.minimaxi.com +security: + - bearerAuth: [] +paths: + /v1/lyrics_generation: + post: + tags: + - Music + summary: 歌词生成 + operationId: generateLyrics + parameters: + - name: Content-Type + in: header + required: true + description: 请求体的媒介类型,请设置为 `application/json`,确保请求数据的格式为 JSON + schema: + type: string + enum: + - application/json + default: application/json + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/GenerateLyricsReq' + required: true + responses: + '200': + description: 成功响应 + content: + application/json: + schema: + $ref: '#/components/schemas/GenerateLyricsResp' +components: + schemas: + GenerateLyricsReq: + type: object + required: + - mode + properties: + mode: + type: string + description: 生成模式。
`write_full_song`:写完整歌曲
`edit`:编辑/续写歌词 + enum: + - write_full_song + - edit + prompt: + type: string + description: 提示词/指令,用于描述歌曲主题、风格或编辑方向。为空时随机生成。 + maxLength: 2000 + lyrics: + type: string + description: 现有歌词内容,仅在 `edit` 模式下有效。可用于续写或修改已有歌词。 + maxLength: 3500 + title: + type: string + description: 歌曲标题。传入后输出将保持该标题不变。 + example: + mode: write_full_song + prompt: 一首关于夏日海边的轻快情歌 + GenerateLyricsResp: + type: object + properties: + song_title: + type: string + description: 生成的歌名。若请求传入 `title` 则保持一致。 + style_tags: + type: string + description: 风格标签,逗号分隔。例如:`Pop, Upbeat, Female Vocals` + lyrics: + type: string + description: >- + 生成的歌词,包含结构标签。可直接用于[音乐生成接口](/api-reference/music-generation)的 + `lyrics` 参数生成歌曲。
支持的结构标签(14种):`[Intro]`, `[Verse]`, + `[Pre-Chorus]`, `[Chorus]`, `[Hook]`, `[Drop]`, `[Bridge]`, + `[Solo]`, `[Build-up]`, `[Instrumental]`, `[Breakdown]`, `[Break]`, + `[Interlude]`, `[Outro]` + base_resp: + $ref: '#/components/schemas/BaseResp' + example: + song_title: 夏日海风的约定 + style_tags: Mandopop, Summer Vibe, Romance, Lighthearted, Beach Pop + lyrics: |- + [Intro] + (Ooh-ooh-ooh) + (Yeah) + 阳光洒满了海面 + + [Verse 1] + 海风轻轻吹拂你发梢 + Smiling face, like a summer dream + 浪花拍打着脚边 + Leaving footprints, you and me + 沙滩上留下我们的笑 + Every moment, a sweet melody + 看着你眼中的闪耀 + Like the stars in the deep blue sea + + [Pre-Chorus] + 你说这感觉多么奇妙 + (So wonderful) + 想要永远停留在这一秒 + (Right here, right now) + 心跳加速,像海浪在奔跑 + + [Chorus] + Oh, 夏日的海边,我们的约定 + 阳光下,你的身影,如此动听 + 微风吹散了烦恼,只留下甜蜜 + 这瞬间,只想和你,永远在一起 + (永远在一起) + + [Verse 2] + ... + base_resp: + status_code: 0 + status_msg: success + BaseResp: + type: object + description: 状态码及详情 + properties: + status_code: + type: integer + description: |- + 状态码及其分别含义如下: + + `0`: 请求成功 + + `1002`: 触发限流,请稍后再试 + + `1004`: 账号鉴权失败,请检查 API-Key 是否填写正确 + + `1008`: 账号余额不足 + + `1026`: 输入包含敏感内容 + + `2013`: 传入参数异常,请检查入参是否按要求填写 + + `2049`: 无效的api key + + 更多内容可查看 [错误码查询列表](/api-reference/errorcode) 了解详情 + status_msg: + type: string + description: 具体错误详情 + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT + description: |- + `HTTP: Bearer Auth` + - Security Scheme Type: http + - HTTP Authorization Scheme: Bearer API_key,用于验证账户信息,可在 [账户管理>接口密钥](https://platform.minimaxi.com/user-center/basic-information/interface-key) 中查看。 + +```` +```` \ No newline at end of file diff --git a/API相关/minimax-音乐生成 (Music Generation).md b/API相关/minimax-音乐生成 (Music Generation).md new file mode 100644 index 0000000..5755716 --- /dev/null +++ b/API相关/minimax-音乐生成 (Music Generation).md @@ -0,0 +1,209 @@ +> ## Documentation Index +> Fetch the complete documentation index at: https://platform.minimaxi.com/docs/llms.txt +> Use this file to discover all available pages before exploring further. + +# 音乐生成 (Music Generation) + +> 使用本接口,输入歌词和歌曲描述,进行歌曲生成。 + + + +## OpenAPI + +````yaml api-reference/music/api/openapi.json post /v1/music_generation +openapi: 3.1.0 +info: + title: MiniMax Music Generation API + description: >- + MiniMax music generation API with support for creating music from text + prompts and lyrics + license: + name: MIT + version: 1.0.0 +servers: + - url: https://api.minimaxi.com +security: + - bearerAuth: [] +paths: + /v1/music_generation: + post: + tags: + - Music + summary: Music Generation + operationId: generateMusic + parameters: + - name: Content-Type + in: header + required: true + description: 请求体的媒介类型,请设置为 `application/json`,确保请求数据的格式为 JSON + schema: + type: string + enum: + - application/json + default: application/json + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/GenerateMusicReq' + required: true + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/GenerateMusicResp' +components: + schemas: + GenerateMusicReq: + type: object + required: + - model + - lyrics + properties: + model: + type: string + description: 使用的模型名称,可选 `music-2.5` + enum: + - music-2.5 + prompt: + type: string + description: >- + 音乐的描述,用于指定风格、情绪和场景。例如“流行音乐, 难过, 适合在下雨的晚上”。
注意:对于 + `music-2.5`:可选,长度限制为 [0, 2000] 个字符。对于非 `music-2.5` 模型:必填,长度限制为 [10, + 2000] 个字符 + maxLength: 2000 + lyrics: + type: string + description: >- + 歌曲的歌词。使用 `\n` 分隔每行。你可以在歌词中加入 `[Intro]`, `[Verse]`, `[Pre Chorus]`, + `[Chorus]`, `[Interlude]`, `[Bridge]`, `[Outro]`, `[Post Chorus]`, + `[Transition]`, `[Break]`, `[Hook]`, `[Build Up]`, `[Inst]`, + `[Solo]` 等结构标签来优化生成的音乐结构。
长度限制:对于 `music-2.5`:[1, 3500] 个字符。对于非 + `music-2.5` 模型:[10, 3500] 个字符 + minLength: 1 + maxLength: 3500 + stream: + type: boolean + description: 是否使用流式传输,默认为 `false` + default: false + output_format: + type: string + description: >- + 音频的返回格式,可选值为 `url` 或 `hex`,默认为 `hex`。当 `stream` 为 `true` 时,仅支持 `hex` + 格式。注意:url 的有效期为 24 小时,请及时下载 + enum: + - url + - hex + default: hex + audio_setting: + $ref: '#/components/schemas/AudioSetting' + aigc_watermark: + type: boolean + description: '是否在音频末尾添加水印,默认为 `false`。仅在非流式 (`stream: false`) 请求时生效' + example: + model: music-2.5 + prompt: 独立民谣,忧郁,内省,渴望,独自漫步,咖啡馆 + lyrics: |- + [verse] + 街灯微亮晚风轻抚 + 影子拉长独自漫步 + 旧外套裹着深深忧郁 + 不知去向渴望何处 + [chorus] + 推开木门香气弥漫 + 熟悉的角落陌生人看 + audio_setting: + sample_rate: 44100 + bitrate: 256000 + format: mp3 + GenerateMusicResp: + type: object + properties: + data: + $ref: '#/components/schemas/MusicData' + base_resp: + $ref: '#/components/schemas/BaseResp' + example: + data: + audio: hex编码的音频数据 + status: 2 + trace_id: 04ede0ab069fb1ba8be5156a24b1e081 + extra_info: + music_duration: 25364 + music_sample_rate: 44100 + music_channel: 2 + bitrate: 256000 + music_size: 813651 + analysis_info: null + base_resp: + status_code: 0 + status_msg: success + AudioSetting: + type: object + description: 音频输出配置 + properties: + sample_rate: + type: integer + description: 采样率。可选值:`16000`, `24000`, `32000`, `44100` + bitrate: + type: integer + description: 比特率。可选值:`32000`, `64000`, `128000`, `256000` + format: + type: string + description: 音频编码格式。 + enum: + - mp3 + - wav + - pcm + MusicData: + type: object + properties: + status: + type: integer + description: |- + 音乐合成状态: + 1: 合成中 + 2: 已完成 + audio: + type: string + description: 当 `output_format` 为 `hex` 时返回,是音频文件的 16 进制编码字符串 + BaseResp: + type: object + description: 状态码及详情 + properties: + status_code: + type: integer + description: |- + 状态码及其分别含义如下: + + `0`: 请求成功 + + `1002`: 触发限流,请稍后再试 + + `1004`: 账号鉴权失败,请检查 API-Key 是否填写正确 + + `1008`: 账号余额不足 + + `1026`: 图片描述涉及敏感内容 + + `2013`: 传入参数异常,请检查入参是否按要求填写 + + `2049`: 无效的api key + + 更多内容可查看 [错误码查询列表](/api-reference/errorcode) 了解详情 + status_msg: + type: string + description: 具体错误详情 + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT + description: |- + `HTTP: Bearer Auth` + - Security Scheme Type: http + - HTTP Authorization Scheme: Bearer API_key,用于验证账户信息,可在 [账户管理>接口密钥](https://platform.minimaxi.com/user-center/basic-information/interface-key) 中查看。 + +```` \ No newline at end of file diff --git a/Capybara music/lyrics/专注时刻_1770368018.txt b/Capybara music/lyrics/专注时刻_1770368018.txt new file mode 100644 index 0000000..2018444 --- /dev/null +++ b/Capybara music/lyrics/专注时刻_1770368018.txt @@ -0,0 +1,8 @@ +[verse] +专注时刻我来了, +咔咔在思考,世界多美妙。 +[chorus] +咔咔咔咔,静心感受每一秒, +在这宁静中,找到内心的微笑。 +[outro] +(水花声...) \ No newline at end of file diff --git a/Capybara music/lyrics/出去撒点野_1770367350.txt b/Capybara music/lyrics/出去撒点野_1770367350.txt new file mode 100644 index 0000000..96550ac --- /dev/null +++ b/Capybara music/lyrics/出去撒点野_1770367350.txt @@ -0,0 +1,11 @@ +[verse] +咔咔咔咔去探险,草地上打个滚 +阳光洒满身,心情像彩虹 +[chorus] +出去撒点野,自由自在 +咔咔的快乐,谁也挡不住 +[bridge] +风吹过树梢,鸟儿在歌唱 +咔咔的笑声,回荡在山岗 +[outro] +(咔咔的喘息声...) \ No newline at end of file diff --git a/Capybara music/lyrics/卡皮巴拉快乐水.txt b/Capybara music/lyrics/卡皮巴拉快乐水.txt new file mode 100644 index 0000000..7566e69 --- /dev/null +++ b/Capybara music/lyrics/卡皮巴拉快乐水.txt @@ -0,0 +1,50 @@ +卡皮巴拉 +卡皮巴拉 +卡皮巴拉 +啦啦啦啦 + +卡皮巴拉趴地上 +一动不动好嚣张 +心里其实在上网 +刷到我就笑出响 (哈哈哈) + +卡皮巴拉 巴拉巴拉 +压力来啦 它说算啦 +一点不慌 就是躺啦 +世界太吵 它在发呆呀 +卡皮巴拉 巴拉巴拉 +烦恼滚啦 快乐到家 +跟着一起 嗷嗷嗷啊 +脑子空空 只剩它 + +卡皮巴拉喝奶茶 +珍珠全都被它夸 +“生活苦就多加糖” +说完继续装木桩 (嘿呀) + +卡皮巴拉 巴拉巴拉 +压力来啦 它说算啦 +一点不慌 就是躺啦 +世界太吵 它在发呆呀 +卡皮巴拉 巴拉巴拉 +烦恼滚啦 快乐到家 +跟着一起 嗷嗷嗷啊 +脑子空空 只剩它 + +今天不卷 +只卷床单 (嘿) +今天不忙 +只忙可爱 +跟它一起 +放空发呆 +一二三 +什么也不想 + +卡皮巴拉 巴拉巴拉 +压力来啦 它说算啦 +一点不慌 就是躺啦 +世界太吵 它在发呆呀 +卡皮巴拉 巴拉巴拉 +烦恼滚啦 快乐到家 +跟着一起 嗷嗷嗷啊 +脑子空空 只剩它 \ No newline at end of file diff --git a/Capybara music/lyrics/卡皮巴拉快乐营业.txt b/Capybara music/lyrics/卡皮巴拉快乐营业.txt new file mode 100644 index 0000000..bfd0bfb --- /dev/null +++ b/Capybara music/lyrics/卡皮巴拉快乐营业.txt @@ -0,0 +1,58 @@ +早八打工人 +心却躺平人 +桌面壁纸换上 +卡皮巴拉一整屏 (嘿) + +它坐在河边 +像个退休中年 +我卷生卷死 +它只发呆发呆再发呆 +办公室拉满 +我是表情包主演 +老板在开会 +我在偷看它泡温泉 + +卡皮巴拉 卡皮巴拉 拉 +看你就把压力清空啦 (啊对对对) +谁骂我韭菜我就回他 +我已经转职水豚啦 +卡皮巴拉 卡皮巴拉 拉 +世界很吵你超安静呀 (好家伙) +人生好难打 打不过就挂 +挂一张你 我立刻满血复活啦 + +朋友失恋了 +眼泪刷刷往下掉 +我发一张图 +“兄弟先学习一下松弛感” +外卖又涨价 +工位又多一堆活 +但你眯着眼 +像在说“一切都还来得及” + +卡皮巴拉 卡皮巴拉 拉 +看你就把压力清空啦 (真香啊) +谁骂我社恐我就说他 +我只会和水豚社交啊 +卡皮巴拉 卡皮巴拉 拉 +世界很丧你很治愈呀 (稳住别浪) +生活再刮风 风大也不怕 +抱紧你的图 我就自带防护罩 + +升职加薪没我 +摸鱼排行榜有我 (懂的都懂) +卷不赢卷王 +那我就卷成你同款发呆模样 +左手放空 +右手放松 +嘴里默念八个大字 +“开心就好 随缘躺平” + +卡皮巴拉 卡皮巴拉 拉 +看你就把压力清空啦 (我悟了) +谁劝我上进我就回他 +“先学会像水豚活着吧” +卡皮巴拉 卡皮巴拉 拉 +世界很吵你超安静呀 (哈人啊) +如果有一天 run 不动啦 +我就去投胎 做一只卡皮巴拉 \ No newline at end of file diff --git a/Capybara music/lyrics/卡皮巴拉快乐趴.txt b/Capybara music/lyrics/卡皮巴拉快乐趴.txt new file mode 100644 index 0000000..e7e901d --- /dev/null +++ b/Capybara music/lyrics/卡皮巴拉快乐趴.txt @@ -0,0 +1,55 @@ +今天不上班 +卡皮巴拉躺平在沙滩 +小小太阳帽 +草帽底下梦见一整片菜园 (好香哦) +一口咔咔青菜 +两口嘎嘎胡萝卜 +吃着吃着打个嗝 +“我是不是一只蔬菜发动机?” + +卡皮巴拉啦啦啦 +快乐像病毒一样传染呀 +你一笑 它一哈 +全场都在哈哈哈 +卡皮巴拉吧啦吧 +烦恼直接按下删除呀 +一起躺 平平趴 +世界马上变得好融化 + +同桌小鸭鸭 +排队要跟它合个影 +河马举个牌 +“主播别跑看这边一点” (比个耶) +它说“别催我 +我在加载快乐进度条” +百分之一百满格 +“叮——情绪已经自动修复” + +卡皮巴拉啦啦啦 +快乐像病毒一样传染呀 +你一笑 它一哈 +全场都在哈哈哈 +卡皮巴拉吧啦吧 +烦恼直接按下删除呀 +一起躺 平平趴 +世界马上变得好融化 + +作业山太高 +先发一张可爱自拍 +配文写: +“今天也被温柔的小动物拯救了嗷” (冲呀) +心情掉电时 +就喊出那个暗号—— +“三二一 一起喊” +“卡皮巴拉 拯救我!” + +卡皮巴拉啦啦啦 +快乐像病毒一样传染呀 +你一笑 它一哈 +全场都在哈哈哈 +卡皮巴拉吧啦吧 +烦恼直接按下删除呀 +小朋友 大朋友 +跟着一起摇摆唱起歌 +卡皮巴拉 卡皮巴拉 +明天继续来给你治愈呀 \ No newline at end of file diff --git a/Capybara music/lyrics/卡皮巴拉蹦蹦蹦.txt b/Capybara music/lyrics/卡皮巴拉蹦蹦蹦.txt new file mode 100644 index 0000000..2a22643 --- /dev/null +++ b/Capybara music/lyrics/卡皮巴拉蹦蹦蹦.txt @@ -0,0 +1,44 @@ +卡皮巴拉 +啦啦啦啦 +卡皮巴拉 +啦啦啦啦 + +卡皮巴拉 蹦蹦蹦 +一整天都 在发疯 +卡皮巴拉 转一圈 +左一脚 右一脚 (嘿) + +卡皮巴拉 蹦蹦蹦 +洗脑节奏 响空中 +卡皮巴拉 不要停 +跟着我 一起疯 + +一口菜叶 卡一巴 +两口草莓 巴一拉 +三口西瓜 啦一啦 +嘴巴圆圆 哈哈哈 (哦耶) + +卡皮巴拉 蹦蹦蹦 +一整天都 在发疯 +卡皮巴拉 转一圈 +左一脚 右一脚 (嘿) + +卡皮巴拉 蹦蹦蹦 +洗脑节奏 响空中 +卡皮巴拉 不要停 +跟着我 一起疯 + +卡皮 卡皮 巴拉巴拉 +巴拉 巴拉 卡皮卡皮 (嘿嘿) +听我 听我 跟着跟着 +一句 一句 重复重复 + +卡皮巴拉 蹦蹦蹦 +一整天都 在发疯 +卡皮巴拉 转一圈 +左一脚 右一脚 (嘿) + +卡皮巴拉 蹦蹦蹦 +洗脑节奏 响空中 +卡皮巴拉 到天黑 +明天起床 继续疯 \ No newline at end of file diff --git a/Capybara music/lyrics/泡个热水澡_1770366369.txt b/Capybara music/lyrics/泡个热水澡_1770366369.txt new file mode 100644 index 0000000..164ded7 --- /dev/null +++ b/Capybara music/lyrics/泡个热水澡_1770366369.txt @@ -0,0 +1,8 @@ +[verse] +躺在浴缸里 水汽氤氲起 +闭上眼感受 温暖包围我 +[chorus] +咔咔咔咔 我是咔咔 +泡澡放松 快乐不假 +[outro] +(水花声...) \ No newline at end of file diff --git a/Capybara music/lyrics/洗脑神曲_1770368465.txt b/Capybara music/lyrics/洗脑神曲_1770368465.txt new file mode 100644 index 0000000..b830577 --- /dev/null +++ b/Capybara music/lyrics/洗脑神曲_1770368465.txt @@ -0,0 +1,10 @@ +[verse] +咔咔咔咔,洗脑神曲来啦 +[chorus] +洗脑洗脑,咔咔的节奏 +[verse] +跟着咔咔,摇摆身体 +[chorus] +洗脑洗脑,咔咔的旋律 +[outro] +(咔咔的笑声...) \ No newline at end of file diff --git a/Capybara music/lyrics/睡个好觉_1770371532.txt b/Capybara music/lyrics/睡个好觉_1770371532.txt new file mode 100644 index 0000000..8450183 --- /dev/null +++ b/Capybara music/lyrics/睡个好觉_1770371532.txt @@ -0,0 +1,8 @@ +[verse] +闭上眼睛 深呼吸 +星星点灯 梦里飞 +[chorus] +咔咔咔咔 好梦来 +轻轻摇摆 梦中海 +[outro] +(轻柔的风声...) \ No newline at end of file diff --git a/Capybara music/专注时刻_1770368018.mp3 b/Capybara music/专注时刻_1770368018.mp3 new file mode 100644 index 0000000..1b640df Binary files /dev/null and b/Capybara music/专注时刻_1770368018.mp3 differ diff --git a/Capybara music/出去撒点野_1770367350.mp3 b/Capybara music/出去撒点野_1770367350.mp3 new file mode 100644 index 0000000..0bcf350 Binary files /dev/null and b/Capybara music/出去撒点野_1770367350.mp3 differ diff --git a/Capybara music/卡皮巴拉快乐水.mp3 b/Capybara music/卡皮巴拉快乐水.mp3 new file mode 100644 index 0000000..7714d8c Binary files /dev/null and b/Capybara music/卡皮巴拉快乐水.mp3 differ diff --git a/Capybara music/卡皮巴拉快乐营业.mp3 b/Capybara music/卡皮巴拉快乐营业.mp3 new file mode 100644 index 0000000..9870172 Binary files /dev/null and b/Capybara music/卡皮巴拉快乐营业.mp3 differ diff --git a/Capybara music/卡皮巴拉快乐趴.mp3 b/Capybara music/卡皮巴拉快乐趴.mp3 new file mode 100644 index 0000000..911f4a9 Binary files /dev/null and b/Capybara music/卡皮巴拉快乐趴.mp3 differ diff --git a/Capybara music/卡皮巴拉蹦蹦蹦.mp3 b/Capybara music/卡皮巴拉蹦蹦蹦.mp3 new file mode 100644 index 0000000..67b8465 Binary files /dev/null and b/Capybara music/卡皮巴拉蹦蹦蹦.mp3 differ diff --git a/Capybara music/泡个热水澡_1770366369.mp3 b/Capybara music/泡个热水澡_1770366369.mp3 new file mode 100644 index 0000000..5e9faaa Binary files /dev/null and b/Capybara music/泡个热水澡_1770366369.mp3 differ diff --git a/Capybara music/洗脑神曲_1770368465.mp3 b/Capybara music/洗脑神曲_1770368465.mp3 new file mode 100644 index 0000000..db7f782 Binary files /dev/null and b/Capybara music/洗脑神曲_1770368465.mp3 differ diff --git a/Capybara music/睡个好觉_1770371532.mp3 b/Capybara music/睡个好觉_1770371532.mp3 new file mode 100644 index 0000000..9985dd3 Binary files /dev/null and b/Capybara music/睡个好觉_1770371532.mp3 differ diff --git a/HOW_TO_SET_AUTO_RUN.md b/HOW_TO_SET_AUTO_RUN.md new file mode 100644 index 0000000..8084e5a --- /dev/null +++ b/HOW_TO_SET_AUTO_RUN.md @@ -0,0 +1,17 @@ +# 🛠️ 设置指引 + +您好,由于当前模式无法直接对话,我通过此文档回复您。 + +关于您截图中的选项,请选择 **第一项**: + +### 👉 **Open Antigravity User Settings (Ctrl+,)** + +--- + +### 后续步骤: +1. 点击进入设置页面。 +2. 在顶部的搜索框中输入:**`auto run`** 或 **`security`**。 +3. 找到类似 **"Terminal: Auto Run"** 或 **"Security: Approvals"** 的选项。 +4. 将其修改为 **"Always Allow"** (总是允许) 或 **"Trust"** (信任)。 + +这样我就能自动执行测试任务,无需您频繁确认了。感谢配合!🚀 diff --git a/PRD.md b/PRD.md index ffaf9da..421f9df 100644 --- a/PRD.md +++ b/PRD.md @@ -310,17 +310,19 @@ Airhub 采用 **双层主题色体系**,确保品牌统一性的同时支持 ### 字体规范 -| 用途 | 字体 | 字重 | -|------|------|------| -| **正文/UI** | Inter | 400-600 | -| **Logo/像素元素** | Press Start 2P | 400 | -| **回退字体栈** | -apple-system, BlinkMacSystemFont, Roboto | - | +| 用途 | 字体 | 字重 | 说明 | +|------|------|------|------| +| **标题/Display** | Outfit | 500-700 | 现代几何无衬线,略带圆润,兼具科技感与友好感 | +| **正文/UI** | DM Sans | 400-600 | 几何无衬线,干净利落,阅读体验优秀 | +| **Logo/像素元素** | Press Start 2P | 400 | 像素风复古字体 | +| **回退字体栈** | -apple-system, BlinkMacSystemFont, Roboto | - | 系统字体兜底(含中文) | -> **为什么选择 Inter?** -> - 专为数字屏幕设计,高可读性 -> - 被 GitHub、Figma、Linear 等知名产品使用 -> - 支持多语言,中英文混排效果好 -> - 免费开源,无授权问题 +> **字体选择理念 (遵循 frontend-design 规范)** +> - 拒绝 Inter/Arial/Roboto 等"AI 味"通用字体 +> - Display + Body 双字体搭配,层次分明 +> - Outfit 的圆润几何感与卡皮巴拉的友好气质匹配 +> - DM Sans 比 Inter 更有个性,同时保持极佳可读性 +> - 两者均为 Google Fonts 免费开源字体 --- diff --git a/agent-manage.html b/agent-manage.html index f34fbd7..605d4c6 100644 --- a/agent-manage.html +++ b/agent-manage.html @@ -1,4 +1,4 @@ - + @@ -7,7 +7,7 @@ content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"> Airhub - 角色记忆 - + + + + + +
+
+
+
+ + + + + + + +
+ +
+
正在为您编曲...
+
+
+ +
+
+
+
+
+
+ +
+
+ +
+
+
+
+ + +
+
+
+ 生成音乐后
点我看歌词
+
+
+
+
+ +
+ + + + + + + + + + + + +
+
+
+ + +
+ + + 0:00 + + 0:00 +
+ + + + + +
+ + +
+ +
+
🛁
+
Chill Lofi
+
慵懒 · 治愈 · 水声
+
+ + +
+
🏃
+
Happy Funk
+
活力 · 奔跑 · 阳光
+
+ + +
+
💤
+
Deep Sleep
+
白噪音 · 助眠 · 梦境
+
+ + +
+
🧠
+
Focus Flow
+
心流 · 专注 · 效率
+
+ + +
+
🎁
+
盲盒惊喜
+
AI 随机生成神曲
+
+ + +
+
+
自由创作
+
输入灵感 · 生成音乐
+
+
+ + +
+
+ + +
+ +
+ + + + + + + + + + + + + + \ No newline at end of file diff --git a/notifications.html b/notifications.html index 503c34d..4696546 100644 --- a/notifications.html +++ b/notifications.html @@ -1,4 +1,4 @@ - + @@ -7,7 +7,7 @@ content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"> Airhub - 消息通知 - + -
-
📱 iPhone 16 (393 × 852)
+
+ +
+ +
+
设备
+
+ + + + +
+
-
- - - -
- -
-
- + +
+
页面
+
+ + + + + + + + + + + + + + + + + + + + +
-

- 💡 这是 iPhone 真实尺寸预览。
- 修改代码后刷新此页面即可看到更新效果。 -

+ +
+
+ iPhone 12 Pro +  ·  + 390 × 844 +  ·  + index.html +
+ +
+
+ +
+
+ + +
- \ No newline at end of file + diff --git a/privacy.html b/privacy.html index da551f7..447443c 100644 --- a/privacy.html +++ b/privacy.html @@ -1,4 +1,4 @@ - + @@ -7,7 +7,7 @@ content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"> Airhub - 隐私政策 - +