fixed edit

This commit is contained in:
mohamadmahdi jebeli 2025-07-31 09:03:27 +03:30
parent 6f6e34e391
commit ae5b280ffa
20 changed files with 107 additions and 121 deletions

View File

@ -6,5 +6,5 @@ class ApiConfig {
static const String updateCategories = "/user/favoriteCategory"; static const String updateCategories = "/user/favoriteCategory";
static const String getFavoriteCategories = "/user/getfavoriteCategory"; static const String getFavoriteCategories = "/user/getfavoriteCategory";
static const String addReservation = "/reservation/add"; static const String addReservation = "/reservation/add";
static const String getReservations = "/reservation/get"; // <-- این خط اضافه شد static const String getReservations = "/reservation/get";
} }

View File

@ -1,5 +1,3 @@
// lib/core/config/http_overrides.dart
import 'dart:io'; import 'dart:io';
class MyHttpOverrides extends HttpOverrides { class MyHttpOverrides extends HttpOverrides {

View File

@ -110,17 +110,15 @@ class OfferModel extends Equatable {
final originalPriceValue = (json['Price'] as num?)?.toDouble() ?? 0.0; final originalPriceValue = (json['Price'] as num?)?.toDouble() ?? 0.0;
final finalPriceValue = (json['NPrice'] as num?)?.toDouble() ?? 0.0; final finalPriceValue = (json['NPrice'] as num?)?.toDouble() ?? 0.0;
// ******** شروع تغییر ۱ ********
// مدیریت هوشمند فاصله و عکسها از منابع مختلف
final distanceFromServer = (json['distance'] as num?)?.round() ?? (json['Distance'] as num?)?.round() ?? 0; final distanceFromServer = (json['distance'] as num?)?.round() ?? (json['Distance'] as num?)?.round() ?? 0;
List<String> images = []; List<String> images = [];
if (json['imageData'] is List) { // برای سازگاری با پاسخ MQTT if (json['imageData'] is List) {
images = (json['imageData'] as List<dynamic>) images = (json['imageData'] as List<dynamic>)
.map((imageData) => imageData['Url']?.toString() ?? '') .map((imageData) => imageData['Url']?.toString() ?? '')
.where((url) => url.isNotEmpty) .where((url) => url.isNotEmpty)
.toList(); .toList();
} else if (json['Images'] is List) { // برای پاسخ جدید API رزرو } else if (json['Images'] is List) {
images = (json['Images'] as List<dynamic>) images = (json['Images'] as List<dynamic>)
.map((imageData) => (imageData is Map) ? imageData['Url']?.toString() ?? '' : '') .map((imageData) => (imageData is Map) ? imageData['Url']?.toString() ?? '' : '')
.where((url) => url.isNotEmpty) .where((url) => url.isNotEmpty)
@ -129,13 +127,12 @@ class OfferModel extends Equatable {
final typeData = json['typeData'] ?? json['Type']; final typeData = json['typeData'] ?? json['Type'];
final discountTypeName = (typeData is Map) ? typeData['Name']?.toString() : typeData?.toString(); final discountTypeName = (typeData is Map) ? typeData['Name']?.toString() : typeData?.toString();
// ******** پایان تغییر ۱ ********
return OfferModel( return OfferModel(
id: json['ID'] ?? '', id: json['ID'] ?? '',
title: json['Name'] ?? 'بدون عنوان', title: json['Name'] ?? 'بدون عنوان',
discount: json['Description'] ?? '', discount: json['Description'] ?? '',
imageUrls: images, // <-- استفاده از لیست جدید imageUrls: images,
category: json['categoryData']?['Name']?.toString() ?? '', category: json['categoryData']?['Name']?.toString() ?? '',
expiryTime: DateTime.tryParse(json['EndDate'] ?? '') ?? DateTime.now().add(const Duration(days: 1)), expiryTime: DateTime.tryParse(json['EndDate'] ?? '') ?? DateTime.now().add(const Duration(days: 1)),
discountType: discountTypeName ?? '', discountType: discountTypeName ?? '',
@ -146,7 +143,7 @@ class OfferModel extends Equatable {
latitude: shopData.latitude, latitude: shopData.latitude,
longitude: shopData.longitude, longitude: shopData.longitude,
features: shopData.properties, features: shopData.properties,
distanceInMeters: distanceFromServer, // <-- استفاده از متغیر جدید distanceInMeters: distanceFromServer,
isOpen: checkIsOpen, isOpen: checkIsOpen,
workingHours: [], workingHours: [],
rating: 0.0, rating: 0.0,

View File

@ -5,7 +5,7 @@ import 'package:flutter_animate/flutter_animate.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
// ignore: depend_on_referenced_packages // ignore: depend_on_referenced_packages
import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:proxibuy/core/config/http_overrides.dart'; // این خط را اضافه کنید import 'package:proxibuy/core/config/http_overrides.dart';
import 'package:proxibuy/firebase_options.dart'; import 'package:proxibuy/firebase_options.dart';
import 'package:proxibuy/presentation/auth/bloc/auth_bloc.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/notification_preferences/bloc/notification_preferences_bloc.dart';

View File

@ -77,7 +77,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
}, },
); );
if (isClosed) return; // FIX: Add check here if (isClosed) return;
if (response.statusCode == 200) { if (response.statusCode == 200) {
final accessToken = response.data['data']['accessToken']; final accessToken = response.data['data']['accessToken'];
@ -91,26 +91,23 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
emit(AuthFailure(response.data['message'] ?? 'کد صحیح نیست')); emit(AuthFailure(response.data['message'] ?? 'کد صحیح نیست'));
} }
} on DioException catch (e) { } on DioException catch (e) {
if (isClosed) return; // FIX: Add check here if (isClosed) return;
emit(AuthFailure(e.response?.data['message'] ?? 'خطایی در سرور رخ داد')); emit(AuthFailure(e.response?.data['message'] ?? 'خطایی در سرور رخ داد'));
} }
} }
Future<void> _onUpdateUserInfo( Future<void> _onUpdateUserInfo(
UpdateUserInfoEvent event, Emitter<AuthState> emit) async { UpdateUserInfoEvent event, Emitter<AuthState> emit) async {
// ******** لاگ ۱: شروع پردازش ********
debugPrint("AuthBloc: 🔵 ایونت UpdateUserInfoEvent دریافت شد با نام: ${event.name}"); debugPrint("AuthBloc: 🔵 ایونت UpdateUserInfoEvent دریافت شد با نام: ${event.name}");
emit(AuthLoading()); emit(AuthLoading());
try { try {
final token = await _storage.read(key: 'accessToken'); final token = await _storage.read(key: 'accessToken');
if (token == null) { if (token == null) {
// ******** لاگ خطا: توکن وجود ندارد ********
debugPrint("AuthBloc: 🔴 خطا: توکن کاربر یافت نشد."); debugPrint("AuthBloc: 🔴 خطا: توکن کاربر یافت نشد.");
emit(const AuthFailure("شما وارد نشده‌اید.")); emit(const AuthFailure("شما وارد نشده‌اید."));
return; return;
} }
// ******** لاگ ۲: ارسال درخواست به سرور ********
debugPrint("AuthBloc: 🟡 در حال ارسال درخواست آپدیت به سرور..."); debugPrint("AuthBloc: 🟡 در حال ارسال درخواست آپدیت به سرور...");
final response = await _dio.post( final response = await _dio.post(
ApiConfig.baseUrl + ApiConfig.updateUser, ApiConfig.baseUrl + ApiConfig.updateUser,
@ -118,7 +115,6 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
options: Options(headers: {'Authorization': 'Bearer $token'}), options: Options(headers: {'Authorization': 'Bearer $token'}),
); );
// ******** لاگ ۳: پاسخ سرور ********
debugPrint("AuthBloc: 🟠 پاسخ سرور دریافت شد. StatusCode: ${response.statusCode}"); debugPrint("AuthBloc: 🟠 پاسخ سرور دریافت شد. StatusCode: ${response.statusCode}");
if (isClosed) { if (isClosed) {
@ -127,16 +123,13 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
} }
if (response.statusCode == 200) { if (response.statusCode == 200) {
// ******** لاگ ۴: موفقیتآمیز بودن عملیات ********
debugPrint("AuthBloc: ✅ درخواست موفق بود. در حال emit کردن AuthSuccess..."); debugPrint("AuthBloc: ✅ درخواست موفق بود. در حال emit کردن AuthSuccess...");
emit(AuthSuccess()); emit(AuthSuccess());
} else { } else {
// ******** لاگ خطا: پاسخ ناموفق از سرور ********
debugPrint("AuthBloc: 🔴 سرور پاسخ ناموفق داد: ${response.data['message']}"); debugPrint("AuthBloc: 🔴 سرور پاسخ ناموفق داد: ${response.data['message']}");
emit(AuthFailure(response.data['message'] ?? 'خطا در ثبت اطلاعات')); emit(AuthFailure(response.data['message'] ?? 'خطا در ثبت اطلاعات'));
} }
} on DioException catch (e) { } on DioException catch (e) {
// ******** لاگ خطا: خطای Dio ********
debugPrint("AuthBloc: 🔴 خطای DioException رخ داد: ${e.response?.data['message']}"); debugPrint("AuthBloc: 🔴 خطای DioException رخ داد: ${e.response?.data['message']}");
if (isClosed) return; if (isClosed) return;
emit(AuthFailure(e.response?.data['message'] ?? 'خطا در ارتباط با سرور')); emit(AuthFailure(e.response?.data['message'] ?? 'خطا در ارتباط با سرور'));

View File

@ -17,6 +17,7 @@ class NotificationPreferencesBloc
on<ToggleCategorySelection>(_onToggleCategorySelection); on<ToggleCategorySelection>(_onToggleCategorySelection);
on<SubmitPreferences>(_onSubmitPreferences); on<SubmitPreferences>(_onSubmitPreferences);
on<LoadFavoriteCategories>(_onLoadFavoriteCategories); on<LoadFavoriteCategories>(_onLoadFavoriteCategories);
on<ResetSubmissionStatus>(_onResetSubmissionStatus);
add(LoadCategories()); add(LoadCategories());
} }
@ -55,7 +56,7 @@ class NotificationPreferencesBloc
try { try {
final token = await _storage.read(key: 'accessToken'); final token = await _storage.read(key: 'accessToken');
if (token == null) { if (token == null) {
if (isClosed) return; // بررسی قبل از emit if (isClosed) return;
emit(state.copyWith(isLoading: false, errorMessage: "شما وارد نشده‌اید.")); emit(state.copyWith(isLoading: false, errorMessage: "شما وارد نشده‌اید."));
return; return;
} }
@ -66,7 +67,7 @@ class NotificationPreferencesBloc
options: Options(headers: {'Authorization': 'Bearer $token'}), options: Options(headers: {'Authorization': 'Bearer $token'}),
); );
if (isClosed) return; // بررسی قبل از emit if (isClosed) return;
if (response.statusCode == 200) { if (response.statusCode == 200) {
emit(state.copyWith(isLoading: false, submissionSuccess: true)); emit(state.copyWith(isLoading: false, submissionSuccess: true));
@ -76,7 +77,7 @@ class NotificationPreferencesBloc
errorMessage: response.data['message'] ?? 'خطا در ثبت اطلاعات')); errorMessage: response.data['message'] ?? 'خطا در ثبت اطلاعات'));
} }
} on DioException catch (e) { } on DioException catch (e) {
if (isClosed) return; // بررسی قبل از emit if (isClosed) return;
emit(state.copyWith( emit(state.copyWith(
isLoading: false, isLoading: false,
errorMessage: e.response?.data['message'] ?? 'خطا در ارتباط با سرور')); errorMessage: e.response?.data['message'] ?? 'خطا در ارتباط با سرور'));
@ -89,7 +90,7 @@ class NotificationPreferencesBloc
try { try {
final token = await _storage.read(key: 'accessToken'); final token = await _storage.read(key: 'accessToken');
if (token == null) { if (token == null) {
if (isClosed) return; // بررسی قبل از emit if (isClosed) return;
emit(state.copyWith(isLoading: false, errorMessage: "شما وارد نشده‌اید.")); emit(state.copyWith(isLoading: false, errorMessage: "شما وارد نشده‌اید."));
return; return;
} }
@ -99,7 +100,7 @@ class NotificationPreferencesBloc
options: Options(headers: {'Authorization': 'Bearer $token'}), options: Options(headers: {'Authorization': 'Bearer $token'}),
); );
if (isClosed) return; // بررسی قبل از emit if (isClosed) return;
if (response.statusCode == 200) { if (response.statusCode == 200) {
final List<dynamic> fCategory = response.data['data']['FCategory']; final List<dynamic> fCategory = response.data['data']['FCategory'];
@ -115,10 +116,16 @@ class NotificationPreferencesBloc
errorMessage: response.data['message'] ?? 'خطا در دریافت اطلاعات')); errorMessage: response.data['message'] ?? 'خطا در دریافت اطلاعات'));
} }
} on DioException catch (e) { } on DioException catch (e) {
if (isClosed) return; // بررسی قبل از emit if (isClosed) return;
emit(state.copyWith( emit(state.copyWith(
isLoading: false, isLoading: false,
errorMessage: e.response?.data['message'] ?? 'خطا در ارتباط با سرور')); errorMessage: e.response?.data['message'] ?? 'خطا در ارتباط با سرور'));
} }
} }
void _onResetSubmissionStatus(
ResetSubmissionStatus event, Emitter<NotificationPreferencesState> emit) {
emit(state.copyWith(submissionSuccess: false));
}
} }

