feat: integrate subscription cards and support program-specific reward point validation

This commit is contained in:
Suherdy Yacob 2026-06-16 07:16:32 +07:00
parent e0b959b858
commit b25f9e151a

View File

@ -11,7 +11,8 @@ class RewardsScreen extends StatefulWidget {
}
class _RewardsScreenState extends State<RewardsScreen> {
double _userPoints = 0.0;
double _userPoints = 0.0; // Main loyalty points balance shown in the top banner
Map<int, double> _programPoints = {}; // Maps programId to card points
List<dynamic> _rewards = [];
bool _isLoading = true;
@ -26,30 +27,64 @@ class _RewardsScreenState extends State<RewardsScreen> {
setState(() => _isLoading = true);
try {
final cards = await OdooService().getLoyaltyCards(widget.partnerId);
// Fetch both loyalty cards and subscription cards in parallel
final results = await Future.wait([
OdooService().getLoyaltyCards(widget.partnerId),
OdooService().getSubscriptionCards(widget.partnerId),
]);
double totalPoints = 0.0;
List<dynamic> rawRewards = [];
final loyaltyCards = results[0];
final subscriptionCards = results[1];
if (cards.isNotEmpty) {
totalPoints = safeDouble(cards.first['points']);
final prog = cards.first['program_id'];
int? programId;
double loyaltyPoints = 0.0;
final Map<int, double> pointsMap = {};
final List<int> programIds = [];
// 1. Process main loyalty card
if (loyaltyCards.isNotEmpty) {
loyaltyPoints = safeDouble(loyaltyCards.first['points']);
final prog = loyaltyCards.first['program_id'];
int? progId;
if (prog is List && prog.isNotEmpty) {
programId = prog[0] as int?;
progId = prog[0] as int?;
} else if (prog is int) {
programId = prog;
progId = prog;
}
if (progId != null) {
pointsMap[progId] = loyaltyPoints;
programIds.add(progId);
}
}
if (programId != null) {
rawRewards = await OdooService().getLoyaltyRewards(programId);
// 2. Process active subscription cards
for (final card in subscriptionCards) {
final pts = safeDouble(card['points']);
final prog = card['program_id'];
int? progId;
if (prog is List && prog.isNotEmpty) {
progId = prog[0] as int?;
} else if (prog is int) {
progId = prog;
}
if (progId != null) {
pointsMap[progId] = pts;
if (!programIds.contains(progId)) {
programIds.add(progId);
}
}
}
// 3. Fetch rewards for all resolved program IDs
List<dynamic> fetchedRewards = [];
if (programIds.isNotEmpty) {
fetchedRewards = await OdooService().getLoyaltyRewards(programIds);
}
if (mounted) {
setState(() {
_userPoints = totalPoints;
_rewards = rawRewards;
_userPoints = loyaltyPoints;
_programPoints = pointsMap;
_rewards = fetchedRewards;
_isLoading = false;
});
}
@ -195,10 +230,28 @@ class _RewardsScreenState extends State<RewardsScreen> {
itemBuilder: (context, index) {
final reward = _rewards[index];
final reqPoints = safeDouble(reward['required_points']);
final isAvailable = _userPoints >= reqPoints;
final String desc = safeString(reward['description']) ?? 'Loyalty Reward';
final String type = safeString(reward['reward_type']) ?? 'product';
// Resolve reward's program ID and name
final progVal = reward['program_id'];
int? rewardProgramId;
String programName = 'Reward';
if (progVal is List && progVal.isNotEmpty) {
rewardProgramId = progVal[0] as int?;
if (progVal.length > 1) {
programName = progVal[1] as String;
}
} else if (progVal is int) {
rewardProgramId = progVal;
}
// Determine point balance and availability based on the specific program
final currentCardPoints = rewardProgramId != null
? (_programPoints[rewardProgramId] ?? 0.0)
: 0.0;
final isAvailable = currentCardPoints >= reqPoints;
// Decide icon based on reward type
IconData iconData = Icons.local_offer_rounded;
if (type == 'product') {
@ -279,6 +332,16 @@ class _RewardsScreenState extends State<RewardsScreen> {
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Program Source Name
Text(
programName.toUpperCase(),
style: theme.textTheme.labelSmall?.copyWith(
color: colorScheme.primary.withValues(alpha: 0.65),
fontWeight: FontWeight.bold,
letterSpacing: 1.0,
),
),
const SizedBox(height: 4),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -357,7 +420,7 @@ class _RewardsScreenState extends State<RewardsScreen> {
),
const SizedBox(width: 4),
Text(
'Need ${(reqPoints - _userPoints).toStringAsFixed(0)} more pts',
'Need ${(reqPoints - currentCardPoints).toStringAsFixed(0)} more pts',
style: theme.textTheme.bodySmall?.copyWith(
color: colorScheme.error,
fontWeight: FontWeight.bold,