Reserve Page
This commit is contained in:
parent
789ae430fb
commit
c0d1bee773
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M9.56994 18.8201C9.37994 18.8201 9.18994 18.7501 9.03994 18.6001L2.96994 12.5301C2.67994 12.2401 2.67994 11.7601 2.96994 11.4701L9.03994 5.40012C9.32994 5.11012 9.80994 5.11012 10.0999 5.40012C10.3899 5.69012 10.3899 6.17012 10.0999 6.46012L4.55994 12.0001L10.0999 17.5401C10.3899 17.8301 10.3899 18.3101 10.0999 18.6001C9.95994 18.7501 9.75994 18.8201 9.56994 18.8201Z" fill="#292D32"/>
|
||||||
|
<path d="M20.4999 12.75H3.66992C3.25992 12.75 2.91992 12.41 2.91992 12C2.91992 11.59 3.25992 11.25 3.66992 11.25H20.4999C20.9099 11.25 21.2499 11.59 21.2499 12C21.2499 12.41 20.9099 12.75 20.4999 12.75Z" fill="#292D32"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 721 B |
|
|
@ -0,0 +1,7 @@
|
||||||
|
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M3.49353 11.4194L11.4602 3.45276" stroke="#5F5F5F" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M8.27466 13.0193L9.07466 12.2193" stroke="#5F5F5F" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M10.0691 11.2258L11.6624 9.63245" stroke="#5F5F5F" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M3.27467 7.65929L7.70133 3.23263C9.11467 1.81929 9.82133 1.81263 11.2213 3.21263L14.4947 6.48596C15.8947 7.88596 15.888 8.59263 14.4747 10.006L10.048 14.4326C8.63467 15.846 7.928 15.8526 6.528 14.4526L3.25467 11.1793C1.85467 9.77929 1.85467 9.07929 3.27467 7.65929Z" stroke="#5F5F5F" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M2.20715 15.499H15.5405" stroke="#5F5F5F" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 941 B |
|
|
@ -0,0 +1,6 @@
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M19.5 12.5C19.5 11.12 20.62 10 22 10V9C22 5 21 4 17 4H7C3 4 2 5 2 9V9.5C3.38 9.5 4.5 10.62 4.5 12C4.5 13.38 3.38 14.5 2 14.5V15C2 19 3 20 7 20H17C21 20 22 19 22 15C20.62 15 19.5 13.88 19.5 12.5Z" stroke="#9D9D9B" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M9 14.75L15 8.75" stroke="#9D9D9B" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M14.9945 14.75H15.0035" stroke="#9D9D9B" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M8.99451 9.25H9.00349" stroke="#9D9D9B" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 736 B |
|
|
@ -14,4 +14,6 @@ class AppColors {
|
||||||
static const Color selectedImg = Color.fromARGB(255, 76, 175, 80);
|
static const Color selectedImg = Color.fromARGB(255, 76, 175, 80);
|
||||||
static const Color singleOfferType = Color.fromARGB(255, 244, 67, 54);
|
static const Color singleOfferType = Color.fromARGB(255, 244, 67, 54);
|
||||||
static const Color countdown = Color.fromARGB(255, 54, 124, 57);
|
static const Color countdown = Color.fromARGB(255, 54, 124, 57);
|
||||||
|
static const Color countdownBorderRserve = Color.fromARGB(255, 186, 222, 251);
|
||||||
|
static const Color expiryReserve = Color.fromARGB(255, 183, 28, 28);
|
||||||
}
|
}
|
||||||
|
|
@ -75,12 +75,18 @@ class $AssetsIconsGen {
|
||||||
/// File path: assets/icons/arayesh.svg
|
/// File path: assets/icons/arayesh.svg
|
||||||
SvgGenImage get arayesh => const SvgGenImage('assets/icons/arayesh.svg');
|
SvgGenImage get arayesh => const SvgGenImage('assets/icons/arayesh.svg');
|
||||||
|
|
||||||
|
/// File path: assets/icons/arrow-left.svg
|
||||||
|
SvgGenImage get arrowLeft => const SvgGenImage('assets/icons/arrow-left.svg');
|
||||||
|
|
||||||
/// File path: assets/icons/back.svg
|
/// File path: assets/icons/back.svg
|
||||||
SvgGenImage get back => const SvgGenImage('assets/icons/back.svg');
|
SvgGenImage get back => const SvgGenImage('assets/icons/back.svg');
|
||||||
|
|
||||||
/// File path: assets/icons/backArrow.svg
|
/// File path: assets/icons/backArrow.svg
|
||||||
SvgGenImage get backArrow => const SvgGenImage('assets/icons/backArrow.svg');
|
SvgGenImage get backArrow => const SvgGenImage('assets/icons/backArrow.svg');
|
||||||
|
|
||||||
|
/// File path: assets/icons/card-pos.svg
|
||||||
|
SvgGenImage get cardPos => const SvgGenImage('assets/icons/card-pos.svg');
|
||||||
|
|
||||||
/// File path: assets/icons/cinama.svg
|
/// File path: assets/icons/cinama.svg
|
||||||
SvgGenImage get cinama => const SvgGenImage('assets/icons/cinama.svg');
|
SvgGenImage get cinama => const SvgGenImage('assets/icons/cinama.svg');
|
||||||
|
|
||||||
|
|
@ -180,6 +186,10 @@ class $AssetsIconsGen {
|
||||||
/// File path: assets/icons/tickPb.svg
|
/// File path: assets/icons/tickPb.svg
|
||||||
SvgGenImage get tickPb => const SvgGenImage('assets/icons/tickPb.svg');
|
SvgGenImage get tickPb => const SvgGenImage('assets/icons/tickPb.svg');
|
||||||
|
|
||||||
|
/// File path: assets/icons/ticket-discount.svg
|
||||||
|
SvgGenImage get ticketDiscount =>
|
||||||
|
const SvgGenImage('assets/icons/ticket-discount.svg');
|
||||||
|
|
||||||
/// File path: assets/icons/timer-pause.svg
|
/// File path: assets/icons/timer-pause.svg
|
||||||
SvgGenImage get timerPause =>
|
SvgGenImage get timerPause =>
|
||||||
const SvgGenImage('assets/icons/timer-pause.svg');
|
const SvgGenImage('assets/icons/timer-pause.svg');
|
||||||
|
|
@ -212,8 +222,10 @@ class $AssetsIconsGen {
|
||||||
vector,
|
vector,
|
||||||
addImg,
|
addImg,
|
||||||
arayesh,
|
arayesh,
|
||||||
|
arrowLeft,
|
||||||
back,
|
back,
|
||||||
backArrow,
|
backArrow,
|
||||||
|
cardPos,
|
||||||
cinama,
|
cinama,
|
||||||
clock,
|
clock,
|
||||||
clockProduct,
|
clockProduct,
|
||||||
|
|
@ -244,6 +256,7 @@ class $AssetsIconsGen {
|
||||||
tickCircle,
|
tickCircle,
|
||||||
tickSquare,
|
tickSquare,
|
||||||
tickPb,
|
tickPb,
|
||||||
|
ticketDiscount,
|
||||||
timerPause,
|
timerPause,
|
||||||
volumeHigh,
|
volumeHigh,
|
||||||
warning2,
|
warning2,
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,7 @@ class MockOfferDataSource implements OfferDataSource {
|
||||||
publishedAt: DateTime.now().subtract(const Duration(days: 5)),
|
publishedAt: DateTime.now().subtract(const Duration(days: 5)),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
qrCodeData: 'PROXIBUY-OFFER-ID-1',
|
||||||
),
|
),
|
||||||
OfferModel(
|
OfferModel(
|
||||||
id: '2',
|
id: '2',
|
||||||
|
|
@ -174,7 +174,7 @@ class MockOfferDataSource implements OfferDataSource {
|
||||||
publishedAt: DateTime.now().subtract(const Duration(days: 5)),
|
publishedAt: DateTime.now().subtract(const Duration(days: 5)),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
qrCodeData: 'PROXIBUY-OFFER-ID-1',
|
||||||
),
|
),
|
||||||
OfferModel(
|
OfferModel(
|
||||||
id: '3',
|
id: '3',
|
||||||
|
|
@ -257,6 +257,7 @@ class MockOfferDataSource implements OfferDataSource {
|
||||||
publishedAt: DateTime.now().subtract(const Duration(days: 5)),
|
publishedAt: DateTime.now().subtract(const Duration(days: 5)),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
qrCodeData: 'PROXIBUY-OFFER-ID-1',
|
||||||
),
|
),
|
||||||
OfferModel(
|
OfferModel(
|
||||||
id: '4',
|
id: '4',
|
||||||
|
|
@ -339,7 +340,7 @@ class MockOfferDataSource implements OfferDataSource {
|
||||||
publishedAt: DateTime.now().subtract(const Duration(days: 5)),
|
publishedAt: DateTime.now().subtract(const Duration(days: 5)),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
qrCodeData: 'PROXIBUY-OFFER-ID-1',
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,8 @@ class OfferModel extends Equatable {
|
||||||
final double finalPrice;
|
final double finalPrice;
|
||||||
final List<String> features;
|
final List<String> features;
|
||||||
final DiscountInfoModel? discountInfo;
|
final DiscountInfoModel? discountInfo;
|
||||||
final List<CommentModel> comments; // <-- این خط اضافه شد
|
final List<CommentModel> comments;
|
||||||
|
final String qrCodeData;
|
||||||
|
|
||||||
const OfferModel({
|
const OfferModel({
|
||||||
required this.id,
|
required this.id,
|
||||||
|
|
@ -47,7 +48,8 @@ class OfferModel extends Equatable {
|
||||||
required this.finalPrice,
|
required this.finalPrice,
|
||||||
this.features = const [],
|
this.features = const [],
|
||||||
this.discountInfo,
|
this.discountInfo,
|
||||||
this.comments = const [], // <-- این خط اضافه شد
|
this.comments = const [],
|
||||||
|
required this.qrCodeData,
|
||||||
});
|
});
|
||||||
|
|
||||||
String get coverImageUrl =>
|
String get coverImageUrl =>
|
||||||
|
|
@ -64,7 +66,8 @@ class OfferModel extends Equatable {
|
||||||
longitude,
|
longitude,
|
||||||
features,
|
features,
|
||||||
discountInfo,
|
discountInfo,
|
||||||
comments, // <-- این خط اضافه شد
|
comments,
|
||||||
|
qrCodeData
|
||||||
];
|
];
|
||||||
|
|
||||||
String get distanceAsString {
|
String get distanceAsString {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
class Product {
|
||||||
|
final String category;
|
||||||
|
final String name;
|
||||||
|
final String imageUrl;
|
||||||
|
final double originalPrice;
|
||||||
|
final double discountPercentage;
|
||||||
|
|
||||||
|
Product({
|
||||||
|
required this.category,
|
||||||
|
required this.name,
|
||||||
|
required this.imageUrl,
|
||||||
|
required this.originalPrice,
|
||||||
|
required this.discountPercentage,
|
||||||
|
});
|
||||||
|
|
||||||
|
double get finalPrice => originalPrice * (1 - discountPercentage / 100);
|
||||||
|
}
|
||||||
|
|
@ -7,6 +7,7 @@ import 'package:proxibuy/core/config/app_colors.dart';
|
||||||
import 'package:proxibuy/core/gen/assets.gen.dart';
|
import 'package:proxibuy/core/gen/assets.gen.dart';
|
||||||
import 'package:proxibuy/data/models/offer_model.dart';
|
import 'package:proxibuy/data/models/offer_model.dart';
|
||||||
import 'package:proxibuy/data/repositories/offer_repository.dart';
|
import 'package:proxibuy/data/repositories/offer_repository.dart';
|
||||||
|
import 'package:proxibuy/presentation/pages/reservation_details_screen.dart';
|
||||||
import 'package:proxibuy/presentation/product_detail/bloc/product_detail_bloc.dart';
|
import 'package:proxibuy/presentation/product_detail/bloc/product_detail_bloc.dart';
|
||||||
import 'package:proxibuy/presentation/product_detail/bloc/product_detail_event.dart';
|
import 'package:proxibuy/presentation/product_detail/bloc/product_detail_event.dart';
|
||||||
import 'package:proxibuy/presentation/product_detail/bloc/product_detail_state.dart';
|
import 'package:proxibuy/presentation/product_detail/bloc/product_detail_state.dart';
|
||||||
|
|
@ -54,41 +55,52 @@ class ProductDetailPage extends StatelessWidget {
|
||||||
bottom: 30,
|
bottom: 30,
|
||||||
left: 24,
|
left: 24,
|
||||||
right: 24,
|
right: 24,
|
||||||
child: ElevatedButton(
|
// دکمه را در یک BlocBuilder قرار میدهیم تا به state دسترسی داشته باشد
|
||||||
onPressed: () {
|
child: BlocBuilder<ProductDetailBloc, ProductDetailState>(
|
||||||
print("دکمه فشرده شد!");
|
builder: (context, state) {
|
||||||
},
|
// دکمه فقط زمانی نمایش داده میشود که اطلاعات محصول با موفقیت لود شده باشد
|
||||||
style: ElevatedButton.styleFrom(
|
if (state is ProductDetailLoadSuccess) {
|
||||||
backgroundColor: Colors.green,
|
return ElevatedButton(
|
||||||
elevation: 5,
|
onPressed: () {
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
// با کلیک روی دکمه، به صفحه تایید رزرو منتقل میشویم
|
||||||
shape: RoundedRectangleBorder(
|
// و اطلاعات محصول را به آن پاس میدهیم
|
||||||
borderRadius: BorderRadius.circular(15),
|
Navigator.of(context).push(
|
||||||
),
|
MaterialPageRoute(
|
||||||
),
|
builder: (_) => ReservationConfirmationPage(offer: state.offer),
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
SvgPicture.asset(Assets.icons.receiptDisscount.path),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
const Text(
|
|
||||||
'رزرو تخفیف',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.normal,
|
|
||||||
color: Colors.black,
|
|
||||||
),
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: AppColors.confirm, // رنگ دکمه را به سبز تغییر دادم تا با مفهوم رزرو همخوانی داشته باشد
|
||||||
|
elevation: 5,
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(50), // گردی بیشتر برای زیبایی
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
child: Row(
|
||||||
)
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
.animate()
|
children: [
|
||||||
.fadeIn(
|
SvgPicture.asset(Assets.icons.receiptDisscount.path,),
|
||||||
delay: 200.ms,
|
const SizedBox(width: 12),
|
||||||
duration: 400.ms,
|
const Text(
|
||||||
curve: Curves.easeOut,
|
'رزرو تخفیف',
|
||||||
)
|
style: TextStyle(
|
||||||
.slideY(begin: 2, duration: 500.ms, curve: Curves.easeOut),
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.normal,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
).animate()
|
||||||
|
.fadeIn(delay: 200.ms, duration: 400.ms, curve: Curves.easeOut)
|
||||||
|
.slideY(begin: 2, duration: 500.ms, curve: Curves.easeOut);
|
||||||
|
}
|
||||||
|
// اگر اطلاعات در حال لود شدن باشد، چیزی نمایش داده نمیشود
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,358 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
import 'package:proxibuy/core/config/app_colors.dart';
|
||||||
|
import 'package:proxibuy/core/gen/assets.gen.dart';
|
||||||
|
import 'package:proxibuy/data/models/offer_model.dart';
|
||||||
|
import 'package:qr_flutter/qr_flutter.dart';
|
||||||
|
import 'package:flutter_animate/flutter_animate.dart';
|
||||||
|
|
||||||
|
class ReservationConfirmationPage extends StatefulWidget {
|
||||||
|
final OfferModel offer;
|
||||||
|
|
||||||
|
const ReservationConfirmationPage({super.key, required this.offer});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ReservationConfirmationPage> createState() =>
|
||||||
|
_ReservationConfirmationPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ReservationConfirmationPageState extends State<ReservationConfirmationPage> {
|
||||||
|
Timer? _timer;
|
||||||
|
Duration _remaining = Duration.zero;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_calculateRemainingTime();
|
||||||
|
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||||||
|
_calculateRemainingTime();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
return Directionality(
|
||||||
|
textDirection: TextDirection.rtl,
|
||||||
|
child: Scaffold(
|
||||||
|
backgroundColor: Colors.grey[50],
|
||||||
|
appBar: _buildCustomAppBar(context),
|
||||||
|
body: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 32.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// ✅ انیمیشن برای عنوان اصلی
|
||||||
|
Text(
|
||||||
|
'تخفیف ${widget.offer.discountType} رزرو شد!',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.black87,
|
||||||
|
),
|
||||||
|
).animate().fadeIn(delay: 300.ms, duration: 500.ms).slideY(begin: -0.2, end: 0),
|
||||||
|
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
// ✅ انیمیشن برای خط جداکننده
|
||||||
|
const Divider(thickness: 1.5)
|
||||||
|
.animate()
|
||||||
|
.fadeIn(delay: 400.ms)
|
||||||
|
.scaleX(begin: 0, duration: 600.ms, curve: Curves.easeInOut),
|
||||||
|
|
||||||
|
const SizedBox(height: 18),
|
||||||
|
_buildOfferDetailsCard()
|
||||||
|
.animate()
|
||||||
|
.fadeIn(delay: 600.ms, duration: 500.ms)
|
||||||
|
.slideX(begin: 0.5, end: 0, curve: Curves.easeOutCubic),
|
||||||
|
|
||||||
|
const SizedBox(height: 18),
|
||||||
|
_buildTimerCard()
|
||||||
|
.animate()
|
||||||
|
.fadeIn(delay: 800.ms, duration: 500.ms)
|
||||||
|
.scale(begin: const Offset(0.8, 0.8), curve: Curves.easeOutBack),
|
||||||
|
|
||||||
|
const SizedBox(height: 18),
|
||||||
|
_buildQrCodeCard()
|
||||||
|
.animate()
|
||||||
|
.fadeIn(delay: 1000.ms, duration: 500.ms)
|
||||||
|
.flipV(begin: -0.5, end: 0, curve: Curves.easeOut),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
PreferredSizeWidget _buildCustomAppBar(BuildContext context) {
|
||||||
|
return PreferredSize(
|
||||||
|
preferredSize: const Size.fromHeight(70.0),
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: const BorderRadius.vertical(
|
||||||
|
bottom: Radius.circular(15),
|
||||||
|
),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.08),
|
||||||
|
blurRadius: 10,
|
||||||
|
offset: const Offset(0, 4),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: SafeArea(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
SizedBox(height: 15),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'رزرو شده',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.black,
|
||||||
|
fontWeight: FontWeight.normal,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: SvgPicture.asset(Assets.icons.arrowLeft.path),
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildOfferDetailsCard() {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(4),
|
||||||
|
decoration: BoxDecoration(borderRadius: BorderRadius.circular(16)),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
child: CachedNetworkImage(
|
||||||
|
imageUrl: widget.offer.coverImageUrl,
|
||||||
|
width: 110,
|
||||||
|
height: 110,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
// جزئیات متنی
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
widget.offer.title,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.normal,
|
||||||
|
color: AppColors.hint,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
SvgPicture.asset(Assets.icons.ticketDiscount.path),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
Text(
|
||||||
|
'(${widget.offer.discount})',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
color: AppColors.singleOfferType,
|
||||||
|
fontWeight: FontWeight.normal,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
Text(
|
||||||
|
widget.offer.originalPrice.toStringAsFixed(0),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Colors.grey.shade600,
|
||||||
|
decoration: TextDecoration.lineThrough,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
SvgPicture.asset(Assets.icons.cardPos.path,height: 22,color: Color.fromARGB(255, 157, 157, 155),),
|
||||||
|
SizedBox(width: 6,),
|
||||||
|
Text(
|
||||||
|
'${widget.offer.finalPrice.toStringAsFixed(0)} تومان',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.normal,
|
||||||
|
color: AppColors.singleOfferType,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildTimerCard() {
|
||||||
|
if (_remaining.inSeconds <= 0) {
|
||||||
|
return const Center(
|
||||||
|
child: Text(
|
||||||
|
'مهلت این تخفیف به پایان رسیده است',
|
||||||
|
style: TextStyle(color: AppColors.singleOfferType, fontSize: 16),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String days = _remaining.inDays.toString();
|
||||||
|
String hours = (_remaining.inHours % 24).toString();
|
||||||
|
String minutes = (_remaining.inMinutes % 60).toString();
|
||||||
|
String seconds = (_remaining.inSeconds % 60).toString();
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 20),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Color.fromARGB(255, 246, 246, 246),
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
Text("مدت",style: TextStyle(color: AppColors.expiryReserve,fontSize: 15),),
|
||||||
|
SizedBox(height: 7,),
|
||||||
|
Text("اعتبار",style: TextStyle(color: AppColors.expiryReserve,fontSize: 15),),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(width: 25,),
|
||||||
|
_buildTimeBlock(seconds, 'ثانیه'),
|
||||||
|
SizedBox(width: 20,),
|
||||||
|
_buildTimeBlock(minutes, 'دقیقه'),
|
||||||
|
SizedBox(width: 20,),
|
||||||
|
_buildTimeBlock(hours, 'ساعت'),
|
||||||
|
SizedBox(width: 20,),
|
||||||
|
if (_remaining.inDays > 0) _buildTimeBlock(days, 'روز'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 20 ,),
|
||||||
|
Text("لطفا QR Code زیر رو به فروشنده نشون بده.",style: TextStyle(fontSize: 15),)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildTimeBlock(String value, String label) {
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 3),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(color: AppColors.countdownBorderRserve, width: 1.5),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(4.0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
value,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontFamily: 'Dana',
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
label,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontFamily: 'Dana',
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Widget _buildQrCodeCard() {
|
||||||
|
return Center(
|
||||||
|
child: Container(
|
||||||
|
width: 500,
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Color.fromARGB(255, 246, 246, 246),
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: QrImageView(
|
||||||
|
data: widget.offer.qrCodeData,
|
||||||
|
version: QrVersions.auto,
|
||||||
|
size: 280.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 10,),
|
||||||
|
Text(widget.offer.qrCodeData,style: TextStyle(fontWeight: FontWeight.bold,fontSize: 20),)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
16
pubspec.lock
16
pubspec.lock
|
|
@ -829,6 +829,22 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.5.0"
|
version: "1.5.0"
|
||||||
|
qr:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: qr
|
||||||
|
sha256: "5a1d2586170e172b8a8c8470bbbffd5eb0cd38a66c0d77155ea138d3af3a4445"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.2"
|
||||||
|
qr_flutter:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: qr_flutter
|
||||||
|
sha256: "5095f0fc6e3f71d08adef8feccc8cea4f12eec18a2e31c2e8d82cb6019f4b097"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.1.0"
|
||||||
rxdart:
|
rxdart:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@ dependencies:
|
||||||
flutter_animate: ^4.5.2
|
flutter_animate: ^4.5.2
|
||||||
maps_launcher: ^3.0.0+1
|
maps_launcher: ^3.0.0+1
|
||||||
slide_countdown: ^2.0.2
|
slide_countdown: ^2.0.2
|
||||||
|
qr_flutter: ^4.1.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue