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> { 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 = []; List<dynamic> _rewards = [];
bool _isLoading = true; bool _isLoading = true;
@ -26,30 +27,64 @@ class _RewardsScreenState extends State<RewardsScreen> {
setState(() => _isLoading = true); setState(() => _isLoading = true);
try { 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; final loyaltyCards = results[0];
List<dynamic> rawRewards = []; final subscriptionCards = results[1];
if (cards.isNotEmpty) { double loyaltyPoints = 0.0;
totalPoints = safeDouble(cards.first['points']); final Map<int, double> pointsMap = {};
final prog = cards.first['program_id']; final List<int> programIds = [];
int? programId;
// 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) { if (prog is List && prog.isNotEmpty) {
programId = prog[0] as int?; progId = prog[0] as int?;
} else if (prog is int) { } else if (prog is int) {
programId = prog; progId = prog;
}
if (progId != null) {
pointsMap[progId] = loyaltyPoints;
programIds.add(progId);
}
} }
if (programId != null) { // 2. Process active subscription cards
rawRewards = await OdooService().getLoyaltyRewards(programId); 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) { if (mounted) {
setState(() { setState(() {
_userPoints = totalPoints; _userPoints = loyaltyPoints;
_rewards = rawRewards; _programPoints = pointsMap;
_rewards = fetchedRewards;
_isLoading = false; _isLoading = false;
}); });
} }
@ -195,10 +230,28 @@ class _RewardsScreenState extends State<RewardsScreen> {
itemBuilder: (context, index) { itemBuilder: (context, index) {
final reward = _rewards[index]; final reward = _rewards[index];
final reqPoints = safeDouble(reward['required_points']); final reqPoints = safeDouble(reward['required_points']);
final isAvailable = _userPoints >= reqPoints;
final String desc = safeString(reward['description']) ?? 'Loyalty Reward'; final String desc = safeString(reward['description']) ?? 'Loyalty Reward';
final String type = safeString(reward['reward_type']) ?? 'product'; 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 // Decide icon based on reward type
IconData iconData = Icons.local_offer_rounded; IconData iconData = Icons.local_offer_rounded;
if (type == 'product') { if (type == 'product') {
@ -279,6 +332,16 @@ class _RewardsScreenState extends State<RewardsScreen> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ 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( Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -357,7 +420,7 @@ class _RewardsScreenState extends State<RewardsScreen> {
), ),
const SizedBox(width: 4), const SizedBox(width: 4),
Text( Text(
'Need ${(reqPoints - _userPoints).toStringAsFixed(0)} more pts', 'Need ${(reqPoints - currentCardPoints).toStringAsFixed(0)} more pts',
style: theme.textTheme.bodySmall?.copyWith( style: theme.textTheme.bodySmall?.copyWith(
color: colorScheme.error, color: colorScheme.error,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,