import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_spinkit/flutter_spinkit.dart'; import '../../../application/auth/auth_bloc.dart'; import '../../../application/pin/pin_change_form/pin_change_form_bloc.dart'; import '../../../common/theme/theme.dart'; import '../../../domain/wallet/wallet.dart'; import '../../../injection.dart'; import '../../components/toast/flushbar.dart'; import '../../router/app_router.gr.dart'; import '../wallet/wallet_ui.dart'; import 'pin_rules.dart'; import 'widgets/pin_code_field.dart'; enum _Step { oldPin, newPin, confirm } /// Ganti PIN (ยง6.3). @RoutePage() class PinChangePage extends StatefulWidget implements AutoRouteWrapper { const PinChangePage({super.key}); @override State createState() => _PinChangePageState(); @override Widget wrappedRoute(BuildContext context) => BlocProvider(create: (_) => getIt(), child: this); } class _PinChangePageState extends State { final TextEditingController _controller = TextEditingController(); _Step _step = _Step.oldPin; String _oldPin = ''; String _newPin = ''; String? _errorText; bool _isLocked = false; @override void dispose() { _controller.dispose(); _oldPin = ''; _newPin = ''; super.dispose(); } void _goTo(_Step step, {String? error}) { setState(() { _step = step; _errorText = error; }); _controller.clear(); } void _onCompleted(String value) { switch (_step) { case _Step.oldPin: _oldPin = value; _goTo(_Step.newPin); case _Step.newPin: final error = value == _oldPin ? 'PIN baru harus berbeda dari PIN lama.' : validateNewPin( value, birthDate: context.read().state.user.birthDate, ); if (error != null) { _goTo(_Step.newPin, error: error); return; } _newPin = value; _goTo(_Step.confirm); case _Step.confirm: if (value != _newPin) { _newPin = ''; _goTo(_Step.newPin, error: 'PIN tidak sama. Silakan ulangi.'); return; } context.read().add( PinChangeFormEvent.submitted( oldPin: _oldPin, pin: _newPin, confirmPin: value, ), ); _controller.clear(); } } void _onFailure(WalletFailure failure) { _oldPin = ''; _newPin = ''; failure.maybeMap( pinInvalid: (_) => _goTo(_Step.oldPin, error: walletFailureMessage(failure)), pinLocked: (_) { _goTo(_Step.oldPin, error: walletFailureMessage(failure)); setState(() => _isLocked = true); }, pinNotSet: (_) => context.router.replace(PinSetupRoute(purpose: PinOtpPurpose.setup)), rejected: (_) => _goTo( _Step.newPin, error: 'PIN baru tidak memenuhi syarat. Coba PIN lain.', ), orElse: () { _goTo(_Step.oldPin); AppFlushbar.showError(context, walletFailureMessage(failure)); }, ); } @override Widget build(BuildContext context) { return BlocConsumer( listenWhen: (p, c) => p.failureOrSuccessOption != c.failureOrSuccessOption, listener: (context, state) { state.failureOrSuccessOption.fold( () {}, (either) => either.fold(_onFailure, (_) { _oldPin = ''; _newPin = ''; AppFlushbar.showSuccess(context, 'PIN berhasil diganti'); context.router.maybePop(); }), ); }, builder: (context, state) { final title = switch (_step) { _Step.oldPin => 'Masukkan PIN lama', _Step.newPin => 'Masukkan PIN baru', _Step.confirm => 'Konfirmasi PIN baru', }; return Scaffold( appBar: AppBar(title: const Text('Ganti PIN')), body: Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: AppStyle.xl.copyWith(fontWeight: FontWeight.w700), ), const SizedBox(height: 8), Text( 'PIN 6 digit, bukan angka berulang, berurutan, atau tanggal ' 'lahir.', style: AppStyle.md.copyWith(color: AppColor.textSecondary), ), const SizedBox(height: 28), PinCodeField( key: ValueKey(_step), controller: _controller, enabled: !state.isSubmitting && !_isLocked, hasError: _errorText != null, onCompleted: _onCompleted, ), const SizedBox(height: 16), if (state.isSubmitting) const Center( child: SpinKitFadingCircle( color: AppColor.primary, size: 28, ), ) else if (_errorText != null) Text( _errorText!, style: AppStyle.md.copyWith( color: AppColor.error, fontWeight: FontWeight.w600, ), ), const SizedBox(height: 8), if (_step == _Step.oldPin) Align( alignment: Alignment.centerRight, child: TextButton( onPressed: () => context.router.replace( PinSetupRoute(purpose: PinOtpPurpose.reset), ), child: Text( 'Lupa PIN', style: AppStyle.md.copyWith( color: AppColor.primary, fontWeight: FontWeight.w700, ), ), ), ), ], ), ), ); }, ); } }