diff --git a/lib/screens/loyalty_dashboard.dart b/lib/screens/loyalty_dashboard.dart index cb13e5f..c539e1d 100644 --- a/lib/screens/loyalty_dashboard.dart +++ b/lib/screens/loyalty_dashboard.dart @@ -37,10 +37,28 @@ class _LoyaltyDashboardState extends State { OdooService().getCmsContent(), ]); - final cards = results[0] as List; - final subs = results[1] as List; + final rawCards = results[0] as List; + final rawSubs = results[1] as List; final cms = results[2] as Map; + final List cards = []; + final List subs = [...rawSubs]; + + for (var card in rawCards) { + final progName = (card['program_id']?[1] as String? ?? '').toLowerCase(); + final isSub = progName.contains('subscription') || + card['subscription_start_date'] != null || + card['subscription_end_date'] != null; + if (isSub) { + final code = card['code']; + if (!subs.any((s) => s['code'] == code)) { + subs.add(card); + } + } else { + cards.add(card); + } + } + if (mounted) { setState(() { _loyaltyCards = cards; diff --git a/lib/widgets/subscription_list_widget.dart b/lib/widgets/subscription_list_widget.dart index 9f62a43..6cbe7f6 100644 --- a/lib/widgets/subscription_list_widget.dart +++ b/lib/widgets/subscription_list_widget.dart @@ -1,8 +1,9 @@ import 'package:flutter/material.dart'; import '../theme/app_theme.dart'; -/// Compact "My Subscriptions" list — displayed on the home tab below the loyalty card. -/// Each row shows the subscription name, active/expired badge, validity period, and card code. +/// "My Subscriptions" list displayed on the home tab below the loyalty card. +/// Renders each subscription as a beautiful standalone card visually distinct +/// from the points-based loyalty card. class SubscriptionListWidget extends StatelessWidget { final List subscriptions; @@ -16,44 +17,32 @@ class SubscriptionListWidget extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( - padding: const EdgeInsets.fromLTRB(16, 20, 16, 10), + padding: const EdgeInsets.fromLTRB(16, 24, 16, 8), child: Text( 'MY SUBSCRIPTIONS', style: Theme.of(context).textTheme.labelLarge?.copyWith( color: AppTheme.onSurfaceVariant, letterSpacing: 1.2, fontSize: 11, + fontWeight: FontWeight.bold, ), ), ), - Container( - margin: const EdgeInsets.symmetric(horizontal: 16), - decoration: const BoxDecoration( - color: AppTheme.surfaceContainerLow, - ), - child: Column( - children: List.generate(subscriptions.length, (index) { - final sub = subscriptions[index]; - final isLast = index == subscriptions.length - 1; - return _SubscriptionTile(sub: sub, isLast: isLast); - }), - ), - ), + ...subscriptions.map((sub) => _SubscriptionCard(sub: sub)), ], ); } } -class _SubscriptionTile extends StatelessWidget { +class _SubscriptionCard extends StatelessWidget { final dynamic sub; - final bool isLast; - const _SubscriptionTile({required this.sub, required this.isLast}); + const _SubscriptionCard({required this.sub}); /// Determine active/expired status from subscription_end_date. bool _isActive() { final endRaw = sub['subscription_end_date']; - if (endRaw == null || endRaw == false) return true; // no end date = no expiry + if (endRaw == null || endRaw == false) return true; // no end date = active try { final endDate = DateTime.parse(endRaw.toString()); return endDate.isAfter(DateTime.now()); @@ -94,94 +83,117 @@ class _SubscriptionTile extends StatelessWidget { final active = _isActive(); return Container( + margin: const EdgeInsets.fromLTRB(16, 8, 16, 8), + padding: const EdgeInsets.all(20), decoration: BoxDecoration( - border: isLast - ? null - : const Border( - bottom: BorderSide( - color: AppTheme.surfaceContainer, - width: 1, - ), - ), + color: AppTheme.surfaceContainerLowest, + border: Border.all( + color: active ? AppTheme.secondaryContainer : AppTheme.surfaceContainerHighest, + width: 1.5, + ), + borderRadius: BorderRadius.zero, ), - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Icon - Container( - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - color: active - ? AppTheme.secondaryContainer.withValues(alpha: 0.35) - : AppTheme.surfaceContainer, - shape: BoxShape.rectangle, - ), - child: Icon( - Icons.verified_rounded, - size: 22, - color: active ? AppTheme.secondary : AppTheme.outlineVariant, - ), - ), - - const SizedBox(width: 14), - - // Name + dates - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - programName, - style: Theme.of(context).textTheme.titleSmall?.copyWith( - fontWeight: FontWeight.bold, - color: AppTheme.onSurface, - ), - ), - const SizedBox(height: 3), - Text( - '$startDate → $endDate', - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: AppTheme.onSurfaceVariant, - ), - ), - if (code.isNotEmpty) ...[ - const SizedBox(height: 2), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Icon( + Icons.card_membership_rounded, + size: 18, + color: active ? AppTheme.secondary : AppTheme.outlineVariant, + ), + const SizedBox(width: 8), Text( - code, - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: AppTheme.outlineVariant, - fontFamily: 'monospace', - fontSize: 11, + 'SUBSCRIPTION CARD', + style: Theme.of(context).textTheme.labelLarge?.copyWith( + color: active ? AppTheme.secondary : AppTheme.outlineVariant, + fontWeight: FontWeight.bold, + fontSize: 10, + letterSpacing: 1.0, ), ), ], - ], - ), - ), - - const SizedBox(width: 8), - - // Active / Expired badge - Container( - padding: const EdgeInsets.symmetric(horizontal: 9, vertical: 4), - decoration: BoxDecoration( - color: active - ? const Color(0xFF1B5E20).withValues(alpha: 0.12) - : const Color(0xFFB02500).withValues(alpha: 0.10), - borderRadius: BorderRadius.circular(4), - ), - child: Text( - active ? 'ACTIVE' : 'EXPIRED', - style: TextStyle( - fontSize: 10, - fontWeight: FontWeight.bold, - letterSpacing: 0.8, - color: active - ? const Color(0xFF2E7D32) - : const Color(0xFFB02500), ), - ), + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: active + ? const Color(0xFF1B5E20).withValues(alpha: 0.10) + : const Color(0xFFB02500).withValues(alpha: 0.08), + borderRadius: BorderRadius.circular(4), + ), + child: Text( + active ? 'ACTIVE' : 'EXPIRED', + style: TextStyle( + fontSize: 9, + fontWeight: FontWeight.bold, + letterSpacing: 0.8, + color: active + ? const Color(0xFF2E7D32) + : const Color(0xFFB02500), + ), + ), + ), + ], + ), + const SizedBox(height: 14), + Text( + programName, + style: Theme.of(context).textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.bold, + color: AppTheme.onSurface, + ), + ), + const SizedBox(height: 12), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Card Number', + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: AppTheme.onSurfaceVariant, + fontSize: 10, + ), + ), + const SizedBox(height: 2), + Text( + code.isNotEmpty ? code : 'N/A', + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + fontFamily: 'monospace', + fontWeight: FontWeight.bold, + color: AppTheme.onSurface, + ), + ), + ], + ), + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + 'Validity Period', + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: AppTheme.onSurfaceVariant, + fontSize: 10, + ), + ), + const SizedBox(height: 2), + Text( + '$startDate - $endDate', + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: AppTheme.onSurface, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ], ), ], ),