- 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>
141 lines
4.2 KiB
Dart
141 lines
4.2 KiB
Dart
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,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|