Chat-screen performance (customer + mitra): - Parent screens have zero `ref.watch` — only `ref.listen` for side effects - Body extracted into its own `ConsumerStatefulWidget`; AppBar parts split into narrow `.select` consumers (mode, sensitivity, timer) - Per-second timer ticks routed to dedicated providers (`chatRemainingSecondsProvider` + new `mitraChatRemainingSecondsProvider`) so WS `session_tick` frames don't invalidate the rest of the chat state Dispose-in-ref bug fix: - `home_screen.dart`, `payment_screen.dart`, `mitra_chat_screen.dart` — ref-using cleanup moved from `dispose()` to `deactivate()`. Modern Riverpod invalidates `ref` the moment `dispose()` runs; the resulting silent error corrupts the widget-tree finalize and the next screen appears frozen - `halo_lints` package added at repo root with `no_ref_in_dispose` rule to catch this pattern in CI / IDE analysis - `custom_lint` activated in both apps' `analysis_options.yaml` (was installed but never wired in — also brings `riverpod_lint`'s `avoid_ref_inside_state_dispose` online) - CLAUDE.md Pitfalls section added to client_app + mitra_app Phase 4 §3 retryable blast-failure (Option A): - Backend `expirePairingRequest` + all-rejected use `recordIntermediateFailure` instead of `failPaymentSession` so the payment session stays `confirmed` for re-blast - WS `pairing_failed` payload carries `is_terminal: false` on the retryable paths; client parses the flag and exposes `retryBlast()` - "Coba cari lagi" CTA on S7 Timeout now re-blasts on the same payment - Pairing service test updated to reflect the new semantics Customer waiting-payment screen navigation patch: - `_navigateTerminal` uses `Future.microtask` + `addPostFrameCallback` redundancy after a release-mode bug where polling stopped but `context.go` never fired, leaving the screen visually stuck on "menunggu pembayaran" See requirement/resume-2026-05-15.md for next-day pickup checklist (mitra release rebuild + S21 Ultra install + retest is the gating item). Bundles unrelated in-flight Phase 4 §2.x work that was already on disk (ESP screen removal, USP one-time gate scaffolding, bestie-availability public route, OTP service edits, Maestro flow tweaks) — kept together to avoid a partial-rebase mess. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
82 lines
2.6 KiB
Dart
82 lines
2.6 KiB
Dart
import 'package:flutter/material.dart';
|
|
import '../../../core/theme/halo_tokens.dart';
|
|
|
|
/// Floating expired banner shown above the chat input when the session
|
|
/// timer has hit zero but the session is still in `closing` grace.
|
|
///
|
|
/// Mirrors Figma `v3.jsx::HBChatExpiredBanner` (line 423): brand-pink
|
|
/// background, white text, `⏰` icon, "habis nih... mau lanjutin curhat
|
|
/// sama {name}?" copy, white `perpanjang` button.
|
|
class ChatExpiredBanner extends StatelessWidget {
|
|
final String mitraName;
|
|
final VoidCallback onExtend;
|
|
|
|
const ChatExpiredBanner({
|
|
super.key,
|
|
required this.mitraName,
|
|
required this.onExtend,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Container(
|
|
margin: const EdgeInsets.fromLTRB(12, 0, 12, 8),
|
|
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
|
|
decoration: BoxDecoration(
|
|
color: HaloTokens.brand,
|
|
borderRadius: BorderRadius.circular(12),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: HaloTokens.brand.withValues(alpha: 0.31),
|
|
blurRadius: 16,
|
|
offset: const Offset(0, 4),
|
|
),
|
|
],
|
|
),
|
|
child: Row(
|
|
children: [
|
|
const Text('⏰', style: TextStyle(fontSize: 16)),
|
|
const SizedBox(width: 10),
|
|
Expanded(
|
|
child: RichText(
|
|
text: TextSpan(
|
|
style: const TextStyle(
|
|
fontFamily: HaloTokens.fontBody,
|
|
fontSize: 12.5,
|
|
height: 1.4,
|
|
color: Colors.white,
|
|
),
|
|
children: [
|
|
const TextSpan(
|
|
text: 'habis nih...',
|
|
style: TextStyle(fontWeight: FontWeight.w600),
|
|
),
|
|
TextSpan(text: ' mau lanjutin curhat sama $mitraName?'),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
ElevatedButton(
|
|
onPressed: onExtend,
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.white,
|
|
foregroundColor: HaloTokens.brand,
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(999)),
|
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
|
minimumSize: const Size(0, 32),
|
|
elevation: 0,
|
|
textStyle: const TextStyle(
|
|
fontFamily: HaloTokens.fontBody,
|
|
fontSize: 11.5,
|
|
fontWeight: FontWeight.w700,
|
|
),
|
|
),
|
|
child: const Text('perpanjang'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|