Files
enaklo-flutter/lib/presentation/pages/promo/widgets/promo_banner.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

162 lines
4.8 KiB
Dart

import 'package:flutter/material.dart';
import '../../../../common/theme/theme.dart';
import '../../../../sample/promo_sample_data.dart';
import '../promo_ui.dart';
/// Kartu besar untuk promo unggulan di bagian atas EnakPromo.
class PromoBanner extends StatelessWidget {
const PromoBanner({super.key, required this.promo, required this.onTap});
final Promo promo;
final VoidCallback onTap;
static const double _iconSize = 96;
@override
Widget build(BuildContext context) {
final gradient = promo.type.gradient;
return DecoratedBox(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
gradient: LinearGradient(
colors: gradient,
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
boxShadow: [
BoxShadow(
color: gradient.last.withOpacity(0.3),
blurRadius: 12,
offset: const Offset(0, 6),
),
],
),
child: Material(
type: MaterialType.transparency,
child: InkWell(
borderRadius: BorderRadius.circular(20),
onTap: onTap,
child: ClipRRect(
borderRadius: BorderRadius.circular(20),
child: Stack(
children: [
// Lingkaran dekoratif di belakang ikon.
Positioned(
right: -36,
top: -36,
child: _Circle(size: 150, opacity: 0.1),
),
Positioned(
right: 40,
bottom: -48,
child: _Circle(size: 100, opacity: 0.08),
),
Positioned(
right: 14,
bottom: 14,
child: PromoTypeIcon(type: promo.type, size: _iconSize),
),
Padding(
padding: const EdgeInsets.fromLTRB(
18,
16,
_iconSize + 20,
16,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_Pill(text: promo.rewardLabel, color: promo.type.color),
const SizedBox(height: 8),
Text(
promo.title,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: AppStyle.xl.copyWith(
color: AppColor.textWhite,
fontWeight: FontWeight.w800,
height: 1.2,
),
),
const SizedBox(height: 4),
Text(
promo.subtitle,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: AppStyle.sm.copyWith(
color: AppColor.textWhite.withOpacity(0.9),
),
),
const Spacer(),
Row(
children: [
Icon(
Icons.schedule,
size: 14,
color: AppColor.textWhite.withOpacity(0.9),
),
const SizedBox(width: 4),
Text(
promoTimeLabel(promo),
style: AppStyle.xs.copyWith(
color: AppColor.textWhite.withOpacity(0.9),
fontWeight: FontWeight.w700,
),
),
],
),
],
),
),
],
),
),
),
),
);
}
}
class _Pill extends StatelessWidget {
const _Pill({required this.text, required this.color});
final String text;
final Color color;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: AppColor.white,
borderRadius: BorderRadius.circular(20),
),
child: Text(
text,
style: AppStyle.xs.copyWith(color: color, fontWeight: FontWeight.w800),
),
);
}
}
class _Circle extends StatelessWidget {
const _Circle({required this.size, required this.opacity});
final double size;
final double opacity;
@override
Widget build(BuildContext context) {
return Container(
width: size,
height: size,
decoration: BoxDecoration(
color: AppColor.white.withOpacity(opacity),
shape: BoxShape.circle,
),
);
}
}