import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:intl/intl.dart'; import '../../../common/api/api_failure.dart'; import '../../../common/extension/extension.dart'; import '../../../common/theme/theme.dart'; import '../../../domain/wallet/wallet.dart'; import '../../components/assets/assets.gen.dart'; /// Pesan ramah untuk customer. [rejectedMessage] dipakai untuk penolakan /// aturan bisnis (`304`), karena `cause` dari server tidak boleh tampil mentah. String walletFailureMessage(WalletFailure failure, {String? rejectedMessage}) { return failure.map( serverError: (value) => value.failure.maybeMap( connectionError: (_) => 'Tidak ada koneksi internet. Coba lagi.', connectionTimeout: (_) => 'Koneksi lambat. Coba lagi.', orElse: () => 'Terjadi kesalahan, coba lagi', ), unexpectedError: (_) => 'Terjadi kesalahan, coba lagi', unauthorized: (_) => 'Sesi kamu sudah berakhir. Silakan masuk lagi.', invalidRequest: (_) => 'Permintaan tidak dapat diproses. Coba lagi.', rejected: (_) => rejectedMessage ?? 'Permintaan tidak dapat diproses.', notFound: (_) => 'Data tidak ditemukan', tooManyRequests: (value) => 'Terlalu sering meminta kode. Coba lagi dalam ' '${value.retryAfterSeconds} detik.', internalError: (_) => 'Terjadi kesalahan, coba lagi', pinNotSet: (_) => 'Kamu belum membuat PIN.', pinInvalid: (value) => value.remainingAttempts == null ? 'PIN salah.' : 'PIN salah, sisa ${value.remainingAttempts} percobaan.', pinLocked: (value) => value.lockedUntil == null ? 'PIN terkunci. Silakan gunakan Lupa PIN.' : 'PIN terkunci sampai ${formatWibTime(value.lockedUntil!)}.', transferBlocked: (value) => value.transferBlockedUntil == null ? 'Transfer sementara ditahan karena PIN baru direset.' : 'Transfer bisa dilakukan lagi pada ' '${formatWibDateTime(value.transferBlockedUntil!)}.', ); } /// Jam WIB, dengan tanggal bila bukan hari ini: "14:30 WIB" / /// "1 Okt 2026 14:30 WIB". String formatWibTime(DateTime time) { final wib = time.toWib; final today = DateTime.now().toWib; final sameDay = wib.year == today.year && wib.month == today.month && wib.day == today.day; return sameDay ? '${wib.toHourMinute} WIB' : formatWibDateTime(time); } String formatWibDateTime(DateTime time) => '${time.toWib.toShortDatetime} WIB'; /// Tanggal kalender WIB dari timestamp, mis. `expires_at`. String formatWibDate(DateTime time) => time.toWib.toDate; /// Nilai rupiah EnakPoint selalu "setara potongan", tidak pernah saldo atau /// uang (§1 aturan 2). String pointDiscountLabel(int discountValue) => 'setara potongan ${discountValue.currencyFormatRp}'; /// Jumlah bertanda: "+875" / "−120" (tanda minus U+2212). String formatSignedAmount(int amount) => amount >= 0 ? '+${amount.thousandFormat}' : '−${amount.abs().thousandFormat}'; Color signedAmountColor(int amount) => amount >= 0 ? AppColor.success : AppColor.error; String formatCountdown(Duration duration) { final seconds = duration.inSeconds < 0 ? 0 : duration.inSeconds; final m = (seconds ~/ 60).toString().padLeft(2, '0'); final s = (seconds % 60).toString().padLeft(2, '0'); return '$m:$s'; } /// Hanya digit, untuk nomor HP dan jumlah. String digitsOnly(String value) => value.replaceAll(RegExp(r'[^0-9]'), ''); /// Tanggal pendek untuk chip filter: "1 Sep 2026". String formatShortDate(DateTime date) => DateFormat('d MMM yyyy', 'id_ID').format(date); /// Menampilkan jumlah dengan pemisah ribuan saat diketik: "10000" → "10.000". /// Baca nilainya dengan [digitsOnly]. class ThousandsInputFormatter extends TextInputFormatter { static const int _maxDigits = 12; @override TextEditingValue formatEditUpdate( TextEditingValue oldValue, TextEditingValue newValue, ) { final digits = digitsOnly(newValue.text); if (digits.isEmpty) return const TextEditingValue(); if (digits.length > _maxDigits) return oldValue; final text = int.parse(digits).thousandFormat; return TextEditingValue( text: text, selection: TextSelection.collapsed(offset: text.length), ); } } /// Ikon 3D EnakPoint / EnakCoin. class WalletCurrencyIcon extends StatelessWidget { const WalletCurrencyIcon({super.key, required this.currency, this.size = 24}); final WalletCurrency currency; final double size; @override Widget build(BuildContext context) { final asset = currency == WalletCurrency.point ? Assets.icons.enakpointIcon : Assets.icons.enakcoinIcon; // Aset aslinya 512–1024px; decode seukuran tampilan saja. final cacheSize = (size * MediaQuery.devicePixelRatioOf(context)).round(); return asset.image( width: size, height: size, fit: BoxFit.contain, cacheWidth: cacheSize, ); } }