seaislee1209 066eb8f820 feat: music-creation page + MiniMax API integration + Flutter dev setup
Music Creation Page:
- Vinyl 3D flip to view lyrics, tonearm animation, glow rotation effect
- Circular SVG progress ring, speech bubble feedback, confirm dialog
- Playlist modal, free creation input, lyrics formatting optimization
- MiniMax API real music generation with SSE streaming progress

Backend:
- FastAPI proxy server.py for MiniMax API calls
- Music + lyrics file persistence to Capybara music/ directory
- GET /api/playlist endpoint for auto-building playlist from files

UI/UX Refinements:
- frontend-design skill compliance across all pages
- Glassmorphism effects, modal interactions, scroll tap prevention
- iPhone 12 Pro responsive layout (390x844)

Flutter Development Preparation:
- Installed flutter-expert skill with 6 reference docs
- Added 5 Cursor Rules: official Flutter, clean architecture, UI performance, testing, Dart standards

Assets:
- 9 Capybara music MP3 files + lyrics TXT files
- MiniMax API documentation

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-06 18:23:19 +08:00

260 lines
4.7 KiB
Markdown

# 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<CounterEvent, CounterState> {
CounterBloc() : super(const CounterState(value: 0)) {
on<CounterIncremented>((event, emit) {
emit(state.copyWith(value: state.value + 1));
});
on<CounterDecremented>((event, emit) {
emit(state.copyWith(value: state.value - 1));
});
}
}
```
---
## Cubit (Recommended for Simpler Logic)
```dart
class CounterCubit extends Cubit<int> {
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<CounterBloc, CounterState>(
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<AuthBloc, AuthState>(
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<FormBloc, FormState>(
listener: (context, state) {
if (state.status == FormStatus.success) {
context.pop();
}
},
builder: (context, state) {
return ElevatedButton(
onPressed: state.isValid
? () => context.read<FormBloc>().add(FormSubmitted())
: null,
child: const Text('Submit'),
);
},
);
```
---
## Accessing Bloc Without Rebuilds
```dart
context.read<CounterBloc>().add(CounterIncremented());
```
⚠️ **Never use `watch` inside callbacks**
---
## Async Bloc Pattern (API Calls)
```dart
on<UserRequested>((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<AuthBloc>().state;
if (authState is Unauthenticated) {
return '/login';
}
return null;
}
```
---
## Testing Bloc
```dart
blocTest<CounterBloc, CounterState>(
'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 |