added upload page
This commit is contained in:
parent
c0d1bee773
commit
15782a7d34
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
|
||||
|
||||
<application
|
||||
android:label="Proxibuy"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="24" height="25" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 17.7298C11.3 17.7298 10.6 17.4598 10.07 16.9298L3.55002 10.4098C3.26002 10.1198 3.26002 9.63982 3.55002 9.34982C3.84002 9.05982 4.32002 9.05982 4.61002 9.34982L11.13 15.8698C11.61 16.3498 12.39 16.3498 12.87 15.8698L19.39 9.34982C19.68 9.05982 20.16 9.05982 20.45 9.34982C20.74 9.63982 20.74 10.1198 20.45 10.4098L13.93 16.9298C13.4 17.4598 12.7 17.7298 12 17.7298Z" fill="#2196F3"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 500 B |
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="25" height="25" viewBox="0 0 25 25" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.5 8.17643C11.8 8.17643 11.1 8.44643 10.57 8.97643L4.05002 15.4964C3.76002 15.7864 3.76002 16.2664 4.05002 16.5564C4.34002 16.8464 4.82002 16.8464 5.11002 16.5564L11.63 10.0364C12.11 9.55643 12.89 9.55643 13.37 10.0364L19.89 16.5564C20.18 16.8464 20.66 16.8464 20.95 16.5564C21.24 16.2664 21.24 15.7864 20.95 15.4964L14.43 8.97643C13.9 8.44643 13.2 8.17643 12.5 8.17643Z" fill="#2196F3"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 504 B |
|
|
@ -0,0 +1,5 @@
|
|||
<svg width="53" height="53" viewBox="0 0 53 53" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path opacity="0.4" d="M15.4569 48.4764H38.1636C44.1436 48.4764 46.5269 44.8147 46.8086 40.3514L47.9353 22.4547C48.2386 17.7747 44.5119 13.8097 39.8103 13.8097C38.4886 13.8097 37.2753 13.0514 36.6686 11.8814L35.1086 8.73973C34.1119 6.76807 31.5119 5.14307 29.3019 5.14307H24.3403C22.1086 5.14307 19.5086 6.76807 18.5119 8.73973L16.9519 11.8814C16.3453 13.0514 15.1319 13.8097 13.8103 13.8097C9.10859 13.8097 5.38193 17.7747 5.68526 22.4547L6.81193 40.3514C7.07193 44.8147 9.47693 48.4764 15.4569 48.4764Z" fill="#176BAD"/>
|
||||
<path d="M30.0601 19.7681H23.5601C22.6717 19.7681 21.9351 19.0314 21.9351 18.1431C21.9351 17.2547 22.6717 16.5181 23.5601 16.5181H30.0601C30.9484 16.5181 31.6851 17.2547 31.6851 18.1431C31.6851 19.0314 30.9484 19.7681 30.0601 19.7681Z" fill="#176BAD"/>
|
||||
<path d="M26.8101 40.0915C30.8547 40.0915 34.1335 36.8127 34.1335 32.7682C34.1335 28.7236 30.8547 25.4448 26.8101 25.4448C22.7656 25.4448 19.4868 28.7236 19.4868 32.7682C19.4868 36.8127 22.7656 40.0915 26.8101 40.0915Z" fill="#176BAD"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
|
|
@ -0,0 +1,7 @@
|
|||
<svg width="24" height="25" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9 10.8169C10.1046 10.8169 11 9.92146 11 8.81689C11 7.71232 10.1046 6.81689 9 6.81689C7.89543 6.81689 7 7.71232 7 8.81689C7 9.92146 7.89543 10.8169 9 10.8169Z" stroke="#2196F3" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M13 2.81689H9C4 2.81689 2 4.81689 2 9.81689V15.8169C2 20.8169 4 22.8169 9 22.8169H15C20 22.8169 22 20.8169 22 15.8169V10.8169" stroke="#2196F3" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M15.75 5.81689H21.25" stroke="#2196F3" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<path d="M18.5 8.56689V3.06689" stroke="#2196F3" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<path d="M2.66992 19.767L7.59992 16.457C8.38992 15.927 9.52992 15.987 10.2399 16.597L10.5699 16.887C11.3499 17.557 12.6099 17.557 13.3899 16.887L17.5499 13.317C18.3299 12.647 19.5899 12.647 20.3699 13.317L21.9999 14.717" stroke="#2196F3" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<AddPhotoState> {
|
||||
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<void> 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<String>.from(currentState.imageUrls)
|
||||
..insert(0, pickedFile.path);
|
||||
emit(AddPhotoLoaded(updatedUrls, currentState.remainingPhotos - 1));
|
||||
}
|
||||
} catch (e) {
|
||||
emit(AddPhotoError('خطا در انتخاب عکس: ${e.toString()}'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<String> imageUrls;
|
||||
final int remainingPhotos;
|
||||
|
||||
AddPhotoLoaded(this.imageUrls, this.remainingPhotos);
|
||||
}
|
||||
|
||||
class AddPhotoError extends AddPhotoState {
|
||||
final String message;
|
||||
|
||||
AddPhotoError(this.message);
|
||||
}
|
||||
|
|
@ -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<OfferRepository>(
|
||||
create: (context) => OfferRepository(
|
||||
offerDataSource: MockOfferDataSource(),
|
||||
),
|
||||
create:
|
||||
(context) =>
|
||||
OfferRepository(offerDataSource: MockOfferDataSource()),
|
||||
),
|
||||
],
|
||||
child: MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider<AuthBloc>(
|
||||
create: (context) => AuthBloc(),
|
||||
),
|
||||
BlocProvider<AuthBloc>(create: (context) => AuthBloc()),
|
||||
BlocProvider<NotificationPreferencesBloc>(
|
||||
create: (context) => NotificationPreferencesBloc()..add(LoadCategories()),
|
||||
create:
|
||||
(context) =>
|
||||
NotificationPreferencesBloc()..add(LoadCategories()),
|
||||
),
|
||||
BlocProvider<OffersBloc>(
|
||||
create: (context) => OffersBloc(
|
||||
offerRepository: context.read<OfferRepository>(),
|
||||
),
|
||||
create:
|
||||
(context) => OffersBloc(
|
||||
offerRepository: context.read<OfferRepository>(),
|
||||
),
|
||||
),
|
||||
BlocProvider<ReservationCubit>(
|
||||
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 {
|
|||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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: <Widget>[
|
||||
ListTile(
|
||||
leading: const Icon(Icons.photo_library, color: AppColors.primary),
|
||||
title: const Text('انتخاب از گالری'),
|
||||
onTap: () {
|
||||
Navigator.of(bottomSheetContext).pop();
|
||||
context.read<AddPhotoCubit>().pickImage(ImageSource.gallery);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.camera_alt, color: AppColors.primary),
|
||||
title: const Text('گرفتن عکس با دوربین'),
|
||||
onTap: () {
|
||||
Navigator.of(bottomSheetContext).pop();
|
||||
context.read<AddPhotoCubit>().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<AddPhotoCubit, AddPhotoState>(
|
||||
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<ReservationCubit>().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<ReservationCubit>().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),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<OffersPage> {
|
|||
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<OffersPage> {
|
|||
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<ReservationCubit, ReservationState>(
|
||||
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 {
|
|||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<ProductDetailBloc, ProductDetailState>(
|
||||
builder: (context, state) {
|
||||
// دکمه فقط زمانی نمایش داده میشود که اطلاعات محصول با موفقیت لود شده باشد
|
||||
if (state is ProductDetailLoadSuccess) {
|
||||
return ElevatedButton(
|
||||
onPressed: () {
|
||||
// با کلیک روی دکمه، به صفحه تایید رزرو منتقل میشویم
|
||||
// و اطلاعات محصول را به آن پاس میدهیم
|
||||
context.read<ReservationCubit>().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<ProductDetailView> {
|
|||
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<ProductDetailView> {
|
|||
shouldShowMinutes:
|
||||
(d) =>
|
||||
d.inSeconds >
|
||||
0, // دقیقه تا آخرین ثانیه نمایش داده میشود
|
||||
0,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
|
|
|
|||
|
|
@ -68,7 +68,6 @@ class _ReservationConfirmationPageState extends State<ReservationConfirmationPag
|
|||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// ✅ انیمیشن برای عنوان اصلی
|
||||
Text(
|
||||
'تخفیف ${widget.offer.discountType} رزرو شد!',
|
||||
style: const TextStyle(
|
||||
|
|
@ -79,7 +78,6 @@ class _ReservationConfirmationPageState extends State<ReservationConfirmationPag
|
|||
).animate().fadeIn(delay: 300.ms, duration: 500.ms).slideY(begin: -0.2, end: 0),
|
||||
|
||||
const SizedBox(height: 8),
|
||||
// ✅ انیمیشن برای خط جداکننده
|
||||
const Divider(thickness: 1.5)
|
||||
.animate()
|
||||
.fadeIn(delay: 400.ms)
|
||||
|
|
@ -178,7 +176,6 @@ class _ReservationConfirmationPageState extends State<ReservationConfirmationPag
|
|||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
// جزئیات متنی
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,162 @@
|
|||
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/gen/assets.gen.dart';
|
||||
import 'package:proxibuy/data/models/offer_model.dart';
|
||||
import 'package:proxibuy/data/repositories/offer_repository.dart';
|
||||
import 'package:proxibuy/presentation/reservation/cubit/reservation_cubit.dart';
|
||||
import 'package:proxibuy/presentation/widgets/reserved_list_item_card.dart';
|
||||
|
||||
class ReservedListPage extends StatefulWidget {
|
||||
const ReservedListPage({super.key});
|
||||
|
||||
@override
|
||||
State<ReservedListPage> createState() => _ReservedListPageState();
|
||||
}
|
||||
|
||||
class _ReservedListPageState extends State<ReservedListPage> {
|
||||
late final List<String> _reservedIds;
|
||||
Future<List<OfferModel?>>? _reservedOffersFuture;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_reservedIds = context.read<ReservationCubit>().state.reservedProductIds;
|
||||
_reservedOffersFuture = _fetchReservedOffers();
|
||||
}
|
||||
|
||||
Future<List<OfferModel?>> _fetchReservedOffers() {
|
||||
final offerRepo = context.read<OfferRepository>();
|
||||
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<List<OfferModel?>>(
|
||||
future: _reservedOffersFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
if (snapshot.hasError) {
|
||||
return Center(
|
||||
child: Text('خطا در بارگذاری اطلاعات: ${snapshot.error}'),
|
||||
);
|
||||
}
|
||||
|
||||
if (!snapshot.hasData || snapshot.data!.isEmpty) {
|
||||
return const Center(
|
||||
child: Text('هیچ آیتم رزرو شدهای وجود ندارد.'),
|
||||
);
|
||||
}
|
||||
|
||||
final reservedOffers =
|
||||
snapshot.data!.whereType<OfferModel>().toList();
|
||||
|
||||
if (reservedOffers.isEmpty) {
|
||||
return const Center(
|
||||
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(),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import 'package:bloc/bloc.dart';
|
||||
|
||||
part 'reservation_state.dart';
|
||||
|
||||
class ReservationCubit extends Cubit<ReservationState> {
|
||||
ReservationCubit() : super(const ReservationState());
|
||||
|
||||
void reserveProduct(String productId) {
|
||||
if (state.reservedProductIds.contains(productId)) return;
|
||||
|
||||
final updatedList = List<String>.from(state.reservedProductIds)..add(productId);
|
||||
emit(state.copyWith(reservedProductIds: updatedList));
|
||||
// در اینجا میتوانید لاگیک مربوط به ارسال درخواست به API را نیز اضافه کنید
|
||||
}
|
||||
|
||||
// متد برای بررسی اینکه آیا یک محصول خاص رزرو شده است یا نه
|
||||
bool isProductReserved(String productId) {
|
||||
return state.reservedProductIds.contains(productId);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
part of 'reservation_cubit.dart';
|
||||
|
||||
class ReservationState {
|
||||
final List<String> reservedProductIds;
|
||||
|
||||
const ReservationState({this.reservedProductIds = const []});
|
||||
|
||||
ReservationState copyWith({List<String>? reservedProductIds}) {
|
||||
return ReservationState(
|
||||
reservedProductIds: reservedProductIds ?? this.reservedProductIds,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<String> 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<String>.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]),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<ReservedListItemCard> createState() => _ReservedListItemCardState();
|
||||
}
|
||||
|
||||
class _ReservedListItemCardState extends State<ReservedListItemCard> {
|
||||
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<Widget> 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),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -6,11 +6,15 @@
|
|||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <file_selector_linux/file_selector_plugin.h>
|
||||
#include <flutter_localization/flutter_localization_plugin.h>
|
||||
#include <maps_launcher/maps_launcher_plugin.h>
|
||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
file_selector_linux
|
||||
flutter_localization
|
||||
maps_launcher
|
||||
url_launcher_linux
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
|
|
|
|||
120
pubspec.lock
120
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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <file_selector_windows/file_selector_windows.h>
|
||||
#include <flutter_localization/flutter_localization_plugin_c_api.h>
|
||||
#include <geolocator_windows/geolocator_windows.h>
|
||||
#include <maps_launcher/maps_launcher_plugin.h>
|
||||
|
|
@ -13,6 +14,8 @@
|
|||
#include <url_launcher_windows/url_launcher_windows.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
FileSelectorWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
||||
FlutterLocalizationPluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FlutterLocalizationPluginCApi"));
|
||||
GeolocatorWindowsRegisterWithRegistrar(
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
file_selector_windows
|
||||
flutter_localization
|
||||
geolocator_windows
|
||||
maps_launcher
|
||||
|
|
|
|||
Loading…
Reference in New Issue