import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../../core/availability/mitra_availability_notifier.dart'; import '../../../core/constants.dart'; import '../../../core/pairing/pairing_notifier.dart'; /// Shown when a "Curhat lagi" attempt against a specific bestie can't proceed /// — either a 409 `targeted_mitra_offline` response on the targeted POST, or /// one of the intermediate WS events (`returning_chat_timeout`, /// `returning_chat_rejected`). /// /// CTAs: /// - "Chat dengan bestie lain" — only rendered when /// [mitraAvailabilityProvider] reports `available == true` at the time of /// build. Tapping calls [Pairing.fallbackToBlast] (reuses the same payment /// session — no double-charge) and closes the dialog. The caller is expected /// to be the searching screen, which will transition into PairingSearchingData /// and stay put. /// - "Kembali" — pops dialog and routes home. Backend has already audit-logged /// the targeted failure; payment session stays `confirmed` until the sweeper /// expires it. class BestieUnavailableDialog extends ConsumerWidget { final String paymentSessionId; final String mitraName; final TopicSensitivity topicSensitivity; const BestieUnavailableDialog({ super.key, required this.paymentSessionId, required this.mitraName, required this.topicSensitivity, }); /// Convenience: show this dialog and return when it closes. Per project /// memory ("Riverpod ref.listen in build is unsafe"), callers should /// invoke this from `ref.listenManual` callbacks in `initState`, not from /// `build`. static Future show( BuildContext context, { required String paymentSessionId, required String mitraName, required TopicSensitivity topicSensitivity, }) { return showDialog( context: context, barrierDismissible: false, builder: (_) => BestieUnavailableDialog( paymentSessionId: paymentSessionId, mitraName: mitraName, topicSensitivity: topicSensitivity, ), ); } @override Widget build(BuildContext context, WidgetRef ref) { // Snapshot at dialog-open time — we don't keep listening, we just check // whether other bestie are around right now. final availabilityAsync = ref.watch(mitraAvailabilityProvider); final hasOtherAvailable = availabilityAsync.valueOrNull ?? false; return AlertDialog( title: const Text('Bestie sedang tidak online'), content: Text( '$mitraName sedang tidak bisa menerima chat saat ini. ' 'Kamu bisa coba chat dengan bestie lain atau kembali ke beranda.', ), actions: [ TextButton( onPressed: () { // Reset pairing state and route home. Payment session stays // confirmed until sweeper expires it — no extra API call needed. ref.read(pairingProvider.notifier).reset(); Navigator.of(context).pop(); context.go('/home'); }, child: const Text('Kembali'), ), if (hasOtherAvailable) ElevatedButton( onPressed: () { // Close the dialog first, then kick off the fallback. The // searching screen will pick up the new PairingSearchingData // state and render normally (no targeted overlay). Navigator.of(context).pop(); // ignore: discarded_futures ref.read(pairingProvider.notifier).fallbackToBlast( paymentSessionId: paymentSessionId, topicSensitivity: topicSensitivity, ); }, child: const Text('Chat dengan bestie lain'), ), ], ); } }