import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:qr_flutter/qr_flutter.dart'; import '../../../../common/extension/extension.dart'; import '../../../../common/theme/theme.dart'; import '../../../../common/ui/painter/dashed_line_painter.dart'; import '../../../../sample/reward_sample_data.dart'; import '../../../components/toast/flushbar.dart'; import '../reward_ui.dart'; class RewardDetailSheet extends StatelessWidget { const RewardDetailSheet({super.key, required this.reward}); final Reward reward; static Future show(BuildContext context, Reward reward) { return showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: AppColor.white, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), builder: (_) => RewardDetailSheet(reward: reward), ); } bool get _isActive => reward.status == RewardStatus.active; @override Widget build(BuildContext context) { 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), if (_isActive && reward.couponCode != null) _CouponBox(code: reward.couponCode!) else _buildInactiveNote(), const SizedBox(height: 20), _InfoRow( label: 'Ditukar pada', value: formatRewardDate(reward.redeemedAt), ), _InfoRow( label: 'Berlaku sampai', value: formatRewardDate(reward.validUntil), valueColor: isRewardExpiringSoon(reward) ? AppColor.warning : null, ), if (reward.usedAt != null) _InfoRow( label: 'Dipakai pada', value: formatRewardDate(reward.usedAt!), ), _InfoRow( label: 'Poin ditukar', value: '${reward.pointsUsed.thousandFormat} poin', ), if (reward.termsAndConditions != null) ...[ const SizedBox(height: 16), Text( 'Syarat & Ketentuan', style: AppStyle.md.copyWith(fontWeight: FontWeight.w700), ), const SizedBox(height: 6), Text( reward.termsAndConditions!, style: AppStyle.sm.copyWith( color: AppColor.textSecondary, height: 1.5, ), ), ], ], ), ), ), ); } Widget _buildHeader() { return Row( children: [ Container( width: 56, height: 56, alignment: Alignment.center, decoration: BoxDecoration( color: _isActive ? AppColor.primary.withOpacity(0.06) : AppColor.background, borderRadius: BorderRadius.circular(14), ), child: RewardEmoji(reward: reward, size: 28), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( reward.categoryName.toUpperCase(), style: AppStyle.xs.copyWith( color: AppColor.textSecondary, fontWeight: FontWeight.w700, letterSpacing: 0.6, ), ), const SizedBox(height: 2), Text( reward.name, style: AppStyle.xl.copyWith(fontWeight: FontWeight.w700), ), Text( reward.description, style: AppStyle.sm.copyWith(color: AppColor.textSecondary), ), ], ), ), const SizedBox(width: 8), RewardStatusBadge(status: reward.status), ], ); } Widget _buildInactiveNote() { final message = switch (reward.status) { RewardStatus.used => reward.usedAt == null ? 'Reward ini sudah dipakai.' : 'Reward ini sudah dipakai pada ' '${formatRewardDate(reward.usedAt!)}.', _ => 'Masa berlaku reward ini sudah habis pada ' '${formatRewardDate(reward.validUntil)}.', }; return Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: AppColor.background, borderRadius: BorderRadius.circular(AppValue.borderRadius), ), child: Row( children: [ Icon(Icons.info_outline, size: 20, color: reward.status.color), const SizedBox(width: 10), Expanded( child: Text( message, style: AppStyle.sm.copyWith(color: AppColor.textSecondary), ), ), ], ), ); } } class _CouponBox extends StatelessWidget { const _CouponBox({required this.code}); final String code; Future _copy(BuildContext context) async { await Clipboard.setData(ClipboardData(text: code)); if (!context.mounted) return; AppFlushbar.showSuccess(context, 'Kode reward disalin'); } @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( border: Border.all(color: AppColor.border), borderRadius: BorderRadius.circular(16), ), child: Column( children: [ QrImageView( data: code, size: 168, padding: EdgeInsets.zero, backgroundColor: AppColor.white, ), const SizedBox(height: 12), Text( 'Tunjukkan QR atau kode ini ke kasir', style: AppStyle.sm.copyWith(color: AppColor.textSecondary), ), const SizedBox(height: 14), CustomPaint( size: const Size(double.infinity, 1), painter: DashedLinePainter(), ), const SizedBox(height: 6), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Flexible( child: Text( code, style: AppStyle.xl.copyWith( fontWeight: FontWeight.w700, letterSpacing: 2, ), ), ), IconButton( onPressed: () => _copy(context), tooltip: 'Salin kode', icon: const Icon( Icons.copy_rounded, size: 20, color: AppColor.primary, ), ), ], ), ], ), ); } } class _InfoRow extends StatelessWidget { const _InfoRow({required this.label, required this.value, this.valueColor}); final String label; final String value; final Color? valueColor; @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( color: valueColor ?? AppColor.textPrimary, fontWeight: FontWeight.w600, ), ), ], ), ); } }