feat: track subscription program IDs and implement dynamic formatting for free rewards and point values
This commit is contained in:
parent
b25f9e151a
commit
484ed297f1
@ -13,6 +13,7 @@ class RewardsScreen extends StatefulWidget {
|
||||
class _RewardsScreenState extends State<RewardsScreen> {
|
||||
double _userPoints = 0.0; // Main loyalty points balance shown in the top banner
|
||||
Map<int, double> _programPoints = {}; // Maps programId to card points
|
||||
Set<int> _subscriptionProgramIds = {}; // Record program IDs that are subscription cards
|
||||
List<dynamic> _rewards = [];
|
||||
bool _isLoading = true;
|
||||
|
||||
@ -56,6 +57,8 @@ class _RewardsScreenState extends State<RewardsScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
final Set<int> subProgIds = {};
|
||||
|
||||
// 2. Process active subscription cards
|
||||
for (final card in subscriptionCards) {
|
||||
final pts = safeDouble(card['points']);
|
||||
@ -68,6 +71,7 @@ class _RewardsScreenState extends State<RewardsScreen> {
|
||||
}
|
||||
if (progId != null) {
|
||||
pointsMap[progId] = pts;
|
||||
subProgIds.add(progId);
|
||||
if (!programIds.contains(progId)) {
|
||||
programIds.add(progId);
|
||||
}
|
||||
@ -84,6 +88,7 @@ class _RewardsScreenState extends State<RewardsScreen> {
|
||||
setState(() {
|
||||
_userPoints = loyaltyPoints;
|
||||
_programPoints = pointsMap;
|
||||
_subscriptionProgramIds = subProgIds;
|
||||
_rewards = fetchedRewards;
|
||||
_isLoading = false;
|
||||
});
|
||||
@ -104,6 +109,25 @@ class _RewardsScreenState extends State<RewardsScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
String _formatPoints(double points) {
|
||||
final int val = points.round();
|
||||
if (val >= 1000) {
|
||||
final double k = val / 1000;
|
||||
if (k % 1 == 0) {
|
||||
return '${k.toInt()}K';
|
||||
} else {
|
||||
return '${k.toStringAsFixed(1)}K';
|
||||
}
|
||||
}
|
||||
return val.toString();
|
||||
}
|
||||
|
||||
String _formatWithCommas(double value) {
|
||||
final int val = value.round();
|
||||
final RegExp reg = RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))');
|
||||
return val.toString().replaceAllMapped(reg, (Match match) => '${match[1]},');
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
@ -160,7 +184,7 @@ class _RewardsScreenState extends State<RewardsScreen> {
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'${_userPoints.toStringAsFixed(0)} Points',
|
||||
'${_formatWithCommas(_userPoints)} Points',
|
||||
style: theme.textTheme.headlineMedium?.copyWith(
|
||||
color: colorScheme.onPrimary,
|
||||
fontWeight: FontWeight.bold,
|
||||
@ -246,11 +270,19 @@ class _RewardsScreenState extends State<RewardsScreen> {
|
||||
rewardProgramId = progVal;
|
||||
}
|
||||
|
||||
// Determine if it is a subscription reward
|
||||
final isSubscription = rewardProgramId != null &&
|
||||
_subscriptionProgramIds.contains(rewardProgramId);
|
||||
|
||||
// If it's a subscription reward and required points <= 1, treat it as FREE.
|
||||
// For regular loyalty program, only treat <= 0 as FREE.
|
||||
final bool isFree = reqPoints <= 0 || (isSubscription && reqPoints <= 1);
|
||||
|
||||
// Determine point balance and availability based on the specific program
|
||||
final currentCardPoints = rewardProgramId != null
|
||||
? (_programPoints[rewardProgramId] ?? 0.0)
|
||||
: 0.0;
|
||||
final isAvailable = currentCardPoints >= reqPoints;
|
||||
final isAvailable = isFree ? true : (currentCardPoints >= reqPoints);
|
||||
|
||||
// Decide icon based on reward type
|
||||
IconData iconData = Icons.local_offer_rounded;
|
||||
@ -292,28 +324,51 @@ class _RewardsScreenState extends State<RewardsScreen> {
|
||||
),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
reqPoints.toStringAsFixed(0),
|
||||
style: theme.textTheme.titleLarge?.copyWith(
|
||||
color: colorScheme.primary,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontFamily: 'monospace',
|
||||
child: isFree
|
||||
? Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.card_giftcard_rounded,
|
||||
color: colorScheme.primary,
|
||||
size: 28,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'FREE',
|
||||
style: theme.textTheme.labelSmall?.copyWith(
|
||||
color: colorScheme.primary,
|
||||
fontWeight: FontWeight.bold,
|
||||
letterSpacing: 1.0,
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Text(
|
||||
_formatPoints(reqPoints),
|
||||
style: theme.textTheme.titleLarge?.copyWith(
|
||||
color: colorScheme.primary,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontFamily: 'monospace',
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
'PTS',
|
||||
style: theme.textTheme.labelSmall?.copyWith(
|
||||
color: colorScheme.primary.withValues(alpha: 0.8),
|
||||
fontWeight: FontWeight.w900,
|
||||
letterSpacing: 1.0,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
'PTS',
|
||||
style: theme.textTheme.labelSmall?.copyWith(
|
||||
color: colorScheme.primary.withValues(alpha: 0.8),
|
||||
fontWeight: FontWeight.w900,
|
||||
letterSpacing: 1.0,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Custom Dashed Divider (Simulating Tear-off Voucher)
|
||||
@ -420,7 +475,7 @@ class _RewardsScreenState extends State<RewardsScreen> {
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'Need ${(reqPoints - currentCardPoints).toStringAsFixed(0)} more pts',
|
||||
'Need ${_formatWithCommas(reqPoints - currentCardPoints)} more pts',
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: colorScheme.error,
|
||||
fontWeight: FontWeight.bold,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user