Files
enaklo-flutter/lib/presentation/pages/promo/promo_ui.dart
T
efrilmandClaude Opus 5.5 c35d0eedbe EnakPromo: halaman promo dan detail promo
- Banner promo unggulan, info cara dapat, filter per tipe hadiah, daftar
  promo berlangsung dan segera hadir
- Detail promo: hadiah, periode, outlet, cara dapat, S&K, CTA ke Reward
  Saya atau daftar outlet
- Data contoh mengikuti bentuk campaign backend (POINTS/COINS/REWARD)
- Filter chip Reward Saya dipindah ke komponen bersama FilterBarDelegate

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

141 lines
4.2 KiB
Dart
Raw 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:intl/intl.dart';
import '../../../common/theme/theme.dart';
import '../../../domain/wallet/wallet.dart';
import '../../../sample/promo_sample_data.dart';
import '../../components/assets/assets.gen.dart';
import '../wallet/wallet_ui.dart';
/// Promo aktif dengan sisa hari segini dianggap segera berakhir.
const int promoEndingSoonDays = 3;
extension PromoTypeUi on PromoType {
String get label => switch (this) {
PromoType.point => 'EnakPoint',
PromoType.coin => 'EnakCoin',
PromoType.reward => 'Hadiah',
};
/// Warna aksen untuk teks dan latar tipis; cukup gelap di atas putih.
Color get color => switch (this) {
PromoType.point => AppColor.primary,
PromoType.coin => const Color(0xFFC26A00),
PromoType.reward => AppColor.secondaryDark,
};
/// Latar banner; teks putih di atasnya.
List<Color> get gradient => switch (this) {
PromoType.point => AppColor.primaryGradient,
PromoType.coin => const [Color(0xFFF59E0B), Color(0xFFC2410C)],
PromoType.reward => const [Color(0xFF43A047), Color(0xFF1B5E20)],
};
}
bool isPromoUpcoming(Promo promo) => promo.startDate.isAfter(DateTime.now());
/// Sisa hari kalender sampai [Promo.endDate]; 0 berarti berakhir hari ini.
int promoDaysLeft(Promo promo) {
final now = DateTime.now();
final today = DateTime(now.year, now.month, now.day);
final end = DateTime(
promo.endDate.year,
promo.endDate.month,
promo.endDate.day,
);
return end.difference(today).inDays;
}
bool isPromoEndingSoon(Promo promo) =>
!isPromoUpcoming(promo) && promoDaysLeft(promo) <= promoEndingSoonDays;
/// "12 Okt".
String formatPromoDate(DateTime date) =>
DateFormat('d MMM', 'id_ID').format(date);
/// "1 Okt – 25 Okt 2026".
String promoPeriodLabel(Promo promo) =>
'${formatPromoDate(promo.startDate)} – '
'${DateFormat('d MMM yyyy', 'id_ID').format(promo.endDate)}';
String promoOutletLabel(Promo promo) =>
promo.outlets.isEmpty ? 'Semua outlet' : promo.outlets.join(', ');
/// "Mulai 4 Okt" / "Berakhir 2 hari lagi" / "s/d 25 Okt".
String promoTimeLabel(Promo promo) {
if (isPromoUpcoming(promo)) {
return 'Mulai ${formatPromoDate(promo.startDate)}';
}
final daysLeft = promoDaysLeft(promo);
if (daysLeft <= 0) return 'Berakhir hari ini';
if (daysLeft <= promoEndingSoonDays) return 'Berakhir $daysLeft hari lagi';
return 's/d ${formatPromoDate(promo.endDate)}';
}
/// Ikon 3D sesuai hadiah promo.
class PromoTypeIcon extends StatelessWidget {
const PromoTypeIcon({super.key, required this.type, this.size = 32});
final PromoType type;
final double size;
@override
Widget build(BuildContext context) {
return switch (type) {
PromoType.point => WalletCurrencyIcon(
currency: WalletCurrency.point,
size: size,
),
PromoType.coin => WalletCurrencyIcon(
currency: WalletCurrency.coin,
size: size,
),
PromoType.reward => Assets.icons.enakrewardIcon.image(
width: size,
height: size,
fit: BoxFit.contain,
// Aset aslinya besar; decode seukuran tampilan saja.
cacheWidth: (size * MediaQuery.devicePixelRatioOf(context)).round(),
),
};
}
}
/// Label waktu promo: biru bila belum mulai, oranye bila segera berakhir.
class PromoTimeBadge extends StatelessWidget {
const PromoTimeBadge({super.key, required this.promo});
final Promo promo;
@override
Widget build(BuildContext context) {
final color = isPromoUpcoming(promo)
? AppColor.info
: isPromoEndingSoon(promo)
? AppColor.warning
: AppColor.textSecondary;
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(20),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.schedule, size: 12, color: color),
const SizedBox(width: 4),
Text(
promoTimeLabel(promo),
style: AppStyle.xs.copyWith(
color: color,
fontWeight: FontWeight.w700,
),
),
],
),
);
}
}