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> {
|
class _RewardsScreenState extends State<RewardsScreen> {
|
||||||
double _userPoints = 0.0; // Main loyalty points balance shown in the top banner
|
double _userPoints = 0.0; // Main loyalty points balance shown in the top banner
|
||||||
Map<int, double> _programPoints = {}; // Maps programId to card points
|
Map<int, double> _programPoints = {}; // Maps programId to card points
|
||||||
|
Set<int> _subscriptionProgramIds = {}; // Record program IDs that are subscription cards
|
||||||
List<dynamic> _rewards = [];
|
List<dynamic> _rewards = [];
|
||||||
bool _isLoading = true;
|
bool _isLoading = true;
|
||||||
|
|
||||||
@ -56,6 +57,8 @@ class _RewardsScreenState extends State<RewardsScreen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final Set<int> subProgIds = {};
|
||||||
|
|
||||||
// 2. Process active subscription cards
|
// 2. Process active subscription cards
|
||||||
for (final card in subscriptionCards) {
|
for (final card in subscriptionCards) {
|
||||||
final pts = safeDouble(card['points']);
|
final pts = safeDouble(card['points']);
|
||||||
@ -68,6 +71,7 @@ class _RewardsScreenState extends State<RewardsScreen> {
|
|||||||
}
|
}
|
||||||
if (progId != null) {
|
if (progId != null) {
|
||||||
pointsMap[progId] = pts;
|
pointsMap[progId] = pts;
|
||||||
|
subProgIds.add(progId);
|
||||||
if (!programIds.contains(progId)) {
|
if (!programIds.contains(progId)) {
|
||||||
programIds.add(progId);
|
programIds.add(progId);
|
||||||
}
|
}
|
||||||
@ -84,6 +88,7 @@ class _RewardsScreenState extends State<RewardsScreen> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_userPoints = loyaltyPoints;
|
_userPoints = loyaltyPoints;
|
||||||
_programPoints = pointsMap;
|
_programPoints = pointsMap;
|
||||||
|
_subscriptionProgramIds = subProgIds;
|
||||||
_rewards = fetchedRewards;
|
_rewards = fetchedRewards;
|
||||||
_isLoading = false;
|
_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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
@ -160,7 +184,7 @@ class _RewardsScreenState extends State<RewardsScreen> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
'${_userPoints.toStringAsFixed(0)} Points',
|
'${_formatWithCommas(_userPoints)} Points',
|
||||||
style: theme.textTheme.headlineMedium?.copyWith(
|
style: theme.textTheme.headlineMedium?.copyWith(
|
||||||
color: colorScheme.onPrimary,
|
color: colorScheme.onPrimary,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
@ -246,11 +270,19 @@ class _RewardsScreenState extends State<RewardsScreen> {
|
|||||||
rewardProgramId = progVal;
|
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
|
// Determine point balance and availability based on the specific program
|
||||||
final currentCardPoints = rewardProgramId != null
|
final currentCardPoints = rewardProgramId != null
|
||||||
? (_programPoints[rewardProgramId] ?? 0.0)
|
? (_programPoints[rewardProgramId] ?? 0.0)
|
||||||
: 0.0;
|
: 0.0;
|
||||||
final isAvailable = currentCardPoints >= reqPoints;
|
final isAvailable = isFree ? true : (currentCardPoints >= reqPoints);
|
||||||
|
|
||||||
// Decide icon based on reward type
|
// Decide icon based on reward type
|
||||||
IconData iconData = Icons.local_offer_rounded;
|
IconData iconData = Icons.local_offer_rounded;
|
||||||
@ -292,28 +324,51 @@ class _RewardsScreenState extends State<RewardsScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||||
child: Column(
|
child: isFree
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
? Column(
|
||||||
children: [
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
Text(
|
children: [
|
||||||
reqPoints.toStringAsFixed(0),
|
Icon(
|
||||||
style: theme.textTheme.titleLarge?.copyWith(
|
Icons.card_giftcard_rounded,
|
||||||
color: colorScheme.primary,
|
color: colorScheme.primary,
|
||||||
fontWeight: FontWeight.bold,
|
size: 28,
|
||||||
fontFamily: 'monospace',
|
),
|
||||||
|
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)
|
// Custom Dashed Divider (Simulating Tear-off Voucher)
|
||||||
@ -420,7 +475,7 @@ class _RewardsScreenState extends State<RewardsScreen> {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
Text(
|
Text(
|
||||||
'Need ${(reqPoints - currentCardPoints).toStringAsFixed(0)} more pts',
|
'Need ${_formatWithCommas(reqPoints - currentCardPoints)} more pts',
|
||||||
style: theme.textTheme.bodySmall?.copyWith(
|
style: theme.textTheme.bodySmall?.copyWith(
|
||||||
color: colorScheme.error,
|
color: colorScheme.error,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user