add scanner
This commit is contained in:
parent
dfb93f2fc6
commit
585cb91df5
|
|
@ -5,6 +5,8 @@
|
|||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
|
||||
<application
|
||||
android:label="business_panel"
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -1,5 +1,3 @@
|
|||
// lib/core/config/api_config.dart
|
||||
|
||||
class ApiConfig {
|
||||
// Private constructor to prevent instantiation
|
||||
ApiConfig._();
|
||||
|
|
@ -50,4 +48,11 @@ class ApiConfig {
|
|||
/// Headers: {'Authorization': 'Bearer <token>'}
|
||||
static String editDiscount(String id) => '$baseUrl/discount/edit/$id';
|
||||
static String deleteDiscount(String id) => '$baseUrl/discount/delete/$id';
|
||||
|
||||
// ========== Order Endpoints ==========
|
||||
/// Endpoint to add a new order.
|
||||
/// Method: POST
|
||||
/// Body: {'Discount': discountId, 'User': userId}
|
||||
/// Headers: {'Authorization': 'Bearer <token>'}
|
||||
static const String addOrder = '$baseUrl/order/add';
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:business_panel/core/config/app_colors.dart';
|
||||
import 'package:business_panel/presentation/auth/bloc/auth_bloc.dart';
|
||||
import 'package:business_panel/presentation/home/bloc/home_bloc.dart';
|
||||
import 'package:business_panel/presentation/order/bloc/order_bloc.dart';
|
||||
import 'package:business_panel/presentation/pages/splash_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
|
@ -31,6 +32,7 @@ class MyApp extends StatelessWidget {
|
|||
BlocProvider(
|
||||
create: (context) => HomeBloc()..add(FetchDiscounts()),
|
||||
),
|
||||
BlocProvider(create: (context) => OrderBloc()),
|
||||
],
|
||||
child: MaterialApp(
|
||||
title: 'Proxibuy',
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
// lib/presentation/discount/bloc/discount_bloc.dart
|
||||
|
||||
import 'dart:developer';
|
||||
import 'package:business_panel/core/config/api_config.dart';
|
||||
|
|
@ -6,10 +5,8 @@ import 'package:business_panel/core/services/token_storage_service.dart';
|
|||
import 'package:business_panel/core/utils/logging_interceptor.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'discount_event.dart';
|
||||
import 'discount_state.dart';
|
||||
import 'dart:convert'; // Import for jsonEncode
|
||||
|
||||
class DiscountBloc extends Bloc<DiscountEvent, DiscountState> {
|
||||
final Dio _dio = Dio();
|
||||
|
|
@ -156,6 +153,10 @@ class DiscountBloc extends Bloc<DiscountEvent, DiscountState> {
|
|||
discountId: event.discountId,
|
||||
);
|
||||
});
|
||||
|
||||
on<ClearErrorMessage>((event, emit) {
|
||||
emit(state.copyWith(clearErrorMessage: true));
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _submitDiscountForm(
|
||||
|
|
@ -224,7 +225,7 @@ class DiscountBloc extends Bloc<DiscountEvent, DiscountState> {
|
|||
);
|
||||
|
||||
emit(state.copyWith(isSubmitting: false, isSuccess: true));
|
||||
} on DioException catch (e) {
|
||||
}on DioException catch (e) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
isSubmitting: false,
|
||||
|
|
|
|||
|
|
@ -58,8 +58,9 @@ class NotificationRadiusChanged extends DiscountEvent {
|
|||
|
||||
class SubmitDiscount extends DiscountEvent {}
|
||||
|
||||
// Event for updating an existing discount
|
||||
class UpdateDiscount extends DiscountEvent {
|
||||
final String discountId;
|
||||
UpdateDiscount(this.discountId);
|
||||
}
|
||||
}
|
||||
|
||||
class ClearErrorMessage extends DiscountEvent {} // <-- این رویداد جدید را اضافه کنید
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
// lib/presentation/discount/bloc/discount_state.dart
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
class DiscountState extends Equatable {
|
||||
final String? discountId;
|
||||
// *** CHANGE IS HERE: Storing image ID and URL together ***
|
||||
final List<Map<String, String?>> productImages;
|
||||
final String productName;
|
||||
final String? discountTypeId;
|
||||
|
|
@ -57,6 +54,7 @@ class DiscountState extends Equatable {
|
|||
bool? isLoadingDetails,
|
||||
bool? isSuccess,
|
||||
String? errorMessage,
|
||||
bool? clearErrorMessage,
|
||||
}) {
|
||||
return DiscountState(
|
||||
discountId: discountId ?? this.discountId,
|
||||
|
|
@ -74,7 +72,7 @@ class DiscountState extends Equatable {
|
|||
isSubmitting: isSubmitting ?? this.isSubmitting,
|
||||
isLoadingDetails: isLoadingDetails ?? this.isLoadingDetails,
|
||||
isSuccess: isSuccess ?? this.isSuccess,
|
||||
errorMessage: errorMessage ?? this.errorMessage,
|
||||
errorMessage: (clearErrorMessage == true) ? null : errorMessage ?? this.errorMessage,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
import 'package:bloc/bloc.dart';
|
||||
import 'package:business_panel/core/config/api_config.dart';
|
||||
import 'package:business_panel/core/services/token_storage_service.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
part 'order_event.dart';
|
||||
part 'order_state.dart';
|
||||
|
||||
class OrderBloc extends Bloc<OrderEvent, OrderState> {
|
||||
final Dio _dio = Dio();
|
||||
final TokenStorageService _tokenStorage = TokenStorageService();
|
||||
|
||||
OrderBloc() : super(OrderInitial()) {
|
||||
on<SubmitOrder>((event, emit) async {
|
||||
emit(OrderSubmissionInProgress());
|
||||
try {
|
||||
final token = await _tokenStorage.getAccessToken();
|
||||
if (token == null) {
|
||||
emit(OrderSubmissionFailure("خطای احراز هویت."));
|
||||
return;
|
||||
}
|
||||
|
||||
final response = await _dio.post(
|
||||
ApiConfig.addOrder,
|
||||
data: {
|
||||
'Discount': event.discountId,
|
||||
'User': event.userId,
|
||||
},
|
||||
options: Options(
|
||||
headers: {'Authorization': 'Bearer $token'},
|
||||
),
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
emit(OrderSubmissionSuccess(response.data['message'] ?? "سفارش با موفقیت ثبت شد."));
|
||||
} else {
|
||||
emit(OrderSubmissionFailure(response.data['message'] ?? 'خطا در ثبت سفارش.'));
|
||||
}
|
||||
} on DioException catch (e) {
|
||||
emit(OrderSubmissionFailure(e.response?.data['message'] ?? 'خطای شبکه.'));
|
||||
} catch (e) {
|
||||
emit(OrderSubmissionFailure('خطای پیشبینی نشده: ${e.toString()}'));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
part of 'order_bloc.dart';
|
||||
|
||||
abstract class OrderEvent {}
|
||||
|
||||
class SubmitOrder extends OrderEvent {
|
||||
final String discountId;
|
||||
final String userId;
|
||||
|
||||
SubmitOrder({required this.discountId, required this.userId});
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
part of 'order_bloc.dart';
|
||||
|
||||
abstract class OrderState {}
|
||||
|
||||
class OrderInitial extends OrderState {}
|
||||
|
||||
class OrderSubmissionInProgress extends OrderState {}
|
||||
|
||||
class OrderSubmissionSuccess extends OrderState {
|
||||
final String message;
|
||||
OrderSubmissionSuccess(this.message);
|
||||
}
|
||||
|
||||
class OrderSubmissionFailure extends OrderState {
|
||||
final String error;
|
||||
OrderSubmissionFailure(this.error);
|
||||
}
|
||||
|
|
@ -10,11 +10,39 @@ import 'package:business_panel/presentation/pages/home_page.dart';
|
|||
import 'package:business_panel/presentation/widgets/custom_app_bar.dart';
|
||||
import 'package:business_panel/presentation/widgets/info_popup.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:persian_datetime_picker/persian_datetime_picker.dart';
|
||||
|
||||
class ThousandsSeparatorInputFormatter extends TextInputFormatter {
|
||||
@override
|
||||
TextEditingValue formatEditUpdate(
|
||||
TextEditingValue oldValue, TextEditingValue newValue) {
|
||||
if (newValue.text.isEmpty) {
|
||||
return newValue.copyWith(text: '');
|
||||
}
|
||||
|
||||
final String newText = newValue.text.replaceAll(',', '');
|
||||
final number = int.tryParse(newText);
|
||||
|
||||
if (number == null) {
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
final formatter = NumberFormat('#,##0');
|
||||
final String newString = formatter.format(number);
|
||||
|
||||
return TextEditingValue(
|
||||
text: newString,
|
||||
selection: TextSelection.collapsed(offset: newString.length),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class AddDiscountPage extends StatelessWidget {
|
||||
final String? discountId;
|
||||
|
||||
|
|
@ -49,9 +77,18 @@ class _AddDiscountViewState extends State<_AddDiscountView> {
|
|||
final _descController = TextEditingController();
|
||||
final _priceController = TextEditingController();
|
||||
final _discountPriceController = TextEditingController();
|
||||
final _priceFormatter = NumberFormat('#,##0');
|
||||
|
||||
|
||||
bool get _isEditMode => widget.discountId != null;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_priceController.addListener(() => setState(() {}));
|
||||
_discountPriceController.addListener(() => setState(() {}));
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_nameController.dispose();
|
||||
|
|
@ -126,7 +163,6 @@ class _AddDiscountViewState extends State<_AddDiscountView> {
|
|||
"تخفیف با موفقیت ${_isEditMode ? 'ویرایش' : 'ثبت'} شد!"),
|
||||
backgroundColor: Colors.green),
|
||||
);
|
||||
// HIGHLIGHT: مسیریابی به صفحهی اصلی و حذف تمام صفحات قبلی
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
MaterialPageRoute(
|
||||
builder: (_) => BlocProvider(
|
||||
|
|
@ -142,12 +178,15 @@ class _AddDiscountViewState extends State<_AddDiscountView> {
|
|||
SnackBar(
|
||||
content: Text(state.errorMessage!), backgroundColor: Colors.red),
|
||||
);
|
||||
context.read<DiscountBloc>().add(ClearErrorMessage());
|
||||
}
|
||||
if (state.productName.isNotEmpty && _nameController.text.isEmpty) {
|
||||
_nameController.text = state.productName;
|
||||
_descController.text = state.description;
|
||||
_priceController.text = state.price;
|
||||
_discountPriceController.text = state.discountedPrice;
|
||||
final price = int.tryParse(state.price.replaceAll(',', '')) ?? 0;
|
||||
_priceController.text = _priceFormatter.format(price);
|
||||
final discountedPrice = int.tryParse(state.discountedPrice.replaceAll(',', '')) ?? 0;
|
||||
_discountPriceController.text = _priceFormatter.format(discountedPrice);
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
|
|
@ -185,7 +224,7 @@ class _AddDiscountViewState extends State<_AddDiscountView> {
|
|||
context.read<DiscountBloc>().add(ProductNameChanged(value)),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
_buildDiscountTypeDropdown(state), // Pass state here
|
||||
_buildDiscountTypeDropdown(state),
|
||||
const SizedBox(height: 30),
|
||||
_buildTextField(
|
||||
controller: _descController,
|
||||
|
|
@ -202,25 +241,19 @@ class _AddDiscountViewState extends State<_AddDiscountView> {
|
|||
const SizedBox(height: 30),
|
||||
_buildTimeRangePicker(context),
|
||||
const SizedBox(height: 30),
|
||||
_buildTextField(
|
||||
|
||||
_buildPriceField(
|
||||
controller: _priceController,
|
||||
label: "قیمت بدون تخفیف",
|
||||
isRequired: true,
|
||||
hint: "مثلاً 240000 تومان",
|
||||
keyboardType: TextInputType.number,
|
||||
onChanged: (value) =>
|
||||
context.read<DiscountBloc>().add(PriceChanged(value)),
|
||||
label: "قیمت بدون تخفیف (تومان)",
|
||||
hint: "مثلاً 240,000",
|
||||
onChanged: (value) => context.read<DiscountBloc>().add(PriceChanged(value)),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
_buildTextField(
|
||||
_buildPriceField(
|
||||
controller: _discountPriceController,
|
||||
label: "قیمت با تخفیف",
|
||||
hint: "مثلاً 200000 تومان",
|
||||
isRequired: true,
|
||||
keyboardType: TextInputType.number,
|
||||
onChanged: (value) => context
|
||||
.read<DiscountBloc>()
|
||||
.add(DiscountedPriceChanged(value)),
|
||||
label: "قیمت با تخفیف (تومان)",
|
||||
hint: "مثلاً 200,000",
|
||||
onChanged: (value) => context.read<DiscountBloc>().add(DiscountedPriceChanged(value)),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
_buildNotificationRadiusSlider(),
|
||||
|
|
@ -253,6 +286,30 @@ class _AddDiscountViewState extends State<_AddDiscountView> {
|
|||
);
|
||||
}
|
||||
|
||||
Widget _buildPriceField({
|
||||
required TextEditingController controller,
|
||||
required String label,
|
||||
required String hint,
|
||||
required ValueChanged<String> onChanged,
|
||||
}) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
TextFormField(
|
||||
controller: controller,
|
||||
onChanged: (value) => onChanged(value.replaceAll(',', '')),
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.digitsOnly,
|
||||
ThousandsSeparatorInputFormatter(),
|
||||
],
|
||||
decoration: _inputDecoration(label, hint: hint, isRequired: true),
|
||||
),
|
||||
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSectionTitle({
|
||||
required String title,
|
||||
String? popupTitle,
|
||||
|
|
@ -287,7 +344,6 @@ class _AddDiscountViewState extends State<_AddDiscountView> {
|
|||
return BlocBuilder<DiscountBloc, DiscountState>(
|
||||
buildWhen: (p, c) => p.productImages != c.productImages,
|
||||
builder: (context, state) {
|
||||
// We ensure the list has at least 2 elements for the UI, filling with null
|
||||
final displayImages = List<Map<String, String?>?>.from(state.productImages);
|
||||
while (displayImages.length < 2) {
|
||||
displayImages.add(null);
|
||||
|
|
@ -296,7 +352,6 @@ class _AddDiscountViewState extends State<_AddDiscountView> {
|
|||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: List.generate(2, (index) {
|
||||
// *** CHANGE IS HERE: Read from the map structure ***
|
||||
final imageMap = displayImages[index];
|
||||
final imageUrl = imageMap?['url'];
|
||||
final isUrl = imageUrl?.startsWith('http') ?? false;
|
||||
|
|
@ -345,16 +400,14 @@ class _AddDiscountViewState extends State<_AddDiscountView> {
|
|||
}
|
||||
|
||||
Widget _buildDiscountTypeDropdown(DiscountState state) {
|
||||
// Create a set of available IDs for quick lookup.
|
||||
final availableTypeIds = discountTypes.map((type) => type.id).toSet();
|
||||
|
||||
// Check if the current discount's type ID is in our list. If not, use null.
|
||||
final String? selectedValue = availableTypeIds.contains(state.discountTypeId)
|
||||
? state.discountTypeId
|
||||
: null;
|
||||
|
||||
return DropdownButtonFormField<String>(
|
||||
value: selectedValue, // Use the safe value here.
|
||||
value: selectedValue,
|
||||
icon: SvgPicture.asset(
|
||||
Assets.icons.arrowDown,
|
||||
width: 24,
|
||||
|
|
@ -411,7 +464,6 @@ class _AddDiscountViewState extends State<_AddDiscountView> {
|
|||
Expanded(
|
||||
child: Text(
|
||||
displayText,
|
||||
textDirection: TextDirection.rtl,
|
||||
style: const TextStyle(fontSize: 15),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,124 @@
|
|||
import 'package:audioplayers/audioplayers.dart';
|
||||
import 'package:business_panel/presentation/order/bloc/order_bloc.dart';
|
||||
import 'package:business_panel/presentation/widgets/success_popup.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:jwt_decoder/jwt_decoder.dart';
|
||||
import 'package:mobile_scanner/mobile_scanner.dart';
|
||||
import 'package:vibration/vibration.dart';
|
||||
|
||||
class BarcodeScannerPage extends StatefulWidget {
|
||||
final String discountId;
|
||||
const BarcodeScannerPage({super.key, required this.discountId});
|
||||
|
||||
@override
|
||||
State<BarcodeScannerPage> createState() => _BarcodeScannerPageState();
|
||||
}
|
||||
|
||||
class _BarcodeScannerPageState extends State<BarcodeScannerPage> {
|
||||
final MobileScannerController _scannerController = MobileScannerController();
|
||||
final AudioPlayer _audioPlayer = AudioPlayer();
|
||||
bool _isProcessing = false;
|
||||
|
||||
void _handleBarcode(BarcodeCapture capture) {
|
||||
if (_isProcessing) return;
|
||||
setState(() => _isProcessing = true);
|
||||
|
||||
final String? rawValue = capture.barcodes.first.rawValue;
|
||||
if (rawValue == null) {
|
||||
_showError("بارکد نامعتبر است.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final Map<String, dynamic> decodedToken = JwtDecoder.decode(rawValue);
|
||||
|
||||
final String? userId = decodedToken['userID'];
|
||||
final String? discountIdFromToken = decodedToken['discountID'];
|
||||
|
||||
if (userId == null || discountIdFromToken == null) {
|
||||
_showError("اطلاعات لازم در بارکد یافت نشد.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (discountIdFromToken != widget.discountId) {
|
||||
_showError("این بارکد برای این تخفیف معتبر نیست.");
|
||||
return;
|
||||
}
|
||||
|
||||
context.read<OrderBloc>().add(
|
||||
SubmitOrder(discountId: discountIdFromToken, userId: userId),
|
||||
);
|
||||
|
||||
} catch (e) {
|
||||
_showError("فرمت بارکد صحیح نیست.");
|
||||
}
|
||||
}
|
||||
|
||||
void _showError(String message) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(message), backgroundColor: Colors.red),
|
||||
);
|
||||
setState(() => _isProcessing = false);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scannerController.dispose();
|
||||
_audioPlayer.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text("اسکن بارکد مشتری")),
|
||||
body: BlocListener<OrderBloc, OrderState>(
|
||||
listener: (context, state) async {
|
||||
if (state is OrderSubmissionSuccess) {
|
||||
if (await Vibration.hasVibrator() ?? false) {
|
||||
Vibration.vibrate(duration: 200);
|
||||
}
|
||||
// Ensure you have a success sound file at this path
|
||||
await _audioPlayer.play(AssetSource('sounds/short-success-sound-glockenspiel-treasure-video-game-6346.mp3'));
|
||||
await showSuccessDialog(context, message: state.message);
|
||||
Navigator.of(context).pop();
|
||||
} else if (state is OrderSubmissionFailure) {
|
||||
_showError(state.error);
|
||||
}
|
||||
},
|
||||
child: Stack(
|
||||
children: [
|
||||
MobileScanner(
|
||||
controller: _scannerController,
|
||||
onDetect: _handleBarcode,
|
||||
),
|
||||
Center(
|
||||
child: Container(
|
||||
width: 250,
|
||||
height: 250,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.green, width: 4),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
),
|
||||
BlocBuilder<OrderBloc, OrderState>(
|
||||
builder: (context, state) {
|
||||
if (state is OrderSubmissionInProgress) {
|
||||
return Container(
|
||||
color: Colors.black.withOpacity(0.5),
|
||||
child: const Center(
|
||||
child: CircularProgressIndicator(color: Colors.white),
|
||||
),
|
||||
);
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ import 'package:business_panel/presentation/store_info/bloc/store_info_bloc.dart
|
|||
import 'package:business_panel/presentation/store_info/bloc/store_info_state.dart';
|
||||
import 'package:business_panel/presentation/widgets/custom_app_bar.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/services.dart'; // **ADD THIS IMPORT**
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
|
|
@ -127,6 +127,7 @@ class _StoreInfoPageState extends State<StoreInfoPage> {
|
|||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(state.errorMessage!), backgroundColor: Colors.red),
|
||||
);
|
||||
context.read<StoreInfoBloc>().add(ClearStoreInfoError());
|
||||
}
|
||||
},
|
||||
child: SingleChildScrollView(
|
||||
|
|
@ -181,6 +182,7 @@ class _StoreInfoPageState extends State<StoreInfoPage> {
|
|||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
if(state.logoPath == null) SvgPicture.asset(Assets.icons.addImg),
|
||||
Align(
|
||||
alignment: Alignment.bottomRight,
|
||||
child: CircleAvatar(
|
||||
|
|
@ -248,6 +250,7 @@ class _StoreInfoPageState extends State<StoreInfoPage> {
|
|||
_buildTextField(
|
||||
controller: _addressController,
|
||||
label: "جزئیات آدرس",
|
||||
isRequired: true,
|
||||
maxLines: 3,
|
||||
hint: "خیابان، محله، ساختمان و ....",
|
||||
onChanged:
|
||||
|
|
@ -263,6 +266,8 @@ class _StoreInfoPageState extends State<StoreInfoPage> {
|
|||
child: _buildTextField(
|
||||
controller: _plaqueController,
|
||||
label: "پلاک",
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||
onChanged:
|
||||
(value) => context.read<StoreInfoBloc>().add(
|
||||
PlaqueChanged(value),
|
||||
|
|
@ -274,6 +279,8 @@ class _StoreInfoPageState extends State<StoreInfoPage> {
|
|||
child: _buildTextField(
|
||||
controller: _postalCodeController,
|
||||
label: "کد پستی",
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||
onChanged: (value) => context
|
||||
.read<StoreInfoBloc>()
|
||||
.add(PostalCodeChanged(value)),
|
||||
|
|
@ -305,7 +312,7 @@ class _StoreInfoPageState extends State<StoreInfoPage> {
|
|||
height: 23,
|
||||
),
|
||||
label: const Text(
|
||||
"انتخاب آدرس فروشگاه روی نقشه",
|
||||
"انتخاب آدرس فروشگاه روی نقشه *",
|
||||
style: TextStyle(color: AppColors.button),
|
||||
),
|
||||
),
|
||||
|
|
@ -333,7 +340,9 @@ class _StoreInfoPageState extends State<StoreInfoPage> {
|
|||
_buildTextField(
|
||||
controller: _phoneController,
|
||||
label: "تلفن تماس",
|
||||
isRequired: true,
|
||||
keyboardType: TextInputType.phone,
|
||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||
hint: "شماره تماس ثابت یا موبایل فروشگاه",
|
||||
onChanged: (value) =>
|
||||
context.read<StoreInfoBloc>().add(ContactPhoneChanged(value)),
|
||||
|
|
@ -346,6 +355,8 @@ class _StoreInfoPageState extends State<StoreInfoPage> {
|
|||
_buildTextField(
|
||||
controller: _licenseController,
|
||||
label: "شماره جواز کسب",
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||
hint: "شناسه صنفی 12 رقمی یکتا",
|
||||
onChanged: (value) =>
|
||||
context.read<StoreInfoBloc>().add(LicenseNumberChanged(value)),
|
||||
|
|
@ -353,18 +364,32 @@ class _StoreInfoPageState extends State<StoreInfoPage> {
|
|||
const SizedBox(height: 44),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (_) => BlocProvider.value(
|
||||
value: BlocProvider.of<StoreInfoBloc>(context),
|
||||
child: const StoreInfoDisplayPage(),
|
||||
),
|
||||
child: BlocBuilder<StoreInfoBloc, StoreInfoState>(
|
||||
builder: (context, state) {
|
||||
return ElevatedButton(
|
||||
onPressed: state.isFormValid ? () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (_) => BlocProvider.value(
|
||||
value: BlocProvider.of<StoreInfoBloc>(context),
|
||||
child: const StoreInfoDisplayPage(),
|
||||
),
|
||||
),
|
||||
);
|
||||
} : () {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text("لطفاً تمام فیلدهای ستارهدار را پر کنید."),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: state.isFormValid ? AppColors.button : Colors.grey,
|
||||
),
|
||||
child: const Text("تایید و ادامه"),
|
||||
);
|
||||
},
|
||||
child: const Text("تایید و ادامه"),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 34),
|
||||
|
|
@ -391,6 +416,7 @@ class _StoreInfoPageState extends State<StoreInfoPage> {
|
|||
TextInputType? keyboardType,
|
||||
TextEditingController? controller,
|
||||
ValueChanged<String>? onChanged,
|
||||
List<TextInputFormatter>? inputFormatters,
|
||||
}) {
|
||||
return TextFormField(
|
||||
controller: controller,
|
||||
|
|
@ -398,6 +424,7 @@ class _StoreInfoPageState extends State<StoreInfoPage> {
|
|||
maxLines: maxLines,
|
||||
maxLength: maxLength,
|
||||
keyboardType: keyboardType,
|
||||
inputFormatters: inputFormatters,
|
||||
decoration: InputDecoration(
|
||||
counterText: "",
|
||||
hintText: hint,
|
||||
|
|
@ -577,7 +604,7 @@ class _StoreInfoPageState extends State<StoreInfoPage> {
|
|||
child: DropdownButtonFormField<String>(
|
||||
value: context.watch<StoreInfoBloc>().state.activityTypeId,
|
||||
icon: SvgPicture.asset(
|
||||
Assets.icons.arrowDown,
|
||||
Assets.icons.arrowDown,
|
||||
width: 24,
|
||||
color: Colors.black,
|
||||
),
|
||||
|
|
@ -616,56 +643,4 @@ class _StoreInfoPageState extends State<StoreInfoPage> {
|
|||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// Future<void> _pickWorkingHours(BuildContext context) async {
|
||||
// // ۱. انتخاب تاریخ شروع
|
||||
// Jalali? startDate = await showPersianDatePicker(
|
||||
// context: context,
|
||||
// initialDate: Jalali.now(),
|
||||
// firstDate: Jalali(1400),
|
||||
// lastDate: Jalali(1405),
|
||||
// );
|
||||
// if (startDate == null || !context.mounted) return;
|
||||
|
||||
// // ۲. انتخاب ساعت شروع
|
||||
// TimeOfDay? startTime = await showTimePicker(
|
||||
// context: context,
|
||||
// initialTime: TimeOfDay.now(),
|
||||
// );
|
||||
// if (startTime == null || !context.mounted) return;
|
||||
|
||||
// // ۳. انتخاب تاریخ پایان
|
||||
// Jalali? endDate = await showPersianDatePicker(
|
||||
// context: context,
|
||||
// initialDate: startDate, // شروع از تاریخ انتخابی قبلی
|
||||
// firstDate: startDate, // تاریخ پایان نمیتواند قبل از شروع باشد
|
||||
// lastDate: Jalali(1405),
|
||||
// );
|
||||
// if (endDate == null || !context.mounted) return;
|
||||
|
||||
// // ۴. انتخاب ساعت پایان
|
||||
// TimeOfDay? endTime = await showTimePicker(
|
||||
// context: context,
|
||||
// initialTime: startTime,
|
||||
// );
|
||||
// if (endTime == null || !context.mounted) return;
|
||||
|
||||
// // ۵. تبدیل به آبجکت DateTime و ارسال به BLoC
|
||||
// final DateTime startDateTime = startDate.toDateTime().add(
|
||||
// Duration(hours: startTime.hour, minutes: startTime.minute),
|
||||
// );
|
||||
// final DateTime endDateTime = endDate.toDateTime().add(
|
||||
// Duration(hours: endTime.hour, minutes: endTime.minute),
|
||||
// );
|
||||
|
||||
// context.read<StoreInfoBloc>().add(
|
||||
// WorkingHoursChanged(
|
||||
// startDateTime: startDateTime,
|
||||
// endDateTime: endDateTime,
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
}
|
||||
|
|
@ -13,6 +13,10 @@ class StoreInfoBloc extends Bloc<StoreInfoEvent, StoreInfoState> {
|
|||
final TokenStorageService _tokenStorage = TokenStorageService();
|
||||
|
||||
StoreInfoBloc() : super(StoreInfoState()) {
|
||||
on<ClearStoreInfoError>((event, emit) {
|
||||
emit(state.copyWith(clearErrorMessage: true));
|
||||
});
|
||||
|
||||
on<StoreLogoChanged>((event, emit) {
|
||||
emit(state.copyWith(logoPath: event.imagePath));
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
|
||||
part of 'store_info_bloc.dart';
|
||||
|
||||
abstract class StoreInfoEvent {}
|
||||
|
||||
class ClearStoreInfoError extends StoreInfoEvent {}
|
||||
class StoreLogoChanged extends StoreInfoEvent {
|
||||
final String imagePath;
|
||||
StoreLogoChanged(this.imagePath);
|
||||
|
|
|
|||
|
|
@ -43,6 +43,21 @@ class StoreInfoState {
|
|||
this.activityTypeId,
|
||||
});
|
||||
|
||||
bool get isFormValid =>
|
||||
storeName.isNotEmpty &&
|
||||
activityTypeId != null &&
|
||||
activityTypeId!.isNotEmpty &&
|
||||
address.isNotEmpty &&
|
||||
contactPhone != null &&
|
||||
contactPhone!.isNotEmpty &&
|
||||
workingDays.isNotEmpty &&
|
||||
startTime != null &&
|
||||
startTime!.isNotEmpty &&
|
||||
endTime != null &&
|
||||
endTime!.isNotEmpty &&
|
||||
latitude != null &&
|
||||
longitude != null;
|
||||
|
||||
StoreInfoState copyWith({
|
||||
String? logoPath,
|
||||
String? storeName,
|
||||
|
|
@ -64,6 +79,7 @@ class StoreInfoState {
|
|||
String? endTime,
|
||||
List<String>? features,
|
||||
String? activityTypeId,
|
||||
bool? clearErrorMessage,
|
||||
}) {
|
||||
return StoreInfoState(
|
||||
logoPath: logoPath ?? this.logoPath,
|
||||
|
|
@ -76,7 +92,7 @@ class StoreInfoState {
|
|||
postalCode: postalCode ?? this.postalCode,
|
||||
isSubmitting: isSubmitting ?? this.isSubmitting,
|
||||
isSuccess: isSuccess ?? this.isSuccess,
|
||||
errorMessage: errorMessage ?? this.errorMessage,
|
||||
errorMessage: (clearErrorMessage == true) ? null : errorMessage ?? this.errorMessage,
|
||||
latitude: latitude ?? this.latitude,
|
||||
longitude: longitude ?? this.longitude,
|
||||
contactPhone: contactPhone ?? this.contactPhone,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import 'package:business_panel/core/config/app_colors.dart';
|
||||
import 'package:business_panel/domain/entities/discount_entity.dart';
|
||||
import 'package:business_panel/gen/assets.gen.dart';
|
||||
import 'package:business_panel/presentation/home/bloc/home_bloc.dart';
|
||||
import 'package:business_panel/presentation/pages/add_discount_page.dart';
|
||||
import 'package:business_panel/presentation/order/bloc/order_bloc.dart';
|
||||
import 'package:business_panel/presentation/pages/barcode_scanner_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
|
|
@ -15,8 +15,6 @@ class ActiveDiscountCard extends StatelessWidget {
|
|||
const ActiveDiscountCard({super.key, required this.discount});
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// ... (تمام کد مربوط به _buildDiscountCard از home_page.dart به اینجا منتقل شد)
|
||||
// ... (متدهای کمکی مثل _buildCountdownTimer و _buildTimerLabels هم به اینجا منتقل شدند)
|
||||
final remaining =
|
||||
discount.endDate != null ? discount.endDate!.difference(DateTime.now()) : const Duration(seconds: -1);
|
||||
|
||||
|
|
@ -150,30 +148,42 @@ class ActiveDiscountCard extends StatelessWidget {
|
|||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
Assets.icons.scanBarcode,
|
||||
width: 18,
|
||||
color: Colors.grey.shade700,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
"اسکن بارکد مشتری",
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
color: AppColors.active,
|
||||
InkWell( // <-- ویجت را در InkWell قرار دهید
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (_) => BlocProvider.value(
|
||||
value: context.read<OrderBloc>(),
|
||||
child: BarcodeScannerPage(discountId: discount.id),
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
Assets.icons.scanBarcode,
|
||||
width: 18,
|
||||
color: Colors.grey.shade700,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
"اسکن بارکد مشتری",
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
color: AppColors.active,
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -214,7 +214,7 @@ class DiscountCard extends StatelessWidget {
|
|||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
NumberFormat('#,##0').format(discount.nPrice),
|
||||
"${NumberFormat('#,##0').format(discount.nPrice)} تومان",
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.normal,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
import 'package:business_panel/core/config/app_colors.dart';
|
||||
import 'package:business_panel/gen/assets.gen.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
||||
Future<void> showSuccessDialog(
|
||||
BuildContext context, {
|
||||
required String message,
|
||||
}) async {
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return Dialog(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SvgPicture.asset(Assets.icons.tickCircle, height: 80, color: AppColors.confirm),
|
||||
const SizedBox(height: 24),
|
||||
Text(
|
||||
"موفقیتآمیز!",
|
||||
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
message,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(fontSize: 16, color: Colors.black54),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
ElevatedButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text("فهمیدم"),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
122
pubspec.lock
122
pubspec.lock
|
|
@ -49,6 +49,62 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.12.0"
|
||||
audioplayers:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: audioplayers
|
||||
sha256: e653f162ddfcec1da2040ba2d8553fff1662b5c2a5c636f4c21a3b11bee497de
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.5.0"
|
||||
audioplayers_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: audioplayers_android
|
||||
sha256: "60a6728277228413a85755bd3ffd6fab98f6555608923813ce383b190a360605"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.2.1"
|
||||
audioplayers_darwin:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: audioplayers_darwin
|
||||
sha256: "0811d6924904ca13f9ef90d19081e4a87f7297ddc19fc3d31f60af1aaafee333"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.0"
|
||||
audioplayers_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: audioplayers_linux
|
||||
sha256: f75bce1ce864170ef5e6a2c6a61cd3339e1a17ce11e99a25bae4474ea491d001
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2.1"
|
||||
audioplayers_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: audioplayers_platform_interface
|
||||
sha256: "0e2f6a919ab56d0fec272e801abc07b26ae7f31980f912f24af4748763e5a656"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.1.1"
|
||||
audioplayers_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: audioplayers_web
|
||||
sha256: "1c0f17cec68455556775f1e50ca85c40c05c714a99c5eb1d2d57cc17ba5522d7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.1.1"
|
||||
audioplayers_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: audioplayers_windows
|
||||
sha256: "4048797865105b26d47628e6abb49231ea5de84884160229251f37dfcbe52fd7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2.1"
|
||||
bloc:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -273,6 +329,22 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.11"
|
||||
device_info_plus:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: device_info_plus
|
||||
sha256: "98f28b42168cc509abc92f88518882fd58061ea372d7999aecc424345c7bff6a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.5.0"
|
||||
device_info_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: device_info_plus_platform_interface
|
||||
sha256: e1ea89119e34903dca74b883d0dd78eb762814f97fb6c76f35e9ff74d261a18f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.3"
|
||||
dio:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -797,6 +869,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.9.0"
|
||||
jwt_decoder:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: jwt_decoder
|
||||
sha256: "54774aebf83f2923b99e6416b4ea915d47af3bde56884eb622de85feabbc559f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
latlong2:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -901,6 +981,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
mobile_scanner:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: mobile_scanner
|
||||
sha256: "54005bdea7052d792d35b4fef0f84ec5ddc3a844b250ecd48dc192fb9b4ebc95"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.1"
|
||||
nested:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1234,6 +1322,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
synchronized:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: synchronized
|
||||
sha256: "0669c70faae6270521ee4f05bffd2919892d42d1276e6c495be80174b6bc0ef6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.3.1"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1330,6 +1426,22 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
vibration:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: vibration
|
||||
sha256: "804ee8f9628f31ee71fbe6137a2bc6206a64e101ec22cd9dd6d3a7dc0272591b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
vibration_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vibration_platform_interface
|
||||
sha256: "03e9deaa4df48a1a6212e281bfee5f610d62e9247929dd2f26f4efd4fa5e225c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.0"
|
||||
vm_service:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1378,6 +1490,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.13.0"
|
||||
win32_registry:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: win32_registry
|
||||
sha256: "6f1b564492d0147b330dd794fee8f512cec4977957f310f9951b5f9d83618dae"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
wkt_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1412,4 +1532,4 @@ packages:
|
|||
version: "3.1.3"
|
||||
sdks:
|
||||
dart: ">=3.7.2 <4.0.0"
|
||||
flutter: ">=3.27.0"
|
||||
flutter: ">=3.29.0"
|
||||
|
|
|
|||
|
|
@ -53,6 +53,10 @@ dependencies:
|
|||
firebase_auth: ^5.7.0
|
||||
cloud_firestore: ^5.6.12
|
||||
firebase_storage: ^12.4.10
|
||||
mobile_scanner: ^7.0.1
|
||||
vibration: ^3.1.3
|
||||
audioplayers: ^6.5.0
|
||||
jwt_decoder: ^2.0.1
|
||||
|
||||
|
||||
dev_dependencies:
|
||||
|
|
@ -83,6 +87,7 @@ flutter:
|
|||
assets:
|
||||
- assets/images/
|
||||
- assets/icons/
|
||||
- assets/sounds/
|
||||
|
||||
# An image asset can refer to one or more resolution-specific "variants", see
|
||||
# https://flutter.dev/to/resolution-aware-images
|
||||
|
|
|
|||
Loading…
Reference in New Issue