proxibuy/lib/presentation/widgets/reserved_list_item_card.dart

439 lines
14 KiB
Dart

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<ReservedListItemCard> createState() => _ReservedListItemCardState();
}
class _ReservedListItemCardState extends State<ReservedListItemCard> {
bool _isExpanded = false;
Timer? _timer;
Duration _remaining = Duration.zero;
Future<String>? _qrTokenFuture;
@override
void initState() {
super.initState();
_calculateRemainingTime();
_timer = Timer.periodic(const Duration(seconds: 1), (_) {
_calculateRemainingTime();
});
}
Future<String> _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(<double>[
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<Widget> 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: 20,
vertical: 9,
),
decoration: BoxDecoration(
color: AppColors.singleOfferType,
borderRadius: BorderRadius.circular(20),
),
child: Text(
"تخفیف ${widget.offer.discountType}",
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.normal,
fontSize: 17,
),
),
),
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: 14,
color: AppColors.singleOfferType,
fontWeight: FontWeight.normal,
),
),
const SizedBox(width: 8),
Text(
formatCurrency.format(widget.offer.originalPrice),
style: TextStyle(
fontSize: 14,
color: Colors.grey.shade600,
decoration: TextDecoration.lineThrough,
),
),
],
),
const SizedBox(height: 1),
Text(
'${formatCurrency.format(widget.offer.finalPrice)} تومان',
style: const TextStyle(
color: AppColors.singleOfferType,
fontSize: 18,
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<String>(
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),
);
}
}