Phase 3.1: Complete client_app Riverpod migration (all blocs)

- Migrate SessionClosureBloc → SessionClosureNotifier (@riverpod)
- Migrate PairingBloc → PairingNotifier (@riverpod, WebSocket + timer)
- Migrate ChatBloc → ChatNotifier (@riverpod, WebSocket + message state)
- Remove all flutter_bloc usage from client_app screens and main.dart
- MultiBlocProvider fully removed from client_app
- All screens now use ConsumerWidget/ConsumerStatefulWidget + ref

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-09 14:01:48 +08:00
parent d15b2f05fc
commit bc66bbf50a
12 changed files with 860 additions and 251 deletions

View File

@@ -1,9 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../core/chat/chat_opening_provider.dart';
import '../../../core/chat/session_closure_bloc.dart';
import '../../../core/pairing/pairing_bloc.dart';
import '../../../core/chat/session_closure_notifier.dart';
import '../../../core/pairing/pairing_notifier.dart';
class PricingBottomSheet extends ConsumerWidget {
/// If set, the bottom sheet is in "extension" mode — selecting a tier extends the session.
@@ -16,12 +15,7 @@ class PricingBottomSheet extends ConsumerWidget {
return showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (_) => MultiBlocProvider(
providers: [
BlocProvider.value(value: context.read<PairingBloc>()),
],
child: const PricingBottomSheet(),
),
builder: (_) => const PricingBottomSheet(),
);
}
@@ -30,12 +24,7 @@ class PricingBottomSheet extends ConsumerWidget {
return showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (_) => MultiBlocProvider(
providers: [
BlocProvider.value(value: context.read<SessionClosureBloc>()),
],
child: PricingBottomSheet(extensionSessionId: sessionId),
),
builder: (_) => PricingBottomSheet(extensionSessionId: sessionId),
);
}
@@ -90,7 +79,7 @@ class PricingBottomSheet extends ConsumerWidget {
trailing: const Icon(Icons.arrow_forward_ios, size: 16),
onTap: () {
Navigator.of(context).pop();
_startPairing(context, isFreeTrial: true);
_startPairing(ref, isFreeTrial: true);
},
),
),
@@ -107,14 +96,14 @@ class PricingBottomSheet extends ConsumerWidget {
Navigator.of(context).pop();
if (isExtension) {
_requestExtension(
context,
ref,
sessionId: extensionSessionId!,
durationMinutes: tier.durationMinutes,
price: tier.price,
);
} else {
_startPairing(
context,
ref,
durationMinutes: tier.durationMinutes,
price: tier.price,
);
@@ -130,19 +119,19 @@ class PricingBottomSheet extends ConsumerWidget {
);
}
void _startPairing(BuildContext context, {bool isFreeTrial = false, int? durationMinutes, int? price}) {
context.read<PairingBloc>().add(RequestPairingWithTier(
void _startPairing(WidgetRef ref, {bool isFreeTrial = false, int? durationMinutes, int? price}) {
ref.read(pairingProvider.notifier).requestPairingWithTier(
durationMinutes: durationMinutes,
price: price,
isFreeTrial: isFreeTrial,
));
);
}
void _requestExtension(BuildContext context, {required String sessionId, required int durationMinutes, required int price}) {
context.read<SessionClosureBloc>().add(RequestExtension(
sessionId: sessionId,
void _requestExtension(WidgetRef ref, {required String sessionId, required int durationMinutes, required int price}) {
ref.read(sessionClosureProvider.notifier).requestExtension(
sessionId,
durationMinutes: durationMinutes,
price: price,
));
);
}
}