diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 1746dcf..987642b 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -2,6 +2,8 @@ + + + + diff --git a/assets/icons/arrow-up.svg b/assets/icons/arrow-up.svg new file mode 100644 index 0000000..27f0bd8 --- /dev/null +++ b/assets/icons/arrow-up.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/camera.svg b/assets/icons/camera.svg new file mode 100644 index 0000000..46b1426 --- /dev/null +++ b/assets/icons/camera.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/gallery-add.svg b/assets/icons/gallery-add.svg new file mode 100644 index 0000000..83219b2 --- /dev/null +++ b/assets/icons/gallery-add.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lib/core/config/app_colors.dart b/lib/core/config/app_colors.dart index 6caa5b8..5fbbd02 100644 --- a/lib/core/config/app_colors.dart +++ b/lib/core/config/app_colors.dart @@ -16,4 +16,5 @@ class AppColors { static const Color countdown = Color.fromARGB(255, 54, 124, 57); static const Color countdownBorderRserve = Color.fromARGB(255, 186, 222, 251); static const Color expiryReserve = Color.fromARGB(255, 183, 28, 28); + static const Color uploadElevated = Color.fromARGB(255, 233, 245, 254); } \ No newline at end of file diff --git a/lib/core/gen/assets.gen.dart b/lib/core/gen/assets.gen.dart index af834ff..5c5f752 100644 --- a/lib/core/gen/assets.gen.dart +++ b/lib/core/gen/assets.gen.dart @@ -75,15 +75,24 @@ class $AssetsIconsGen { /// File path: assets/icons/arayesh.svg SvgGenImage get arayesh => const SvgGenImage('assets/icons/arayesh.svg'); + /// File path: assets/icons/arrow-down.svg + SvgGenImage get arrowDown => const SvgGenImage('assets/icons/arrow-down.svg'); + /// File path: assets/icons/arrow-left.svg SvgGenImage get arrowLeft => const SvgGenImage('assets/icons/arrow-left.svg'); + /// File path: assets/icons/arrow-up.svg + SvgGenImage get arrowUp => const SvgGenImage('assets/icons/arrow-up.svg'); + /// File path: assets/icons/back.svg SvgGenImage get back => const SvgGenImage('assets/icons/back.svg'); /// File path: assets/icons/backArrow.svg SvgGenImage get backArrow => const SvgGenImage('assets/icons/backArrow.svg'); + /// File path: assets/icons/camera.svg + SvgGenImage get camera => const SvgGenImage('assets/icons/camera.svg'); + /// File path: assets/icons/card-pos.svg SvgGenImage get cardPos => const SvgGenImage('assets/icons/card-pos.svg'); @@ -113,6 +122,10 @@ class $AssetsIconsGen { /// File path: assets/icons/fastfood.svg SvgGenImage get fastfood => const SvgGenImage('assets/icons/fastfood.svg'); + /// File path: assets/icons/gallery-add.svg + SvgGenImage get galleryAdd => + const SvgGenImage('assets/icons/gallery-add.svg'); + /// File path: assets/icons/global-search.svg SvgGenImage get globalSearch => const SvgGenImage('assets/icons/global-search.svg'); @@ -222,9 +235,12 @@ class $AssetsIconsGen { vector, addImg, arayesh, + arrowDown, arrowLeft, + arrowUp, back, backArrow, + camera, cardPos, cinama, clock, @@ -234,6 +250,7 @@ class $AssetsIconsGen { edit, error, fastfood, + galleryAdd, globalSearch, kafsh, location, diff --git a/lib/features/add_photo/cubit/add_photo_cubit.dart b/lib/features/add_photo/cubit/add_photo_cubit.dart new file mode 100644 index 0000000..393b68e --- /dev/null +++ b/lib/features/add_photo/cubit/add_photo_cubit.dart @@ -0,0 +1,49 @@ +import 'dart:io'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:image_picker/image_picker.dart'; + +part 'add_photo_state.dart'; + +class AddPhotoCubit extends Cubit { + AddPhotoCubit() : super(AddPhotoInitial()); + + final ImagePicker _picker = ImagePicker(); + + void fetchPhotos() { + emit(AddPhotoLoading()); + try { + Future.delayed(const Duration(seconds: 1), () { + final mockImageUrls = [ + 'https://picsum.photos/seed/a/400/600', + 'https://picsum.photos/seed/b/200/200', + 'https://picsum.photos/seed/c/200/200', + 'https://picsum.photos/seed/d/400/200', + 'https://picsum.photos/seed/e/200/200', + 'https://picsum.photos/seed/f/200/200', + ]; + emit(AddPhotoLoaded(mockImageUrls, 10)); + }); + } catch (e) { + emit(AddPhotoError('خطا در بارگذاری تصاویر')); + } + } + + // ✅ متد را طوری تغییر دادیم که منبع عکس را به عنوان ورودی بگیرد + Future pickImage(ImageSource source) async { + final currentState = state; + if (currentState is AddPhotoLoaded) { + try { + final XFile? pickedFile = await _picker.pickImage(source: source); + + if (pickedFile != null) { + final updatedUrls = List.from(currentState.imageUrls) + ..insert(0, pickedFile.path); + emit(AddPhotoLoaded(updatedUrls, currentState.remainingPhotos - 1)); + } + } catch (e) { + emit(AddPhotoError('خطا در انتخاب عکس: ${e.toString()}')); + } + } + } +} \ No newline at end of file diff --git a/lib/features/add_photo/cubit/add_photo_state.dart b/lib/features/add_photo/cubit/add_photo_state.dart new file mode 100644 index 0000000..f9b1bb0 --- /dev/null +++ b/lib/features/add_photo/cubit/add_photo_state.dart @@ -0,0 +1,21 @@ +part of 'add_photo_cubit.dart'; + +@immutable +abstract class AddPhotoState {} + +class AddPhotoInitial extends AddPhotoState {} + +class AddPhotoLoading extends AddPhotoState {} + +class AddPhotoLoaded extends AddPhotoState { + final List imageUrls; + final int remainingPhotos; + + AddPhotoLoaded(this.imageUrls, this.remainingPhotos); +} + +class AddPhotoError extends AddPhotoState { + final String message; + + AddPhotoError(this.message); +} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 00cc52d..d17b5a7 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -8,11 +8,11 @@ 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_event.dart'; import 'package:proxibuy/presentation/offer/bloc/offer_bloc.dart'; // ✅ مسیر BLoC آفر +import 'package:proxibuy/presentation/reservation/cubit/reservation_cubit.dart'; import 'core/config/app_colors.dart'; import 'presentation/pages/onboarding_page.dart'; void main() { - Animate.restartOnHotReload = true; runApp(const MyApp()); @@ -26,23 +26,27 @@ class MyApp extends StatelessWidget { return MultiRepositoryProvider( providers: [ RepositoryProvider( - create: (context) => OfferRepository( - offerDataSource: MockOfferDataSource(), - ), + create: + (context) => + OfferRepository(offerDataSource: MockOfferDataSource()), ), ], child: MultiBlocProvider( providers: [ - BlocProvider( - create: (context) => AuthBloc(), - ), + BlocProvider(create: (context) => AuthBloc()), BlocProvider( - create: (context) => NotificationPreferencesBloc()..add(LoadCategories()), + create: + (context) => + NotificationPreferencesBloc()..add(LoadCategories()), ), BlocProvider( - create: (context) => OffersBloc( - offerRepository: context.read(), - ), + create: + (context) => OffersBloc( + offerRepository: context.read(), + ), + ), + BlocProvider( + create: (context) => ReservationCubit(), ), ], child: MaterialApp( @@ -54,9 +58,7 @@ class MyApp extends StatelessWidget { GlobalWidgetsLocalizations.delegate, GlobalCupertinoLocalizations.delegate, ], - supportedLocales: const [ - Locale('fa'), - ], + supportedLocales: const [Locale('fa')], locale: const Locale('fa'), theme: ThemeData( @@ -76,7 +78,10 @@ class MyApp extends StatelessWidget { filled: true, fillColor: Colors.white, floatingLabelBehavior: FloatingLabelBehavior.always, - contentPadding: const EdgeInsets.symmetric(vertical: 18, horizontal: 20), + contentPadding: const EdgeInsets.symmetric( + vertical: 18, + horizontal: 20, + ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(10), borderSide: const BorderSide(color: AppColors.border), @@ -87,7 +92,10 @@ class MyApp extends StatelessWidget { ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10), - borderSide: const BorderSide(color: AppColors.primary, width: 2), + borderSide: const BorderSide( + color: AppColors.primary, + width: 2, + ), ), labelStyle: const TextStyle(color: Colors.black), hintStyle: TextStyle(color: Colors.black.withOpacity(0.8)), @@ -97,7 +105,9 @@ class MyApp extends StatelessWidget { foregroundColor: Colors.black, // رنگ متن دکمه Outlined padding: const EdgeInsets.symmetric(vertical: 16), side: const BorderSide(color: Colors.grey), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(50)), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(50), + ), textStyle: const TextStyle( fontFamily: 'Dana', fontSize: 16, @@ -110,7 +120,9 @@ class MyApp extends StatelessWidget { backgroundColor: AppColors.button, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 16), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(50)), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(50), + ), textStyle: const TextStyle( fontFamily: 'Dana', fontSize: 16, @@ -124,4 +136,4 @@ class MyApp extends StatelessWidget { ), ); } -} \ No newline at end of file +} diff --git a/lib/presentation/pages/add_photo_screen.dart b/lib/presentation/pages/add_photo_screen.dart new file mode 100644 index 0000000..e0b713d --- /dev/null +++ b/lib/presentation/pages/add_photo_screen.dart @@ -0,0 +1,346 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_animate/flutter_animate.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:proxibuy/core/config/app_colors.dart'; +import 'package:proxibuy/core/gen/assets.gen.dart'; +import 'package:proxibuy/data/models/offer_model.dart'; +import 'package:proxibuy/features/add_photo/cubit/add_photo_cubit.dart'; +import 'package:proxibuy/presentation/pages/reservation_details_screen.dart'; +import 'package:proxibuy/presentation/reservation/cubit/reservation_cubit.dart'; +import 'package:proxibuy/presentation/widgets/flutter_staggered_grid_view.dart'; +import 'package:image_picker/image_picker.dart'; // ✅ ایمپورت جدید + +class AddPhotoScreen extends StatelessWidget { + final String storeName; + final String productId; + final OfferModel offer; + + const AddPhotoScreen({ + super.key, + required this.storeName, + required this.productId, + required this.offer, + }); + + // ✅ متد جدید برای نمایش منوی انتخاب + void _showImageSourceActionSheet(BuildContext context) { + showModalBottomSheet( + context: context, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(20.0)), + ), + builder: (bottomSheetContext) { + return SafeArea( + child: Wrap( + children: [ + ListTile( + leading: const Icon(Icons.photo_library, color: AppColors.primary), + title: const Text('انتخاب از گالری'), + onTap: () { + Navigator.of(bottomSheetContext).pop(); + context.read().pickImage(ImageSource.gallery); + }, + ), + ListTile( + leading: const Icon(Icons.camera_alt, color: AppColors.primary), + title: const Text('گرفتن عکس با دوربین'), + onTap: () { + Navigator.of(bottomSheetContext).pop(); + context.read().pickImage(ImageSource.camera); + }, + ), + ], + ), + ); + }, + ); + } + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => AddPhotoCubit()..fetchPhotos(), + child: Builder(builder: (context) { + return Scaffold( + appBar: _buildCustomAppBar(context), + body: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SizedBox(height: 10,), + _buildHeader() + .animate() + .fadeIn(duration: 500.ms) + .slideY(begin: -0.2, curve: Curves.easeOut), + const SizedBox(height: 24), + SizedBox( + child: BlocBuilder( + builder: (context, state) { + if (state is AddPhotoLoading) { + return const Center(child: CircularProgressIndicator()); + } + if (state is AddPhotoLoaded) { + return PhotoGalleryView( + imageUrls: state.imageUrls, + remainingPhotos: state.remainingPhotos, + ).animate().scale( + delay: 200.ms, + duration: 400.ms, + curve: Curves.easeOutBack); + } + if (state is AddPhotoError) { + return Center(child: Text(state.message)); + } + return const SizedBox.shrink(); + }, + ), + ), + const SizedBox(height: 24), + _buildUploadButton(context) + .animate() + .fadeIn(delay: 400.ms) + .slideY(begin: 0.5, curve: Curves.easeOut), + ], + ), + ), + ); + }), + ); + } + + Widget _buildHeader() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + SvgPicture.asset( + Assets.icons.shop.path, + height: 30, + colorFilter: const ColorFilter.mode( + Color.fromARGB(255, 95, 95, 95), + BlendMode.srcIn, + ), + ), + const SizedBox(width: 10), + Text( + storeName, + style: const TextStyle(fontSize: 22, fontWeight: FontWeight.bold), + ), + ], + ), + const SizedBox(height: 16), + const Text( + 'یه عکس جذاب از محصولی که از ما خریدی بارگذاری کن تا عضو کلاب مشتریان وفادارمون بشی!', + style: TextStyle(fontSize: 16, color: Colors.black, height: 1.5), + ), + ], + ); + } + + Widget _buildUploadButton(BuildContext context) { + return ElevatedButton( + onPressed: () { + final isReserved = + context.read().isProductReserved(productId); + + if (isReserved) { + // ✅ به جای فراخوانی مستقیم، منوی انتخاب را نمایش می‌دهیم + _showImageSourceActionSheet(context); + } else { + _showReservationPopup(context); + } + }, + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 8), + backgroundColor: AppColors.uploadElevated, + side: BorderSide(color: AppColors.active, width: 1.7), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(50)), + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SvgPicture.asset(Assets.icons.galleryAdd.path), + const SizedBox(width: 10), + const Text( + 'بارگذاری', + style: TextStyle(fontSize: 16, color: AppColors.active), + ), + ], + ), + ), + ); + } + + PreferredSizeWidget _buildCustomAppBar(BuildContext context) { + // ... این متد بدون تغییر باقی می‌ماند + return PreferredSize( + preferredSize: const Size.fromHeight(70.0), + child: Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: const BorderRadius.vertical( + bottom: Radius.circular(15), + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.08), + blurRadius: 10, + offset: const Offset(0, 4), + ), + ], + ), + child: SafeArea( + child: Padding( + padding: const EdgeInsets.fromLTRB(16, 8, 16, 8), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + const Text( + 'آپلود عکس', + style: TextStyle( + color: Colors.black, + fontWeight: FontWeight.normal, + fontSize: 18, + ), + ), + IconButton( + icon: SvgPicture.asset(Assets.icons.arrowLeft.path), + onPressed: () => Navigator.of(context).pop(), + ), + ], + ), + ), + ), + ), + ); + } + + void _showReservationPopup(BuildContext context) { + // ... این متد بدون تغییر باقی می‌ماند + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext dialogContext) { + return Dialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(15), + ), + elevation: 10, + backgroundColor: Colors.white, + child: Stack( + clipBehavior: Clip.none, + alignment: Alignment.topCenter, + children: [ + Padding( + padding: const EdgeInsets.only( + top: 40, + left: 20, + right: 20, + bottom: 20, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox(height: 10), + const Padding( + padding: EdgeInsets.all(15.0), + child: Text( + "اول خرید کن، بعدا عکس بگیر!", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ), + const SizedBox(height: 5), + const Text( + "یه محصول رو از فروشگاهمون رزرو کن و ازمون تحویلش بگیر، بعدش می‌تونی عکسشو اینجا آپلود کنی.", + style: TextStyle(color: AppColors.hint, fontSize: 16), + textAlign: TextAlign.start, + ), + const SizedBox(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + GestureDetector( + onTap: () => Navigator.of(dialogContext).pop(), + child: Text( + "الان نه", + style: TextStyle( + color: AppColors.primary, + fontWeight: FontWeight.bold, + ), + ), + ), + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: AppColors.primary, + foregroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + side: const BorderSide(color: AppColors.border), + ), + padding: const EdgeInsets.symmetric( + horizontal: 45, + vertical: 7, + ), + ), + onPressed: () async { + context.read().reserveProduct( + productId, + ); + Navigator.of(dialogContext).pop(); + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => ReservationConfirmationPage( + offer: offer, + ), + ), + ); + }, + child: const Text( + "رزرو محصول", + style: TextStyle(color: Colors.white), + ), + ), + ], + ), + ], + ), + ), + Positioned( + top: -40, + child: Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.3), + blurRadius: 8, + offset: const Offset(0, 4), + ), + ], + ), + child: CircleAvatar( + backgroundColor: Colors.white, + radius: 40, + child: Padding( + padding: const EdgeInsets.all(12.0), + child: SvgPicture.asset(Assets.icons.camera.path), + ), + ), + ), + ), + ], + ), + ); + }, + ); + } +} \ No newline at end of file diff --git a/lib/presentation/pages/offers_page.dart b/lib/presentation/pages/offers_page.dart index 3934891..1c1594f 100644 --- a/lib/presentation/pages/offers_page.dart +++ b/lib/presentation/pages/offers_page.dart @@ -14,6 +14,8 @@ import 'package:proxibuy/presentation/offer/bloc/offer_event.dart'; import 'package:proxibuy/presentation/offer/bloc/offer_state.dart'; import 'package:proxibuy/presentation/offer/bloc/widgets/category_offers_row.dart'; 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:shared_preferences/shared_preferences.dart'; @@ -97,7 +99,10 @@ class _OffersPageState extends State { children: [ SvgPicture.asset(Assets.icons.edit.path), const SizedBox(width: 4), - const Text('ویرایش',style: TextStyle(color: AppColors.active),), + const Text( + 'ویرایش', + style: TextStyle(color: AppColors.active), + ), ], ), ), @@ -153,10 +158,58 @@ class _OffersPageState extends State { child: Assets.icons.logoWithName.svg(height: 40, width: 200), ), actions: [ - IconButton(onPressed: () {}, icon: Assets.icons.notification.svg()), - IconButton(onPressed: () {}, icon: Assets.icons.scanBarcode.svg()), - const SizedBox(width: 8), - ], + IconButton(onPressed: () {}, icon: Assets.icons.notification.svg()), + BlocBuilder( + builder: (context, state) { + final reservedCount = state.reservedProductIds.length; + + return Stack( + alignment: Alignment.center, + children: [ + IconButton( + onPressed: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => const ReservedListPage()), + ); + }, + icon: Assets.icons.scanBarcode.svg(), + ), + if (reservedCount > 0) + Positioned( + top: 0, + right: 2, + child: Container( + padding: const EdgeInsets.all(4), + decoration: BoxDecoration( + color: Colors.green, + shape: BoxShape.circle, + border: Border.all(color: Colors.white, width: 1.5), + ), + constraints: const BoxConstraints( + minWidth: 18, + minHeight: 18, + ), + child: Padding( + padding: const EdgeInsets.fromLTRB(2, 4, 2, 2), + child: Text( + '$reservedCount', + style: const TextStyle( + color: Colors.white, + fontSize: 11, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ), + ), + ), + ), + ], + ); + }, + ), + const SizedBox(width: 8), + ], ), body: SingleChildScrollView( child: Column( @@ -201,7 +254,10 @@ class OffersView extends StatelessWidget { backgroundColor: AppColors.confirm, foregroundColor: Colors.white, disabledBackgroundColor: Colors.grey, - padding: const EdgeInsets.symmetric(vertical: 12,horizontal: 125), + padding: const EdgeInsets.symmetric( + vertical: 12, + horizontal: 125, + ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(50), ), @@ -216,7 +272,7 @@ class OffersView extends StatelessWidget { ), ), const SizedBox(height: 15), - const Text('جست‌وجوی تصادفی') + const Text('جست‌وجوی تصادفی'), ], ), ), @@ -240,12 +296,12 @@ class OffersView extends StatelessWidget { 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); + categoryTitle: category, + offers: offersForCategory, + ) + .animate() + .fade(duration: 500.ms) + .slideY(begin: 0.3, duration: 400.ms, curve: Curves.easeOut); }, ); } @@ -259,4 +315,4 @@ class OffersView extends StatelessWidget { }, ); } -} \ No newline at end of file +} diff --git a/lib/presentation/pages/product_detail_page.dart b/lib/presentation/pages/product_detail_page.dart index 79ae772..97146c6 100644 --- a/lib/presentation/pages/product_detail_page.dart +++ b/lib/presentation/pages/product_detail_page.dart @@ -7,10 +7,12 @@ import 'package:proxibuy/core/config/app_colors.dart'; import 'package:proxibuy/core/gen/assets.gen.dart'; import 'package:proxibuy/data/models/offer_model.dart'; import 'package:proxibuy/data/repositories/offer_repository.dart'; +import 'package:proxibuy/presentation/pages/add_photo_screen.dart'; import 'package:proxibuy/presentation/pages/reservation_details_screen.dart'; import 'package:proxibuy/presentation/product_detail/bloc/product_detail_bloc.dart'; import 'package:proxibuy/presentation/product_detail/bloc/product_detail_event.dart'; import 'package:proxibuy/presentation/product_detail/bloc/product_detail_state.dart'; +import 'package:proxibuy/presentation/reservation/cubit/reservation_cubit.dart'; import 'package:proxibuy/presentation/widgets/comments_section.dart'; import 'package:slide_countdown/slide_countdown.dart'; @@ -55,15 +57,12 @@ class ProductDetailPage extends StatelessWidget { bottom: 30, left: 24, right: 24, - // دکمه را در یک BlocBuilder قرار می‌دهیم تا به state دسترسی داشته باشد child: BlocBuilder( builder: (context, state) { - // دکمه فقط زمانی نمایش داده می‌شود که اطلاعات محصول با موفقیت لود شده باشد if (state is ProductDetailLoadSuccess) { return ElevatedButton( onPressed: () { - // با کلیک روی دکمه، به صفحه تایید رزرو منتقل می‌شویم - // و اطلاعات محصول را به آن پاس می‌دهیم + context.read().reserveProduct(state.offer.id); Navigator.of(context).push( MaterialPageRoute( builder: (_) => ReservationConfirmationPage(offer: state.offer), @@ -71,11 +70,11 @@ class ProductDetailPage extends StatelessWidget { ); }, style: ElevatedButton.styleFrom( - backgroundColor: AppColors.confirm, // رنگ دکمه را به سبز تغییر دادم تا با مفهوم رزرو همخوانی داشته باشد + backgroundColor: AppColors.confirm, elevation: 5, padding: const EdgeInsets.symmetric(vertical: 16), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(50), // گردی بیشتر برای زیبایی + borderRadius: BorderRadius.circular(50), ), ), child: Row( @@ -97,7 +96,6 @@ class ProductDetailPage extends StatelessWidget { .fadeIn(delay: 200.ms, duration: 400.ms, curve: Curves.easeOut) .slideY(begin: 2, duration: 500.ms, curve: Curves.easeOut); } - // اگر اطلاعات در حال لود شدن باشد، چیزی نمایش داده نمی‌شود return const SizedBox.shrink(); }, ), @@ -310,7 +308,13 @@ class _ProductDetailViewState extends State { Widget _buildUploadButton() { return GestureDetector( onTap: () { - print("Upload image tapped!"); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => AddPhotoScreen(storeName: widget.offer.storeName, productId: widget.offer.id, offer: widget.offer,), + ), + ); + }, child: Container( width: 90, @@ -593,7 +597,7 @@ class _ProductDetailViewState extends State { shouldShowMinutes: (d) => d.inSeconds > - 0, // دقیقه تا آخرین ثانیه نمایش داده می‌شود + 0, ), ), const SizedBox(height: 4), diff --git a/lib/presentation/pages/reservation_details_screen.dart b/lib/presentation/pages/reservation_details_screen.dart index 66ca24a..f3efe74 100644 --- a/lib/presentation/pages/reservation_details_screen.dart +++ b/lib/presentation/pages/reservation_details_screen.dart @@ -68,7 +68,6 @@ class _ReservationConfirmationPageState extends State createState() => _ReservedListPageState(); +} + +class _ReservedListPageState extends State { + late final List _reservedIds; + Future>? _reservedOffersFuture; + + @override + void initState() { + super.initState(); + _reservedIds = context.read().state.reservedProductIds; + _reservedOffersFuture = _fetchReservedOffers(); + } + + Future> _fetchReservedOffers() { + final offerRepo = context.read(); + final offerFutures = + _reservedIds.map((id) => offerRepo.fetchOfferById(id)).toList(); + return Future.wait(offerFutures); + } + + @override + Widget build(BuildContext context) { + return Directionality( + textDirection: TextDirection.rtl, + child: Scaffold( + appBar: _buildCustomAppBar(context), + body: FutureBuilder>( + 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().toList(); + + if (reservedOffers.isEmpty) { + return const Center( + child: Text('هیچ آیتم رزرو شده‌ای یافت نشد.'), + ); + } + + return ListView.builder( + padding: const EdgeInsets.all(16.0), + itemCount: reservedOffers.length, + itemBuilder: (context, index) { + final offer = reservedOffers[index]; + return Padding( + padding: const EdgeInsets.only(bottom: 16.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'تخفیف ${offer.discountType} رزرو شد!', + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Colors.black87, + ), + ), + const SizedBox(height: 8), + const Divider(thickness: 1.5), + const SizedBox(height: 2), + ReservedListItemCard(offer: offer), + SizedBox( + height: 15, + ) + ], + ), + ).animate().fadeIn(delay: (100 * index).ms).slideY( + begin: 0.5, + duration: 500.ms, + curve: Curves.easeOutCubic, + ); + }, + ); + }, + ), + ), + ); + } + + PreferredSizeWidget _buildCustomAppBar(BuildContext context) { + return PreferredSize( + preferredSize: const Size.fromHeight(70.0), + child: Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: const BorderRadius.vertical( + bottom: Radius.circular(15), + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.08), + blurRadius: 10, + offset: const Offset(0, 4), + ), + ], + ), + child: SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Column( + children: [ + SizedBox(height: 15), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Row( + children: [ + const Text( + 'رزرو شده‌ها', + style: TextStyle( + color: Colors.black, + fontWeight: FontWeight.normal, + fontSize: 16, + ), + ), + IconButton( + icon: SvgPicture.asset(Assets.icons.arrowLeft.path), + onPressed: () => Navigator.of(context).pop(), + ), + ], + ), + ], + ), + ], + ), + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/presentation/reservation/cubit/reservation_cubit.dart b/lib/presentation/reservation/cubit/reservation_cubit.dart new file mode 100644 index 0000000..f3e5c00 --- /dev/null +++ b/lib/presentation/reservation/cubit/reservation_cubit.dart @@ -0,0 +1,20 @@ +import 'package:bloc/bloc.dart'; + +part 'reservation_state.dart'; + +class ReservationCubit extends Cubit { + ReservationCubit() : super(const ReservationState()); + + void reserveProduct(String productId) { + if (state.reservedProductIds.contains(productId)) return; + + final updatedList = List.from(state.reservedProductIds)..add(productId); + emit(state.copyWith(reservedProductIds: updatedList)); + // در اینجا می‌توانید لاگیک مربوط به ارسال درخواست به API را نیز اضافه کنید + } + + // متد برای بررسی اینکه آیا یک محصول خاص رزرو شده است یا نه + bool isProductReserved(String productId) { + return state.reservedProductIds.contains(productId); + } +} \ No newline at end of file diff --git a/lib/presentation/reservation/cubit/reservation_state.dart b/lib/presentation/reservation/cubit/reservation_state.dart new file mode 100644 index 0000000..ea1db7a --- /dev/null +++ b/lib/presentation/reservation/cubit/reservation_state.dart @@ -0,0 +1,13 @@ +part of 'reservation_cubit.dart'; + +class ReservationState { + final List reservedProductIds; + + const ReservationState({this.reservedProductIds = const []}); + + ReservationState copyWith({List? reservedProductIds}) { + return ReservationState( + reservedProductIds: reservedProductIds ?? this.reservedProductIds, + ); + } +} \ No newline at end of file diff --git a/lib/presentation/widgets/flutter_staggered_grid_view.dart b/lib/presentation/widgets/flutter_staggered_grid_view.dart new file mode 100644 index 0000000..f09e039 --- /dev/null +++ b/lib/presentation/widgets/flutter_staggered_grid_view.dart @@ -0,0 +1,114 @@ +import 'dart:io'; +import 'package:flutter/material.dart'; +import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; + +class PhotoGalleryView extends StatelessWidget { + final List imageUrls; + final int remainingPhotos; + + const PhotoGalleryView({ + super.key, + required this.imageUrls, + required this.remainingPhotos, + }); + + // این ویجت کمکی تشخیص می‌دهد که عکس از فایل محلی است یا از اینترنت + Widget _buildSmartImage(String imageUrl) { + bool isFile = !imageUrl.startsWith('http'); + return ClipRRect( + borderRadius: BorderRadius.circular(12.0), + child: isFile + ? Image.file( + File(imageUrl), + fit: BoxFit.cover, + ) + : Image.network( + imageUrl, + fit: BoxFit.cover, + ), + ); + } + + Widget _buildLastImageOverlay(String imageUrl) { + return ClipRRect( + borderRadius: BorderRadius.circular(12.0), + child: Stack( + fit: StackFit.expand, + children: [ + _buildSmartImage(imageUrl), // از ویجت هوشمند استفاده می‌کنیم + Container( + color: Colors.white.withOpacity(0.7), + child: Center( + child: Text( + '+$remainingPhotos', + style: const TextStyle( + color: Colors.black, + fontSize: 24, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ], + ), + ); + } + + @override + Widget build(BuildContext context) { + if (imageUrls.isEmpty) { + return const Center(child: Text("هنوز عکسی وجود ندارد.")); + } + + // این بخش برای جلوگیری از خطا در صورتی که تعداد عکس‌ها کمتر از نیاز گرید باشد، اضافه شده است. + final displayUrls = List.from(imageUrls); + while (displayUrls.length < 6) { + displayUrls.add('https://via.placeholder.com/200'); // یک عکس جایگزین + } + + + return StaggeredGrid.count( + crossAxisCount: 4, + mainAxisSpacing: 8, + crossAxisSpacing: 7, + children: [ + if (imageUrls.isNotEmpty) + StaggeredGridTile.count( + crossAxisCellCount: 3, + mainAxisCellCount: 2, + child: _buildSmartImage(displayUrls[0]), + ), + if (imageUrls.length > 1) + StaggeredGridTile.count( + crossAxisCellCount: 1, + mainAxisCellCount: 1, + child: _buildSmartImage(displayUrls[1]), + ), + if (imageUrls.length > 2) + StaggeredGridTile.count( + crossAxisCellCount: 1, + mainAxisCellCount: 1, + child: _buildSmartImage(displayUrls[2]), + ), + if (imageUrls.length > 3) + StaggeredGridTile.count( + crossAxisCellCount: 2, + mainAxisCellCount: 1, + child: _buildSmartImage(displayUrls[3]), + ), + if (imageUrls.length > 4) + StaggeredGridTile.count( + crossAxisCellCount: 1, + mainAxisCellCount: 1, + child: _buildSmartImage(displayUrls[4]), + ), + if (imageUrls.length > 5) + StaggeredGridTile.count( + crossAxisCellCount: 1, + mainAxisCellCount: 1, + child: _buildLastImageOverlay(displayUrls[5]), + ), + ], + ); + } +} \ No newline at end of file diff --git a/lib/presentation/widgets/reserved_list_item_card.dart b/lib/presentation/widgets/reserved_list_item_card.dart new file mode 100644 index 0000000..9411ce4 --- /dev/null +++ b/lib/presentation/widgets/reserved_list_item_card.dart @@ -0,0 +1,390 @@ +// lib/presentation/widgets/reserved_list_item_card.dart + +import 'dart:async'; +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:proxibuy/core/gen/assets.gen.dart'; +import 'package:qr_flutter/qr_flutter.dart'; +import 'package:proxibuy/core/config/app_colors.dart'; +import 'package:proxibuy/data/models/offer_model.dart'; +import 'package:slide_countdown/slide_countdown.dart'; + +class ReservedListItemCard extends StatefulWidget { + final OfferModel offer; + + const ReservedListItemCard({super.key, required this.offer}); + + @override + State createState() => _ReservedListItemCardState(); +} + +class _ReservedListItemCardState extends State { + bool _isExpanded = false; + Timer? _timer; + Duration _remaining = Duration.zero; + + @override + void initState() { + super.initState(); + _calculateRemainingTime(); + _timer = Timer.periodic(const Duration(seconds: 1), (_) { + _calculateRemainingTime(); + }); + } + + void _calculateRemainingTime() { + final now = DateTime.now(); + if (widget.offer.expiryTime.isAfter(now)) { + if (mounted) { + setState(() { + _remaining = widget.offer.expiryTime.difference(now); + }); + } + } else { + if (mounted) { + setState(() => _remaining = Duration.zero); + } + _timer?.cancel(); + } + } + + @override + void dispose() { + _timer?.cancel(); + super.dispose(); + } + + String _formatDuration(Duration duration) { + if (duration.inSeconds <= 0) return "پایان یافته"; + final hours = duration.inHours.toString().padLeft(2, '0'); + final minutes = (duration.inMinutes % 60).toString().padLeft(2, '0'); + final seconds = (duration.inSeconds % 60).toString().padLeft(2, '0'); + return '$hours:$minutes:$seconds'; + } + + @override + Widget build(BuildContext context) { + // ویجت اصلی به Column تغییر کرده است + return Column( + children: [ + // بخش اول: کارت اصلی که فقط شامل اطلاعات محصول است + Card( + color: Colors.white, + shape: RoundedRectangleBorder( + side: BorderSide(color: Colors.grey.shade300, width: 1), + borderRadius: BorderRadius.circular(20), + ), + elevation: 0, + clipBehavior: Clip.antiAlias, + margin: EdgeInsets.zero, + child: _buildOfferPrimaryDetails(), // فقط اطلاعات اصلی داخل کارت + ), + _buildActionsRow(), + // بخش سوم: پنل باز شونده QR کد + _buildExpansionPanel(), + ], + ); + } + + // این متد فقط اطلاعات اصلی داخل کارت را می‌سازد + Widget _buildOfferPrimaryDetails() { + return Padding( + padding: const EdgeInsets.fromLTRB(15, 25, 15, 25), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(8), + child: CachedNetworkImage( + imageUrl: widget.offer.coverImageUrl, + width: 90, + height: 90, + fit: BoxFit.cover, + ), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + SvgPicture.asset(Assets.icons.shop.path), + SizedBox(width: 8), + Text( + widget.offer.storeName, + style: const TextStyle( + fontWeight: FontWeight.normal, + fontSize: 16, + ), + ), + ], + ), + const SizedBox(height: 4), + Row( + children: [ + SvgPicture.asset(Assets.icons.shoppingCart.path), + SizedBox(width: 8), + Text( + widget.offer.title, + style: TextStyle(color: AppColors.hint, fontSize: 14), + ), + ], + ), + const SizedBox(height: 8), + Row( + children: [ + SvgPicture.asset(Assets.icons.location.path), + SizedBox(width: 8), + // برای جلوگیری از سرریز شدن متن، از Flexible استفاده می‌کنیم + Flexible( + child: Text( + "${widget.offer.address} (${widget.offer.distanceInMeters} متر تا تخفیف)", + overflow: TextOverflow.ellipsis, + maxLines: 1, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.normal, + color: AppColors.hint, + ), + ), + ), + ], + ), + ], + ), + ), + ], + ), + ); + } + + Widget _buildActionsRow() { + return Padding( + padding: const EdgeInsets.fromLTRB(12, 8, 12, 0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if (_remaining > Duration.zero) + Column( + children: [ + Localizations.override( + context: context, + locale: const Locale('en'), + child: SlideCountdown( + duration: _remaining, + slideDirection: SlideDirection.up, + separator: ':', + style: const TextStyle( + fontSize: 25, + fontWeight: FontWeight.bold, + color: AppColors.countdown, + ), + separatorStyle: const TextStyle( + fontSize: 20, + color: AppColors.countdown, + ), + decoration: const BoxDecoration(color: Colors.white), + shouldShowDays: (d) => d.inDays > 0, + shouldShowHours: (d) => d.inHours > 0, + shouldShowMinutes: (d) => d.inSeconds > 0, + ), + ), + const SizedBox(height: 4), + _buildTimerLabels(_remaining), + ], + ), + SizedBox(width: 10), + TextButton( + onPressed: () => setState(() => _isExpanded = !_isExpanded), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + _isExpanded ? 'بستن' : 'اطلاعات بیشتر', + style: TextStyle(color: AppColors.active), + ), + const SizedBox(width: 12), + AnimatedRotation( + turns: _isExpanded ? 0.5 : 0, + duration: const Duration(milliseconds: 300), + child: SvgPicture.asset( + Assets.icons.arrowDown.path, + height: 20, + ), + ), + ], + ), + ), + ], + ), + ); + } + + Widget _buildTimerLabels(Duration duration) { + const double columnWidth = 40; + const labelStyle = TextStyle(fontSize: 10, color: AppColors.selectedImg); + + List labels = []; + + if (duration.inDays > 0) { + labels = [ + SizedBox( + width: columnWidth, + child: Center(child: Text("ثانیه", style: labelStyle)), + ), + SizedBox( + width: columnWidth, + child: Center(child: Text("دقیقه", style: labelStyle)), + ), + SizedBox( + width: columnWidth, + child: Center(child: Text("ساعت", style: labelStyle)), + ), + SizedBox( + width: columnWidth, + child: Center(child: Text("روز", style: labelStyle)), + ), + ]; + } else if (duration.inHours > 0) { + labels = [ + SizedBox( + width: columnWidth, + child: Center(child: Text("ثانیه", style: labelStyle)), + ), + SizedBox( + width: columnWidth, + child: Center(child: Text("دقیقه", style: labelStyle)), + ), + SizedBox( + width: columnWidth, + child: Center(child: Text("ساعت", style: labelStyle)), + ), + ]; + } else if (duration.inSeconds > 0) { + labels = [ + SizedBox( + width: columnWidth, + child: Center(child: Text("ثانیه", style: labelStyle)), + ), + SizedBox( + width: columnWidth, + child: Center(child: Text("دقیقه", style: labelStyle)), + ), + ]; + } + + return Row(mainAxisAlignment: MainAxisAlignment.center, children: labels); + } + + Widget _buildExpansionPanel() { + return AnimatedCrossFade( + firstChild: Container(), + secondChild: Container( + padding: const EdgeInsets.symmetric(vertical: 24, horizontal: 16), + width: double.infinity, + child: Center( + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + padding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 9, + ), + decoration: BoxDecoration( + color: AppColors.singleOfferType, + borderRadius: BorderRadius.circular(20), + ), + child: Text( + "تخفیف ${widget.offer.discountType}", + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.normal, + fontSize: 17, + ), + ), + ), + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + '(${widget.offer.discount})', + style: const TextStyle( + fontSize: 14, + color: AppColors.singleOfferType, + fontWeight: FontWeight.normal, + ), + ), + const SizedBox(width: 8), + Text( + widget.offer.originalPrice.toStringAsFixed(0), + style: TextStyle( + fontSize: 14, + color: Colors.grey.shade600, + decoration: TextDecoration.lineThrough, + ), + ), + ], + ), + const SizedBox(height: 1), + Text( + '${widget.offer.finalPrice.toStringAsFixed(0)} تومان', + style: const TextStyle( + color: AppColors.singleOfferType, + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ], + ), + SizedBox( + height: 20, + ), + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: Color.fromARGB(255, 246, 246, 246), + borderRadius: BorderRadius.circular(16), + ), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(15.0), + child: QrImageView( + data: widget.offer.qrCodeData, + version: QrVersions.auto, + size: 280.0, + ), + ), + SizedBox(height: 10), + Text( + widget.offer.qrCodeData, + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 20, + ), + ), + ], + ), + ), + ], + ), + ), + ), + crossFadeState: + _isExpanded ? CrossFadeState.showSecond : CrossFadeState.showFirst, + duration: const Duration(milliseconds: 300), + ); + } +} \ No newline at end of file diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index bfafdd0..020f725 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,11 +6,15 @@ #include "generated_plugin_registrant.h" +#include #include #include #include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); + file_selector_plugin_register_with_registrar(file_selector_linux_registrar); g_autoptr(FlPluginRegistrar) flutter_localization_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterLocalizationPlugin"); flutter_localization_plugin_register_with_registrar(flutter_localization_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 866b317..a021899 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + file_selector_linux flutter_localization maps_launcher url_launcher_linux diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index f2712cd..5f18724 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,6 +5,7 @@ import FlutterMacOS import Foundation +import file_selector_macos import flutter_localization import geolocator_apple import maps_launcher @@ -14,6 +15,7 @@ import sqflite_darwin import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) FlutterLocalizationPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalizationPlugin")) GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin")) MapsLauncherPlugin.register(with: registry.registrar(forPlugin: "MapsLauncherPlugin")) diff --git a/pubspec.lock b/pubspec.lock index 3eb44e7..7adbb89 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -209,6 +209,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.27" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" + url: "https://pub.dev" + source: hosted + version: "0.3.4+2" crypto: dependency: transitive description: @@ -289,6 +297,38 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.1" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "54cbbd957e1156d29548c7d9b9ec0c0ebb6de0a90452198683a7d23aed617a33" + url: "https://pub.dev" + source: hosted + version: "0.9.3+2" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: "8c9250b2bd2d8d4268e39c82543bacbaca0fda7d29e0728c3c4bbb7c820fd711" + url: "https://pub.dev" + source: hosted + version: "0.9.4+3" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b + url: "https://pub.dev" + source: hosted + version: "2.6.2" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: "320fcfb6f33caa90f0b58380489fc5ac05d99ee94b61aa96ec2bff0ba81d3c2b" + url: "https://pub.dev" + source: hosted + version: "0.9.3+4" fixnum: dependency: transitive description: @@ -371,6 +411,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: f948e346c12f8d5480d2825e03de228d0eb8c3a737e4cdaa122267b89c022b5e + url: "https://pub.dev" + source: hosted + version: "2.0.28" flutter_shaders: dependency: transitive description: @@ -379,6 +427,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.1.3" + flutter_staggered_grid_view: + dependency: "direct main" + description: + name: flutter_staggered_grid_view + sha256: "19e7abb550c96fbfeb546b23f3ff356ee7c59a019a651f8f102a4ba9b7349395" + url: "https://pub.dev" + source: hosted + version: "0.7.0" flutter_svg: dependency: "direct main" description: @@ -501,6 +557,70 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.2" + image_picker: + dependency: "direct main" + description: + name: image_picker + sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + sha256: "317a5d961cec5b34e777b9252393f2afbd23084aa6e60fcf601dcf6341b9ebeb" + url: "https://pub.dev" + source: hosted + version: "0.8.12+23" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + sha256: "717eb042ab08c40767684327be06a5d8dbb341fe791d514e4b92c7bbe1b7bb83" + url: "https://pub.dev" + source: hosted + version: "3.0.6" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + sha256: "05da758e67bc7839e886b3959848aa6b44ff123ab4b28f67891008afe8ef9100" + url: "https://pub.dev" + source: hosted + version: "0.8.12+2" + image_picker_linux: + dependency: transitive + description: + name: image_picker_linux + sha256: "34a65f6740df08bbbeb0a1abd8e6d32107941fd4868f67a507b25601651022c9" + url: "https://pub.dev" + source: hosted + version: "0.2.1+2" + image_picker_macos: + dependency: transitive + description: + name: image_picker_macos + sha256: "1b90ebbd9dcf98fb6c1d01427e49a55bd96b5d67b8c67cf955d60a5de74207c1" + url: "https://pub.dev" + source: hosted + version: "0.2.1+2" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + sha256: "886d57f0be73c4b140004e78b9f28a8914a09e50c2d816bdd0520051a71236a0" + url: "https://pub.dev" + source: hosted + version: "2.10.1" + image_picker_windows: + dependency: transitive + description: + name: image_picker_windows + sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" image_size_getter: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 28f555c..cefb72b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -50,6 +50,8 @@ dependencies: maps_launcher: ^3.0.0+1 slide_countdown: ^2.0.2 qr_flutter: ^4.1.0 + flutter_staggered_grid_view: ^0.7.0 + image_picker: ^1.1.2 dev_dependencies: flutter_test: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index a125364..ccecd30 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,6 +6,7 @@ #include "generated_plugin_registrant.h" +#include #include #include #include @@ -13,6 +14,8 @@ #include void RegisterPlugins(flutter::PluginRegistry* registry) { + FileSelectorWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FileSelectorWindows")); FlutterLocalizationPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("FlutterLocalizationPluginCApi")); GeolocatorWindowsRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 1888886..f5f5fec 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + file_selector_windows flutter_localization geolocator_windows maps_launcher