import 'dart:async'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart'; import 'package:flutter/material.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:intl/intl.dart'; import 'package:proxibuy/core/gen/assets.gen.dart'; import 'package:qr_flutter/qr_flutter.dart'; import 'package:proxibuy/core/config/app_colors.dart'; import 'package:proxibuy/data/models/offer_model.dart'; import 'package:slide_countdown/slide_countdown.dart'; class ReservedListItemCard extends StatefulWidget { final OfferModel offer; const ReservedListItemCard({super.key, required this.offer}); @override State createState() => _ReservedListItemCardState(); } class _ReservedListItemCardState extends State { bool _isExpanded = false; Timer? _timer; Duration _remaining = Duration.zero; Future? _qrTokenFuture; @override void initState() { super.initState(); _calculateRemainingTime(); _timer = Timer.periodic(const Duration(seconds: 1), (_) { _calculateRemainingTime(); }); } Future _generateQrToken() async { const storage = FlutterSecureStorage(); final userID = await storage.read(key: 'userID'); if (userID == null) { throw Exception("User ID not found"); } final payload = { 'userID': userID, 'discountID': widget.offer.id, 'iat': DateTime.now().millisecondsSinceEpoch ~/ 1000, }; const secretKey = 'your_super_secret_key_for_qr'; final jwt = JWT(payload); final token = jwt.sign(SecretKey(secretKey)); return token; } void _toggleExpansion() { setState(() { _isExpanded = !_isExpanded; if (_isExpanded && _qrTokenFuture == null) { _qrTokenFuture = _generateQrToken(); } }); } void _calculateRemainingTime() { final now = DateTime.now(); if (widget.offer.expiryTime.isAfter(now)) { if (mounted) { setState(() { _remaining = widget.offer.expiryTime.difference(now); }); } } else { if (mounted) { setState(() => _remaining = Duration.zero); } _timer?.cancel(); } } @override void dispose() { _timer?.cancel(); super.dispose(); } @override Widget build(BuildContext context) { final isExpired = _remaining <= Duration.zero; final cardContent = Column( children: [ Card( color: Colors.white, shape: RoundedRectangleBorder( side: BorderSide(color: Colors.grey.shade300, width: 1), borderRadius: BorderRadius.circular(20), ), elevation: 0, clipBehavior: Clip.antiAlias, margin: EdgeInsets.zero, child: _buildOfferPrimaryDetails(), ), _buildActionsRow(), _buildExpansionPanel(), ], ); if (isExpired) { return ColorFiltered( colorFilter: const ColorFilter.matrix([ 0.2126, 0.7152, 0.0722, 0, 0, 0.2126, 0.7152, 0.0722, 0, 0, 0.2126, 0.7152, 0.0722, 0, 0, 0, 0, 0, 1, 0, ]), child: cardContent, ); } return cardContent; } Widget _buildOfferPrimaryDetails() { return Padding( padding: const EdgeInsets.fromLTRB(15, 25, 15, 25), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ ClipRRect( borderRadius: BorderRadius.circular(8), child: CachedNetworkImage( imageUrl: widget.offer.coverImageUrl, width: 90, height: 90, fit: BoxFit.cover, ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ SvgPicture.asset(Assets.icons.shop.path), SizedBox(width: 8), Text( widget.offer.storeName, style: const TextStyle( fontWeight: FontWeight.normal, fontSize: 16, ), ), ], ), const SizedBox(height: 4), Row( children: [ SvgPicture.asset(Assets.icons.shoppingCart.path), SizedBox(width: 8), Text( widget.offer.title, style: TextStyle(color: AppColors.hint, fontSize: 14), ), ], ), const SizedBox(height: 8), Row( children: [ SvgPicture.asset(Assets.icons.location.path), SizedBox(width: 8), Flexible( child: Text( "${widget.offer.address} (${widget.offer.distanceInMeters} متر تا تخفیف)", overflow: TextOverflow.ellipsis, maxLines: 1, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.normal, color: AppColors.hint, ), ), ), ], ), ], ), ), ], ), ); } Widget _buildActionsRow() { return Padding( padding: const EdgeInsets.fromLTRB(12, 8, 12, 0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ if (_remaining > Duration.zero) Column( children: [ Localizations.override( context: context, locale: const Locale('en'), child: SlideCountdown( duration: _remaining, slideDirection: SlideDirection.up, separator: ':', style: const TextStyle( fontSize: 25, fontWeight: FontWeight.bold, color: AppColors.countdown, ), separatorStyle: const TextStyle( fontSize: 20, color: AppColors.countdown, ), decoration: const BoxDecoration(color: Colors.white), shouldShowDays: (d) => d.inDays > 0, shouldShowHours: (d) => d.inHours > 0, shouldShowMinutes: (d) => d.inSeconds > 0, ), ), const SizedBox(height: 4), _buildTimerLabels(_remaining), ], ) else const Text( 'منقضی شده', style: TextStyle( color: Colors.red, fontWeight: FontWeight.bold, fontSize: 16, ), ), SizedBox(width: 10), TextButton( onPressed: _toggleExpansion, child: Row( mainAxisSize: MainAxisSize.min, children: [ Text( _isExpanded ? 'بستن' : 'اطلاعات بیشتر', style: TextStyle(color: AppColors.active), ), const SizedBox(width: 12), AnimatedRotation( turns: _isExpanded ? 0.5 : 0, duration: const Duration(milliseconds: 300), child: SvgPicture.asset( Assets.icons.arrowDown.path, height: 20, ), ), ], ), ), ], ), ); } Widget _buildTimerLabels(Duration duration) { const double columnWidth = 40; const labelStyle = TextStyle(fontSize: 10, color: AppColors.selectedImg); List labels = []; if (duration.inDays > 0) { labels = [ SizedBox( width: columnWidth, child: Center(child: Text("ثانیه", style: labelStyle)), ), SizedBox( width: columnWidth, child: Center(child: Text("دقیقه", style: labelStyle)), ), SizedBox( width: columnWidth, child: Center(child: Text("ساعت", style: labelStyle)), ), SizedBox( width: columnWidth, child: Center(child: Text("روز", style: labelStyle)), ), ]; } else if (duration.inHours > 0) { labels = [ SizedBox( width: columnWidth, child: Center(child: Text("ثانیه", style: labelStyle)), ), SizedBox( width: columnWidth, child: Center(child: Text("دقیقه", style: labelStyle)), ), SizedBox( width: columnWidth, child: Center(child: Text("ساعت", style: labelStyle)), ), ]; } else if (duration.inSeconds > 0) { labels = [ SizedBox( width: columnWidth, child: Center(child: Text("ثانیه", style: labelStyle)), ), SizedBox( width: columnWidth, child: Center(child: Text("دقیقه", style: labelStyle)), ), ]; } return Row(mainAxisAlignment: MainAxisAlignment.center, children: labels); } Widget _buildExpansionPanel() { final formatCurrency = NumberFormat.decimalPattern('fa_IR'); return AnimatedCrossFade( firstChild: Container(), secondChild: Container( padding: const EdgeInsets.symmetric(vertical: 24, horizontal: 16), width: double.infinity, child: Center( child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.center, children: [ Container( padding: const EdgeInsets.symmetric( horizontal: 15, vertical: 12, ), decoration: BoxDecoration( color: AppColors.singleOfferType, borderRadius: BorderRadius.circular(20), ), child: Text( "تخفیف ${widget.offer.discountType}", style: const TextStyle( color: Colors.white, fontWeight: FontWeight.normal, fontSize: 13, overflow: TextOverflow.ellipsis ), ), ), Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Row( mainAxisAlignment: MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.center, children: [ Text( '(${(100 - widget.offer.finalPrice / widget.offer.originalPrice * 100).toInt()}%)', style: const TextStyle( fontSize: 12, color: AppColors.singleOfferType, fontWeight: FontWeight.normal, ), ), const SizedBox(width: 8), Text( formatCurrency.format(widget.offer.originalPrice), style: TextStyle( fontSize: 13, color: Colors.grey.shade600, decoration: TextDecoration.lineThrough, ), ), ], ), const SizedBox(height: 1), Text( '${formatCurrency.format(widget.offer.finalPrice)} تومان', style: const TextStyle( color: AppColors.singleOfferType, fontSize: 15, fontWeight: FontWeight.bold, ), ), ], ), ], ), const SizedBox(height: 20), Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: Color.fromARGB(255, 246, 246, 246), borderRadius: BorderRadius.circular(16), ), child: FutureBuilder( future: _qrTokenFuture, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const SizedBox( height: 280.0, child: Center(child: CircularProgressIndicator()), ); } if (snapshot.hasError) { return const SizedBox( height: 280.0, child: Center(child: Text("خطا در ساخت کد QR")), ); } if (snapshot.hasData) { return QrImageView( data: snapshot.data!, version: QrVersions.auto, size: 280.0, ); } return const SizedBox.shrink(); }, ), ), ], ), ), ), crossFadeState: _isExpanded ? CrossFadeState.showSecond : CrossFadeState.showFirst, duration: const Duration(milliseconds: 300), ); } }