Files
enaklo-flutter/lib/presentation/pages/reward/widgets/redemption_detail_sheet.dart
T
efrilm 38850b281f
Build & Deploy iOS to TestFlight / build-and-deploy (push) Canceled after 0s
feat: integrate enakgame and voucher
2026-10-10 02:33:16 +07:00

211 lines
6.3 KiB
Dart

import 'package:flutter/material.dart';
import '../../../../common/extension/extension.dart';
import '../../../../common/theme/theme.dart';
import '../../../../domain/wallet/wallet.dart';
import '../../wallet/wallet_ui.dart';
import '../reward_ui.dart';
/// Detail voucher milik customer. Untuk memakai voucher, customer
/// menunjukkan layar ini ke kasir; POS belum bisa menandai voucher terpakai.
class RedemptionDetailSheet extends StatelessWidget {
const RedemptionDetailSheet({super.key, required this.redemption});
final VoucherRedemption redemption;
static Future<void> show(BuildContext context, VoucherRedemption redemption) {
return showModalBottomSheet<void>(
context: context,
isScrollControlled: true,
backgroundColor: AppColor.white,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
builder: (_) => RedemptionDetailSheet(redemption: redemption),
);
}
@override
Widget build(BuildContext context) {
final faceValue = voucherFaceValueLabel(
redemption.voucherType,
redemption.faceValue,
);
return ConstrainedBox(
constraints: BoxConstraints(
maxHeight: MediaQuery.sizeOf(context).height * 0.85,
),
child: SafeArea(
top: false,
child: SingleChildScrollView(
padding: const EdgeInsets.fromLTRB(20, 12, 20, 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Center(
child: Container(
width: 40,
height: 4,
decoration: BoxDecoration(
color: AppColor.border,
borderRadius: BorderRadius.circular(2),
),
),
),
const SizedBox(height: 20),
_buildHeader(),
const SizedBox(height: 20),
_buildStatusContent(),
const SizedBox(height: 20),
if (redemption.createdAt != null)
_InfoRow(
label: 'Ditukar pada',
value: formatWibDateTime(redemption.createdAt!),
),
if (redemption.status == VoucherRedemptionStatus.completed &&
redemption.codeExpiresAt != null)
_InfoRow(
label: 'Berlaku sampai',
value: formatWibDateTime(redemption.codeExpiresAt!),
),
_InfoRow(
label: 'EnakPoint ditukar',
value: redemption.pointCost.thousandFormat,
),
if (faceValue != null)
_InfoRow(label: 'Nilai voucher', value: faceValue),
],
),
),
),
);
}
Widget _buildHeader() {
final status = redemption.status;
return Row(
children: [
Container(
width: 56,
height: 56,
alignment: Alignment.center,
decoration: BoxDecoration(
color: AppColor.primary.withOpacity(0.06),
borderRadius: BorderRadius.circular(14),
),
child: VoucherImage(url: redemption.voucherImageUrl, size: 40),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
voucherTypeLabel(
redemption.voucherType,
redemption.faceValue,
).toUpperCase(),
style: AppStyle.xs.copyWith(
color: AppColor.textSecondary,
fontWeight: FontWeight.w700,
letterSpacing: 0.6,
),
),
const SizedBox(height: 2),
Text(
redemption.voucherName,
style: AppStyle.xl.copyWith(fontWeight: FontWeight.w700),
),
],
),
),
if (status != VoucherRedemptionStatus.unknown) ...[
const SizedBox(width: 8),
VoucherRedemptionStatusBadge(status: status),
],
],
);
}
Widget _buildStatusContent() {
switch (redemption.status) {
case VoucherRedemptionStatus.completed:
return redemption.code == null
? const VoucherNoCodeNote()
: VoucherCodeBox(code: redemption.code!);
case VoucherRedemptionStatus.pending:
return const _Note(
icon: Icons.hourglass_top_rounded,
color: AppColor.warning,
message:
'Voucher sedang diproses. EnakPoint sudah terpotong; cek lagi '
'nanti.',
);
case VoucherRedemptionStatus.failed:
return const _Note(
icon: Icons.undo_rounded,
color: AppColor.error,
message: 'Gagal, EnakPoint sudah dikembalikan.',
);
case VoucherRedemptionStatus.unknown:
return const SizedBox.shrink();
}
}
}
class _Note extends StatelessWidget {
const _Note({required this.icon, required this.color, required this.message});
final IconData icon;
final Color color;
final String message;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: AppColor.background,
borderRadius: BorderRadius.circular(AppValue.borderRadius),
),
child: Row(
children: [
Icon(icon, size: 20, color: color),
const SizedBox(width: 10),
Expanded(
child: Text(
message,
style: AppStyle.sm.copyWith(color: AppColor.textSecondary),
),
),
],
),
);
}
}
class _InfoRow extends StatelessWidget {
const _InfoRow({required this.label, required this.value});
final String label;
final String value;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: Row(
children: [
Text(
label,
style: AppStyle.sm.copyWith(color: AppColor.textSecondary),
),
const Spacer(),
Text(value, style: AppStyle.sm.copyWith(fontWeight: FontWeight.w600)),
],
),
);
}
}