View File

@ -9,7 +9,7 @@ abstract class NotificationPreferencesEvent extends Equatable {
class LoadCategories extends NotificationPreferencesEvent {} class LoadCategories extends NotificationPreferencesEvent {}
class LoadFavoriteCategories extends NotificationPreferencesEvent {} // این کلاس اضافه شد class LoadFavoriteCategories extends NotificationPreferencesEvent {}
class ToggleCategorySelection extends NotificationPreferencesEvent { class ToggleCategorySelection extends NotificationPreferencesEvent {
final String categoryId; final String categoryId;
@ -21,3 +21,5 @@ class ToggleCategorySelection extends NotificationPreferencesEvent {
} }
class SubmitPreferences extends NotificationPreferencesEvent {} class SubmitPreferences extends NotificationPreferencesEvent {}
class ResetSubmissionStatus extends NotificationPreferencesEvent {}

View File

@ -1,3 +1,4 @@
// ignore: depend_on_referenced_packages
import 'package:bloc/bloc.dart'; import 'package:bloc/bloc.dart';
import 'package:proxibuy/presentation/offer/bloc/offer_event.dart'; import 'package:proxibuy/presentation/offer/bloc/offer_event.dart';
import 'package:proxibuy/presentation/offer/bloc/offer_state.dart'; import 'package:proxibuy/presentation/offer/bloc/offer_state.dart';
@ -12,13 +13,8 @@ class OffersBloc extends Bloc<OffersEvent, OffersState> {
OffersReceivedFromMqtt event, OffersReceivedFromMqtt event,
Emitter<OffersState> emit, Emitter<OffersState> emit,
) { ) {
// همیشه حالت موفقیت را با لیست دریافتی منتشر کن (حتی اگر خالی باشد).
// این کار تضمین میکند که صفحه از حالت لودینگ خارج میشود و رابط کاربری
// آخرین وضعیت (وجود یا عدم وجود تخفیف) را نمایش میدهد.
emit(OffersLoadSuccess(event.offers)); emit(OffersLoadSuccess(event.offers));
} }
// برای زمانی که مثلا کاربر GPS را خاموش میکند
void _onClearOffers(ClearOffers event, Emitter<OffersState> emit) { void _onClearOffers(ClearOffers event, Emitter<OffersState> emit) {
emit(OffersInitial()); emit(OffersInitial());
} }

View File

@ -33,7 +33,6 @@ class _OfferCardState extends State<OfferCard> {
padding: const EdgeInsets.only(bottom: 10), padding: const EdgeInsets.only(bottom: 10),
child: Column( child: Column(
children: [ children: [
// Hero ویجت به اینجا اضافه شد
Hero( Hero(
tag: 'offer_image_${widget.offer.id}', tag: 'offer_image_${widget.offer.id}',
child: _buildOfferImage(), child: _buildOfferImage(),

View File

@ -1,4 +1,3 @@
// lib/presentation/pages/login_page.dart
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
@ -114,8 +113,6 @@ class _LoginPageState extends State<LoginPage> {
); );
} }
if (state is AuthCodeSentSuccess) { if (state is AuthCodeSentSuccess) {
// ******** شروع تغییر ۱ ********
// BlocProvider.value حذف شد
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
@ -127,7 +124,6 @@ class _LoginPageState extends State<LoginPage> {
), ),
), ),
); );
// ******** پایان تغییر ۱ ********
} }
}, },
builder: (context, state) { builder: (context, state) {

View File

@ -12,6 +12,8 @@ import 'package:proxibuy/core/config/api_config.dart';
import 'package:proxibuy/core/config/app_colors.dart'; 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/presentation/notification_preferences/bloc/notification_preferences_bloc.dart';
import 'package:proxibuy/presentation/notification_preferences/bloc/notification_preferences_event.dart';
import 'package:proxibuy/presentation/offer/bloc/offer_bloc.dart'; import 'package:proxibuy/presentation/offer/bloc/offer_bloc.dart';
import 'package:proxibuy/presentation/offer/bloc/offer_event.dart'; import 'package:proxibuy/presentation/offer/bloc/offer_event.dart';
import 'package:proxibuy/presentation/offer/bloc/offer_state.dart'; import 'package:proxibuy/presentation/offer/bloc/offer_state.dart';
@ -47,16 +49,14 @@ class _OffersPageState extends State<OffersPage> {
super.initState(); super.initState();
_initializePage(); _initializePage();
_initConnectivityListener(); _initConnectivityListener();
_fetchInitialReservations(); // <-- فراخوانی متد جدید _fetchInitialReservations();
} }
// ******** شروع متد جدید ********
// این متد تعداد اولیه رزروها را برای نمایش روی آیکون دریافت میکند
Future<void> _fetchInitialReservations() async { Future<void> _fetchInitialReservations() async {
try { try {
const storage = FlutterSecureStorage(); const storage = FlutterSecureStorage();
final token = await storage.read(key: 'accessToken'); final token = await storage.read(key: 'accessToken');
if (token == null) return; // کاربر لاگین نکرده است if (token == null) return;
final dio = Dio(); final dio = Dio();
final response = await dio.get( final response = await dio.get(
@ -75,15 +75,12 @@ class _OffersPageState extends State<OffersPage> {
.where((id) => id.isNotEmpty) .where((id) => id.isNotEmpty)
.toList(); .toList();
// بهروزرسانی Cubit با لیست شناسهها
context.read<ReservationCubit>().setReservedIds(reservedIds); context.read<ReservationCubit>().setReservedIds(reservedIds);
} }
} catch (e) { } catch (e) {
// اگر خطایی رخ دهد، مشکلی نیست. فقط عدد نمایش داده نمیشود
debugPrint("Error fetching initial reservations: $e"); debugPrint("Error fetching initial reservations: $e");
} }
} }
// ******** پایان متد جدید ********
Future<void> _initializePage() async { Future<void> _initializePage() async {
WidgetsBinding.instance.addPostFrameCallback((_) async { WidgetsBinding.instance.addPostFrameCallback((_) async {
@ -296,18 +293,21 @@ class _OffersPageState extends State<OffersPage> {
), ),
TextButton( TextButton(
onPressed: () async { onPressed: () async {
final result = await Navigator.of(context).push<bool>( await Navigator.of(context).push<bool>(
MaterialPageRoute( MaterialPageRoute(
builder: builder: (context) => const NotificationPreferencesPage(
(context) => const NotificationPreferencesPage(
loadFavoritesOnStart: true, loadFavoritesOnStart: true,
), ),
), ),
); );
if (result == true && mounted) { if (!mounted) return;
context
.read<NotificationPreferencesBloc>()
.add(ResetSubmissionStatus());
_loadPreferences(); _loadPreferences();
}
}, },
child: Row( child: Row(
children: [ children: [

View File

@ -74,7 +74,6 @@ class _OnboardingPageState extends State<OnboardingPage> {
MaterialPageRoute( MaterialPageRoute(
builder: builder:
(context) => BlocProvider( (context) => BlocProvider(
// This creates a NEW AuthBloc
create: (context) => AuthBloc(), create: (context) => AuthBloc(),
child: const LoginPage(), child: const LoginPage(),
), ),

View File

@ -148,13 +148,10 @@ class _OtpPageState extends State<OtpPage> {
}); });
} }
if (state is AuthNeedsInfo) { if (state is AuthNeedsInfo) {
// ******** شروع تغییر ۲ ********
// BlocProvider.value حذف شد
Navigator.of(context).pushAndRemoveUntil( Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(builder: (_) => const UserInfoPage()), MaterialPageRoute(builder: (_) => const UserInfoPage()),
(route) => false, (route) => false,
); );
// ******** پایان تغییر ۲ ********
} }
}, },
builder: (context, state) { builder: (context, state) {

View File

@ -191,7 +191,6 @@ class ProductDetailPage extends StatelessWidget {
} }
} }
// ... بقیه کدهای این فایل بدون تغییر باقی میماند ...
class ProductDetailView extends StatefulWidget { class ProductDetailView extends StatefulWidget {
final OfferModel offer; final OfferModel offer;
@ -589,7 +588,7 @@ class _ProductDetailViewState extends State<ProductDetailView> {
children: [ children: [
Container( Container(
padding: padding:
const EdgeInsets.symmetric(horizontal: 20, vertical: 11), const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.singleOfferType, color: AppColors.singleOfferType,
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),

View File

@ -36,7 +36,6 @@ class _ReservationConfirmationPageState
super.initState(); super.initState();
_playSound(); _playSound();
// ******** پایان تغییر اصلی ********
_calculateRemainingTime(); _calculateRemainingTime();
_timer = Timer.periodic(const Duration(seconds: 1), (timer) { _timer = Timer.periodic(const Duration(seconds: 1), (timer) {
@ -44,10 +43,8 @@ class _ReservationConfirmationPageState
}); });
} }
// متد جدید برای پخش صدا
void _playSound() async { void _playSound() async {
try { try {
// فایل صدایی که در پوشه assets قرار دادهاید
await _audioPlayer.play(AssetSource('sounds/positive-notification-alert-351299.mp3')); await _audioPlayer.play(AssetSource('sounds/positive-notification-alert-351299.mp3'));
} catch (e) { } catch (e) {
debugPrint("Error playing sound: $e"); debugPrint("Error playing sound: $e");
@ -130,7 +127,7 @@ class _ReservationConfirmationPageState
curve: Curves.easeOutBack, curve: Curves.easeOutBack,
), ),
const SizedBox(height: 18), const SizedBox(height: 18),
_buildQrCodeCard() // نمایش QR Code با استفاده از توکن _buildQrCodeCard()
.animate() .animate()
.fadeIn(delay: 1000.ms, duration: 500.ms) .fadeIn(delay: 1000.ms, duration: 500.ms)
.flipV(begin: -0.5, end: 0, curve: Curves.easeOut), .flipV(begin: -0.5, end: 0, curve: Curves.easeOut),
@ -359,7 +356,7 @@ class _ReservationConfirmationPageState
Text( Text(
value, value,
style: const TextStyle( style: const TextStyle(
fontSize: 20, // فونت کمی کوچکتر شد fontSize: 20,
fontFamily: 'Dana', fontFamily: 'Dana',
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Colors.black, color: Colors.black,

View File

@ -41,22 +41,18 @@ class _ReservedListPageState extends State<ReservedListPage> {
if (response.statusCode == 200) { if (response.statusCode == 200) {
final List<dynamic> reserves = response.data['reserves']; final List<dynamic> reserves = response.data['reserves'];
// ******** شروع تغییر ۲ ********
// تبدیل ساختار جدید JSON به فرمت مورد انتظار OfferModel
return reserves.map((reserveData) { return reserves.map((reserveData) {
final discountData = reserveData['Discount'] as Map<String, dynamic>; final discountData = reserveData['Discount'] as Map<String, dynamic>;
final shopData = reserveData['Shop'] as Map<String, dynamic>; final shopData = reserveData['Shop'] as Map<String, dynamic>;
// ترکیب اطلاعات برای سازگاری با مدل
final fullOfferData = { final fullOfferData = {
...discountData, ...discountData,
'shopData': shopData, 'shopData': shopData,
'Distance': reserveData['Distance'], // پاس دادن فاصله به مدل 'Distance': reserveData['Distance'],
}; };
return OfferModel.fromJson(fullOfferData); return OfferModel.fromJson(fullOfferData);
}).toList(); }).toList();
// ******** پایان تغییر ۲ ********
} else { } else {
throw Exception('خطا در دریافت اطلاعات از سرور'); throw Exception('خطا در دریافت اطلاعات از سرور');
} }

View File

@ -1,4 +1,3 @@
// lib/presentation/pages/user_info_page.dart
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
@ -24,9 +23,6 @@ class _UserInfoPageState extends State<UserInfoPage> {
super.dispose(); super.dispose();
} }
// ******** شروع تغییر ۱ ********
// متد ناوبری را ساده میکنیم.
// این متد دیگر Provider جدیدی نمیسازد و از Provider های سراسری استفاده میکند.
void _navigateToNextPage() { void _navigateToNextPage() {
Navigator.of(context).pushReplacement( Navigator.of(context).pushReplacement(
MaterialPageRoute( MaterialPageRoute(
@ -34,7 +30,6 @@ class _UserInfoPageState extends State<UserInfoPage> {
), ),
); );
} }
// ******** پایان تغییر ۱ ********
Widget _buildGenderRadio(String title, String value) { Widget _buildGenderRadio(String title, String value) {
return InkWell( return InkWell(
@ -146,7 +141,6 @@ class _UserInfoPageState extends State<UserInfoPage> {
const SizedBox(height: 55), const SizedBox(height: 55),
BlocConsumer<AuthBloc, AuthState>( BlocConsumer<AuthBloc, AuthState>(
listener: (context, state) { listener: (context, state) {
// ******** لاگ: state جدید دریافت شد ********
debugPrint( debugPrint(
"UserInfoPage: 🟠 Listener یک State جدید دریافت کرد: ${state.runtimeType}", "UserInfoPage: 🟠 Listener یک State جدید دریافت کرد: ${state.runtimeType}",
); );
@ -160,7 +154,6 @@ class _UserInfoPageState extends State<UserInfoPage> {
); );
} }
if (state is AuthSuccess) { if (state is AuthSuccess) {
// ******** لاگ: state موفقیت آمیز بود ********
debugPrint( debugPrint(
"UserInfoPage: ✅ State از نوع AuthSuccess است. فراخوانی _navigateToNextPage...", "UserInfoPage: ✅ State از نوع AuthSuccess است. فراخوانی _navigateToNextPage...",
); );

View File

@ -1,4 +1,3 @@
// lib/presentation/reservation/cubit/reservation_cubit.dart
// ignore: depend_on_referenced_packages // ignore: depend_on_referenced_packages
import 'package:bloc/bloc.dart'; import 'package:bloc/bloc.dart';
@ -15,7 +14,6 @@ class ReservationCubit extends Cubit<ReservationState> {
emit(state.copyWith(reservedProductIds: updatedList)); emit(state.copyWith(reservedProductIds: updatedList));
} }
// متد جدید برای تنظیم کردن همه شناسههای رزرو شده
void setReservedIds(List<String> productIds) { void setReservedIds(List<String> productIds) {
emit(state.copyWith(reservedProductIds: productIds)); emit(state.copyWith(reservedProductIds: productIds));
} }

View File

@ -1,4 +1,4 @@
import 'dart:async'; import 'dart:async';
import 'package:cached_network_image/cached_network_image.dart'; import 'package:cached_network_image/cached_network_image.dart';
import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart'; import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -58,7 +58,6 @@ class _ReservedListItemCardState extends State<ReservedListItemCard> {
void _toggleExpansion() { void _toggleExpansion() {
setState(() { setState(() {
_isExpanded = !_isExpanded; _isExpanded = !_isExpanded;
// توکن فقط زمانی ساخته میشود که کاربر پنل را باز میکند
if (_isExpanded && _qrTokenFuture == null) { if (_isExpanded && _qrTokenFuture == null) {
_qrTokenFuture = _generateQrToken(); _qrTokenFuture = _generateQrToken();
} }
@ -90,7 +89,9 @@ class _ReservedListItemCardState extends State<ReservedListItemCard> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( final isExpired = _remaining <= Duration.zero;
final cardContent = Column(
children: [ children: [
Card( Card(
color: Colors.white, color: Colors.white,
@ -107,6 +108,20 @@ class _ReservedListItemCardState extends State<ReservedListItemCard> {
_buildExpansionPanel(), _buildExpansionPanel(),
], ],
); );
if (isExpired) {
return ColorFiltered(
colorFilter: const ColorFilter.matrix(<double>[
0.2126, 0.7152, 0.0722, 0, 0,
0.2126, 0.7152, 0.0722, 0, 0,
0.2126, 0.7152, 0.0722, 0, 0,
0, 0, 0, 1, 0,
]),
child: cardContent,
);
}
return cardContent;
} }
Widget _buildOfferPrimaryDetails() { Widget _buildOfferPrimaryDetails() {
@ -214,6 +229,15 @@ class _ReservedListItemCardState extends State<ReservedListItemCard> {
const SizedBox(height: 4), const SizedBox(height: 4),
_buildTimerLabels(_remaining), _buildTimerLabels(_remaining),
], ],
)
else
const Text(
'منقضی شده',
style: TextStyle(
color: Colors.red,
fontWeight: FontWeight.bold,
fontSize: 16,
),
), ),
SizedBox(width: 10), SizedBox(width: 10),
TextButton( TextButton(

View File

@ -1,5 +1,3 @@
// lib/services/mqtt_service.dart
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
@ -10,7 +8,7 @@ import 'package:mqtt_client/mqtt_server_client.dart';
class MqttService { class MqttService {
late MqttServerClient client; late MqttServerClient client;
final String server = '5.75.200.241'; final String server = '5.75.197.180';
final int port = 1883; final int port = 1883;
final StreamController<Map<String, dynamic>> _messageStreamController = final StreamController<Map<String, dynamic>> _messageStreamController =
StreamController.broadcast(); StreamController.broadcast();
@ -28,7 +26,7 @@ class MqttService {
client = MqttServerClient.withPort(server, clientId, port); client = MqttServerClient.withPort(server, clientId, port);
client.logging(on: true); client.logging(on: true);
client.keepAlivePeriod = 60; client.keepAlivePeriod = 60;
client.autoReconnect = true; // فعالسازی اتصال مجدد خودکار client.autoReconnect = true;
client.setProtocolV311(); client.setProtocolV311();
debugPrint('--- [MQTT] Attempting to connect...'); debugPrint('--- [MQTT] Attempting to connect...');
@ -64,17 +62,14 @@ class MqttService {
}); });
}; };
// این callback زمانی فراخوانی میشود که اتصال قطع شود
client.onDisconnected = () { client.onDisconnected = () {
debugPrint('❌ [MQTT] Disconnected.'); debugPrint('❌ [MQTT] Disconnected.');
}; };
// این callback زمانی فراخوانی میشود که کلاینت در حال تلاش برای اتصال مجدد خودکار است
client.onAutoReconnect = () { client.onAutoReconnect = () {
debugPrint('↪️ [MQTT] Auto-reconnecting...'); debugPrint('↪️ [MQTT] Auto-reconnecting...');
}; };
// این callback زمانی فراخوانی میشود که اتصال مجدد خودکار موفقیتآمیز باشد
client.onAutoReconnected = () { client.onAutoReconnected = () {
debugPrint('✅ [MQTT] Auto-reconnected successfully.'); debugPrint('✅ [MQTT] Auto-reconnected successfully.');
}; };