Files
efrilmandClaude Opus 5.5 f5e3075205 EnakPoint & EnakCoin wallet, PIN, outlet, order, community, reward
- Wallet EnakPoint & EnakCoin: beranda, riwayat, saldo kedaluwarsa, kode
  bayar, tukar EnakCoin, transfer
- PIN (buat, ganti, lupa), outlet, riwayat order, game spin
- Halaman community, webview, coming soon, dan ikon menu beranda baru
- Redesign Reward Saya: kartu tiket, filter chip, detail kode + QR
- Hapus customer point loader, menu, dan checkout lama
- Base URL ke enaklo-pos-api.altru.id

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 01:43:22 +07:00

132 lines
4.8 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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,
);
}
}