mqtt connected
This commit is contained in:
parent
690813829d
commit
050fb6b620
|
|
@ -4,12 +4,15 @@
|
|||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
|
||||
|
||||
<application
|
||||
android:label="Proxibuy"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<!-- android:networkSecurityConfig="@xml/network_security_config"> -->
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<network-security-config>
|
||||
<domain-config cleartextTrafficPermitted="true">
|
||||
<domain includeSubdomains="true">5.75.200.241</domain>
|
||||
</domain-config>
|
||||
</network-security-config>
|
||||
|
|
@ -4,4 +4,5 @@ class ApiConfig {
|
|||
static const String verifyCode = "/login/getcode";
|
||||
static const String updateUser = "/user/updateName";
|
||||
static const String updateCategories = "/user/favoriteCategory";
|
||||
static const String getFavoriteCategories = "/user/getfavoriteCategory"; // این خط اضافه شد
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
// lib/data/models/comment_model.dart
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
class CommentModel extends Equatable {
|
||||
|
|
@ -19,4 +21,15 @@ class CommentModel extends Equatable {
|
|||
|
||||
@override
|
||||
List<Object?> get props => [id, userName, rating, comment, publishedAt, uploadedImageUrls];
|
||||
|
||||
factory CommentModel.fromJson(Map<String, dynamic> json) {
|
||||
return CommentModel(
|
||||
id: json['id'],
|
||||
userName: json['userName'],
|
||||
rating: (json['rating'] as num).toDouble(),
|
||||
comment: json['comment'],
|
||||
publishedAt: DateTime.parse(json['publishedAt']),
|
||||
uploadedImageUrls: List<String>.from(json['uploadedImageUrls'] ?? []),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,362 +1,362 @@
|
|||
import 'package:proxibuy/data/models/comment_model.dart';
|
||||
import 'package:proxibuy/data/models/discount_info_model.dart';
|
||||
import 'package:proxibuy/data/models/offer_model.dart';
|
||||
import 'package:proxibuy/data/models/working_hours.dart';
|
||||
// import 'package:proxibuy/data/models/comment_model.dart';
|
||||
// import 'package:proxibuy/data/models/discount_info_model.dart';
|
||||
// import 'package:proxibuy/data/models/offer_model.dart';
|
||||
// import 'package:proxibuy/data/models/working_hours.dart';
|
||||
|
||||
abstract class OfferDataSource {
|
||||
Future<List<OfferModel>> getNearbyOffers();
|
||||
Future<OfferModel?> getOfferById(String id);
|
||||
}
|
||||
// abstract class OfferDataSource {
|
||||
// Future<List<OfferModel>> getNearbyOffers();
|
||||
// Future<OfferModel?> getOfferById(String id);
|
||||
// }
|
||||
|
||||
class MockOfferDataSource implements OfferDataSource {
|
||||
final List<OfferModel> _mockOffers = [
|
||||
OfferModel(
|
||||
id: '1',
|
||||
storeName: 'روچیک (Ruchik)',
|
||||
title: 'چیزبرگر',
|
||||
discount: '۲۰٪',
|
||||
imageUrls: [
|
||||
'https://picsum.photos/seed/food/400/200',
|
||||
'https://picsum.photos/seed/burger1/400/400',
|
||||
'https://picsum.photos/seed/burger2/400/400',
|
||||
],
|
||||
category: 'فستفود',
|
||||
distanceInMeters: 130,
|
||||
expiryTime: DateTime.now().add(const Duration(hours: 2, minutes: 30, seconds: 10)),
|
||||
rating: 4.8,
|
||||
workingHours: [
|
||||
WorkingHours(
|
||||
day: 'شنبه',
|
||||
shifts: [Shift(openAt: '۱۰ صبح', closeAt: '۱۰ شب')],
|
||||
),
|
||||
WorkingHours(
|
||||
day: 'یکشنبه',
|
||||
shifts: [Shift(openAt: '۱۰ صبح', closeAt: '۱۰ شب')],
|
||||
),
|
||||
WorkingHours(
|
||||
day: 'دوشنبه',
|
||||
shifts: [Shift(openAt: '۱۰ صبح', closeAt: '۱۰ شب')],
|
||||
),
|
||||
WorkingHours(
|
||||
day: 'سهشنبه',
|
||||
shifts: [Shift(openAt: '۱۰ صبح', closeAt: '۱۰ شب')],
|
||||
),
|
||||
WorkingHours(
|
||||
day: 'چهارشنبه',
|
||||
shifts: [Shift(openAt: '۱۰ صبح', closeAt: '۱۰ شب')],
|
||||
),
|
||||
WorkingHours(
|
||||
day: 'پنجشنبه',
|
||||
shifts: [
|
||||
Shift(openAt: '۱۰ صبح', closeAt: '۱ ظهر'),
|
||||
Shift(openAt: '۵ عصر', closeAt: '۱۱ شب'),
|
||||
],
|
||||
),
|
||||
WorkingHours(day: 'جمعه', shifts: []),
|
||||
],
|
||||
discountType: 'رفیقبازی',
|
||||
isOpen: false,
|
||||
address: 'چهارباغ پایین ',
|
||||
ratingCount: 340,
|
||||
latitude: 32.660,
|
||||
longitude: 51.670,
|
||||
originalPrice: 150000,
|
||||
finalPrice: 120000,
|
||||
features: [
|
||||
"تهیه شده از بهترین و تازهترین مواد اولیه",
|
||||
"محیطی دنج و مناسب برای قرارهای دوستانه",
|
||||
"دارای منوی متنوع برای تمام سلیقهها",
|
||||
],
|
||||
discountInfo: const DiscountInfoModel(
|
||||
name: "رفیقبازی",
|
||||
description: "یه مهمونی دو نفره، یه تخفیف دوتایی؛ پس رفیقتو بیار و تخفیفتو ببر. دوستای بیشتر، تخفیف بیشتر.",
|
||||
),
|
||||
comments: [
|
||||
CommentModel(
|
||||
id: 'c1',
|
||||
userName: 'سارا رضایی',
|
||||
rating: 4.5,
|
||||
comment: 'کیفیت برگر عالی بود و محیط خیلی خوبی داشت. حتما دوباره سر میزنم!',
|
||||
publishedAt: DateTime.now().subtract(const Duration(days: 2)),
|
||||
uploadedImageUrls: [
|
||||
'https://picsum.photos/seed/user_img1/200/200',
|
||||
'https://picsum.photos/seed/user_img2/200/200',
|
||||
]
|
||||
),
|
||||
CommentModel(
|
||||
id: 'c2',
|
||||
userName: 'pbuser_157',
|
||||
rating: 4,
|
||||
comment: 'جای خوب و دنجی بود، فقط یکم شلوغ بود که طبیعیه. در کل راضی بودم.',
|
||||
publishedAt: DateTime.now().subtract(const Duration(days: 5)),
|
||||
),
|
||||
],
|
||||
qrCodeData: 'PROXIBUY-OFFER-ID-1',
|
||||
),
|
||||
OfferModel(
|
||||
id: '2',
|
||||
storeName: 'کاخ سرهنگ',
|
||||
title: 'عصرانه',
|
||||
discount: '۲۰% ',
|
||||
imageUrls: [
|
||||
'https://picsum.photos/seed/food/400/200',
|
||||
'https://picsum.photos/seed/burger1/400/400',
|
||||
'https://picsum.photos/seed/burger2/400/400',
|
||||
],
|
||||
category: 'رستوران',
|
||||
distanceInMeters: 130,
|
||||
expiryTime: DateTime.now().add(const Duration(hours: 5)),
|
||||
rating: 4.8,
|
||||
workingHours: [
|
||||
WorkingHours(
|
||||
day: 'شنبه',
|
||||
shifts: [Shift(openAt: '۱۰ صبح', closeAt: '۱۰ شب')],
|
||||
),
|
||||
WorkingHours(
|
||||
day: 'یکشنبه',
|
||||
shifts: [Shift(openAt: '۱۰ صبح', closeAt: '۱۰ شب')],
|
||||
),
|
||||
WorkingHours(
|
||||
day: 'دوشنبه',
|
||||
shifts: [Shift(openAt: '۱۰ صبح', closeAt: '۱۰ شب')],
|
||||
),
|
||||
WorkingHours(
|
||||
day: 'سهشنبه',
|
||||
shifts: [Shift(openAt: '۱۰ صبح', closeAt: '۱۰ شب')],
|
||||
),
|
||||
WorkingHours(
|
||||
day: 'چهارشنبه',
|
||||
shifts: [Shift(openAt: '۱۰ صبح', closeAt: '۱۰ شب')],
|
||||
),
|
||||
WorkingHours(
|
||||
day: 'پنجشنبه',
|
||||
shifts: [
|
||||
Shift(openAt: '۱۰ صبح', closeAt: '۱ ظهر'),
|
||||
Shift(openAt: '۵ عصر', closeAt: '۱۱ شب'),
|
||||
],
|
||||
),
|
||||
WorkingHours(day: 'جمعه', shifts: []),
|
||||
],
|
||||
discountType: 'رفیقبازی',
|
||||
isOpen: true,
|
||||
address: 'چهارباغ پایین ',
|
||||
ratingCount: 340,
|
||||
latitude: 32.660,
|
||||
longitude: 51.670,
|
||||
originalPrice: 150000,
|
||||
finalPrice: 120000,
|
||||
features: [
|
||||
"تهیه شده از بهترین و تازهترین مواد اولیه",
|
||||
"محیطی دنج و مناسب برای قرارهای دوستانه",
|
||||
"دارای منوی متنوع برای تمام سلیقهها",
|
||||
],
|
||||
discountInfo: const DiscountInfoModel(
|
||||
name: "رفیقبازی",
|
||||
description: "یه مهمونی دو نفره، یه تخفیف دوتایی؛ پس رفیقتو بیار و تخفیفتو ببر. دوستای بیشتر، تخفیف بیشتر.",
|
||||
),
|
||||
comments: [
|
||||
CommentModel(
|
||||
id: 'c1',
|
||||
userName: 'سارا رضایی',
|
||||
rating: 4.5,
|
||||
comment: 'کیفیت برگر عالی بود و محیط خیلی خوبی داشت. حتما دوباره سر میزنم!',
|
||||
publishedAt: DateTime.now().subtract(const Duration(days: 2)),
|
||||
uploadedImageUrls: [
|
||||
'https://picsum.photos/seed/user_img1/200/200',
|
||||
'https://picsum.photos/seed/user_img2/200/200',
|
||||
]
|
||||
),
|
||||
CommentModel(
|
||||
id: 'c2',
|
||||
userName: 'علی اکبری',
|
||||
rating: 4,
|
||||
comment: 'جای خوب و دنجی بود، فقط یکم شلوغ بود که طبیعیه. در کل راضی بودم.',
|
||||
publishedAt: DateTime.now().subtract(const Duration(days: 5)),
|
||||
),
|
||||
],
|
||||
qrCodeData: 'PROXIBUY-OFFER-ID-1',
|
||||
),
|
||||
OfferModel(
|
||||
id: '3',
|
||||
storeName: 'روچیک (Ruchik)',
|
||||
title: 'چیزبرگر',
|
||||
discount: '۲۰٪',
|
||||
imageUrls: [
|
||||
'https://picsum.photos/seed/food/ 400/200',
|
||||
'https://picsum.photos/seed/burger1/400/400',
|
||||
'https://picsum.photos/seed/burger2/400/400',
|
||||
],
|
||||
category: 'فستفود',
|
||||
distanceInMeters: 130,
|
||||
expiryTime: DateTime.now().add(const Duration(hours: 5)),
|
||||
rating: 4.8,
|
||||
workingHours: [
|
||||
WorkingHours(
|
||||
day: 'شنبه',
|
||||
shifts: [Shift(openAt: '۱۰ صبح', closeAt: '۱۰ شب')],
|
||||
),
|
||||
WorkingHours(
|
||||
day: 'یکشنبه',
|
||||
shifts: [Shift(openAt: '۱۰ صبح', closeAt: '۱۰ شب')],
|
||||
),
|
||||
WorkingHours(
|
||||
day: 'دوشنبه',
|
||||
shifts: [Shift(openAt: '۱۰ صبح', closeAt: '۱۰ شب')],
|
||||
),
|
||||
WorkingHours(
|
||||
day: 'سهشنبه',
|
||||
shifts: [Shift(openAt: '۱۰ صبح', closeAt: '۱۰ شب')],
|
||||
),
|
||||
WorkingHours(
|
||||
day: 'چهارشنبه',
|
||||
shifts: [Shift(openAt: '۱۰ صبح', closeAt: '۱۰ شب')],
|
||||
),
|
||||
WorkingHours(
|
||||
day: 'پنجشنبه',
|
||||
shifts: [
|
||||
Shift(openAt: '۱۰ صبح', closeAt: '۱ ظهر'),
|
||||
Shift(openAt: '۵ عصر', closeAt: '۱۱ شب'),
|
||||
],
|
||||
),
|
||||
WorkingHours(day: 'جمعه', shifts: []),
|
||||
],
|
||||
discountType: 'رفیقبازی',
|
||||
isOpen: true,
|
||||
address: 'چهارباغ پایین ',
|
||||
ratingCount: 340,
|
||||
latitude: 32.660,
|
||||
longitude: 51.670,
|
||||
originalPrice: 150000,
|
||||
finalPrice: 120000,
|
||||
features: [
|
||||
"تهیه شده از بهترین و تازهترین مواد اولیه",
|
||||
"محیطی دنج و مناسب برای قرارهای دوستانه",
|
||||
"دارای منوی متنوع برای تمام سلیقهها",
|
||||
],
|
||||
discountInfo: const DiscountInfoModel(
|
||||
name: "رفیقبازی",
|
||||
description: "یه مهمونی دو نفره، یه تخفیف دوتایی؛ پس رفیقتو بیار و تخفیفتو ببر. دوستای بیشتر، تخفیف بیشتر.",
|
||||
),
|
||||
comments: [
|
||||
CommentModel(
|
||||
id: 'c1',
|
||||
userName: 'سارا رضایی',
|
||||
rating: 4.5,
|
||||
comment: 'کیفیت برگر عالی بود و محیط خیلی خوبی داشت. حتما دوباره سر میزنم!',
|
||||
publishedAt: DateTime.now().subtract(const Duration(days: 2)),
|
||||
uploadedImageUrls: [
|
||||
'https://picsum.photos/seed/user_img1/200/200',
|
||||
'https://picsum.photos/seed/user_img2/200/200',
|
||||
]
|
||||
),
|
||||
CommentModel(
|
||||
id: 'c2',
|
||||
userName: 'علی اکبری',
|
||||
rating: 4,
|
||||
comment: 'جای خوب و دنجی بود، فقط یکم شلوغ بود که طبیعیه. در کل راضی بودم.',
|
||||
publishedAt: DateTime.now().subtract(const Duration(days: 5)),
|
||||
),
|
||||
],
|
||||
qrCodeData: 'PROXIBUY-OFFER-ID-1',
|
||||
),
|
||||
OfferModel(
|
||||
id: '4',
|
||||
storeName: 'روچیک (Ruchik)',
|
||||
title: 'چیزبرگر',
|
||||
discount: '۲۰٪',
|
||||
imageUrls: [
|
||||
'https://picsum.photos/seed/food/400/200',
|
||||
'https://picsum.photos/seed/burger1/400/400',
|
||||
'https://picsum.photos/seed/burger2/400/400',
|
||||
],
|
||||
category: 'فستفود',
|
||||
distanceInMeters: 130,
|
||||
expiryTime: DateTime.now().add(const Duration(hours: 5)),
|
||||
rating: 4.8,
|
||||
workingHours: [
|
||||
WorkingHours(
|
||||
day: 'شنبه',
|
||||
shifts: [Shift(openAt: '۱۰ صبح', closeAt: '۱۰ شب')],
|
||||
),
|
||||
WorkingHours(
|
||||
day: 'یکشنبه',
|
||||
shifts: [Shift(openAt: '۱۰ صبح', closeAt: '۱۰ شب')],
|
||||
),
|
||||
WorkingHours(
|
||||
day: 'دوشنبه',
|
||||
shifts: [Shift(openAt: '۱۰ صبح', closeAt: '۱۰ شب')],
|
||||
),
|
||||
WorkingHours(
|
||||
day: 'سهشنبه',
|
||||
shifts: [Shift(openAt: '۱۰ صبح', closeAt: '۱۰ شب')],
|
||||
),
|
||||
WorkingHours(
|
||||
day: 'چهارشنبه',
|
||||
shifts: [Shift(openAt: '۱۰ صبح', closeAt: '۱۰ شب')],
|
||||
),
|
||||
WorkingHours(
|
||||
day: 'پنجشنبه',
|
||||
shifts: [
|
||||
Shift(openAt: '۱۰ صبح', closeAt: '۱ ظهر'),
|
||||
Shift(openAt: '۵ عصر', closeAt: '۱۱ شب'),
|
||||
],
|
||||
),
|
||||
WorkingHours(day: 'جمعه', shifts: []),
|
||||
],
|
||||
discountType: 'رفیقبازی',
|
||||
isOpen: false,
|
||||
address: 'چهارباغ پایین ',
|
||||
ratingCount: 340,
|
||||
latitude: 32.660,
|
||||
longitude: 51.670,
|
||||
originalPrice: 150000,
|
||||
finalPrice: 120000,
|
||||
features: [
|
||||
"تهیه شده از بهترین و تازهترین مواد اولیه",
|
||||
"محیطی دنج و مناسب برای قرارهای دوستانه",
|
||||
"دارای منوی متنوع برای تمام سلیقهها",
|
||||
],
|
||||
discountInfo: const DiscountInfoModel(
|
||||
name: "رفیقبازی",
|
||||
description: "یه مهمونی دو نفره، یه تخفیف دوتایی؛ پس رفیقتو بیار و تخفیفتو ببر. دوستای بیشتر، تخفیف بیشتر.",
|
||||
),
|
||||
comments: [
|
||||
CommentModel(
|
||||
id: 'c1',
|
||||
userName: 'سارا رضایی',
|
||||
rating: 4.5,
|
||||
comment: 'کیفیت برگر عالی بود و محیط خیلی خوبی داشت. حتما دوباره سر میزنم!',
|
||||
publishedAt: DateTime.now().subtract(const Duration(days: 2)),
|
||||
uploadedImageUrls: [
|
||||
'https://picsum.photos/seed/user_img1/200/200',
|
||||
'https://picsum.photos/seed/user_img2/200/200',
|
||||
]
|
||||
),
|
||||
CommentModel(
|
||||
id: 'c2',
|
||||
userName: 'علی اکبری',
|
||||
rating: 4,
|
||||
comment: 'جای خوب و دنجی بود، فقط یکم شلوغ بود که طبیعیه. در کل راضی بودم.',
|
||||
publishedAt: DateTime.now().subtract(const Duration(days: 5)),
|
||||
),
|
||||
],
|
||||
qrCodeData: 'PROXIBUY-OFFER-ID-1',
|
||||
),
|
||||
];
|
||||
// class MockOfferDataSource implements OfferDataSource {
|
||||
// final List<OfferModel> _mockOffers = [
|
||||
// OfferModel(
|
||||
// id: '1',
|
||||
// storeName: 'روچیک (Ruchik)',
|
||||
// title: 'چیزبرگر',
|
||||
// discount: '۲۰٪',
|
||||
// imageUrls: [
|
||||
// 'https://picsum.photos/seed/food/400/200',
|
||||
// 'https://picsum.photos/seed/burger1/400/400',
|
||||
// 'https://picsum.photos/seed/burger2/400/400',
|
||||
// ],
|
||||
// category: 'فستفود',
|
||||
// distanceInMeters: 130,
|
||||
// expiryTime: DateTime.now().add(const Duration(hours: 2, minutes: 30, seconds: 10)),
|
||||
// rating: 4.8,
|
||||
// workingHours: [
|
||||
// WorkingHours(
|
||||
// day: 'شنبه',
|
||||
// shifts: [Shift(openAt: '۱۰ صبح', closeAt: '۱۰ شب')],
|
||||
// ),
|
||||
// WorkingHours(
|
||||
// day: 'یکشنبه',
|
||||
// shifts: [Shift(openAt: '۱۰ صبح', closeAt: '۱۰ شب')],
|
||||
// ),
|
||||
// WorkingHours(
|
||||
// day: 'دوشنبه',
|
||||
// shifts: [Shift(openAt: '۱۰ صبح', closeAt: '۱۰ شب')],
|
||||
// ),
|
||||
// WorkingHours(
|
||||
// day: 'سهشنبه',
|
||||
// shifts: [Shift(openAt: '۱۰ صبح', closeAt: '۱۰ شب')],
|
||||
// ),
|
||||
// WorkingHours(
|
||||
// day: 'چهارشنبه',
|
||||
// shifts: [Shift(openAt: '۱۰ صبح', closeAt: '۱۰ شب')],
|
||||
// ),
|
||||
// WorkingHours(
|
||||
// day: 'پنجشنبه',
|
||||
// shifts: [
|
||||
// Shift(openAt: '۱۰ صبح', closeAt: '۱ ظهر'),
|
||||
// Shift(openAt: '۵ عصر', closeAt: '۱۱ شب'),
|
||||
// ],
|
||||
// ),
|
||||
// WorkingHours(day: 'جمعه', shifts: []),
|
||||
// ],
|
||||
// discountType: 'رفیقبازی',
|
||||
// isOpen: false,
|
||||
// address: 'چهارباغ پایین ',
|
||||
// ratingCount: 340,
|
||||
// latitude: 32.660,
|
||||
// longitude: 51.670,
|
||||
// originalPrice: 150000,
|
||||
// finalPrice: 120000,
|
||||
// features: [
|
||||
// "تهیه شده از بهترین و تازهترین مواد اولیه",
|
||||
// "محیطی دنج و مناسب برای قرارهای دوستانه",
|
||||
// "دارای منوی متنوع برای تمام سلیقهها",
|
||||
// ],
|
||||
// discountInfo: const DiscountInfoModel(
|
||||
// name: "رفیقبازی",
|
||||
// description: "یه مهمونی دو نفره، یه تخفیف دوتایی؛ پس رفیقتو بیار و تخفیفتو ببر. دوستای بیشتر، تخفیف بیشتر.",
|
||||
// ),
|
||||
// comments: [
|
||||
// CommentModel(
|
||||
// id: 'c1',
|
||||
// userName: 'سارا رضایی',
|
||||
// rating: 4.5,
|
||||
// comment: 'کیفیت برگر عالی بود و محیط خیلی خوبی داشت. حتما دوباره سر میزنم!',
|
||||
// publishedAt: DateTime.now().subtract(const Duration(days: 2)),
|
||||
// uploadedImageUrls: [
|
||||
// 'https://picsum.photos/seed/user_img1/200/200',
|
||||
// 'https://picsum.photos/seed/user_img2/200/200',
|
||||
// ]
|
||||
// ),
|
||||
// CommentModel(
|
||||
// id: 'c2',
|
||||
// userName: 'pbuser_157',
|
||||
// rating: 4,
|
||||
// comment: 'جای خوب و دنجی بود، فقط یکم شلوغ بود که طبیعیه. در کل راضی بودم.',
|
||||
// publishedAt: DateTime.now().subtract(const Duration(days: 5)),
|
||||
// ),
|
||||
// ],
|
||||
// qrCodeData: 'PROXIBUY-OFFER-ID-1',
|
||||
// ),
|
||||
// OfferModel(
|
||||
// id: '2',
|
||||
// storeName: 'کاخ سرهنگ',
|
||||
// title: 'عصرانه',
|
||||
// discount: '۲۰% ',
|
||||
// imageUrls: [
|
||||
// 'https://picsum.photos/seed/food/400/200',
|
||||
// 'https://picsum.photos/seed/burger1/400/400',
|
||||
// 'https://picsum.photos/seed/burger2/400/400',
|
||||
// ],
|
||||
// category: 'رستوران',
|
||||
// distanceInMeters: 130,
|
||||
// expiryTime: DateTime.now().add(const Duration(hours: 5)),
|
||||
// rating: 4.8,
|
||||
// workingHours: [
|
||||
// WorkingHours(
|
||||
// day: 'شنبه',
|
||||
// shifts: [Shift(openAt: '۱۰ صبح', closeAt: '۱۰ شب')],
|
||||
// ),
|
||||
// WorkingHours(
|
||||
// day: 'یکشنبه',
|
||||
// shifts: [Shift(openAt: '۱۰ صبح', closeAt: '۱۰ شب')],
|
||||
// ),
|
||||
// WorkingHours(
|
||||
// day: 'دوشنبه',
|
||||
// shifts: [Shift(openAt: '۱۰ صبح', closeAt: '۱۰ شب')],
|
||||
// ),
|
||||
// WorkingHours(
|
||||
// day: 'سهشنبه',
|
||||
// shifts: [Shift(openAt: '۱۰ صبح', closeAt: '۱۰ شب')],
|
||||
// ),
|
||||
// WorkingHours(
|
||||
// day: 'چهارشنبه',
|
||||
// shifts: [Shift(openAt: '۱۰ صبح', closeAt: '۱۰ شب')],
|
||||
// ),
|
||||
// WorkingHours(
|
||||
// day: 'پنجشنبه',
|
||||
// shifts: [
|
||||
// Shift(openAt: '۱۰ صبح', closeAt: '۱ ظهر'),
|
||||
// Shift(openAt: '۵ عصر', closeAt: '۱۱ شب'),
|
||||
// ],
|
||||
// ),
|
||||
// WorkingHours(day: 'جمعه', shifts: []),
|
||||
// ],
|
||||
// discountType: 'رفیقبازی',
|
||||
// isOpen: true,
|
||||
// address: 'چهارباغ پایین ',
|
||||
// ratingCount: 340,
|
||||
// latitude: 32.660,
|
||||
// longitude: 51.670,
|
||||
// originalPrice: 150000,
|
||||
// finalPrice: 120000,
|
||||
// features: [
|
||||
// "تهیه شده از بهترین و تازهترین مواد اولیه",
|
||||
// "محیطی دنج و مناسب برای قرارهای دوستانه",
|
||||
// "دارای منوی متنوع برای تمام سلیقهها",
|
||||
// ],
|
||||
// discountInfo: const DiscountInfoModel(
|
||||
// name: "رفیقبازی",
|
||||
// description: "یه مهمونی دو نفره، یه تخفیف دوتایی؛ پس رفیقتو بیار و تخفیفتو ببر. دوستای بیشتر، تخفیف بیشتر.",
|
||||
// ),
|
||||
// comments: [
|
||||
// CommentModel(
|
||||
// id: 'c1',
|
||||
// userName: 'سارا رضایی',
|
||||
// rating: 4.5,
|
||||
// comment: 'کیفیت برگر عالی بود و محیط خیلی خوبی داشت. حتما دوباره سر میزنم!',
|
||||
// publishedAt: DateTime.now().subtract(const Duration(days: 2)),
|
||||
// uploadedImageUrls: [
|
||||
// 'https://picsum.photos/seed/user_img1/200/200',
|
||||
// 'https://picsum.photos/seed/user_img2/200/200',
|
||||
// ]
|
||||
// ),
|
||||
// CommentModel(
|
||||
// id: 'c2',
|
||||
// userName: 'علی اکبری',
|
||||
// rating: 4,
|
||||
// comment: 'جای خوب و دنجی بود، فقط یکم شلوغ بود که طبیعیه. در کل راضی بودم.',
|
||||
// publishedAt: DateTime.now().subtract(const Duration(days: 5)),
|
||||
// ),
|
||||
// ],
|
||||
// qrCodeData: 'PROXIBUY-OFFER-ID-1',
|
||||
// ),
|
||||
// OfferModel(
|
||||
// id: '3',
|
||||
// storeName: 'روچیک (Ruchik)',
|
||||
// title: 'چیزبرگر',
|
||||
// discount: '۲۰٪',
|
||||
// imageUrls: [
|
||||
// 'https://picsum.photos/seed/food/ 400/200',
|
||||
// 'https://picsum.photos/seed/burger1/400/400',
|
||||
// 'https://picsum.photos/seed/burger2/400/400',
|
||||
// ],
|
||||
// category: 'فستفود',
|
||||
// distanceInMeters: 130,
|
||||
// expiryTime: DateTime.now().add(const Duration(hours: 5)),
|
||||
// rating: 4.8,
|
||||
// workingHours: [
|
||||
// WorkingHours(
|
||||
// day: 'شنبه',
|
||||
// shifts: [Shift(openAt: '۱۰ صبح', closeAt: '۱۰ شب')],
|
||||
// ),
|
||||
// WorkingHours(
|
||||
// day: 'یکشنبه',
|
||||
// shifts: [Shift(openAt: '۱۰ صبح', closeAt: '۱۰ شب')],
|
||||
// ),
|
||||
// WorkingHours(
|
||||
// day: 'دوشنبه',
|
||||
// shifts: [Shift(openAt: '۱۰ صبح', closeAt: '۱۰ شب')],
|
||||
// ),
|
||||
// WorkingHours(
|
||||
// day: 'سهشنبه',
|
||||
// shifts: [Shift(openAt: '۱۰ صبح', closeAt: '۱۰ شب')],
|
||||
// ),
|
||||
// WorkingHours(
|
||||
// day: 'چهارشنبه',
|
||||
// shifts: [Shift(openAt: '۱۰ صبح', closeAt: '۱۰ شب')],
|
||||
// ),
|
||||
// WorkingHours(
|
||||
// day: 'پنجشنبه',
|
||||
// shifts: [
|
||||
// Shift(openAt: '۱۰ صبح', closeAt: '۱ ظهر'),
|
||||
// Shift(openAt: '۵ عصر', closeAt: '۱۱ شب'),
|
||||
// ],
|
||||
// ),
|
||||
// WorkingHours(day: 'جمعه', shifts: []),
|
||||
// ],
|
||||
// discountType: 'رفیقبازی',
|
||||
// isOpen: true,
|
||||
// address: 'چهارباغ پایین ',
|
||||
// ratingCount: 340,
|
||||
// latitude: 32.660,
|
||||
// longitude: 51.670,
|
||||
// originalPrice: 150000,
|
||||
// finalPrice: 120000,
|
||||
// features: [
|
||||
// "تهیه شده از بهترین و تازهترین مواد اولیه",
|
||||
// "محیطی دنج و مناسب برای قرارهای دوستانه",
|
||||
// "دارای منوی متنوع برای تمام سلیقهها",
|
||||
// ],
|
||||
// discountInfo: const DiscountInfoModel(
|
||||
// name: "رفیقبازی",
|
||||
// description: "یه مهمونی دو نفره، یه تخفیف دوتایی؛ پس رفیقتو بیار و تخفیفتو ببر. دوستای بیشتر، تخفیف بیشتر.",
|
||||
// ),
|
||||
// comments: [
|
||||
// CommentModel(
|
||||
// id: 'c1',
|
||||
// userName: 'سارا رضایی',
|
||||
// rating: 4.5,
|
||||
// comment: 'کیفیت برگر عالی بود و محیط خیلی خوبی داشت. حتما دوباره سر میزنم!',
|
||||
// publishedAt: DateTime.now().subtract(const Duration(days: 2)),
|
||||
// uploadedImageUrls: [
|
||||
// 'https://picsum.photos/seed/user_img1/200/200',
|
||||
// 'https://picsum.photos/seed/user_img2/200/200',
|
||||
// ]
|
||||
// ),
|
||||
// CommentModel(
|
||||
// id: 'c2',
|
||||
// userName: 'علی اکبری',
|
||||
// rating: 4,
|
||||
// comment: 'جای خوب و دنجی بود، فقط یکم شلوغ بود که طبیعیه. در کل راضی بودم.',
|
||||
// publishedAt: DateTime.now().subtract(const Duration(days: 5)),
|
||||
// ),
|
||||
// ],
|
||||
// qrCodeData: 'PROXIBUY-OFFER-ID-1',
|
||||
// ),
|
||||
// OfferModel(
|
||||
// id: '4',
|
||||
// storeName: 'روچیک (Ruchik)',
|
||||
// title: 'چیزبرگر',
|
||||
// discount: '۲۰٪',
|
||||
// imageUrls: [
|
||||
// 'https://picsum.photos/seed/food/400/200',
|
||||
// 'https://picsum.photos/seed/burger1/400/400',
|
||||
// 'https://picsum.photos/seed/burger2/400/400',
|
||||
// ],
|
||||
// category: 'فستفود',
|
||||
// distanceInMeters: 130,
|
||||
// expiryTime: DateTime.now().add(const Duration(hours: 5)),
|
||||
// rating: 4.8,
|
||||
// workingHours: [
|
||||
// WorkingHours(
|
||||
// day: 'شنبه',
|
||||
// shifts: [Shift(openAt: '۱۰ صبح', closeAt: '۱۰ شب')],
|
||||
// ),
|
||||
// WorkingHours(
|
||||
// day: 'یکشنبه',
|
||||
// shifts: [Shift(openAt: '۱۰ صبح', closeAt: '۱۰ شب')],
|
||||
// ),
|
||||
// WorkingHours(
|
||||
// day: 'دوشنبه',
|
||||
// shifts: [Shift(openAt: '۱۰ صبح', closeAt: '۱۰ شب')],
|
||||
// ),
|
||||
// WorkingHours(
|
||||
// day: 'سهشنبه',
|
||||
// shifts: [Shift(openAt: '۱۰ صبح', closeAt: '۱۰ شب')],
|
||||
// ),
|
||||
// WorkingHours(
|
||||
// day: 'چهارشنبه',
|
||||
// shifts: [Shift(openAt: '۱۰ صبح', closeAt: '۱۰ شب')],
|
||||
// ),
|
||||
// WorkingHours(
|
||||
// day: 'پنجشنبه',
|
||||
// shifts: [
|
||||
// Shift(openAt: '۱۰ صبح', closeAt: '۱ ظهر'),
|
||||
// Shift(openAt: '۵ عصر', closeAt: '۱۱ شب'),
|
||||
// ],
|
||||
// ),
|
||||
// WorkingHours(day: 'جمعه', shifts: []),
|
||||
// ],
|
||||
// discountType: 'رفیقبازی',
|
||||
// isOpen: false,
|
||||
// address: 'چهارباغ پایین ',
|
||||
// ratingCount: 340,
|
||||
// latitude: 32.660,
|
||||
// longitude: 51.670,
|
||||
// originalPrice: 150000,
|
||||
// finalPrice: 120000,
|
||||
// features: [
|
||||
// "تهیه شده از بهترین و تازهترین مواد اولیه",
|
||||
// "محیطی دنج و مناسب برای قرارهای دوستانه",
|
||||
// "دارای منوی متنوع برای تمام سلیقهها",
|
||||
// ],
|
||||
// discountInfo: const DiscountInfoModel(
|
||||
// name: "رفیقبازی",
|
||||
// description: "یه مهمونی دو نفره، یه تخفیف دوتایی؛ پس رفیقتو بیار و تخفیفتو ببر. دوستای بیشتر، تخفیف بیشتر.",
|
||||
// ),
|
||||
// comments: [
|
||||
// CommentModel(
|
||||
// id: 'c1',
|
||||
// userName: 'سارا رضایی',
|
||||
// rating: 4.5,
|
||||
// comment: 'کیفیت برگر عالی بود و محیط خیلی خوبی داشت. حتما دوباره سر میزنم!',
|
||||
// publishedAt: DateTime.now().subtract(const Duration(days: 2)),
|
||||
// uploadedImageUrls: [
|
||||
// 'https://picsum.photos/seed/user_img1/200/200',
|
||||
// 'https://picsum.photos/seed/user_img2/200/200',
|
||||
// ]
|
||||
// ),
|
||||
// CommentModel(
|
||||
// id: 'c2',
|
||||
// userName: 'علی اکبری',
|
||||
// rating: 4,
|
||||
// comment: 'جای خوب و دنجی بود، فقط یکم شلوغ بود که طبیعیه. در کل راضی بودم.',
|
||||
// publishedAt: DateTime.now().subtract(const Duration(days: 5)),
|
||||
// ),
|
||||
// ],
|
||||
// qrCodeData: 'PROXIBUY-OFFER-ID-1',
|
||||
// ),
|
||||
// ];
|
||||
|
||||
@override
|
||||
Future<List<OfferModel>> getNearbyOffers() async {
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
return _mockOffers;
|
||||
}
|
||||
// @override
|
||||
// Future<List<OfferModel>> getNearbyOffers() async {
|
||||
// await Future.delayed(const Duration(seconds: 1));
|
||||
// return _mockOffers;
|
||||
// }
|
||||
|
||||
@override
|
||||
Future<OfferModel?> getOfferById(String id) async {
|
||||
await Future.delayed(const Duration(milliseconds: 300));
|
||||
try {
|
||||
return _mockOffers.firstWhere((offer) => offer.id == id);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
// @override
|
||||
// Future<OfferModel?> getOfferById(String id) async {
|
||||
// await Future.delayed(const Duration(milliseconds: 300));
|
||||
// try {
|
||||
// return _mockOffers.firstWhere((offer) => offer.id == id);
|
||||
// } catch (e) {
|
||||
// return null;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
// lib/data/models/discount_info_model.dart
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
class DiscountInfoModel extends Equatable {
|
||||
|
|
@ -11,4 +13,11 @@ class DiscountInfoModel extends Equatable {
|
|||
|
||||
@override
|
||||
List<Object?> get props => [name, description];
|
||||
|
||||
factory DiscountInfoModel.fromJson(Map<String, dynamic> json) {
|
||||
return DiscountInfoModel(
|
||||
name: json['name'],
|
||||
description: json['description'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,43 @@
|
|||
// lib/data/models/offer_model.dart
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:proxibuy/data/models/comment_model.dart'; // <-- این خط اضافه شد
|
||||
import 'package:proxibuy/data/models/comment_model.dart';
|
||||
import 'package:proxibuy/data/models/discount_info_model.dart';
|
||||
import 'package:proxibuy/data/models/working_hours.dart';
|
||||
|
||||
// کلاس کمکی برای دادههای فروشگاه
|
||||
class ShopData {
|
||||
final String id;
|
||||
final String name;
|
||||
final String category;
|
||||
final String address;
|
||||
final double latitude;
|
||||
final double longitude;
|
||||
final List<String> properties;
|
||||
|
||||
const ShopData({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.category,
|
||||
required this.address,
|
||||
required this.latitude,
|
||||
required this.longitude,
|
||||
required this.properties,
|
||||
});
|
||||
|
||||
factory ShopData.fromJson(Map<String, dynamic> json) {
|
||||
return ShopData(
|
||||
id: json['_id'] ?? '',
|
||||
name: json['Name'] ?? 'نام فروشگاه نامشخص',
|
||||
category: json['Category'] ?? 'بدون دستهبندی',
|
||||
address: json['Address'] ?? 'آدرس نامشخص',
|
||||
latitude: (json['Map']['coordinates'][1] as num?)?.toDouble() ?? 0.0,
|
||||
longitude: (json['Map']['coordinates'][0] as num?)?.toDouble() ?? 0.0,
|
||||
properties: List<String>.from(json['Property'] ?? []),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class OfferModel extends Equatable {
|
||||
final String id;
|
||||
final String storeName;
|
||||
|
|
@ -52,23 +87,95 @@ class OfferModel extends Equatable {
|
|||
required this.qrCodeData,
|
||||
});
|
||||
|
||||
factory OfferModel.fromJson(Map<String, dynamic> json) { // <-- پارامتر calculatedDistance حذف شد
|
||||
final shopData = ShopData.fromJson(json['shopData']);
|
||||
|
||||
final now = DateTime.now();
|
||||
bool checkIsOpen = false;
|
||||
try {
|
||||
final startTimeParts = (json['StartTime'] as String).split(':');
|
||||
final endTimeParts = (json['EndTime'] as String).split(':');
|
||||
final startHour = int.parse(startTimeParts[0]);
|
||||
final startMinute = int.parse(startTimeParts[1]);
|
||||
final endHour = int.parse(endTimeParts[0]);
|
||||
final endMinute = int.parse(endTimeParts[1]);
|
||||
|
||||
final startTime = DateTime(now.year, now.month, now.day, startHour, startMinute);
|
||||
final endTime = DateTime(now.year, now.month, now.day, endHour, endMinute);
|
||||
|
||||
checkIsOpen = now.isAfter(startTime) && now.isBefore(endTime);
|
||||
} catch(e) {
|
||||
checkIsOpen = false;
|
||||
}
|
||||
|
||||
final originalPriceValue = (json['Price'] as num?)?.toDouble() ?? 0.0;
|
||||
final finalPriceValue = (json['NPrice'] as num?)?.toDouble() ?? 0.0;
|
||||
|
||||
// **رفع خطا: تبدیل امن عدد فاصله به int**
|
||||
final distanceFromServer = (json['distance'] as num?)?.round() ?? 0;
|
||||
|
||||
|
||||
return OfferModel(
|
||||
id: json['ID'] ?? '',
|
||||
title: json['Name'] ?? 'بدون عنوان',
|
||||
discount: json['Description'] ?? '',
|
||||
imageUrls: (json['Images'] as List<dynamic>?)
|
||||
?.map((imgId) => "$imgId")
|
||||
.toList() ?? [],
|
||||
category: json['shopData']['Category']?.toString() ?? 'بدون دستهبندی',
|
||||
expiryTime: DateTime.tryParse(json['EndDate'] ?? '') ?? DateTime.now().add(const Duration(days: 1)),
|
||||
discountType: json['Type']?.toString() ?? '',
|
||||
originalPrice: originalPriceValue,
|
||||
finalPrice: finalPriceValue,
|
||||
qrCodeData: json['QRcode'] ?? '',
|
||||
storeName: shopData.name,
|
||||
address: shopData.address,
|
||||
latitude: shopData.latitude,
|
||||
longitude: shopData.longitude,
|
||||
features: shopData.properties,
|
||||
distanceInMeters: distanceFromServer, // <-- **استفاده از مسافت سرور**
|
||||
isOpen: checkIsOpen,
|
||||
workingHours: [],
|
||||
rating: 0.0,
|
||||
ratingCount: 0,
|
||||
comments: [],
|
||||
discountInfo: null,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
OfferModel copyWith() {
|
||||
return OfferModel(
|
||||
id: id,
|
||||
storeName: storeName,
|
||||
title: title,
|
||||
discount: discount,
|
||||
imageUrls: imageUrls,
|
||||
category: category,
|
||||
distanceInMeters: distanceInMeters,
|
||||
expiryTime: expiryTime,
|
||||
address: address,
|
||||
workingHours: workingHours,
|
||||
discountType: discountType,
|
||||
isOpen: isOpen,
|
||||
rating: rating,
|
||||
ratingCount: ratingCount,
|
||||
latitude: latitude,
|
||||
longitude: longitude,
|
||||
originalPrice: originalPrice,
|
||||
finalPrice: finalPrice,
|
||||
features: features,
|
||||
discountInfo: discountInfo,
|
||||
comments: comments,
|
||||
qrCodeData: qrCodeData,
|
||||
);
|
||||
}
|
||||
|
||||
String get coverImageUrl =>
|
||||
imageUrls.isNotEmpty ? imageUrls.first : 'https://via.placeholder.com/400x200.png?text=No+Image';
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
id,
|
||||
title,
|
||||
storeName,
|
||||
rating,
|
||||
ratingCount,
|
||||
latitude,
|
||||
longitude,
|
||||
features,
|
||||
discountInfo,
|
||||
comments,
|
||||
qrCodeData
|
||||
];
|
||||
List<Object?> get props => [id];
|
||||
|
||||
String get distanceAsString {
|
||||
if (distanceInMeters < 1000) {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
// lib/data/models/working_hours.dart
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
|
|
@ -9,6 +10,13 @@ class Shift extends Equatable {
|
|||
|
||||
@override
|
||||
List<Object?> get props => [openAt, closeAt];
|
||||
|
||||
factory Shift.fromJson(Map<String, dynamic> json) {
|
||||
return Shift(
|
||||
openAt: json['openAt'],
|
||||
closeAt: json['closeAt'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class WorkingHours extends Equatable {
|
||||
|
|
@ -21,4 +29,14 @@ class WorkingHours extends Equatable {
|
|||
|
||||
@override
|
||||
List<Object?> get props => [day, shifts];
|
||||
|
||||
factory WorkingHours.fromJson(Map<String, dynamic> json) {
|
||||
var shiftsFromJson = json['shifts'] as List;
|
||||
List<Shift> shiftList = shiftsFromJson.map((s) => Shift.fromJson(s)).toList();
|
||||
|
||||
return WorkingHours(
|
||||
day: json['day'],
|
||||
shifts: shiftList,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,26 +1,26 @@
|
|||
import 'package:proxibuy/data/models/datasources/offer_data_source.dart';
|
||||
import 'package:proxibuy/data/models/offer_model.dart';
|
||||
// import 'package:proxibuy/data/models/datasources/offer_data_source.dart';
|
||||
// import 'package:proxibuy/data/models/offer_model.dart';
|
||||
|
||||
class OfferRepository {
|
||||
final OfferDataSource _offerDataSource;
|
||||
// class OfferRepository {
|
||||
// final OfferDataSource _offerDataSource;
|
||||
|
||||
OfferRepository({required OfferDataSource offerDataSource})
|
||||
: _offerDataSource = offerDataSource;
|
||||
// OfferRepository({required OfferDataSource offerDataSource})
|
||||
// : _offerDataSource = offerDataSource;
|
||||
|
||||
Future<List<OfferModel>> fetchOffers({required List<String> selectedCategories}) async {
|
||||
final allOffers = await _offerDataSource.getNearbyOffers();
|
||||
// Future<List<OfferModel>> fetchOffers({required List<String> selectedCategories}) async {
|
||||
// final allOffers = await _offerDataSource.getNearbyOffers();
|
||||
|
||||
if (selectedCategories.isEmpty) {
|
||||
return allOffers;
|
||||
}
|
||||
// if (selectedCategories.isEmpty) {
|
||||
// return allOffers;
|
||||
// }
|
||||
|
||||
final filteredOffers = allOffers
|
||||
.where((offer) => selectedCategories.contains(offer.category))
|
||||
.toList();
|
||||
// final filteredOffers = allOffers
|
||||
// .where((offer) => selectedCategories.contains(offer.category))
|
||||
// .toList();
|
||||
|
||||
return filteredOffers;
|
||||
}
|
||||
Future<OfferModel?> fetchOfferById(String id) async {
|
||||
return _offerDataSource.getOfferById(id);
|
||||
}
|
||||
}
|
||||
// return filteredOffers;
|
||||
// }
|
||||
// Future<OfferModel?> fetchOfferById(String id) async {
|
||||
// return _offerDataSource.getOfferById(id);
|
||||
// }
|
||||
// }
|
||||
|
|
@ -10,14 +10,11 @@ import 'package:proxibuy/firebase_options.dart';
|
|||
import 'package:proxibuy/presentation/auth/bloc/auth_bloc.dart';
|
||||
import 'package:proxibuy/presentation/notification_preferences/bloc/notification_preferences_bloc.dart';
|
||||
import 'package:proxibuy/presentation/offer/bloc/offer_bloc.dart';
|
||||
import 'package:proxibuy/presentation/pages/offers_page.dart';
|
||||
import 'package:proxibuy/presentation/pages/otp_page.dart';
|
||||
import 'package:proxibuy/presentation/pages/user_info_page.dart';
|
||||
import 'package:proxibuy/presentation/reservation/cubit/reservation_cubit.dart';
|
||||
// import 'package:proxibuy/services/mqtt_service.dart';
|
||||
import 'package:proxibuy/services/mqtt_service.dart';
|
||||
import 'core/config/app_colors.dart';
|
||||
import 'presentation/pages/onboarding_page.dart';
|
||||
import 'package:proxibuy/presentation/pages/splash_screen.dart'; // <--- ایمپورت جدید
|
||||
import 'package:proxibuy/presentation/pages/splash_screen.dart';
|
||||
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
|
@ -33,20 +30,18 @@ class MyApp extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
RepositoryProvider<MqttService>(
|
||||
create: (context) => MqttService(),
|
||||
),
|
||||
BlocProvider<AuthBloc>(
|
||||
create: (context) => AuthBloc()..add(CheckAuthStatusEvent()),
|
||||
),
|
||||
RepositoryProvider<OfferRepository>(
|
||||
create: (context) =>
|
||||
OfferRepository(offerDataSource: MockOfferDataSource()),
|
||||
),
|
||||
// RepositoryProvider برای OfferRepository حذف شد
|
||||
BlocProvider<ReservationCubit>(
|
||||
create: (context) => ReservationCubit(),
|
||||
),
|
||||
BlocProvider<OffersBloc>(
|
||||
create: (context) => OffersBloc(
|
||||
offerRepository: context.read<OfferRepository>(),
|
||||
),
|
||||
create: (context) => OffersBloc(),
|
||||
),
|
||||
BlocProvider<NotificationPreferencesBloc>(
|
||||
create: (context) => NotificationPreferencesBloc(),
|
||||
|
|
@ -55,7 +50,7 @@ class MyApp extends StatelessWidget {
|
|||
child: MaterialApp(
|
||||
title: 'Proxibuy',
|
||||
debugShowCheckedModeBanner: false,
|
||||
home: const SplashScreen(), // <--- استفاده از صفحه اسپلش
|
||||
home: const SplashScreen(),
|
||||
localizationsDelegates: const [
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
|
|
@ -131,36 +126,36 @@ class MyApp extends StatelessWidget {
|
|||
}
|
||||
}
|
||||
|
||||
class AppRouter extends StatelessWidget {
|
||||
const AppRouter({super.key});
|
||||
// class AppRouter extends StatelessWidget {
|
||||
// const AppRouter({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final authState = context.select((AuthBloc bloc) => bloc.state);
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// final authState = context.select((AuthBloc bloc) => bloc.state);
|
||||
|
||||
if (authState is AuthCodeSentSuccess) {
|
||||
return OtpPage(
|
||||
phoneNumber: "+${authState.countryCode}${authState.phone}",
|
||||
phone: authState.phone,
|
||||
countryCode: authState.countryCode,
|
||||
);
|
||||
}
|
||||
// if (authState is AuthCodeSentSuccess) {
|
||||
// return OtpPage(
|
||||
// phoneNumber: "+${authState.countryCode}${authState.phone}",
|
||||
// phone: authState.phone,
|
||||
// countryCode: authState.countryCode,
|
||||
// );
|
||||
// }
|
||||
|
||||
if (authState is AuthLoading) {
|
||||
final currentState = context.read<AuthBloc>().state;
|
||||
if (currentState is! AuthCodeSentSuccess) {
|
||||
return const Scaffold(body: Center(child: CircularProgressIndicator()));
|
||||
}
|
||||
}
|
||||
// if (authState is AuthLoading) {
|
||||
// final currentState = context.read<AuthBloc>().state;
|
||||
// if (currentState is! AuthCodeSentSuccess) {
|
||||
// return const Scaffold(body: Center(child: CircularProgressIndicator()));
|
||||
// }
|
||||
// }
|
||||
|
||||
if (authState is AuthSuccess) {
|
||||
return const OffersPage();
|
||||
}
|
||||
// if (authState is AuthSuccess) {
|
||||
// return const OffersPage();
|
||||
// }
|
||||
|
||||
if (authState is AuthNeedsInfo) {
|
||||
return const UserInfoPage();
|
||||
}
|
||||
// if (authState is AuthNeedsInfo) {
|
||||
// return const UserInfoPage();
|
||||
// }
|
||||
|
||||
return const OnboardingPage();
|
||||
}
|
||||
}
|
||||
// return const OnboardingPage();
|
||||
// }
|
||||
// }
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
// lib/presentation/auth/bloc/auth_bloc.dart
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
|
@ -74,8 +76,10 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||
if (response.statusCode == 200) {
|
||||
final accessToken = response.data['data']['accessToken'];
|
||||
final refreshToken = response.data['data']['refreshToken'];
|
||||
final userID = response.data['data']['ID']; // <-- خط جدید: استخراج ID
|
||||
await _storage.write(key: 'accessToken', value: accessToken);
|
||||
await _storage.write(key: 'refreshToken', value: refreshToken);
|
||||
await _storage.write(key: 'userID', value: userID); // <-- خط جدید: ذخیره ID
|
||||
emit(AuthNeedsInfo());
|
||||
} else {
|
||||
emit(AuthFailure(response.data['message'] ?? 'کد صحیح نیست'));
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ class NotificationPreferencesBloc
|
|||
on<LoadCategories>(_onLoadCategories);
|
||||
on<ToggleCategorySelection>(_onToggleCategorySelection);
|
||||
on<SubmitPreferences>(_onSubmitPreferences);
|
||||
on<LoadFavoriteCategories>(_onLoadFavoriteCategories); // این خط اضافه شد
|
||||
|
||||
add(LoadCategories());
|
||||
}
|
||||
|
|
@ -77,4 +78,40 @@ class NotificationPreferencesBloc
|
|||
errorMessage: e.response?.data['message'] ?? 'خطا در ارتباط با سرور'));
|
||||
}
|
||||
}
|
||||
|
||||
// این متد اضافه شد
|
||||
Future<void> _onLoadFavoriteCategories(
|
||||
LoadFavoriteCategories event, Emitter<NotificationPreferencesState> emit) async {
|
||||
emit(state.copyWith(isLoading: true, errorMessage: null));
|
||||
try {
|
||||
final token = await _storage.read(key: 'accessToken');
|
||||
if (token == null) {
|
||||
emit(state.copyWith(isLoading: false, errorMessage: "شما وارد نشدهاید."));
|
||||
return;
|
||||
}
|
||||
|
||||
final response = await _dio.get(
|
||||
ApiConfig.baseUrl + ApiConfig.getFavoriteCategories,
|
||||
options: Options(headers: {'Authorization': 'Bearer $token'}),
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final List<dynamic> fCategory = response.data['data']['FCategory'];
|
||||
final Set<String> favoriteCategoryIds =
|
||||
fCategory.map((category) => category['ID'] as String).toSet();
|
||||
emit(state.copyWith(
|
||||
selectedCategoryIds: favoriteCategoryIds,
|
||||
isLoading: false,
|
||||
));
|
||||
} else {
|
||||
emit(state.copyWith(
|
||||
isLoading: false,
|
||||
errorMessage: response.data['message'] ?? 'خطا در دریافت اطلاعات'));
|
||||
}
|
||||
} on DioException catch (e) {
|
||||
emit(state.copyWith(
|
||||
isLoading: false,
|
||||
errorMessage: e.response?.data['message'] ?? 'خطا در ارتباط با سرور'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,6 +9,8 @@ abstract class NotificationPreferencesEvent extends Equatable {
|
|||
|
||||
class LoadCategories extends NotificationPreferencesEvent {}
|
||||
|
||||
class LoadFavoriteCategories extends NotificationPreferencesEvent {} // این کلاس اضافه شد
|
||||
|
||||
class ToggleCategorySelection extends NotificationPreferencesEvent {
|
||||
final String categoryId;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,31 +1,41 @@
|
|||
// ignore: depend_on_referenced_packages
|
||||
// lib/presentation/offer/bloc/offer_bloc.dart
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:proxibuy/data/repositories/offer_repository.dart';
|
||||
import 'package:proxibuy/presentation/offer/bloc/offer_event.dart';
|
||||
import 'package:proxibuy/presentation/offer/bloc/offer_state.dart';
|
||||
|
||||
|
||||
class OffersBloc extends Bloc<OffersEvent, OffersState> {
|
||||
final OfferRepository _offerRepository;
|
||||
|
||||
OffersBloc({required OfferRepository offerRepository})
|
||||
: _offerRepository = offerRepository,
|
||||
super(OffersInitial()) {
|
||||
on<OffersFetchRequested>(_onFetchRequested);
|
||||
OffersBloc() : super(OffersInitial()) {
|
||||
on<OffersReceivedFromMqtt>(_onOffersReceivedFromMqtt);
|
||||
on<ClearOffers>(_onClearOffers); // رویداد جدید برای پاک کردن دیتا
|
||||
}
|
||||
|
||||
Future<void> _onFetchRequested(
|
||||
OffersFetchRequested event,
|
||||
void _onOffersReceivedFromMqtt(
|
||||
OffersReceivedFromMqtt event,
|
||||
Emitter<OffersState> emit,
|
||||
) async {
|
||||
emit(OffersLoadInProgress());
|
||||
try {
|
||||
final offers = await _offerRepository.fetchOffers(
|
||||
selectedCategories: event.selectedCategories,
|
||||
);
|
||||
emit(OffersLoadSuccess(offers));
|
||||
} catch (e) {
|
||||
emit(OffersLoadFailure(e.toString()));
|
||||
) {
|
||||
// فقط در صورتی که لیست جدید خالی نباشد، آن را جایگزین کن
|
||||
if (event.offers.isNotEmpty) {
|
||||
emit(OffersLoadSuccess(event.offers));
|
||||
}
|
||||
// اگر لیست جدید خالی بود، و قبلا دیتایی داشتیم، حالت را تغییر نده
|
||||
// این کار از نمایش صفحه خالی جلوگیری میکند
|
||||
else if (state is! OffersLoadSuccess) {
|
||||
// اگر اولین بار است و لیست خالی است، حالت موفقیت با لیست خالی را نشان بده
|
||||
emit(const OffersLoadSuccess([]));
|
||||
}
|
||||
}
|
||||
|
||||
// برای زمانی که مثلا کاربر GPS را خاموش میکند
|
||||
void _onClearOffers(ClearOffers event, Emitter<OffersState> emit) {
|
||||
emit(OffersInitial());
|
||||
}
|
||||
}
|
||||
// مدیریت رویداد جدید
|
||||
void _onOffersReceivedFromMqtt(
|
||||
OffersReceivedFromMqtt event,
|
||||
Emitter<OffersState> emit,
|
||||
) {
|
||||
// جایگزین کردن لیست پیشنهادها با دادههای جدید
|
||||
emit(OffersLoadSuccess(event.offers));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
// lib/presentation/offer/bloc/offer_event.dart
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:proxibuy/data/models/offer_model.dart';
|
||||
|
||||
abstract class OffersEvent extends Equatable {
|
||||
const OffersEvent();
|
||||
|
|
@ -16,3 +18,14 @@ class OffersFetchRequested extends OffersEvent {
|
|||
@override
|
||||
List<Object> get props => [selectedCategories];
|
||||
}
|
||||
|
||||
class OffersReceivedFromMqtt extends OffersEvent {
|
||||
final List<OfferModel> offers;
|
||||
|
||||
const OffersReceivedFromMqtt(this.offers);
|
||||
|
||||
@override
|
||||
List<Object> get props => [offers];
|
||||
}
|
||||
|
||||
class ClearOffers extends OffersEvent {} // این کلاس را اضافه کنید
|
||||
|
|
|
|||
|
|
@ -22,9 +22,9 @@ class CategoryOffersRow extends StatelessWidget {
|
|||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
|
||||
child: Text(
|
||||
categoryTitle,
|
||||
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.headlineSmall?.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
|
|
@ -44,7 +44,8 @@ class CategoryOffersRow extends StatelessWidget {
|
|||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (_) {
|
||||
return ProductDetailPage(offerId: offer.id,);
|
||||
// کل آبجکت offer پاس داده میشود
|
||||
return ProductDetailPage(offer: offer);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -11,18 +11,38 @@ import 'package:proxibuy/presentation/reservation/cubit/reservation_cubit.dart';
|
|||
import 'package:proxibuy/presentation/widgets/category_selection_card.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class NotificationPreferencesPage extends StatelessWidget {
|
||||
const NotificationPreferencesPage({super.key});
|
||||
class NotificationPreferencesPage extends StatefulWidget {
|
||||
// This parameter is used to decide whether to fetch favorite categories on start
|
||||
final bool loadFavoritesOnStart;
|
||||
|
||||
static Route<void> route() {
|
||||
return MaterialPageRoute<void>(
|
||||
// The constructor now accepts the 'loadFavoritesOnStart' parameter
|
||||
const NotificationPreferencesPage({super.key, this.loadFavoritesOnStart = false});
|
||||
|
||||
static Route<bool> route({bool loadFavorites = false}) {
|
||||
return MaterialPageRoute<bool>(
|
||||
builder: (_) => BlocProvider(
|
||||
create: (context) => NotificationPreferencesBloc(),
|
||||
child: const NotificationPreferencesPage(),
|
||||
// The widget is created here, passing the parameter correctly
|
||||
child: NotificationPreferencesPage(loadFavoritesOnStart: loadFavorites),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
State<NotificationPreferencesPage> createState() =>
|
||||
_NotificationPreferencesPageState();
|
||||
}
|
||||
|
||||
class _NotificationPreferencesPageState extends State<NotificationPreferencesPage> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// If the flag is true, dispatch the event to load favorites from the API
|
||||
if (widget.loadFavoritesOnStart) {
|
||||
context.read<NotificationPreferencesBloc>().add(LoadFavoriteCategories());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
|
|
@ -100,6 +120,7 @@ class NotificationPreferencesPage extends StatelessWidget {
|
|||
body: BlocListener<NotificationPreferencesBloc, NotificationPreferencesState>(
|
||||
listener: (context, state) {
|
||||
if (state.submissionSuccess) {
|
||||
// Pop the page and return 'true' to signal a successful update
|
||||
if (Navigator.canPop(context)) {
|
||||
Navigator.of(context).pop(true);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
// lib/presentation/pages/offers_page.dart
|
||||
|
||||
import 'dart:async';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_animate/flutter_animate.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:proxibuy/core/config/app_colors.dart';
|
||||
|
|
@ -14,11 +18,11 @@ import 'package:proxibuy/presentation/offer/bloc/widgets/category_offers_row.dar
|
|||
import 'package:proxibuy/presentation/pages/notification_preferences_page.dart';
|
||||
import 'package:proxibuy/presentation/pages/reserved_list_page.dart';
|
||||
import 'package:proxibuy/presentation/reservation/cubit/reservation_cubit.dart';
|
||||
import 'package:proxibuy/presentation/widgets/gps_dialog.dart';
|
||||
import 'package:proxibuy/presentation/widgets/notification_permission_dialog.dart';
|
||||
import 'package:proxibuy/services/mqtt_service.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class OffersPage extends StatefulWidget {
|
||||
// این پارامتر دیگر استفاده نمیشود اما برای سازگاری باقی میماند
|
||||
final bool showDialogsOnLoad;
|
||||
|
||||
const OffersPage({super.key, this.showDialogsOnLoad = false});
|
||||
|
|
@ -29,25 +33,155 @@ class OffersPage extends StatefulWidget {
|
|||
|
||||
class _OffersPageState extends State<OffersPage> {
|
||||
List<String> _selectedCategories = [];
|
||||
StreamSubscription? _locationServiceSubscription;
|
||||
StreamSubscription? _mqttMessageSubscription;
|
||||
Timer? _locationTimer;
|
||||
bool _isSubscribedToOffers = false;
|
||||
bool _isGpsEnabled = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadOffersAndPreferences();
|
||||
|
||||
if (widget.showDialogsOnLoad) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
if (mounted) {
|
||||
await showGPSDialog(context);
|
||||
_initializePage();
|
||||
}
|
||||
if (mounted) {
|
||||
await showNotificationPermissionDialog(context);
|
||||
|
||||
Future<void> _initializePage() async {
|
||||
await _loadPreferences();
|
||||
_subscribeToUserOffersOnLoad();
|
||||
_initLocationListener();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_locationServiceSubscription?.cancel();
|
||||
_mqttMessageSubscription?.cancel();
|
||||
_locationTimer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _subscribeToUserOffersOnLoad() async {
|
||||
final storage = const FlutterSecureStorage();
|
||||
final userID = await storage.read(key: 'userID');
|
||||
if (userID != null && mounted) {
|
||||
_subscribeToUserOffers(userID);
|
||||
}
|
||||
}
|
||||
|
||||
void _initLocationListener() {
|
||||
_checkInitialGpsStatus();
|
||||
_locationServiceSubscription =
|
||||
Geolocator.getServiceStatusStream().listen((status) {
|
||||
final isEnabled = status == ServiceStatus.enabled;
|
||||
if (mounted && _isGpsEnabled != isEnabled) {
|
||||
setState(() {
|
||||
_isGpsEnabled = isEnabled;
|
||||
});
|
||||
}
|
||||
|
||||
if (isEnabled) {
|
||||
_startSendingLocationUpdates();
|
||||
} else {
|
||||
print("❌ Location Service Disabled. Stopping updates.");
|
||||
_locationTimer?.cancel();
|
||||
context.read<OffersBloc>().add(ClearOffers());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _checkInitialGpsStatus() async {
|
||||
final status = await Geolocator.isLocationServiceEnabled();
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isGpsEnabled = status;
|
||||
});
|
||||
if (_isGpsEnabled) {
|
||||
_startSendingLocationUpdates();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadOffersAndPreferences() async {
|
||||
void _startSendingLocationUpdates() {
|
||||
print("🚀 Starting periodic location updates.");
|
||||
_locationTimer?.cancel();
|
||||
_locationTimer = Timer.periodic(const Duration(seconds: 15), (timer) {
|
||||
_sendLocationUpdate();
|
||||
});
|
||||
_sendLocationUpdate();
|
||||
}
|
||||
|
||||
Future<void> _sendLocationUpdate() async {
|
||||
final mqttService = context.read<MqttService>();
|
||||
if (!mqttService.isConnected) {
|
||||
print("⚠️ MQTT not connected in OffersPage. Cannot send location.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
LocationPermission permission = await Geolocator.checkPermission();
|
||||
if (permission == LocationPermission.denied) {
|
||||
permission = await Geolocator.requestPermission();
|
||||
}
|
||||
|
||||
if (permission == LocationPermission.denied ||
|
||||
permission == LocationPermission.deniedForever) {
|
||||
print("🚫 Location permission denied by user.");
|
||||
return;
|
||||
}
|
||||
|
||||
final position = await Geolocator.getCurrentPosition(
|
||||
desiredAccuracy: LocationAccuracy.high,
|
||||
);
|
||||
|
||||
const storage = FlutterSecureStorage();
|
||||
final userID = await storage.read(key: 'userID');
|
||||
|
||||
if (userID == null) {
|
||||
print("⚠️ UserID not found. Cannot send location.");
|
||||
return;
|
||||
}
|
||||
|
||||
final payload = {
|
||||
"userID": userID,
|
||||
"lat": 32.6685,
|
||||
"lng": 51.6826,
|
||||
};
|
||||
|
||||
mqttService.publish("proxybuy/sendGps", payload);
|
||||
|
||||
} catch (e) {
|
||||
print("❌ Error sending location update in OffersPage: $e");
|
||||
}
|
||||
}
|
||||
|
||||
void _subscribeToUserOffers(String userID) {
|
||||
if (_isSubscribedToOffers) return;
|
||||
|
||||
final mqttService = context.read<MqttService>();
|
||||
final topic = 'user-proxybuy/$userID';
|
||||
mqttService.subscribe(topic);
|
||||
_isSubscribedToOffers = true;
|
||||
|
||||
_mqttMessageSubscription = mqttService.messages.listen((message) {
|
||||
final data = message['data'];
|
||||
if (data != null && data is List) {
|
||||
try {
|
||||
List<OfferModel> offers = data
|
||||
.whereType<Map<String, dynamic>>()
|
||||
.map((json) => OfferModel.fromJson(json))
|
||||
.toList();
|
||||
|
||||
if (mounted) {
|
||||
context.read<OffersBloc>().add(OffersReceivedFromMqtt(offers));
|
||||
}
|
||||
} catch (e, stackTrace) {
|
||||
print("❌ Error parsing offers from MQTT: $e");
|
||||
print(stackTrace);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _loadPreferences() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final savedCategories =
|
||||
prefs.getStringList('user_selected_categories') ?? [];
|
||||
|
|
@ -56,9 +190,6 @@ class _OffersPageState extends State<OffersPage> {
|
|||
setState(() {
|
||||
_selectedCategories = savedCategories;
|
||||
});
|
||||
context.read<OffersBloc>().add(
|
||||
OffersFetchRequested(selectedCategories: savedCategories),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -78,13 +209,11 @@ class _OffersPageState extends State<OffersPage> {
|
|||
TextButton(
|
||||
onPressed: () async {
|
||||
final result = await Navigator.of(context).push<bool>(
|
||||
MaterialPageRoute(
|
||||
builder: (_) => const NotificationPreferencesPage(),
|
||||
),
|
||||
NotificationPreferencesPage.route(loadFavorites: true),
|
||||
);
|
||||
|
||||
if (result == true && mounted) {
|
||||
_loadOffersAndPreferences();
|
||||
_loadPreferences();
|
||||
}
|
||||
},
|
||||
child: Row(
|
||||
|
|
@ -219,7 +348,7 @@ class _OffersPageState extends State<OffersPage> {
|
|||
body: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [_buildFavoriteCategoriesSection(), const OffersView()],
|
||||
children: [_buildFavoriteCategoriesSection(), OffersView(isGpsEnabled: _isGpsEnabled)],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -228,20 +357,89 @@ class _OffersPageState extends State<OffersPage> {
|
|||
}
|
||||
|
||||
class OffersView extends StatelessWidget {
|
||||
const OffersView({super.key});
|
||||
final bool isGpsEnabled;
|
||||
|
||||
const OffersView({super.key, required this.isGpsEnabled});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<OffersBloc, OffersState>(
|
||||
builder: (context, state) {
|
||||
if (state is OffersLoadInProgress || state is OffersInitial) {
|
||||
if (!isGpsEnabled) {
|
||||
return _buildGpsActivationUI(context);
|
||||
}
|
||||
|
||||
if (state is OffersInitial) {
|
||||
return const SizedBox(
|
||||
height: 300,
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
CircularProgressIndicator(),
|
||||
SizedBox(height: 20),
|
||||
Text("در حال یافتن بهترین پیشنهادها برای شما..."),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (state is OffersLoadSuccess) {
|
||||
if (state.offers.isEmpty) {
|
||||
return const SizedBox(
|
||||
height: 300,
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text("فعلاً تخفیفی در این اطراف نیست!"),
|
||||
Text("کمی قدم بزنید..."),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final groupedOffers = groupBy(
|
||||
state.offers,
|
||||
(OfferModel offer) => offer.category,
|
||||
);
|
||||
final categories = groupedOffers.keys.toList();
|
||||
|
||||
return ListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
padding: const EdgeInsets.only(top: 16),
|
||||
itemCount: categories.length,
|
||||
itemBuilder: (context, index) {
|
||||
final category = categories[index];
|
||||
final offersForCategory = groupedOffers[category]!;
|
||||
|
||||
return CategoryOffersRow(
|
||||
categoryTitle: category,
|
||||
offers: offersForCategory,
|
||||
)
|
||||
.animate()
|
||||
.fade(duration: 500.ms)
|
||||
.slideY(begin: 0.3, duration: 400.ms, curve: Curves.easeOut);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (state is OffersLoadFailure) {
|
||||
return SizedBox(
|
||||
height: 200,
|
||||
child: Center(child: Text("خطا در بارگذاری: ${state.error}")),
|
||||
);
|
||||
}
|
||||
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildGpsActivationUI(BuildContext context) {
|
||||
return Center(
|
||||
child: SizedBox(
|
||||
child: Center(
|
||||
|
|
@ -284,40 +482,4 @@ class OffersView extends StatelessWidget {
|
|||
),
|
||||
);
|
||||
}
|
||||
|
||||
final groupedOffers = groupBy(
|
||||
state.offers,
|
||||
(OfferModel offer) => offer.category,
|
||||
);
|
||||
final categories = groupedOffers.keys.toList();
|
||||
|
||||
return ListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
padding: const EdgeInsets.only(top: 16),
|
||||
itemCount: categories.length,
|
||||
itemBuilder: (context, index) {
|
||||
final category = categories[index];
|
||||
final offersForCategory = groupedOffers[category]!;
|
||||
|
||||
return CategoryOffersRow(
|
||||
categoryTitle: category,
|
||||
offers: offersForCategory,
|
||||
)
|
||||
.animate()
|
||||
.fade(duration: 500.ms)
|
||||
.slideY(begin: 0.3, duration: 400.ms, curve: Curves.easeOut);
|
||||
},
|
||||
);
|
||||
}
|
||||
if (state is OffersLoadFailure) {
|
||||
return SizedBox(
|
||||
height: 200,
|
||||
child: Center(child: Text("خطا در بارگذاری: ${state.error}")),
|
||||
);
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
// lib/presentation/pages/otp_page.dart
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:proxibuy/data/models/datasources/offer_data_source.dart';
|
||||
import 'package:proxibuy/data/repositories/offer_repository.dart';
|
||||
// import 'package:proxibuy/data/models/datasources/offer_data_source.dart'; // حذف شد
|
||||
// import 'package:proxibuy/data/repositories/offer_repository.dart'; // حذف شد
|
||||
import 'package:proxibuy/presentation/auth/bloc/auth_bloc.dart';
|
||||
import 'package:proxibuy/presentation/notification_preferences/bloc/notification_preferences_bloc.dart';
|
||||
import 'package:proxibuy/presentation/offer/bloc/offer_bloc.dart';
|
||||
|
|
@ -152,9 +154,8 @@ class _OtpPageState extends State<OtpPage> {
|
|||
});
|
||||
}
|
||||
if (state is AuthNeedsInfo) {
|
||||
final offerRepository = OfferRepository(
|
||||
offerDataSource: MockOfferDataSource(),
|
||||
);
|
||||
// **تغییر اصلی در این قسمت است**
|
||||
// دیگر نیازی به ساخت OfferRepository نیست
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
MaterialPageRoute(
|
||||
builder:
|
||||
|
|
@ -163,11 +164,9 @@ class _OtpPageState extends State<OtpPage> {
|
|||
BlocProvider.value(
|
||||
value: context.read<AuthBloc>(),
|
||||
),
|
||||
// OffersBloc دیگر به ریپازیتوری نیاز ندارد
|
||||
BlocProvider<OffersBloc>(
|
||||
create:
|
||||
(_) => OffersBloc(
|
||||
offerRepository: offerRepository,
|
||||
),
|
||||
create: (_) => OffersBloc(),
|
||||
),
|
||||
BlocProvider<ReservationCubit>(
|
||||
create: (_) => ReservationCubit(),
|
||||
|
|
|
|||
|
|
@ -17,55 +17,36 @@ import 'package:proxibuy/presentation/widgets/comments_section.dart';
|
|||
import 'package:slide_countdown/slide_countdown.dart';
|
||||
|
||||
class ProductDetailPage extends StatelessWidget {
|
||||
final String offerId;
|
||||
final OfferModel offer;
|
||||
|
||||
const ProductDetailPage({super.key, required this.offerId});
|
||||
const ProductDetailPage({super.key, required this.offer});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create:
|
||||
(context) => ProductDetailBloc(
|
||||
offerRepository: context.read<OfferRepository>(),
|
||||
)..add(ProductDetailFetchRequested(offerId: offerId)),
|
||||
child: Scaffold(
|
||||
return Scaffold(
|
||||
body: Stack(
|
||||
children: [
|
||||
BlocBuilder<ProductDetailBloc, ProductDetailState>(
|
||||
builder: (context, state) {
|
||||
if (state is ProductDetailLoadInProgress ||
|
||||
state is ProductDetailInitial) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
if (state is ProductDetailLoadFailure) {
|
||||
return Center(child: Text('خطا: ${state.error}'));
|
||||
}
|
||||
if (state is ProductDetailLoadSuccess) {
|
||||
return ProductDetailView(offer: state.offer)
|
||||
// ویجت نمایش جزئیات مستقیما ساخته میشود
|
||||
ProductDetailView(offer: offer)
|
||||
.animate()
|
||||
.fadeIn(duration: 400.ms, curve: Curves.easeOut)
|
||||
.slideY(
|
||||
begin: 0.2,
|
||||
duration: 400.ms,
|
||||
curve: Curves.easeOut,
|
||||
);
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
),
|
||||
Positioned(
|
||||
bottom: 30,
|
||||
left: 24,
|
||||
right: 24,
|
||||
child: BlocBuilder<ProductDetailBloc, ProductDetailState>(
|
||||
builder: (context, state) {
|
||||
if (state is ProductDetailLoadSuccess) {
|
||||
return ElevatedButton(
|
||||
// BlocBuilder حذف شد
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
context.read<ReservationCubit>().reserveProduct(state.offer.id);
|
||||
context.read<ReservationCubit>().reserveProduct(offer.id);
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (_) => ReservationConfirmationPage(offer: state.offer),
|
||||
builder: (_) =>
|
||||
ReservationConfirmationPage(offer: offer),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
|
@ -80,7 +61,7 @@ class ProductDetailPage extends StatelessWidget {
|
|||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
SvgPicture.asset(Assets.icons.receiptDisscount.path,),
|
||||
SvgPicture.asset(Assets.icons.receiptDisscount.path),
|
||||
const SizedBox(width: 12),
|
||||
const Text(
|
||||
'رزرو تخفیف',
|
||||
|
|
@ -92,17 +73,10 @@ class ProductDetailPage extends StatelessWidget {
|
|||
),
|
||||
],
|
||||
),
|
||||
).animate()
|
||||
.fadeIn(delay: 200.ms, duration: 400.ms, curve: Curves.easeOut)
|
||||
.slideY(begin: 2, duration: 500.ms, curve: Curves.easeOut);
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
),
|
||||
).animate().fadeIn(delay: 200.ms).slideY(begin: 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import 'package:flutter_svg/svg.dart';
|
|||
import 'package:proxibuy/core/gen/assets.gen.dart';
|
||||
import 'package:proxibuy/data/models/offer_model.dart';
|
||||
import 'package:proxibuy/data/repositories/offer_repository.dart';
|
||||
import 'package:proxibuy/presentation/offer/bloc/offer_bloc.dart';
|
||||
import 'package:proxibuy/presentation/offer/bloc/offer_state.dart';
|
||||
import 'package:proxibuy/presentation/reservation/cubit/reservation_cubit.dart';
|
||||
import 'package:proxibuy/presentation/widgets/reserved_list_item_card.dart';
|
||||
|
||||
|
|
@ -17,20 +19,30 @@ class ReservedListPage extends StatefulWidget {
|
|||
|
||||
class _ReservedListPageState extends State<ReservedListPage> {
|
||||
late final List<String> _reservedIds;
|
||||
Future<List<OfferModel?>>? _reservedOffersFuture;
|
||||
// دیگر نیازی به Future نیست
|
||||
List<OfferModel> _reservedOffers = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_reservedIds = context.read<ReservationCubit>().state.reservedProductIds;
|
||||
_reservedOffersFuture = _fetchReservedOffers();
|
||||
// اطلاعات مستقیما از BLoC خوانده میشود
|
||||
_fetchReservedOffersFromBloc();
|
||||
}
|
||||
|
||||
Future<List<OfferModel?>> _fetchReservedOffers() {
|
||||
final offerRepo = context.read<OfferRepository>();
|
||||
final offerFutures =
|
||||
_reservedIds.map((id) => offerRepo.fetchOfferById(id)).toList();
|
||||
return Future.wait(offerFutures);
|
||||
void _fetchReservedOffersFromBloc() {
|
||||
final offersState = context.read<OffersBloc>().state;
|
||||
// بررسی میکند که آیا پیشنهادها قبلا بارگذاری شدهاند یا خیر
|
||||
if (offersState is OffersLoadSuccess) {
|
||||
final allOffers = offersState.offers;
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_reservedOffers = allOffers
|
||||
.where((offer) => _reservedIds.contains(offer.id))
|
||||
.toList();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -39,39 +51,16 @@ class _ReservedListPageState extends State<ReservedListPage> {
|
|||
textDirection: TextDirection.rtl,
|
||||
child: Scaffold(
|
||||
appBar: _buildCustomAppBar(context),
|
||||
body: FutureBuilder<List<OfferModel?>>(
|
||||
future: _reservedOffersFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
if (snapshot.hasError) {
|
||||
return Center(
|
||||
child: Text('خطا در بارگذاری اطلاعات: ${snapshot.error}'),
|
||||
);
|
||||
}
|
||||
|
||||
if (!snapshot.hasData || snapshot.data!.isEmpty) {
|
||||
return const Center(
|
||||
child: Text('هیچ آیتم رزرو شدهای وجود ندارد.'),
|
||||
);
|
||||
}
|
||||
|
||||
final reservedOffers =
|
||||
snapshot.data!.whereType<OfferModel>().toList();
|
||||
|
||||
if (reservedOffers.isEmpty) {
|
||||
return const Center(
|
||||
// FutureBuilder با یک ویجت ساده جایگزین شد
|
||||
body: _reservedOffers.isEmpty
|
||||
? const Center(
|
||||
child: Text('هیچ آیتم رزرو شدهای یافت نشد.'),
|
||||
);
|
||||
}
|
||||
|
||||
return ListView.builder(
|
||||
)
|
||||
: ListView.builder(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
itemCount: reservedOffers.length,
|
||||
itemCount: _reservedOffers.length,
|
||||
itemBuilder: (context, index) {
|
||||
final offer = reservedOffers[index];
|
||||
final offer = _reservedOffers[index];
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 16.0),
|
||||
child: Column(
|
||||
|
|
@ -101,11 +90,9 @@ class _ReservedListPageState extends State<ReservedListPage> {
|
|||
curve: Curves.easeOutCubic,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
)
|
||||
) ); }
|
||||
|
||||
}
|
||||
|
||||
PreferredSizeWidget _buildCustomAppBar(BuildContext context) {
|
||||
|
|
@ -160,4 +147,3 @@ class _ReservedListPageState extends State<ReservedListPage> {
|
|||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,13 @@
|
|||
// lib/presentation/pages/splash_screen.dart
|
||||
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:proxibuy/presentation/auth/bloc/auth_bloc.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:proxibuy/presentation/pages/onboarding_page.dart';
|
||||
import 'package:proxibuy/presentation/pages/offers_page.dart';
|
||||
import 'package:proxibuy/core/gen/assets.gen.dart';
|
||||
import 'package:proxibuy/services/mqtt_service.dart';
|
||||
|
||||
class SplashScreen extends StatefulWidget {
|
||||
const SplashScreen({super.key});
|
||||
|
|
@ -14,31 +17,57 @@ class SplashScreen extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _SplashScreenState extends State<SplashScreen> {
|
||||
late final StreamSubscription _authSubscription;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final authBloc = context.read<AuthBloc>();
|
||||
// با کمی تاخیر برای نمایش لوگو، فرآیند را شروع میکنیم
|
||||
Timer(const Duration(seconds: 2), _checkAuthAndNavigate);
|
||||
}
|
||||
|
||||
_authSubscription = authBloc.stream.listen((state) {
|
||||
_authSubscription.cancel();
|
||||
if (state is AuthSuccess) {
|
||||
Future<void> _checkAuthAndNavigate() async {
|
||||
final storage = const FlutterSecureStorage();
|
||||
final token = await storage.read(key: 'accessToken');
|
||||
|
||||
if (token != null && token.isNotEmpty) {
|
||||
// کاربر احراز هویت شده است
|
||||
debugPrint("--- SplashScreen: User is authenticated. Connecting to MQTT...");
|
||||
try {
|
||||
final mqttService = context.read<MqttService>();
|
||||
|
||||
// ۱. منتظر میمانیم تا اتصال کامل برقرار شود
|
||||
await mqttService.connect(token);
|
||||
|
||||
// ۲. پس از اطمینان از اتصال، به صفحه بعد میرویم
|
||||
if (mounted && mqttService.isConnected) {
|
||||
debugPrint("--- SplashScreen: MQTT Connected. Navigating to OffersPage.");
|
||||
Navigator.of(context).pushReplacement(
|
||||
MaterialPageRoute(builder: (_) => const OffersPage()),
|
||||
);
|
||||
} else {
|
||||
} else if (mounted) {
|
||||
// اگر به هر دلیلی پس از اتمام متد، اتصال برقرار نبود
|
||||
debugPrint("--- SplashScreen: MQTT connection failed after attempt. Navigating to Onboarding.");
|
||||
Navigator.of(context).pushReplacement(
|
||||
MaterialPageRoute(builder: (_) => const OnboardingPage()),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint("❌ SplashScreen: Critical error during MQTT connection: $e");
|
||||
if (mounted) {
|
||||
Navigator.of(context).pushReplacement(
|
||||
MaterialPageRoute(builder: (_) => const OnboardingPage()),
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// کاربر احراز هویت نشده است
|
||||
debugPrint("--- SplashScreen: User not authenticated. Navigating to Onboarding.");
|
||||
if (mounted) {
|
||||
Navigator.of(context).pushReplacement(
|
||||
MaterialPageRoute(builder: (_) => const OnboardingPage()),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_authSubscription.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
|||
|
|
@ -1,33 +1,33 @@
|
|||
// ignore: depend_on_referenced_packages
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:proxibuy/data/repositories/offer_repository.dart';
|
||||
import 'package:proxibuy/presentation/product_detail/bloc/product_detail_event.dart';
|
||||
import 'package:proxibuy/presentation/product_detail/bloc/product_detail_state.dart';
|
||||
// // ignore: depend_on_referenced_packages
|
||||
// import 'package:bloc/bloc.dart';
|
||||
// import 'package:proxibuy/data/repositories/offer_repository.dart';
|
||||
// import 'package:proxibuy/presentation/product_detail/bloc/product_detail_event.dart';
|
||||
// import 'package:proxibuy/presentation/product_detail/bloc/product_detail_state.dart';
|
||||
|
||||
|
||||
class ProductDetailBloc extends Bloc<ProductDetailEvent, ProductDetailState> {
|
||||
final OfferRepository _offerRepository;
|
||||
// class ProductDetailBloc extends Bloc<ProductDetailEvent, ProductDetailState> {
|
||||
// final OfferRepository _offerRepository;
|
||||
|
||||
ProductDetailBloc({required OfferRepository offerRepository})
|
||||
: _offerRepository = offerRepository,
|
||||
super(ProductDetailInitial()) {
|
||||
on<ProductDetailFetchRequested>(_onFetchRequested);
|
||||
}
|
||||
// ProductDetailBloc({required OfferRepository offerRepository})
|
||||
// : _offerRepository = offerRepository,
|
||||
// super(ProductDetailInitial()) {
|
||||
// on<ProductDetailFetchRequested>(_onFetchRequested);
|
||||
// }
|
||||
|
||||
Future<void> _onFetchRequested(
|
||||
ProductDetailFetchRequested event,
|
||||
Emitter<ProductDetailState> emit,
|
||||
) async {
|
||||
emit(ProductDetailLoadInProgress());
|
||||
try {
|
||||
final offer = await _offerRepository.fetchOfferById(event.offerId);
|
||||
if (offer != null) {
|
||||
emit(ProductDetailLoadSuccess(offer));
|
||||
} else {
|
||||
emit(const ProductDetailLoadFailure('محصول مورد نظر یافت نشد.'));
|
||||
}
|
||||
} catch (e) {
|
||||
emit(ProductDetailLoadFailure(e.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Future<void> _onFetchRequested(
|
||||
// ProductDetailFetchRequested event,
|
||||
// Emitter<ProductDetailState> emit,
|
||||
// ) async {
|
||||
// emit(ProductDetailLoadInProgress());
|
||||
// try {
|
||||
// final offer = await _offerRepository.fetchOfferById(event.offerId);
|
||||
// if (offer != null) {
|
||||
// emit(ProductDetailLoadSuccess(offer));
|
||||
// } else {
|
||||
// emit(const ProductDetailLoadFailure('محصول مورد نظر یافت نشد.'));
|
||||
// }
|
||||
// } catch (e) {
|
||||
// emit(ProductDetailLoadFailure(e.toString()));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
|
@ -1,60 +1,123 @@
|
|||
// import 'dart:async';
|
||||
// import 'dart:math';
|
||||
// import 'package:mqtt_client/mqtt_client.dart';
|
||||
// import 'package:mqtt_client/mqtt_server_client.dart';
|
||||
// lib/services/mqtt_service.dart
|
||||
|
||||
// class MqttService {
|
||||
// late MqttServerClient client;
|
||||
// final String server = '5.75.200.241';
|
||||
// final int port = 1883;
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:mqtt_client/mqtt_client.dart';
|
||||
import 'package:mqtt_client/mqtt_server_client.dart';
|
||||
|
||||
// Future<void> connect(String token) async {
|
||||
// // 1. معادلسازی پارامترها
|
||||
// final String clientId = 'nest-' + Random().nextInt(0xFFFFFF).toRadixString(16).padLeft(6, '0');
|
||||
// final String username = 'ignored';
|
||||
// final String password = token; // ✨ توکن شما مستقیماً به عنوان پسورد در نظر گرفته میشود
|
||||
class MqttService {
|
||||
late MqttServerClient client;
|
||||
final String server = '5.75.200.241';
|
||||
final int port = 1883;
|
||||
final StreamController<Map<String, dynamic>> _messageStreamController =
|
||||
StreamController.broadcast();
|
||||
|
||||
// // 2. ساخت کلاینت
|
||||
// client = MqttServerClient.withPort(server, clientId, port);
|
||||
// client.logging(on: true);
|
||||
// client.keepAlivePeriod = 60;
|
||||
// client.autoReconnect = false; // معادل reconnectPeriod: 0
|
||||
// client.setProtocolV311();
|
||||
Stream<Map<String, dynamic>> get messages => _messageStreamController.stream;
|
||||
|
||||
// // 3. ساخت پیام اتصال با پارامترهای تعریف شده
|
||||
// final connMessage = MqttConnectMessage()
|
||||
// .withClientIdentifier(clientId)
|
||||
// .startClean()
|
||||
// .authenticateAs(username, password); // ارسال نام کاربری و رمز عبور (توکن)
|
||||
bool get isConnected => client.connectionStatus?.state == MqttConnectionState.connected;
|
||||
|
||||
// client.connectionMessage = connMessage;
|
||||
Future<void> connect(String token) async {
|
||||
final String clientId =
|
||||
'nest-' + Random().nextInt(0xFFFFFF).toRadixString(16).padLeft(6, '0');
|
||||
final String username = 'ignored';
|
||||
final String password = token;
|
||||
|
||||
// // 4. تعریف Callbackها و اتصال
|
||||
// client.onConnected = () {
|
||||
// print('✅ MQTT Connected');
|
||||
// client.updates!.listen((List<MqttReceivedMessage<MqttMessage>> c) {
|
||||
// final MqttPublishMessage recMess = c[0].payload as MqttPublishMessage;
|
||||
// final String payload =
|
||||
// MqttPublishPayload.bytesToStringAsString(recMess.payload.message);
|
||||
// print('Received message: "$payload" from topic: ${c[0].topic}');
|
||||
// });
|
||||
client = MqttServerClient.withPort(server, clientId, port);
|
||||
client.logging(on: true);
|
||||
client.keepAlivePeriod = 60;
|
||||
client.autoReconnect = false;
|
||||
client.setProtocolV311();
|
||||
|
||||
// client.subscribe('test/topic', MqttQos.atLeastOnce);
|
||||
// };
|
||||
debugPrint('--- [MQTT] Attempting to connect...');
|
||||
debugPrint('--- [MQTT] Server: $server:$port');
|
||||
debugPrint('--- [MQTT] ClientID: $clientId');
|
||||
|
||||
// client.onDisconnected = () {
|
||||
// print('❌ MQTT Disconnected');
|
||||
// };
|
||||
final connMessage = MqttConnectMessage()
|
||||
.withClientIdentifier(clientId)
|
||||
.startClean()
|
||||
.authenticateAs(username, password);
|
||||
|
||||
// client.onSubscribed = (String topic) {
|
||||
// print('✅ Subscribed to $topic');
|
||||
// };
|
||||
client.connectionMessage = connMessage;
|
||||
|
||||
// try {
|
||||
// await client.connect();
|
||||
// } catch (e) {
|
||||
// print('Exception: $e');
|
||||
// client.disconnect();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
client.onConnected = () {
|
||||
debugPrint('✅ [MQTT] Connected successfully.');
|
||||
client.updates!.listen((List<MqttReceivedMessage<MqttMessage>> c) {
|
||||
final MqttPublishMessage recMess = c[0].payload as MqttPublishMessage;
|
||||
|
||||
final String payload =
|
||||
MqttPublishPayload.bytesToStringAsString(recMess.payload.message);
|
||||
|
||||
debugPrint('<<<<< [MQTT] Received Data <<<<<');
|
||||
debugPrint('<<<<< [MQTT] Topic: ${c[0].topic}');
|
||||
debugPrint('<<<<< [MQTT] Payload as String: $payload');
|
||||
debugPrint('<<<<< ======================== <<<<<');
|
||||
|
||||
try {
|
||||
final Map<String, dynamic> jsonPayload = json.decode(payload);
|
||||
_messageStreamController.add(jsonPayload);
|
||||
} catch (e) {
|
||||
debugPrint("❌ [MQTT] Error decoding received JSON: $e");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
client.onDisconnected = () {
|
||||
debugPrint('❌ [MQTT] Disconnected.');
|
||||
};
|
||||
|
||||
client.onSubscribed = (String topic) {
|
||||
debugPrint('✅ [MQTT] Subscribed to topic: $topic');
|
||||
};
|
||||
|
||||
client.pongCallback = () {
|
||||
debugPrint('🏓 [MQTT] Ping response received');
|
||||
};
|
||||
|
||||
try {
|
||||
await client.connect();
|
||||
} on NoConnectionException catch (e) {
|
||||
debugPrint('❌ [MQTT] Connection failed - No Connection Exception: $e');
|
||||
client.disconnect();
|
||||
} on SocketException catch (e) {
|
||||
debugPrint('❌ [MQTT] Connection failed - Socket Exception: $e');
|
||||
client.disconnect();
|
||||
} catch (e) {
|
||||
debugPrint('❌ [MQTT] Connection failed - General Exception: $e');
|
||||
client.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
void subscribe(String topic) {
|
||||
if (isConnected) {
|
||||
client.subscribe(topic, MqttQos.atLeastOnce);
|
||||
} else {
|
||||
debugPrint("⚠️ [MQTT] Cannot subscribe. Client is not connected.");
|
||||
}
|
||||
}
|
||||
|
||||
void publish(String topic, Map<String, dynamic> message) {
|
||||
if (isConnected) {
|
||||
final builder = MqttClientPayloadBuilder();
|
||||
final payloadString = json.encode(message);
|
||||
builder.addString(payloadString);
|
||||
|
||||
debugPrint('>>>>> [MQTT] Publishing Data >>>>>');
|
||||
debugPrint('>>>>> [MQTT] Topic: $topic');
|
||||
debugPrint('>>>>> [MQTT] Payload: $payloadString');
|
||||
debugPrint('>>>>> ======================= >>>>>');
|
||||
|
||||
client.publishMessage(topic, MqttQos.atLeastOnce, builder.payload!);
|
||||
} else {
|
||||
debugPrint("⚠️ [MQTT] Cannot publish. Client is not connected.");
|
||||
}
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
debugPrint("--- [MQTT] Disposing MQTT Service.");
|
||||
_messageStreamController.close();
|
||||
client.disconnect();
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue