auth api
This commit is contained in:
parent
61611fef04
commit
690813829d
|
|
@ -1,5 +1,8 @@
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.application")
|
id("com.android.application")
|
||||||
|
// START: FlutterFire Configuration
|
||||||
|
id("com.google.gms.google-services")
|
||||||
|
// END: FlutterFire Configuration
|
||||||
id("kotlin-android")
|
id("kotlin-android")
|
||||||
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
||||||
id("dev.flutter.flutter-gradle-plugin")
|
id("dev.flutter.flutter-gradle-plugin")
|
||||||
|
|
@ -24,7 +27,7 @@ android {
|
||||||
applicationId = "com.example.proxibuy"
|
applicationId = "com.example.proxibuy"
|
||||||
// You can update the following values to match your application needs.
|
// You can update the following values to match your application needs.
|
||||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||||
minSdk = flutter.minSdkVersion
|
minSdk = 23
|
||||||
targetSdk = flutter.targetSdkVersion
|
targetSdk = flutter.targetSdkVersion
|
||||||
versionCode = flutter.versionCode
|
versionCode = flutter.versionCode
|
||||||
versionName = flutter.versionName
|
versionName = flutter.versionName
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
{
|
||||||
|
"project_info": {
|
||||||
|
"project_number": "800272350428",
|
||||||
|
"project_id": "proxibuy-3b5e0",
|
||||||
|
"storage_bucket": "proxibuy-3b5e0.firebasestorage.app"
|
||||||
|
},
|
||||||
|
"client": [
|
||||||
|
{
|
||||||
|
"client_info": {
|
||||||
|
"mobilesdk_app_id": "1:800272350428:android:d6af1e013bae09d9c78819",
|
||||||
|
"android_client_info": {
|
||||||
|
"package_name": "com.example.business_panel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"oauth_client": [],
|
||||||
|
"api_key": [
|
||||||
|
{
|
||||||
|
"current_key": "AIzaSyCMGweIbZBsFNXabKRJJJcVLPwcmqhuSwg"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"services": {
|
||||||
|
"appinvite_service": {
|
||||||
|
"other_platform_oauth_client": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"client_info": {
|
||||||
|
"mobilesdk_app_id": "1:800272350428:android:6cbc063052753bc9c78819",
|
||||||
|
"android_client_info": {
|
||||||
|
"package_name": "com.example.proxibuy"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"oauth_client": [],
|
||||||
|
"api_key": [
|
||||||
|
{
|
||||||
|
"current_key": "AIzaSyCMGweIbZBsFNXabKRJJJcVLPwcmqhuSwg"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"services": {
|
||||||
|
"appinvite_service": {
|
||||||
|
"other_platform_oauth_client": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"configuration_version": "1"
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,9 @@ pluginManagement {
|
||||||
plugins {
|
plugins {
|
||||||
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
||||||
id("com.android.application") version "8.7.0" apply false
|
id("com.android.application") version "8.7.0" apply false
|
||||||
|
// START: FlutterFire Configuration
|
||||||
|
id("com.google.gms.google-services") version("4.3.15") apply false
|
||||||
|
// END: FlutterFire Configuration
|
||||||
id("org.jetbrains.kotlin.android") version "1.8.22" apply false
|
id("org.jetbrains.kotlin.android") version "1.8.22" apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{"flutter":{"platforms":{"android":{"default":{"projectId":"proxibuy-3b5e0","appId":"1:800272350428:android:6cbc063052753bc9c78819","fileOutput":"android/app/google-services.json"}},"dart":{"lib/firebase_options.dart":{"projectId":"proxibuy-3b5e0","configurations":{"android":"1:800272350428:android:6cbc063052753bc9c78819"}}}}}}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
class ApiConfig {
|
||||||
|
static const String baseUrl = "https://proxybuy.liara.run";
|
||||||
|
static const String sendCode = "/login/sendcode";
|
||||||
|
static const String verifyCode = "/login/getcode";
|
||||||
|
static const String updateUser = "/user/updateName";
|
||||||
|
static const String updateCategories = "/user/favoriteCategory";
|
||||||
|
}
|
||||||
|
|
@ -5,7 +5,7 @@ import 'package:proxibuy/data/models/working_hours.dart';
|
||||||
|
|
||||||
abstract class OfferDataSource {
|
abstract class OfferDataSource {
|
||||||
Future<List<OfferModel>> getNearbyOffers();
|
Future<List<OfferModel>> getNearbyOffers();
|
||||||
Future<OfferModel?> getOfferById(String id); // <<<<<<< جدید
|
Future<OfferModel?> getOfferById(String id);
|
||||||
}
|
}
|
||||||
|
|
||||||
class MockOfferDataSource implements OfferDataSource {
|
class MockOfferDataSource implements OfferDataSource {
|
||||||
|
|
@ -71,7 +71,7 @@ class MockOfferDataSource implements OfferDataSource {
|
||||||
name: "رفیقبازی",
|
name: "رفیقبازی",
|
||||||
description: "یه مهمونی دو نفره، یه تخفیف دوتایی؛ پس رفیقتو بیار و تخفیفتو ببر. دوستای بیشتر، تخفیف بیشتر.",
|
description: "یه مهمونی دو نفره، یه تخفیف دوتایی؛ پس رفیقتو بیار و تخفیفتو ببر. دوستای بیشتر، تخفیف بیشتر.",
|
||||||
),
|
),
|
||||||
comments: [ // <-- بخش نظرات اضافه شد
|
comments: [
|
||||||
CommentModel(
|
CommentModel(
|
||||||
id: 'c1',
|
id: 'c1',
|
||||||
userName: 'سارا رضایی',
|
userName: 'سارا رضایی',
|
||||||
|
|
@ -154,7 +154,7 @@ class MockOfferDataSource implements OfferDataSource {
|
||||||
name: "رفیقبازی",
|
name: "رفیقبازی",
|
||||||
description: "یه مهمونی دو نفره، یه تخفیف دوتایی؛ پس رفیقتو بیار و تخفیفتو ببر. دوستای بیشتر، تخفیف بیشتر.",
|
description: "یه مهمونی دو نفره، یه تخفیف دوتایی؛ پس رفیقتو بیار و تخفیفتو ببر. دوستای بیشتر، تخفیف بیشتر.",
|
||||||
),
|
),
|
||||||
comments: [ // <-- بخش نظرات اضافه شد
|
comments: [
|
||||||
CommentModel(
|
CommentModel(
|
||||||
id: 'c1',
|
id: 'c1',
|
||||||
userName: 'سارا رضایی',
|
userName: 'سارا رضایی',
|
||||||
|
|
@ -218,7 +218,7 @@ class MockOfferDataSource implements OfferDataSource {
|
||||||
Shift(openAt: '۵ عصر', closeAt: '۱۱ شب'),
|
Shift(openAt: '۵ عصر', closeAt: '۱۱ شب'),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
WorkingHours(day: 'جمعه', shifts: []), // تعطیل
|
WorkingHours(day: 'جمعه', shifts: []),
|
||||||
],
|
],
|
||||||
discountType: 'رفیقبازی',
|
discountType: 'رفیقبازی',
|
||||||
isOpen: true,
|
isOpen: true,
|
||||||
|
|
@ -237,7 +237,7 @@ class MockOfferDataSource implements OfferDataSource {
|
||||||
name: "رفیقبازی",
|
name: "رفیقبازی",
|
||||||
description: "یه مهمونی دو نفره، یه تخفیف دوتایی؛ پس رفیقتو بیار و تخفیفتو ببر. دوستای بیشتر، تخفیف بیشتر.",
|
description: "یه مهمونی دو نفره، یه تخفیف دوتایی؛ پس رفیقتو بیار و تخفیفتو ببر. دوستای بیشتر، تخفیف بیشتر.",
|
||||||
),
|
),
|
||||||
comments: [ // <-- بخش نظرات اضافه شد
|
comments: [
|
||||||
CommentModel(
|
CommentModel(
|
||||||
id: 'c1',
|
id: 'c1',
|
||||||
userName: 'سارا رضایی',
|
userName: 'سارا رضایی',
|
||||||
|
|
@ -320,7 +320,7 @@ class MockOfferDataSource implements OfferDataSource {
|
||||||
name: "رفیقبازی",
|
name: "رفیقبازی",
|
||||||
description: "یه مهمونی دو نفره، یه تخفیف دوتایی؛ پس رفیقتو بیار و تخفیفتو ببر. دوستای بیشتر، تخفیف بیشتر.",
|
description: "یه مهمونی دو نفره، یه تخفیف دوتایی؛ پس رفیقتو بیار و تخفیفتو ببر. دوستای بیشتر، تخفیف بیشتر.",
|
||||||
),
|
),
|
||||||
comments: [ // <-- بخش نظرات اضافه شد
|
comments: [
|
||||||
CommentModel(
|
CommentModel(
|
||||||
id: 'c1',
|
id: 'c1',
|
||||||
userName: 'سارا رضایی',
|
userName: 'سارا رضایی',
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ class OfferRepository {
|
||||||
return filteredOffers;
|
return filteredOffers;
|
||||||
}
|
}
|
||||||
Future<OfferModel?> fetchOfferById(String id) async {
|
Future<OfferModel?> fetchOfferById(String id) async {
|
||||||
// در آینده این متد میتواند یک درخواست API برای گرفتن اطلاعات یک محصول خاص ارسال کند
|
|
||||||
return _offerDataSource.getOfferById(id);
|
return _offerDataSource.getOfferById(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:proxibuy/core/gen/assets.gen.dart';
|
import 'package:proxibuy/core/gen/assets.gen.dart';
|
||||||
|
|
||||||
class CategoryEntity {
|
class CategoryEntity {
|
||||||
final int id;
|
final String id;
|
||||||
final String name;
|
final String name;
|
||||||
final SvgGenImage icon;
|
final SvgGenImage icon;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
// lib/domain/entities/onboarding_entity.dart
|
|
||||||
class OnboardingEntity {
|
class OnboardingEntity {
|
||||||
final String imagePath;
|
final String imagePath;
|
||||||
final String title;
|
final String title;
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,6 @@ class AddPhotoCubit extends Cubit<AddPhotoState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ متد را طوری تغییر دادیم که منبع عکس را به عنوان ورودی بگیرد
|
|
||||||
Future<void> pickImage(ImageSource source) async {
|
Future<void> pickImage(ImageSource source) async {
|
||||||
final currentState = state;
|
final currentState = state;
|
||||||
if (currentState is AddPhotoLoaded) {
|
if (currentState is AddPhotoLoaded) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
// File generated by FlutterFire CLI.
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
|
||||||
|
import 'package:flutter/foundation.dart'
|
||||||
|
show defaultTargetPlatform, kIsWeb, TargetPlatform;
|
||||||
|
|
||||||
|
/// Default [FirebaseOptions] for use with your Firebase apps.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// import 'firebase_options.dart';
|
||||||
|
/// // ...
|
||||||
|
/// await Firebase.initializeApp(
|
||||||
|
/// options: DefaultFirebaseOptions.currentPlatform,
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
class DefaultFirebaseOptions {
|
||||||
|
static FirebaseOptions get currentPlatform {
|
||||||
|
if (kIsWeb) {
|
||||||
|
throw UnsupportedError(
|
||||||
|
'DefaultFirebaseOptions have not been configured for web - '
|
||||||
|
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
switch (defaultTargetPlatform) {
|
||||||
|
case TargetPlatform.android:
|
||||||
|
return android;
|
||||||
|
case TargetPlatform.iOS:
|
||||||
|
throw UnsupportedError(
|
||||||
|
'DefaultFirebaseOptions have not been configured for ios - '
|
||||||
|
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||||
|
);
|
||||||
|
case TargetPlatform.macOS:
|
||||||
|
throw UnsupportedError(
|
||||||
|
'DefaultFirebaseOptions have not been configured for macos - '
|
||||||
|
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||||
|
);
|
||||||
|
case TargetPlatform.windows:
|
||||||
|
throw UnsupportedError(
|
||||||
|
'DefaultFirebaseOptions have not been configured for windows - '
|
||||||
|
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||||
|
);
|
||||||
|
case TargetPlatform.linux:
|
||||||
|
throw UnsupportedError(
|
||||||
|
'DefaultFirebaseOptions have not been configured for linux - '
|
||||||
|
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
throw UnsupportedError(
|
||||||
|
'DefaultFirebaseOptions are not supported for this platform.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const FirebaseOptions android = FirebaseOptions(
|
||||||
|
apiKey: 'AIzaSyCMGweIbZBsFNXabKRJJJcVLPwcmqhuSwg',
|
||||||
|
appId: '1:800272350428:android:6cbc063052753bc9c78819',
|
||||||
|
messagingSenderId: '800272350428',
|
||||||
|
projectId: 'proxibuy-3b5e0',
|
||||||
|
storageBucket: 'proxibuy-3b5e0.firebasestorage.app',
|
||||||
|
);
|
||||||
|
}
|
||||||
227
lib/main.dart
227
lib/main.dart
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:firebase_core/firebase_core.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_animate/flutter_animate.dart';
|
import 'package:flutter_animate/flutter_animate.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
@ -5,17 +6,23 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
import 'package:proxibuy/data/models/datasources/offer_data_source.dart';
|
import 'package:proxibuy/data/models/datasources/offer_data_source.dart';
|
||||||
import 'package:proxibuy/data/repositories/offer_repository.dart';
|
import 'package:proxibuy/data/repositories/offer_repository.dart';
|
||||||
|
import 'package:proxibuy/firebase_options.dart';
|
||||||
import 'package:proxibuy/presentation/auth/bloc/auth_bloc.dart';
|
import 'package:proxibuy/presentation/auth/bloc/auth_bloc.dart';
|
||||||
import 'package:proxibuy/presentation/notification_preferences/bloc/notification_preferences_bloc.dart';
|
import 'package:proxibuy/presentation/notification_preferences/bloc/notification_preferences_bloc.dart';
|
||||||
import 'package:proxibuy/presentation/notification_preferences/bloc/notification_preferences_event.dart';
|
|
||||||
import 'package:proxibuy/presentation/offer/bloc/offer_bloc.dart';
|
import 'package:proxibuy/presentation/offer/bloc/offer_bloc.dart';
|
||||||
|
import 'package:proxibuy/presentation/pages/offers_page.dart';
|
||||||
|
import 'package:proxibuy/presentation/pages/otp_page.dart';
|
||||||
|
import 'package:proxibuy/presentation/pages/user_info_page.dart';
|
||||||
import 'package:proxibuy/presentation/reservation/cubit/reservation_cubit.dart';
|
import 'package:proxibuy/presentation/reservation/cubit/reservation_cubit.dart';
|
||||||
|
// import 'package:proxibuy/services/mqtt_service.dart';
|
||||||
import 'core/config/app_colors.dart';
|
import 'core/config/app_colors.dart';
|
||||||
import 'presentation/pages/onboarding_page.dart';
|
import 'presentation/pages/onboarding_page.dart';
|
||||||
|
import 'package:proxibuy/presentation/pages/splash_screen.dart'; // <--- ایمپورت جدید
|
||||||
|
|
||||||
void main() {
|
void main() async {
|
||||||
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
|
||||||
Animate.restartOnHotReload = true;
|
Animate.restartOnHotReload = true;
|
||||||
|
|
||||||
runApp(const MyApp());
|
runApp(const MyApp());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -24,118 +31,136 @@ class MyApp extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MultiRepositoryProvider(
|
return MultiBlocProvider(
|
||||||
providers: [
|
providers: [
|
||||||
|
BlocProvider<AuthBloc>(
|
||||||
|
create: (context) => AuthBloc()..add(CheckAuthStatusEvent()),
|
||||||
|
),
|
||||||
RepositoryProvider<OfferRepository>(
|
RepositoryProvider<OfferRepository>(
|
||||||
create:
|
create: (context) =>
|
||||||
(context) =>
|
OfferRepository(offerDataSource: MockOfferDataSource()),
|
||||||
OfferRepository(offerDataSource: MockOfferDataSource()),
|
),
|
||||||
|
BlocProvider<ReservationCubit>(
|
||||||
|
create: (context) => ReservationCubit(),
|
||||||
|
),
|
||||||
|
BlocProvider<OffersBloc>(
|
||||||
|
create: (context) => OffersBloc(
|
||||||
|
offerRepository: context.read<OfferRepository>(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
BlocProvider<NotificationPreferencesBloc>(
|
||||||
|
create: (context) => NotificationPreferencesBloc(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
child: MultiBlocProvider(
|
child: MaterialApp(
|
||||||
providers: [
|
title: 'Proxibuy',
|
||||||
BlocProvider<AuthBloc>(create: (context) => AuthBloc()),
|
debugShowCheckedModeBanner: false,
|
||||||
BlocProvider<NotificationPreferencesBloc>(
|
home: const SplashScreen(), // <--- استفاده از صفحه اسپلش
|
||||||
create:
|
localizationsDelegates: const [
|
||||||
(context) =>
|
GlobalMaterialLocalizations.delegate,
|
||||||
NotificationPreferencesBloc()..add(LoadCategories()),
|
GlobalWidgetsLocalizations.delegate,
|
||||||
),
|
GlobalCupertinoLocalizations.delegate,
|
||||||
BlocProvider<OffersBloc>(
|
|
||||||
create:
|
|
||||||
(context) => OffersBloc(
|
|
||||||
offerRepository: context.read<OfferRepository>(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
BlocProvider<ReservationCubit>(
|
|
||||||
create: (context) => ReservationCubit(),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
child: MaterialApp(
|
supportedLocales: const [Locale('fa')],
|
||||||
title: 'Proxibuy',
|
locale: const Locale('fa'),
|
||||||
debugShowCheckedModeBanner: false,
|
theme: ThemeData(
|
||||||
|
fontFamily: 'Dana',
|
||||||
localizationsDelegates: const [
|
scaffoldBackgroundColor: Colors.white,
|
||||||
GlobalMaterialLocalizations.delegate,
|
colorScheme: ColorScheme.fromSeed(
|
||||||
GlobalWidgetsLocalizations.delegate,
|
seedColor: AppColors.primary,
|
||||||
GlobalCupertinoLocalizations.delegate,
|
primary: AppColors.primary,
|
||||||
],
|
surface: Colors.white,
|
||||||
supportedLocales: const [Locale('fa')],
|
),
|
||||||
locale: const Locale('fa'),
|
inputDecorationTheme: InputDecorationTheme(
|
||||||
|
filled: true,
|
||||||
theme: ThemeData(
|
fillColor: Colors.white,
|
||||||
fontFamily: 'Dana',
|
floatingLabelBehavior: FloatingLabelBehavior.always,
|
||||||
scaffoldBackgroundColor: Colors.white,
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
colorScheme: ColorScheme.fromSeed(
|
vertical: 18,
|
||||||
seedColor: AppColors.primary,
|
horizontal: 20,
|
||||||
primary: AppColors.primary,
|
|
||||||
surface: Colors.white,
|
|
||||||
),
|
),
|
||||||
appBarTheme: const AppBarTheme(
|
border: OutlineInputBorder(
|
||||||
backgroundColor: AppColors.primary,
|
borderRadius: BorderRadius.circular(10),
|
||||||
foregroundColor: Colors.white,
|
borderSide: const BorderSide(color: AppColors.border),
|
||||||
elevation: 0,
|
|
||||||
),
|
),
|
||||||
inputDecorationTheme: InputDecorationTheme(
|
enabledBorder: OutlineInputBorder(
|
||||||
filled: true,
|
borderRadius: BorderRadius.circular(10),
|
||||||
fillColor: Colors.white,
|
borderSide: const BorderSide(color: AppColors.border),
|
||||||
floatingLabelBehavior: FloatingLabelBehavior.always,
|
|
||||||
contentPadding: const EdgeInsets.symmetric(
|
|
||||||
vertical: 18,
|
|
||||||
horizontal: 20,
|
|
||||||
),
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
borderSide: const BorderSide(color: AppColors.border),
|
|
||||||
),
|
|
||||||
enabledBorder: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
borderSide: const BorderSide(color: AppColors.border),
|
|
||||||
),
|
|
||||||
focusedBorder: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
borderSide: const BorderSide(
|
|
||||||
color: AppColors.primary,
|
|
||||||
width: 2,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
labelStyle: const TextStyle(color: Colors.black),
|
|
||||||
// ignore: deprecated_member_use
|
|
||||||
hintStyle: TextStyle(color: Colors.black.withOpacity(0.8)),
|
|
||||||
),
|
),
|
||||||
outlinedButtonTheme: OutlinedButtonThemeData(
|
focusedBorder: OutlineInputBorder(
|
||||||
style: OutlinedButton.styleFrom(
|
borderRadius: BorderRadius.circular(10),
|
||||||
foregroundColor: Colors.black, // رنگ متن دکمه Outlined
|
borderSide: const BorderSide(color: AppColors.primary, width: 2),
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
|
||||||
side: const BorderSide(color: Colors.grey),
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(50),
|
|
||||||
),
|
|
||||||
textStyle: const TextStyle(
|
|
||||||
fontFamily: 'Dana',
|
|
||||||
fontSize: 16,
|
|
||||||
color: Colors.black,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
labelStyle: const TextStyle(color: Colors.black),
|
||||||
style: ElevatedButton.styleFrom(
|
// ignore: deprecated_member_use
|
||||||
backgroundColor: AppColors.button,
|
hintStyle: TextStyle(color: Colors.black.withOpacity(0.8)),
|
||||||
foregroundColor: Colors.white,
|
),
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
outlinedButtonTheme: OutlinedButtonThemeData(
|
||||||
shape: RoundedRectangleBorder(
|
style: OutlinedButton.styleFrom(
|
||||||
borderRadius: BorderRadius.circular(50),
|
foregroundColor: Colors.black,
|
||||||
),
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
textStyle: const TextStyle(
|
side: const BorderSide(color: Colors.grey),
|
||||||
fontFamily: 'Dana',
|
shape: RoundedRectangleBorder(
|
||||||
fontSize: 16,
|
borderRadius: BorderRadius.circular(50),
|
||||||
fontWeight: FontWeight.bold,
|
),
|
||||||
),
|
textStyle: const TextStyle(
|
||||||
|
fontFamily: 'Dana',
|
||||||
|
fontSize: 16,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: AppColors.button,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(50),
|
||||||
|
),
|
||||||
|
textStyle: const TextStyle(
|
||||||
|
fontFamily: 'Dana',
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
home: const OnboardingPage(),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class AppRouter extends StatelessWidget {
|
||||||
|
const AppRouter({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final authState = context.select((AuthBloc bloc) => bloc.state);
|
||||||
|
|
||||||
|
if (authState is AuthCodeSentSuccess) {
|
||||||
|
return OtpPage(
|
||||||
|
phoneNumber: "+${authState.countryCode}${authState.phone}",
|
||||||
|
phone: authState.phone,
|
||||||
|
countryCode: authState.countryCode,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authState is AuthLoading) {
|
||||||
|
final currentState = context.read<AuthBloc>().state;
|
||||||
|
if (currentState is! AuthCodeSentSuccess) {
|
||||||
|
return const Scaffold(body: Center(child: CircularProgressIndicator()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authState is AuthSuccess) {
|
||||||
|
return const OffersPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authState is AuthNeedsInfo) {
|
||||||
|
return const UserInfoPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
return const OnboardingPage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,43 +1,116 @@
|
||||||
// ignore: depend_on_referenced_packages
|
|
||||||
import 'package:bloc/bloc.dart';
|
import 'package:bloc/bloc.dart';
|
||||||
// ignore: depend_on_referenced_packages
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
import 'dart:async';
|
import 'package:proxibuy/core/config/api_config.dart';
|
||||||
|
|
||||||
part 'auth_event.dart';
|
part 'auth_event.dart';
|
||||||
part 'auth_state.dart';
|
part 'auth_state.dart';
|
||||||
|
|
||||||
class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||||
|
late final Dio _dio;
|
||||||
|
final _storage = const FlutterSecureStorage();
|
||||||
|
|
||||||
AuthBloc() : super(AuthInitial()) {
|
AuthBloc() : super(AuthInitial()) {
|
||||||
on<SendOTPEvent>((event, emit) async {
|
_dio = Dio();
|
||||||
emit(AuthLoading());
|
_dio.interceptors.add(
|
||||||
await Future.delayed(const Duration(seconds: 1));
|
LogInterceptor(
|
||||||
if (event.phoneNumber.isNotEmpty) {
|
requestHeader: true,
|
||||||
emit(AuthCodeSentSuccess());
|
requestBody: true,
|
||||||
} else {
|
responseBody: true,
|
||||||
emit(AuthFailure('شماره موبایل معتبر نیست.'));
|
error: true,
|
||||||
}
|
),
|
||||||
});
|
);
|
||||||
|
|
||||||
on<VerifyOTPEvent>((event, emit) async {
|
on<CheckAuthStatusEvent>(_onCheckAuthStatus);
|
||||||
emit(AuthLoading());
|
on<SendOTPEvent>(_onSendOTP);
|
||||||
await Future.delayed(const Duration(seconds: 1));
|
on<VerifyOTPEvent>(_onVerifyOTP);
|
||||||
if (event.otp == '12345') {
|
on<UpdateUserInfoEvent>(_onUpdateUserInfo);
|
||||||
emit(AuthVerified());
|
on<LogoutEvent>(_onLogout);
|
||||||
} else {
|
|
||||||
emit(AuthFailure('کد تایید صحیح نمیباشد.'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
on<SaveUserInfoEvent>((event, emit) async {
|
|
||||||
emit(AuthLoading());
|
|
||||||
await Future.delayed(const Duration(milliseconds: 500));
|
|
||||||
|
|
||||||
if (event.name.trim().isEmpty) {
|
|
||||||
emit(AuthFailure('لطفاً نام خود را وارد کنید.'));
|
|
||||||
} else {
|
|
||||||
emit(UserInfoSaved());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
Future<void> _onCheckAuthStatus(
|
||||||
|
CheckAuthStatusEvent event, Emitter<AuthState> emit) async {
|
||||||
|
final token = await _storage.read(key: 'accessToken');
|
||||||
|
if (token != null && token.isNotEmpty) {
|
||||||
|
emit(AuthSuccess());
|
||||||
|
} else {
|
||||||
|
emit(AuthInitial());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onSendOTP(SendOTPEvent event, Emitter<AuthState> emit) async {
|
||||||
|
emit(AuthLoading());
|
||||||
|
try {
|
||||||
|
final response = await _dio.post(
|
||||||
|
ApiConfig.baseUrl + ApiConfig.sendCode,
|
||||||
|
data: {'Phone': event.phoneNumber, 'Code': event.countryCode},
|
||||||
|
);
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
emit(AuthCodeSentSuccess(
|
||||||
|
phone: event.phoneNumber,
|
||||||
|
countryCode: event.countryCode,
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
emit(AuthFailure(response.data['message'] ?? 'خطایی رخ داد'));
|
||||||
|
}
|
||||||
|
} on DioException catch (e) {
|
||||||
|
emit(AuthFailure(e.response?.data['message'] ?? 'خطا در ارتباط با سرور'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onVerifyOTP(VerifyOTPEvent event, Emitter<AuthState> emit) async {
|
||||||
|
emit(AuthLoading());
|
||||||
|
try {
|
||||||
|
final response = await _dio.post(
|
||||||
|
ApiConfig.baseUrl + ApiConfig.verifyCode,
|
||||||
|
data: {
|
||||||
|
'Phone': event.phoneNumber,
|
||||||
|
'Code': event.countryCode,
|
||||||
|
'OTP': event.otp,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
final accessToken = response.data['data']['accessToken'];
|
||||||
|
final refreshToken = response.data['data']['refreshToken'];
|
||||||
|
await _storage.write(key: 'accessToken', value: accessToken);
|
||||||
|
await _storage.write(key: 'refreshToken', value: refreshToken);
|
||||||
|
emit(AuthNeedsInfo());
|
||||||
|
} else {
|
||||||
|
emit(AuthFailure(response.data['message'] ?? 'کد صحیح نیست'));
|
||||||
|
}
|
||||||
|
} on DioException catch (e) {
|
||||||
|
emit(AuthFailure(e.response?.data['message'] ?? 'خطایی در سرور رخ داد'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onUpdateUserInfo(
|
||||||
|
UpdateUserInfoEvent event, Emitter<AuthState> emit) async {
|
||||||
|
emit(AuthLoading());
|
||||||
|
try {
|
||||||
|
final token = await _storage.read(key: 'accessToken');
|
||||||
|
if (token == null) {
|
||||||
|
emit(const AuthFailure("شما وارد نشدهاید."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final response = await _dio.post(
|
||||||
|
ApiConfig.baseUrl + ApiConfig.updateUser,
|
||||||
|
data: {'Name': event.name, 'Gender': event.gender},
|
||||||
|
options: Options(headers: {'Authorization': 'Bearer $token'}),
|
||||||
|
);
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
emit(AuthSuccess());
|
||||||
|
} else {
|
||||||
|
emit(AuthFailure(response.data['message'] ?? 'خطا در ثبت اطلاعات'));
|
||||||
|
}
|
||||||
|
} on DioException catch (e) {
|
||||||
|
emit(AuthFailure(e.response?.data['message'] ?? 'خطا در ارتباط با سرور'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onLogout(LogoutEvent event, Emitter<AuthState> emit) async {
|
||||||
|
await _storage.deleteAll();
|
||||||
|
emit(AuthInitial());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
part of 'auth_bloc.dart';
|
part of 'auth_bloc.dart';
|
||||||
|
|
||||||
@immutable
|
@immutable
|
||||||
|
|
@ -6,19 +5,24 @@ abstract class AuthEvent {}
|
||||||
|
|
||||||
class SendOTPEvent extends AuthEvent {
|
class SendOTPEvent extends AuthEvent {
|
||||||
final String phoneNumber;
|
final String phoneNumber;
|
||||||
|
final String countryCode;
|
||||||
SendOTPEvent({required this.phoneNumber});
|
SendOTPEvent({required this.phoneNumber, required this.countryCode});
|
||||||
}
|
}
|
||||||
|
|
||||||
class VerifyOTPEvent extends AuthEvent {
|
class VerifyOTPEvent extends AuthEvent {
|
||||||
|
final String phoneNumber;
|
||||||
|
final String countryCode;
|
||||||
final String otp;
|
final String otp;
|
||||||
|
VerifyOTPEvent({required this.phoneNumber, required this.countryCode, required this.otp});
|
||||||
VerifyOTPEvent({required this.otp});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class SaveUserInfoEvent extends AuthEvent {
|
class CheckAuthStatusEvent extends AuthEvent {}
|
||||||
|
|
||||||
|
class LogoutEvent extends AuthEvent {}
|
||||||
|
|
||||||
|
class UpdateUserInfoEvent extends AuthEvent {
|
||||||
final String name;
|
final String name;
|
||||||
final String gender;
|
final String gender;
|
||||||
|
|
||||||
SaveUserInfoEvent({required this.name, required this.gender});
|
UpdateUserInfoEvent({required this.name, required this.gender});
|
||||||
}
|
}
|
||||||
|
|
@ -1,21 +1,34 @@
|
||||||
|
|
||||||
part of 'auth_bloc.dart';
|
part of 'auth_bloc.dart';
|
||||||
|
|
||||||
@immutable
|
@immutable
|
||||||
abstract class AuthState {}
|
abstract class AuthState extends Equatable {
|
||||||
|
const AuthState();
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
class AuthInitial extends AuthState {}
|
class AuthInitial extends AuthState {}
|
||||||
|
|
||||||
class AuthLoading extends AuthState {}
|
class AuthLoading extends AuthState {}
|
||||||
|
|
||||||
class AuthCodeSentSuccess extends AuthState {}
|
class AuthCodeSentSuccess extends AuthState {
|
||||||
|
final String phone;
|
||||||
|
final String countryCode;
|
||||||
|
|
||||||
class AuthVerified extends AuthState {}
|
const AuthCodeSentSuccess({required this.phone, required this.countryCode});
|
||||||
|
|
||||||
class UserInfoSaved extends AuthState {}
|
@override
|
||||||
|
List<Object?> get props => [phone, countryCode];
|
||||||
|
}
|
||||||
|
|
||||||
|
class AuthNeedsInfo extends AuthState {}
|
||||||
|
|
||||||
|
class AuthSuccess extends AuthState {}
|
||||||
|
|
||||||
class AuthFailure extends AuthState {
|
class AuthFailure extends AuthState {
|
||||||
final String message;
|
final String message;
|
||||||
|
const AuthFailure(this.message);
|
||||||
|
|
||||||
AuthFailure(this.message);
|
@override
|
||||||
|
List<Object?> get props => [message];
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
|
import 'package:proxibuy/core/config/api_config.dart';
|
||||||
import 'package:proxibuy/core/gen/assets.gen.dart';
|
import 'package:proxibuy/core/gen/assets.gen.dart';
|
||||||
import 'package:proxibuy/domain/entities/category_entity.dart';
|
import 'package:proxibuy/domain/entities/category_entity.dart';
|
||||||
import 'notification_preferences_event.dart';
|
import 'notification_preferences_event.dart';
|
||||||
|
|
@ -6,31 +9,37 @@ import 'notification_preferences_state.dart';
|
||||||
|
|
||||||
class NotificationPreferencesBloc
|
class NotificationPreferencesBloc
|
||||||
extends Bloc<NotificationPreferencesEvent, NotificationPreferencesState> {
|
extends Bloc<NotificationPreferencesEvent, NotificationPreferencesState> {
|
||||||
|
final Dio _dio = Dio();
|
||||||
|
final FlutterSecureStorage _storage = const FlutterSecureStorage();
|
||||||
|
|
||||||
NotificationPreferencesBloc() : super(const NotificationPreferencesState()) {
|
NotificationPreferencesBloc() : super(const NotificationPreferencesState()) {
|
||||||
on<LoadCategories>(_onLoadCategories);
|
on<LoadCategories>(_onLoadCategories);
|
||||||
on<ToggleCategorySelection>(_onToggleCategorySelection);
|
on<ToggleCategorySelection>(_onToggleCategorySelection);
|
||||||
|
on<SubmitPreferences>(_onSubmitPreferences);
|
||||||
|
|
||||||
|
add(LoadCategories());
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onLoadCategories(
|
void _onLoadCategories(
|
||||||
LoadCategories event, Emitter<NotificationPreferencesState> emit) {
|
LoadCategories event, Emitter<NotificationPreferencesState> emit) {
|
||||||
final categories = [
|
final categories = [
|
||||||
CategoryEntity(id: 1, name: 'تریا', icon: Assets.icons.teria),
|
CategoryEntity(id: "e33dd7f9-5b20-4273-8eea-59da6ca5f206", name: 'لوازم دیجیتال', icon: Assets.icons.digital),
|
||||||
CategoryEntity(id: 2, name: 'پوشاک', icon: Assets.icons.pooshak),
|
CategoryEntity(id: "b73a868a-a2d2-4d96-8fd4-615327ed9629", name: 'کافیشاپ', icon: Assets.icons.coffeeshop),
|
||||||
CategoryEntity(id: 3, name: 'فستفود', icon: Assets.icons.fastfood),
|
CategoryEntity(id: "b5881239-bfd5-4c27-967a-187316a7e0b7", name: 'رستوران', icon: Assets.icons.resturan),
|
||||||
CategoryEntity(id: 4, name: 'کافیشاپ', icon: Assets.icons.coffeeshop),
|
CategoryEntity(id: "6803b940-3e19-48cd-9190-28d9f25421ff", name: 'فستفود', icon: Assets.icons.fastfood),
|
||||||
CategoryEntity(id: 5, name: 'رستوران', icon: Assets.icons.resturan),
|
CategoryEntity(id: "71e371f8-a47a-4a58-aee6-4ed0f26bf29b", name: 'پوشاک', icon: Assets.icons.pooshak),
|
||||||
CategoryEntity(id: 6, name: 'لوازم دیجیتال', icon: Assets.icons.digital),
|
CategoryEntity(id: "42acff41-1165-4e62-89b9-58db7329ec3a", name: 'تریا', icon: Assets.icons.teria),
|
||||||
CategoryEntity(id: 7, name: 'کیفوکفش', icon: Assets.icons.kafsh),
|
CategoryEntity(id: "2f38918c-5566-4aec-a0a9-2c7c48b1e878", name: 'کیفوکفش', icon: Assets.icons.kafsh),
|
||||||
CategoryEntity(id: 8, name: 'سینما', icon: Assets.icons.cinama),
|
CategoryEntity(id: "52c51010-3a63-4264-a350-e011c889f3dd", name: 'سینما', icon: Assets.icons.cinama),
|
||||||
CategoryEntity(id: 9, name: 'لوازم آرایشی', icon: Assets.icons.arayesh),
|
CategoryEntity(id: "34185954-f79f-4b9e-8eb2-1702679c40a0", name: 'لوازم آرایشی', icon: Assets.icons.arayesh),
|
||||||
CategoryEntity(id: 10, name: 'طلا و زیورآلات', icon: Assets.icons.tala),
|
CategoryEntity(id: "e4517b0c-aacf-4758-94bd-85f45062980f", name: 'طلا و زیورآلات', icon: Assets.icons.tala),
|
||||||
];
|
];
|
||||||
emit(state.copyWith(categories: categories));
|
emit(state.copyWith(categories: categories));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onToggleCategorySelection(ToggleCategorySelection event,
|
void _onToggleCategorySelection(
|
||||||
Emitter<NotificationPreferencesState> emit) {
|
ToggleCategorySelection event, Emitter<NotificationPreferencesState> emit) {
|
||||||
final selectedIds = Set<int>.from(state.selectedCategoryIds);
|
final selectedIds = Set<String>.from(state.selectedCategoryIds);
|
||||||
if (selectedIds.contains(event.categoryId)) {
|
if (selectedIds.contains(event.categoryId)) {
|
||||||
selectedIds.remove(event.categoryId);
|
selectedIds.remove(event.categoryId);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -38,4 +47,34 @@ class NotificationPreferencesBloc
|
||||||
}
|
}
|
||||||
emit(state.copyWith(selectedCategoryIds: selectedIds));
|
emit(state.copyWith(selectedCategoryIds: selectedIds));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _onSubmitPreferences(
|
||||||
|
SubmitPreferences event, Emitter<NotificationPreferencesState> emit) async {
|
||||||
|
emit(state.copyWith(isLoading: true, errorMessage: null, submissionSuccess: false));
|
||||||
|
try {
|
||||||
|
final token = await _storage.read(key: 'accessToken');
|
||||||
|
if (token == null) {
|
||||||
|
emit(state.copyWith(isLoading: false, errorMessage: "شما وارد نشدهاید."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final response = await _dio.post(
|
||||||
|
ApiConfig.baseUrl + ApiConfig.updateCategories,
|
||||||
|
data: {"FCategory": state.selectedCategoryIds.toList()},
|
||||||
|
options: Options(headers: {'Authorization': 'Bearer $token'}),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
emit(state.copyWith(isLoading: false, submissionSuccess: true));
|
||||||
|
} else {
|
||||||
|
emit(state.copyWith(
|
||||||
|
isLoading: false,
|
||||||
|
errorMessage: response.data['message'] ?? 'خطا در ثبت اطلاعات'));
|
||||||
|
}
|
||||||
|
} on DioException catch (e) {
|
||||||
|
emit(state.copyWith(
|
||||||
|
isLoading: false,
|
||||||
|
errorMessage: e.response?.data['message'] ?? 'خطا در ارتباط با سرور'));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -10,10 +10,12 @@ abstract class NotificationPreferencesEvent extends Equatable {
|
||||||
class LoadCategories extends NotificationPreferencesEvent {}
|
class LoadCategories extends NotificationPreferencesEvent {}
|
||||||
|
|
||||||
class ToggleCategorySelection extends NotificationPreferencesEvent {
|
class ToggleCategorySelection extends NotificationPreferencesEvent {
|
||||||
final int categoryId;
|
final String categoryId;
|
||||||
|
|
||||||
const ToggleCategorySelection(this.categoryId);
|
const ToggleCategorySelection(this.categoryId);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object> get props => [categoryId];
|
List<Object> get props => [categoryId];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class SubmitPreferences extends NotificationPreferencesEvent {}
|
||||||
|
|
@ -1,27 +1,43 @@
|
||||||
|
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:proxibuy/domain/entities/category_entity.dart';
|
import 'package:proxibuy/domain/entities/category_entity.dart';
|
||||||
|
|
||||||
class NotificationPreferencesState extends Equatable {
|
class NotificationPreferencesState extends Equatable {
|
||||||
final List<CategoryEntity> categories;
|
final List<CategoryEntity> categories;
|
||||||
final Set<int> selectedCategoryIds;
|
final Set<String> selectedCategoryIds;
|
||||||
|
final bool isLoading;
|
||||||
|
final String? errorMessage;
|
||||||
|
final bool submissionSuccess;
|
||||||
|
|
||||||
const NotificationPreferencesState({
|
const NotificationPreferencesState({
|
||||||
this.categories = const [],
|
this.categories = const [],
|
||||||
this.selectedCategoryIds = const {},
|
this.selectedCategoryIds = const {},
|
||||||
|
this.isLoading = false,
|
||||||
|
this.errorMessage,
|
||||||
|
this.submissionSuccess = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
NotificationPreferencesState copyWith({
|
NotificationPreferencesState copyWith({
|
||||||
List<CategoryEntity>? categories,
|
List<CategoryEntity>? categories,
|
||||||
Set<int>? selectedCategoryIds,
|
Set<String>? selectedCategoryIds,
|
||||||
|
bool? isLoading,
|
||||||
|
String? errorMessage,
|
||||||
|
bool? submissionSuccess,
|
||||||
}) {
|
}) {
|
||||||
return NotificationPreferencesState(
|
return NotificationPreferencesState(
|
||||||
categories: categories ?? this.categories,
|
categories: categories ?? this.categories,
|
||||||
selectedCategoryIds: selectedCategoryIds ?? this.selectedCategoryIds,
|
selectedCategoryIds: selectedCategoryIds ?? this.selectedCategoryIds,
|
||||||
|
isLoading: isLoading ?? this.isLoading,
|
||||||
|
errorMessage: errorMessage,
|
||||||
|
submissionSuccess: submissionSuccess ?? this.submissionSuccess,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object> get props => [categories, selectedCategoryIds];
|
List<Object?> get props => [
|
||||||
|
categories,
|
||||||
|
selectedCategoryIds,
|
||||||
|
isLoading,
|
||||||
|
errorMessage,
|
||||||
|
submissionSuccess,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
@ -9,7 +9,6 @@ abstract class OffersEvent extends Equatable {
|
||||||
}
|
}
|
||||||
|
|
||||||
class OffersFetchRequested extends OffersEvent {
|
class OffersFetchRequested extends OffersEvent {
|
||||||
// ✅ ۱. یک فیلد برای نگهداری دستهبندیها اضافه کنید
|
|
||||||
final List<String> selectedCategories;
|
final List<String> selectedCategories;
|
||||||
|
|
||||||
const OffersFetchRequested({required this.selectedCategories});
|
const OffersFetchRequested({required this.selectedCategories});
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:proxibuy/data/models/offer_model.dart';
|
import 'package:proxibuy/data/models/offer_model.dart';
|
||||||
import 'package:proxibuy/presentation/offer/bloc/widgets/offer_card.dart';
|
import 'package:proxibuy/presentation/offer/bloc/widgets/offer_card.dart';
|
||||||
import 'package:proxibuy/presentation/pages/product_detail_page.dart'; // <-- این خط را اضافه کن
|
import 'package:proxibuy/presentation/pages/product_detail_page.dart';
|
||||||
|
|
||||||
class CategoryOffersRow extends StatelessWidget {
|
class CategoryOffersRow extends StatelessWidget {
|
||||||
final String categoryTitle;
|
final String categoryTitle;
|
||||||
|
|
|
||||||
|
|
@ -118,23 +118,19 @@ class _OfferCardState extends State<OfferCard> {
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
|
|
||||||
// ================== شروع تغییرات ==================
|
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
SvgPicture.asset(Assets.icons.location.path),
|
SvgPicture.asset(Assets.icons.location.path),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
// ویجت Flexible باعث میشود که آدرس، فضای باقیمانده را پر کند
|
|
||||||
// و در صورت طولانی بودن، کوتاه شود.
|
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Text(
|
child: Text(
|
||||||
widget.offer.address,
|
widget.offer.address,
|
||||||
style: textTheme.bodySmall,
|
style: textTheme.bodySmall,
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis, // نمایش سه نقطه در انتهای متن طولانی
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
// این بخش همیشه به طور کامل نمایش داده میشود
|
|
||||||
Text(
|
Text(
|
||||||
'(${widget.offer.distanceInMeters.toString()}متر تا تخفیف)',
|
'(${widget.offer.distanceInMeters.toString()}متر تا تخفیف)',
|
||||||
style: textTheme.bodySmall,
|
style: textTheme.bodySmall,
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@ class AddPhotoScreen extends StatelessWidget {
|
||||||
required this.offer,
|
required this.offer,
|
||||||
});
|
});
|
||||||
|
|
||||||
// ✅ متد جدید برای نمایش منوی انتخاب
|
|
||||||
void _showImageSourceActionSheet(BuildContext context) {
|
void _showImageSourceActionSheet(BuildContext context) {
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
|
|
@ -147,7 +146,6 @@ class AddPhotoScreen extends StatelessWidget {
|
||||||
context.read<ReservationCubit>().isProductReserved(productId);
|
context.read<ReservationCubit>().isProductReserved(productId);
|
||||||
|
|
||||||
if (isReserved) {
|
if (isReserved) {
|
||||||
// ✅ به جای فراخوانی مستقیم، منوی انتخاب را نمایش میدهیم
|
|
||||||
_showImageSourceActionSheet(context);
|
_showImageSourceActionSheet(context);
|
||||||
} else {
|
} else {
|
||||||
_showReservationPopup(context);
|
_showReservationPopup(context);
|
||||||
|
|
@ -177,7 +175,6 @@ class AddPhotoScreen extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
PreferredSizeWidget _buildCustomAppBar(BuildContext context) {
|
PreferredSizeWidget _buildCustomAppBar(BuildContext context) {
|
||||||
// ... این متد بدون تغییر باقی میماند
|
|
||||||
return PreferredSize(
|
return PreferredSize(
|
||||||
preferredSize: const Size.fromHeight(70.0),
|
preferredSize: const Size.fromHeight(70.0),
|
||||||
child: Container(
|
child: Container(
|
||||||
|
|
@ -222,7 +219,6 @@ class AddPhotoScreen extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showReservationPopup(BuildContext context) {
|
void _showReservationPopup(BuildContext context) {
|
||||||
// ... این متد بدون تغییر باقی میماند
|
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
barrierDismissible: false,
|
barrierDismissible: false,
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,8 @@
|
||||||
// lib/presentation/pages/login_page.dart
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:country_picker/country_picker.dart';
|
import 'package:country_picker/country_picker.dart';
|
||||||
import 'package:proxibuy/presentation/auth/bloc/auth_bloc.dart';
|
import 'package:proxibuy/presentation/auth/bloc/auth_bloc.dart';
|
||||||
import 'package:proxibuy/presentation/pages/otp_page.dart';
|
import 'package:proxibuy/presentation/pages/otp_page.dart';
|
||||||
import '../../core/config/app_colors.dart';
|
|
||||||
import '../../core/gen/assets.gen.dart';
|
import '../../core/gen/assets.gen.dart';
|
||||||
|
|
||||||
class LoginPage extends StatefulWidget {
|
class LoginPage extends StatefulWidget {
|
||||||
|
|
@ -18,7 +15,16 @@ class LoginPage extends StatefulWidget {
|
||||||
class _LoginPageState extends State<LoginPage> {
|
class _LoginPageState extends State<LoginPage> {
|
||||||
final TextEditingController _phoneController = TextEditingController();
|
final TextEditingController _phoneController = TextEditingController();
|
||||||
Country _selectedCountry = Country.parse('IR');
|
Country _selectedCountry = Country.parse('IR');
|
||||||
bool _keepSignedIn = false;
|
|
||||||
|
void _sendOtp() {
|
||||||
|
context.read<AuthBloc>().add(
|
||||||
|
SendOTPEvent(
|
||||||
|
phoneNumber: _phoneController.text,
|
||||||
|
countryCode: _selectedCountry.phoneCode,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// bool _keepSignedIn = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
|
@ -92,17 +98,18 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Row(
|
// Row(
|
||||||
children: [
|
// children: [
|
||||||
Checkbox(
|
// Checkbox(
|
||||||
value: _keepSignedIn,
|
// value: _keepSignedIn,
|
||||||
onChanged: (value) =>
|
// onChanged:
|
||||||
setState(() => _keepSignedIn = value ?? false),
|
// (value) =>
|
||||||
activeColor: AppColors.primary,
|
// setState(() => _keepSignedIn = value ?? false),
|
||||||
),
|
// activeColor: AppColors.primary,
|
||||||
Text("مرا به خاطر بسپار", style: textTheme.bodyMedium),
|
// ),
|
||||||
],
|
// Text("مرا به خاطر بسپار", style: textTheme.bodyMedium),
|
||||||
),
|
// ],
|
||||||
|
// ),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
BlocConsumer<AuthBloc, AuthState>(
|
BlocConsumer<AuthBloc, AuthState>(
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
|
|
@ -115,23 +122,38 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (state is AuthCodeSentSuccess) {
|
if (state is AuthCodeSentSuccess) {
|
||||||
final fullPhoneNumber = "0${_phoneController.text}";
|
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (_) =>
|
builder:
|
||||||
OtpPage(phoneNumber: fullPhoneNumber)));
|
(_) => OtpPage(
|
||||||
|
phoneNumber:
|
||||||
|
"${state.countryCode}${state.phone}",
|
||||||
|
phone: state.phone,
|
||||||
|
countryCode: state.countryCode,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
if (state is AuthLoading) {
|
bool isLoading = state is AuthLoading;
|
||||||
return const Center(child: CircularProgressIndicator());
|
|
||||||
}
|
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
onPressed: _sendOtp,
|
onPressed: isLoading ? null : _sendOtp,
|
||||||
child: const Text("کد یکبار مصرف"),
|
child:
|
||||||
|
isLoading
|
||||||
|
? const SizedBox(
|
||||||
|
height: 20,
|
||||||
|
width: 20,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
color: Colors.white,
|
||||||
|
strokeWidth: 3,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const Text("کد یکبار مصرف"),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -192,10 +214,4 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
onSelect: (Country country) => setState(() => _selectedCountry = country),
|
onSelect: (Country country) => setState(() => _selectedCountry = country),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
void _sendOtp() {
|
|
||||||
context
|
|
||||||
.read<AuthBloc>()
|
|
||||||
.add(SendOTPEvent(phoneNumber: _phoneController.text));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,6 @@ import 'package:proxibuy/core/gen/assets.gen.dart';
|
||||||
import 'package:proxibuy/presentation/notification_preferences/bloc/notification_preferences_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/notification_preferences/bloc/notification_preferences_event.dart';
|
||||||
import 'package:proxibuy/presentation/notification_preferences/bloc/notification_preferences_state.dart';
|
import 'package:proxibuy/presentation/notification_preferences/bloc/notification_preferences_state.dart';
|
||||||
import 'package:proxibuy/presentation/offer/bloc/offer_bloc.dart';
|
|
||||||
import 'package:proxibuy/presentation/offer/bloc/offer_event.dart';
|
|
||||||
import 'package:proxibuy/presentation/pages/offers_page.dart';
|
import 'package:proxibuy/presentation/pages/offers_page.dart';
|
||||||
import 'package:proxibuy/presentation/pages/reserved_list_page.dart';
|
import 'package:proxibuy/presentation/pages/reserved_list_page.dart';
|
||||||
import 'package:proxibuy/presentation/reservation/cubit/reservation_cubit.dart';
|
import 'package:proxibuy/presentation/reservation/cubit/reservation_cubit.dart';
|
||||||
|
|
@ -18,7 +16,10 @@ class NotificationPreferencesPage extends StatelessWidget {
|
||||||
|
|
||||||
static Route<void> route() {
|
static Route<void> route() {
|
||||||
return MaterialPageRoute<void>(
|
return MaterialPageRoute<void>(
|
||||||
builder: (_) => const NotificationPreferencesPage(),
|
builder: (_) => BlocProvider(
|
||||||
|
create: (context) => NotificationPreferencesBloc(),
|
||||||
|
child: const NotificationPreferencesPage(),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -96,182 +97,160 @@ class NotificationPreferencesPage extends StatelessWidget {
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: Padding(
|
body: BlocListener<NotificationPreferencesBloc, NotificationPreferencesState>(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 16.0),
|
listener: (context, state) {
|
||||||
child: Column(
|
if (state.submissionSuccess) {
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
if (Navigator.canPop(context)) {
|
||||||
children: [
|
Navigator.of(context).pop(true);
|
||||||
const Text(
|
} else {
|
||||||
'دریافت اعلان',
|
Navigator.pushReplacement(
|
||||||
style: TextStyle(
|
context,
|
||||||
fontFamily: 'Dana',
|
MaterialPageRoute(
|
||||||
fontSize: 20,
|
builder: (context) => const OffersPage(showDialogsOnLoad: true),
|
||||||
fontWeight: FontWeight.bold,
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (state.errorMessage != null) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(state.errorMessage!),
|
||||||
|
backgroundColor: Colors.red,
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
const SizedBox(height: 4),
|
}
|
||||||
const Divider(),
|
},
|
||||||
RichText(
|
child: Padding(
|
||||||
text: TextSpan(
|
padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'دریافت اعلان',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontFamily: 'Dana',
|
fontFamily: 'Dana',
|
||||||
fontSize: 14,
|
fontSize: 20,
|
||||||
color: AppColors.hint,
|
fontWeight: FontWeight.bold,
|
||||||
height: 1.5,
|
|
||||||
),
|
),
|
||||||
children: const <TextSpan>[
|
|
||||||
TextSpan(
|
|
||||||
text:
|
|
||||||
'ترجیح میدی از کدام دستهبندیها اعلان تخفیف دریافت کنی؟ ',
|
|
||||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
|
|
||||||
),
|
|
||||||
TextSpan(text: '(حداقل یک مورد رو انتخاب کن).'),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 4),
|
||||||
const SizedBox(height: 24),
|
const Divider(),
|
||||||
Expanded(
|
RichText(
|
||||||
child: BlocBuilder<
|
text: TextSpan(
|
||||||
NotificationPreferencesBloc,
|
style: TextStyle(
|
||||||
NotificationPreferencesState
|
fontFamily: 'Dana',
|
||||||
>(
|
fontSize: 14,
|
||||||
builder: (context, state) {
|
color: AppColors.hint,
|
||||||
if (state.categories.isEmpty) {
|
height: 1.5,
|
||||||
return const Center(child: CircularProgressIndicator());
|
),
|
||||||
}
|
children: const <TextSpan>[
|
||||||
|
TextSpan(
|
||||||
const double sidePadding = 24.0;
|
text:
|
||||||
const double crossAxisSpacing = 16.0;
|
'ترجیح میدی از کدام دستهبندیها اعلان تخفیف دریافت کنی؟ ',
|
||||||
const int crossAxisCount = 3;
|
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
|
||||||
const double mainAxisSpacing = 24.0;
|
|
||||||
const double childAspectRatio = 0.9;
|
|
||||||
final screenWidth = MediaQuery.of(context).size.width;
|
|
||||||
|
|
||||||
final totalHorizontalPadding = sidePadding * 2;
|
|
||||||
final totalSpacing = crossAxisSpacing * (crossAxisCount - 1);
|
|
||||||
final availableWidth = screenWidth - totalHorizontalPadding;
|
|
||||||
final itemWidth =
|
|
||||||
(availableWidth - totalSpacing) / crossAxisCount;
|
|
||||||
final itemHeight = itemWidth / childAspectRatio;
|
|
||||||
|
|
||||||
return SingleChildScrollView(
|
|
||||||
child: Center(
|
|
||||||
child: Wrap(
|
|
||||||
alignment: WrapAlignment.center,
|
|
||||||
spacing: crossAxisSpacing,
|
|
||||||
runSpacing: mainAxisSpacing,
|
|
||||||
children:
|
|
||||||
state.categories.map((category) {
|
|
||||||
final isSelected = state.selectedCategoryIds
|
|
||||||
.contains(category.id);
|
|
||||||
return SizedBox(
|
|
||||||
width: 100,
|
|
||||||
height: itemHeight,
|
|
||||||
child: Center(
|
|
||||||
child: CategorySelectionCard(
|
|
||||||
name: category.name,
|
|
||||||
icon: category.icon,
|
|
||||||
isSelected: isSelected,
|
|
||||||
showSelectableIndicator:
|
|
||||||
state.selectedCategoryIds.isNotEmpty,
|
|
||||||
onTap: () {
|
|
||||||
context
|
|
||||||
.read<NotificationPreferencesBloc>()
|
|
||||||
.add(
|
|
||||||
ToggleCategorySelection(
|
|
||||||
category.id,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
TextSpan(text: '(حداقل یک مورد رو انتخاب کن).'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
Expanded(
|
||||||
|
child: BlocBuilder<NotificationPreferencesBloc, NotificationPreferencesState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
if (state.categories.isEmpty && state.isLoading) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
|
||||||
|
final double horizontalPadding = 24.0;
|
||||||
|
final double crossAxisSpacing = 16.0;
|
||||||
|
final int crossAxisCount = 3;
|
||||||
|
final screenWidth = MediaQuery.of(context).size.width;
|
||||||
|
final itemWidth = (screenWidth - (horizontalPadding * 2) - (crossAxisSpacing * (crossAxisCount - 1))) / crossAxisCount;
|
||||||
|
final itemHeight = itemWidth / 0.9;
|
||||||
|
|
||||||
|
return SingleChildScrollView(
|
||||||
|
child: Wrap(
|
||||||
|
spacing: crossAxisSpacing,
|
||||||
|
runSpacing: 24.0,
|
||||||
|
alignment: WrapAlignment.center,
|
||||||
|
children: state.categories.map((category) {
|
||||||
|
final isSelected =
|
||||||
|
state.selectedCategoryIds.contains(category.id);
|
||||||
|
return SizedBox(
|
||||||
|
width: itemWidth,
|
||||||
|
height: itemHeight,
|
||||||
|
child: CategorySelectionCard(
|
||||||
|
name: category.name,
|
||||||
|
icon: category.icon,
|
||||||
|
isSelected: isSelected,
|
||||||
|
showSelectableIndicator: state.selectedCategoryIds.isNotEmpty,
|
||||||
|
onTap: () {
|
||||||
|
context
|
||||||
|
.read<NotificationPreferencesBloc>()
|
||||||
|
.add(ToggleCategorySelection(category.id));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
BlocBuilder<NotificationPreferencesBloc, NotificationPreferencesState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
final areCategoriesSelected = state.selectedCategoryIds.isNotEmpty;
|
||||||
|
|
||||||
|
if (areCategoriesSelected) {
|
||||||
|
return SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: !state.isLoading
|
||||||
|
? () async {
|
||||||
|
final bloc = context.read<NotificationPreferencesBloc>();
|
||||||
|
|
||||||
|
final selectedCategoryNames = bloc.state.categories
|
||||||
|
.where((cat) => bloc.state.selectedCategoryIds.contains(cat.id))
|
||||||
|
.map((cat) => cat.name)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
await prefs.setStringList('user_selected_categories', selectedCategoryNames);
|
||||||
|
|
||||||
|
bloc.add(SubmitPreferences());
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: AppColors.confirm,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
disabledBackgroundColor: Colors.grey,
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(50),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: state.isLoading
|
||||||
|
? const CircularProgressIndicator(color: Colors.white)
|
||||||
|
: const Text(
|
||||||
|
'اعمال',
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'Dana',
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 20),
|
||||||
BlocBuilder<
|
],
|
||||||
NotificationPreferencesBloc,
|
),
|
||||||
NotificationPreferencesState
|
|
||||||
>(
|
|
||||||
builder: (context, state) {
|
|
||||||
final isEnabled = state.selectedCategoryIds.isNotEmpty;
|
|
||||||
return SizedBox(
|
|
||||||
width: double.infinity,
|
|
||||||
child:
|
|
||||||
isEnabled
|
|
||||||
? ElevatedButton(
|
|
||||||
onPressed:
|
|
||||||
isEnabled
|
|
||||||
? () async {
|
|
||||||
final selectedCategoryNames =
|
|
||||||
state.categories
|
|
||||||
.where(
|
|
||||||
(cat) => state
|
|
||||||
.selectedCategoryIds
|
|
||||||
.contains(cat.id),
|
|
||||||
)
|
|
||||||
.map((cat) => cat.name)
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
final prefs =
|
|
||||||
await SharedPreferences.getInstance();
|
|
||||||
await prefs.setStringList(
|
|
||||||
'user_selected_categories',
|
|
||||||
selectedCategoryNames,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (context.mounted) {
|
|
||||||
context.read<OffersBloc>().add(
|
|
||||||
OffersFetchRequested(
|
|
||||||
selectedCategories:
|
|
||||||
selectedCategoryNames,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!context.mounted) return;
|
|
||||||
|
|
||||||
Navigator.pushReplacement(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder:
|
|
||||||
(context) => const OffersPage(
|
|
||||||
showDialogsOnLoad: true,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: AppColors.confirm,
|
|
||||||
foregroundColor: Colors.white,
|
|
||||||
disabledBackgroundColor: Colors.grey,
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(50),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: const Text(
|
|
||||||
'اعمال',
|
|
||||||
style: TextStyle(
|
|
||||||
fontFamily: 'Dana',
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: const SizedBox(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,14 +1,12 @@
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_animate/flutter_animate.dart'; // <-- این خط را اضافه کن
|
import 'package:flutter_animate/flutter_animate.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
import 'package:geolocator/geolocator.dart';
|
import 'package:geolocator/geolocator.dart';
|
||||||
import 'package:proxibuy/core/config/app_colors.dart';
|
import 'package:proxibuy/core/config/app_colors.dart';
|
||||||
import 'package:proxibuy/core/gen/assets.gen.dart';
|
import 'package:proxibuy/core/gen/assets.gen.dart';
|
||||||
import 'package:proxibuy/data/models/offer_model.dart';
|
import 'package:proxibuy/data/models/offer_model.dart';
|
||||||
import 'package:proxibuy/presentation/notification_preferences/bloc/notification_preferences_bloc.dart';
|
|
||||||
import 'package:proxibuy/presentation/notification_preferences/bloc/notification_preferences_event.dart';
|
|
||||||
import 'package:proxibuy/presentation/offer/bloc/offer_bloc.dart';
|
import 'package:proxibuy/presentation/offer/bloc/offer_bloc.dart';
|
||||||
import 'package:proxibuy/presentation/offer/bloc/offer_event.dart';
|
import 'package:proxibuy/presentation/offer/bloc/offer_event.dart';
|
||||||
import 'package:proxibuy/presentation/offer/bloc/offer_state.dart';
|
import 'package:proxibuy/presentation/offer/bloc/offer_state.dart';
|
||||||
|
|
@ -81,17 +79,11 @@ class _OffersPageState extends State<OffersPage> {
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final result = await Navigator.of(context).push<bool>(
|
final result = await Navigator.of(context).push<bool>(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder:
|
builder: (_) => const NotificationPreferencesPage(),
|
||||||
(_) => BlocProvider.value(
|
|
||||||
value:
|
|
||||||
context.read<NotificationPreferencesBloc>()
|
|
||||||
..add(LoadCategories()),
|
|
||||||
child: const NotificationPreferencesPage(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result == true) {
|
if (result == true && mounted) {
|
||||||
_loadOffersAndPreferences();
|
_loadOffersAndPreferences();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,28 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
import 'package:proxibuy/data/models/datasources/offer_data_source.dart';
|
||||||
|
import 'package:proxibuy/data/repositories/offer_repository.dart';
|
||||||
import 'package:proxibuy/presentation/auth/bloc/auth_bloc.dart';
|
import 'package:proxibuy/presentation/auth/bloc/auth_bloc.dart';
|
||||||
|
import 'package:proxibuy/presentation/notification_preferences/bloc/notification_preferences_bloc.dart';
|
||||||
|
import 'package:proxibuy/presentation/offer/bloc/offer_bloc.dart';
|
||||||
import 'package:proxibuy/presentation/pages/user_info_page.dart';
|
import 'package:proxibuy/presentation/pages/user_info_page.dart';
|
||||||
|
import 'package:proxibuy/presentation/reservation/cubit/reservation_cubit.dart';
|
||||||
import '../../core/config/app_colors.dart';
|
import '../../core/config/app_colors.dart';
|
||||||
import '../../core/gen/assets.gen.dart';
|
import '../../core/gen/assets.gen.dart';
|
||||||
import '../utils/otp_timer_helper.dart';
|
import '../utils/otp_timer_helper.dart';
|
||||||
|
|
||||||
class OtpPage extends StatefulWidget {
|
class OtpPage extends StatefulWidget {
|
||||||
final String phoneNumber;
|
final String phoneNumber;
|
||||||
const OtpPage({super.key, required this.phoneNumber});
|
final String phone;
|
||||||
|
final String countryCode;
|
||||||
|
|
||||||
|
const OtpPage({
|
||||||
|
super.key,
|
||||||
|
required this.phoneNumber,
|
||||||
|
required this.phone,
|
||||||
|
required this.countryCode,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<OtpPage> createState() => _OtpPageState();
|
State<OtpPage> createState() => _OtpPageState();
|
||||||
|
|
@ -77,19 +90,24 @@ class _OtpPageState extends State<OtpPage> {
|
||||||
height: 1.5,
|
height: 1.5,
|
||||||
),
|
),
|
||||||
children: <TextSpan>[
|
children: <TextSpan>[
|
||||||
const TextSpan(text: 'کد تایید به شماره ',style: TextStyle(fontSize: 15)),
|
const TextSpan(
|
||||||
|
text: 'کد تایید به شماره ',
|
||||||
|
style: TextStyle(fontSize: 15),
|
||||||
|
),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: widget.phoneNumber,
|
text: widget.phoneNumber,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontWeight:
|
fontWeight: FontWeight.bold,
|
||||||
FontWeight.bold,
|
fontSize: 15,
|
||||||
fontSize: 15
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const TextSpan(text: ' ارسال شد.',style: TextStyle(fontSize: 15)),
|
const TextSpan(
|
||||||
|
text: ' ارسال شد.',
|
||||||
|
style: TextStyle(fontSize: 15),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
textDirection: TextDirection.rtl, // جهت متن برای RichText
|
textDirection: TextDirection.rtl,
|
||||||
),
|
),
|
||||||
SizedBox(height: 15),
|
SizedBox(height: 15),
|
||||||
Row(
|
Row(
|
||||||
|
|
@ -133,22 +151,57 @@ class _OtpPageState extends State<OtpPage> {
|
||||||
_errorMessage = state.message;
|
_errorMessage = state.message;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (state is AuthVerified) {
|
if (state is AuthNeedsInfo) {
|
||||||
|
final offerRepository = OfferRepository(
|
||||||
|
offerDataSource: MockOfferDataSource(),
|
||||||
|
);
|
||||||
Navigator.of(context).pushAndRemoveUntil(
|
Navigator.of(context).pushAndRemoveUntil(
|
||||||
MaterialPageRoute(builder: (_) => const UserInfoPage()),
|
MaterialPageRoute(
|
||||||
|
builder:
|
||||||
|
(_) => MultiBlocProvider(
|
||||||
|
providers: [
|
||||||
|
BlocProvider.value(
|
||||||
|
value: context.read<AuthBloc>(),
|
||||||
|
),
|
||||||
|
BlocProvider<OffersBloc>(
|
||||||
|
create:
|
||||||
|
(_) => OffersBloc(
|
||||||
|
offerRepository: offerRepository,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
BlocProvider<ReservationCubit>(
|
||||||
|
create: (_) => ReservationCubit(),
|
||||||
|
),
|
||||||
|
BlocProvider<NotificationPreferencesBloc>(
|
||||||
|
create:
|
||||||
|
(_) => NotificationPreferencesBloc(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
child: const UserInfoPage(),
|
||||||
|
),
|
||||||
|
),
|
||||||
(route) => false,
|
(route) => false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
|
bool isLoading = state is AuthLoading;
|
||||||
if (state is AuthLoading) {
|
if (state is AuthLoading) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
}
|
}
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
|
height: 60,
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
onPressed: _isOtpComplete ? _verifyOtp : null,
|
onPressed:
|
||||||
child: const Text("ورود"),
|
(_isOtpComplete && !isLoading) ? _verifyOtp : null,
|
||||||
|
child:
|
||||||
|
isLoading
|
||||||
|
? const CircularProgressIndicator(
|
||||||
|
color: Colors.white,
|
||||||
|
strokeWidth: 3,
|
||||||
|
)
|
||||||
|
: const Text("ورود"),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -268,20 +321,26 @@ class _OtpPageState extends State<OtpPage> {
|
||||||
void _verifyOtp() {
|
void _verifyOtp() {
|
||||||
final otpCode = _controllers.map((c) => c.text).join();
|
final otpCode = _controllers.map((c) => c.text).join();
|
||||||
if (otpCode.length == 5) {
|
if (otpCode.length == 5) {
|
||||||
context.read<AuthBloc>().add(VerifyOTPEvent(otp: otpCode));
|
context.read<AuthBloc>().add(
|
||||||
|
VerifyOTPEvent(
|
||||||
|
otp: otpCode,
|
||||||
|
phoneNumber: widget.phone,
|
||||||
|
countryCode: widget.countryCode,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _resendOtp() {
|
void _resendOtp() {
|
||||||
setState(() {
|
for (var controller in _controllers) {
|
||||||
_hasError = false;
|
controller.clear();
|
||||||
_errorMessage = null;
|
}
|
||||||
for (var controller in _controllers) {
|
FocusScope.of(context).requestFocus(_focusNodes[0]);
|
||||||
controller.clear();
|
setState(() => _isOtpComplete = false);
|
||||||
}
|
|
||||||
_isOtpComplete = false;
|
context.read<AuthBloc>().add(
|
||||||
});
|
SendOTPEvent(phoneNumber: widget.phone, countryCode: widget.countryCode),
|
||||||
context.read<AuthBloc>().add(SendOTPEvent(phoneNumber: widget.phoneNumber));
|
);
|
||||||
_otpTimer.resetTimer();
|
_otpTimer.resetTimer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:proxibuy/presentation/auth/bloc/auth_bloc.dart';
|
||||||
|
import 'package:proxibuy/presentation/pages/onboarding_page.dart';
|
||||||
|
import 'package:proxibuy/presentation/pages/offers_page.dart';
|
||||||
|
import 'package:proxibuy/core/gen/assets.gen.dart';
|
||||||
|
|
||||||
|
class SplashScreen extends StatefulWidget {
|
||||||
|
const SplashScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SplashScreen> createState() => _SplashScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SplashScreenState extends State<SplashScreen> {
|
||||||
|
late final StreamSubscription _authSubscription;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
final authBloc = context.read<AuthBloc>();
|
||||||
|
|
||||||
|
_authSubscription = authBloc.stream.listen((state) {
|
||||||
|
_authSubscription.cancel();
|
||||||
|
if (state is AuthSuccess) {
|
||||||
|
Navigator.of(context).pushReplacement(
|
||||||
|
MaterialPageRoute(builder: (_) => const OffersPage()),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
Navigator.of(context).pushReplacement(
|
||||||
|
MaterialPageRoute(builder: (_) => const OnboardingPage()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_authSubscription.cancel();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
body: Center(
|
||||||
|
child: Assets.icons.logo.svg(height: 160),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
// lib/presentation/pages/user_info_page.dart
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:proxibuy/presentation/auth/bloc/auth_bloc.dart';
|
import 'package:proxibuy/presentation/auth/bloc/auth_bloc.dart';
|
||||||
|
|
@ -15,7 +14,7 @@ class UserInfoPage extends StatefulWidget {
|
||||||
|
|
||||||
class _UserInfoPageState extends State<UserInfoPage> {
|
class _UserInfoPageState extends State<UserInfoPage> {
|
||||||
final _nameController = TextEditingController();
|
final _nameController = TextEditingController();
|
||||||
String _selectedGender = 'مرد';
|
String _selectedGender = 'male';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
|
@ -32,8 +31,9 @@ class _UserInfoPageState extends State<UserInfoPage> {
|
||||||
Radio<String>(
|
Radio<String>(
|
||||||
value: value,
|
value: value,
|
||||||
groupValue: _selectedGender,
|
groupValue: _selectedGender,
|
||||||
onChanged: (newValue) => setState(() => _selectedGender = newValue!),
|
onChanged:
|
||||||
activeColor: AppColors.primary,
|
(newValue) => setState(() => _selectedGender = newValue!),
|
||||||
|
activeColor: AppColors.primary,
|
||||||
),
|
),
|
||||||
Text(title, style: const TextStyle(color: Colors.grey)),
|
Text(title, style: const TextStyle(color: Colors.grey)),
|
||||||
],
|
],
|
||||||
|
|
@ -41,6 +41,24 @@ class _UserInfoPageState extends State<UserInfoPage> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _submitUserInfo() {
|
||||||
|
if (_nameController.text.isNotEmpty) {
|
||||||
|
context.read<AuthBloc>().add(
|
||||||
|
UpdateUserInfoEvent(
|
||||||
|
name: _nameController.text,
|
||||||
|
gender: _selectedGender,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(
|
||||||
|
content: Text('لطفا نام خود را وارد کنید.'),
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final textTheme = Theme.of(context).textTheme;
|
final textTheme = Theme.of(context).textTheme;
|
||||||
|
|
@ -49,10 +67,7 @@ class _UserInfoPageState extends State<UserInfoPage> {
|
||||||
body: Stack(
|
body: Stack(
|
||||||
children: [
|
children: [
|
||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
child: Image.asset(
|
child: Image.asset(Assets.images.userinfo.path, fit: BoxFit.cover),
|
||||||
Assets.images.userinfo.path,
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
|
||||||
DraggableScrollableSheet(
|
DraggableScrollableSheet(
|
||||||
|
|
@ -77,7 +92,7 @@ class _UserInfoPageState extends State<UserInfoPage> {
|
||||||
height: 5,
|
height: 5,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
// ignore: deprecated_member_use
|
// ignore: deprecated_member_use
|
||||||
color:Colors.grey.withOpacity(0.5),
|
color: Colors.grey.withOpacity(0.5),
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -93,7 +108,10 @@ class _UserInfoPageState extends State<UserInfoPage> {
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
),
|
),
|
||||||
hintText: "مثلا نام کوچک شما",
|
hintText: "مثلا نام کوچک شما",
|
||||||
hintStyle: TextStyle(fontSize: 15, color: Colors.grey),
|
hintStyle: TextStyle(
|
||||||
|
fontSize: 15,
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
|
|
@ -109,44 +127,48 @@ class _UserInfoPageState extends State<UserInfoPage> {
|
||||||
spacing: 10.0,
|
spacing: 10.0,
|
||||||
runSpacing: 8.0,
|
runSpacing: 8.0,
|
||||||
children: [
|
children: [
|
||||||
_buildGenderRadio('مرد', 'مرد'),
|
_buildGenderRadio('مرد', 'male'),
|
||||||
_buildGenderRadio('زن', 'زن'),
|
_buildGenderRadio('زن', 'female'),
|
||||||
_buildGenderRadio('تمایلی به پاسخ ندارم', 'نامشخص'),
|
_buildGenderRadio('تمایلی به پاسخ ندارم', 'none'),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 55),
|
const SizedBox(height: 55),
|
||||||
|
|
||||||
BlocConsumer<AuthBloc, AuthState>(
|
BlocConsumer<AuthBloc, AuthState>(
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
if (state is UserInfoSaved) {
|
if (state is AuthFailure) {
|
||||||
Navigator.of(context).pushReplacement(
|
|
||||||
MaterialPageRoute(builder: (_) => const NotificationPreferencesPage()),
|
|
||||||
);
|
|
||||||
} else if (state is AuthFailure) {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text(state.message), backgroundColor: Colors.red),
|
SnackBar(
|
||||||
|
content: Text(state.message),
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
if (state is AuthLoading) {
|
final isLoading = state is AuthLoading;
|
||||||
return const Center(child: CircularProgressIndicator());
|
|
||||||
}
|
return Column(
|
||||||
return SizedBox(
|
children: [
|
||||||
width: double.infinity,
|
SizedBox(
|
||||||
child: ElevatedButton(
|
width: double.infinity,
|
||||||
style: ElevatedButton.styleFrom(
|
height: 60,
|
||||||
backgroundColor: AppColors.confirm,
|
child: ElevatedButton(
|
||||||
foregroundColor: Colors.white,
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: AppColors.confirm,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
),
|
||||||
|
onPressed: isLoading ? null : _submitUserInfo,
|
||||||
|
child:
|
||||||
|
isLoading
|
||||||
|
? const CircularProgressIndicator(
|
||||||
|
color: Colors.white,
|
||||||
|
)
|
||||||
|
: const Text("اعمال"),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
onPressed: () {
|
const SizedBox(height: 9),
|
||||||
context.read<AuthBloc>().add(SaveUserInfoEvent(
|
],
|
||||||
name: _nameController.text,
|
|
||||||
gender: _selectedGender,
|
|
||||||
));
|
|
||||||
},
|
|
||||||
child: const Text("اعمال"),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
@ -155,7 +177,9 @@ class _UserInfoPageState extends State<UserInfoPage> {
|
||||||
Center(
|
Center(
|
||||||
child: TextButton(
|
child: TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pushReplacement(NotificationPreferencesPage.route());
|
Navigator.of(context).pushReplacement(
|
||||||
|
NotificationPreferencesPage.route(),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
child: const Text(
|
child: const Text(
|
||||||
"رد شدن",
|
"رد شدن",
|
||||||
|
|
@ -173,4 +197,4 @@ class _UserInfoPageState extends State<UserInfoPage> {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ abstract class ProductDetailEvent extends Equatable {
|
||||||
List<Object> get props => [];
|
List<Object> get props => [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// این ایونت زمانی فراخوانی میشود که بخواهیم جزئیات یک محصول را دریافت کنیم
|
|
||||||
class ProductDetailFetchRequested extends ProductDetailEvent {
|
class ProductDetailFetchRequested extends ProductDetailEvent {
|
||||||
final String offerId;
|
final String offerId;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
// lib/presentation/utils/otp_timer_helper.dart
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ class CategorySelectionCard extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// رنگ بردر و متن بر اساس انتخاب شدن تغییر میکند
|
|
||||||
final textAndBorderColor = isSelected ? AppColors.confirm : AppColors.unselectedBorder;
|
final textAndBorderColor = isSelected ? AppColors.confirm : AppColors.unselectedBorder;
|
||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
|
|
@ -32,7 +31,6 @@ class CategorySelectionCard extends StatelessWidget {
|
||||||
child: Stack(
|
child: Stack(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
children: [
|
children: [
|
||||||
// این ویجت تغییرات در دکوریشن را به صورت انیمیشنی نمایش میدهد
|
|
||||||
AnimatedContainer(
|
AnimatedContainer(
|
||||||
duration: const Duration(milliseconds: 250),
|
duration: const Duration(milliseconds: 250),
|
||||||
curve: Curves.easeInOut,
|
curve: Curves.easeInOut,
|
||||||
|
|
@ -58,14 +56,12 @@ class CategorySelectionCard extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// این ویجت انیمیشن بین نمایش تیک، دایره یا هیچکدام را مدیریت میکند
|
|
||||||
Positioned(
|
Positioned(
|
||||||
top: 5,
|
top: 5,
|
||||||
right: 5,
|
right: 5,
|
||||||
child: AnimatedSwitcher(
|
child: AnimatedSwitcher(
|
||||||
duration: const Duration(milliseconds: 250),
|
duration: const Duration(milliseconds: 250),
|
||||||
transitionBuilder: (child, animation) {
|
transitionBuilder: (child, animation) {
|
||||||
// انیمیشن محو شدن و بزرگ شدن همزمان
|
|
||||||
return FadeTransition(
|
return FadeTransition(
|
||||||
opacity: animation,
|
opacity: animation,
|
||||||
child: ScaleTransition(
|
child: ScaleTransition(
|
||||||
|
|
@ -81,7 +77,6 @@ class CategorySelectionCard extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
// رنگ متن هم به صورت انیمیشنی (توسط بازسازی ویجت) تغییر میکند
|
|
||||||
Text(
|
Text(
|
||||||
name,
|
name,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
|
@ -98,20 +93,17 @@ class CategorySelectionCard extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// این متد کمکی مشخص میکند کدام نشانگر (تیک، دایره یا هیچکدام) نمایش داده شود
|
|
||||||
Widget _buildIndicator() {
|
Widget _buildIndicator() {
|
||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
// نمایش تیک در صورت انتخاب
|
|
||||||
return SvgPicture.asset(
|
return SvgPicture.asset(
|
||||||
Assets.icons.tickCircle.path,
|
Assets.icons.tickCircle.path,
|
||||||
key: const ValueKey('tick'), // کلید برای کارکرد صحیح AnimatedSwitcher
|
key: const ValueKey('tick'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showSelectableIndicator) {
|
if (showSelectableIndicator) {
|
||||||
// نمایش دایره در صورتی که آیتمهای دیگر انتخاب شدهاند
|
|
||||||
return Container(
|
return Container(
|
||||||
key: const ValueKey('circle'), // کلید برای کارکرد صحیح AnimatedSwitcher
|
key: const ValueKey('circle'),
|
||||||
width: 12,
|
width: 12,
|
||||||
height: 12,
|
height: 12,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
|
|
@ -122,7 +114,6 @@ class CategorySelectionCard extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// در غیر این صورت، چیزی نمایش نده
|
|
||||||
return const SizedBox.shrink(key: ValueKey('empty'));
|
return const SizedBox.shrink(key: ValueKey('empty'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -12,7 +12,6 @@ class PhotoGalleryView extends StatelessWidget {
|
||||||
required this.remainingPhotos,
|
required this.remainingPhotos,
|
||||||
});
|
});
|
||||||
|
|
||||||
// این ویجت کمکی تشخیص میدهد که عکس از فایل محلی است یا از اینترنت
|
|
||||||
Widget _buildSmartImage(String imageUrl) {
|
Widget _buildSmartImage(String imageUrl) {
|
||||||
bool isFile = !imageUrl.startsWith('http');
|
bool isFile = !imageUrl.startsWith('http');
|
||||||
return ClipRRect(
|
return ClipRRect(
|
||||||
|
|
@ -61,10 +60,9 @@ class PhotoGalleryView extends StatelessWidget {
|
||||||
return const Center(child: Text("هنوز عکسی وجود ندارد."));
|
return const Center(child: Text("هنوز عکسی وجود ندارد."));
|
||||||
}
|
}
|
||||||
|
|
||||||
// این بخش برای جلوگیری از خطا در صورتی که تعداد عکسها کمتر از نیاز گرید باشد، اضافه شده است.
|
|
||||||
final displayUrls = List<String>.from(imageUrls);
|
final displayUrls = List<String>.from(imageUrls);
|
||||||
while (displayUrls.length < 6) {
|
while (displayUrls.length < 6) {
|
||||||
displayUrls.add('https://via.placeholder.com/200'); // یک عکس جایگزین
|
displayUrls.add('https://via.placeholder.com/200');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,27 +55,27 @@ Future<void> showGPSDialog(BuildContext context) async {
|
||||||
onTap: () => Navigator.of(context).pop(),
|
onTap: () => Navigator.of(context).pop(),
|
||||||
child: Text("الان نه",style: TextStyle(color: AppColors.primary,fontWeight: FontWeight.bold),),
|
child: Text("الان نه",style: TextStyle(color: AppColors.primary,fontWeight: FontWeight.bold),),
|
||||||
),
|
),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: AppColors.primary,
|
backgroundColor: AppColors.primary,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
side: const BorderSide(color: AppColors.border),
|
side: const BorderSide(color: AppColors.border),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 45, vertical: 7),
|
||||||
|
),
|
||||||
|
onPressed: () async {
|
||||||
|
await Geolocator.openLocationSettings();
|
||||||
|
// ignore: use_build_context_synchronously
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
child: const Text(
|
||||||
|
"فعالسازی",
|
||||||
|
style: TextStyle(color: Colors.white),
|
||||||
),
|
),
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 45, vertical: 7),
|
|
||||||
),
|
),
|
||||||
onPressed: () async {
|
|
||||||
await Geolocator.openLocationSettings();
|
|
||||||
// ignore: use_build_context_synchronously
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
child: const Text(
|
|
||||||
"فعالسازی",
|
|
||||||
style: TextStyle(color: Colors.white),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -24,19 +24,11 @@ class OnboardingIndicatorPainter extends CustomPainter {
|
||||||
final center = Offset(size.width / 2, size.height / 2);
|
final center = Offset(size.width / 2, size.height / 2);
|
||||||
final radius = min(size.width, size.height) / 2;
|
final radius = min(size.width, size.height) / 2;
|
||||||
|
|
||||||
// ================== شروع تغییرات ==================
|
|
||||||
|
|
||||||
// 1. تعریف اندازه فاصله بین قوسها (بر حسب درجه)
|
|
||||||
// این عدد را میتوانید برای کم و زیاد کردن فاصله تغییر دهید
|
|
||||||
const double gapInDegrees = 30.0;
|
const double gapInDegrees = 30.0;
|
||||||
const double gapInRadians = gapInDegrees * (pi / 180);
|
const double gapInRadians = gapInDegrees * (pi / 180);
|
||||||
|
|
||||||
// 2. محاسبه طول جدید هر قوس (۹۰ درجه منهای اندازه فاصله)
|
|
||||||
const double arcAngle = (pi / 2) - gapInRadians;
|
const double arcAngle = (pi / 2) - gapInRadians;
|
||||||
|
|
||||||
// =================== پایان تغییرات ===================
|
|
||||||
|
|
||||||
|
|
||||||
final startAngles = [-pi / 2, 0.0, pi / 2, pi];
|
final startAngles = [-pi / 2, 0.0, pi / 2, pi];
|
||||||
|
|
||||||
for (int i = 0; i < pageCount; i++) {
|
for (int i = 0; i < pageCount; i++) {
|
||||||
|
|
@ -44,15 +36,14 @@ class OnboardingIndicatorPainter extends CustomPainter {
|
||||||
..color = (i == currentPage) ? activeColor : inactiveColor
|
..color = (i == currentPage) ? activeColor : inactiveColor
|
||||||
..style = PaintingStyle.stroke
|
..style = PaintingStyle.stroke
|
||||||
..strokeWidth = strokeWidth
|
..strokeWidth = strokeWidth
|
||||||
..strokeCap = StrokeCap.round; // StrokeCap.round لبههای خط را گرد میکند که زیباتر است
|
..strokeCap = StrokeCap.round;
|
||||||
|
|
||||||
// 3. محاسبه نقطه شروع جدید (با افزودن نصف فاصله برای وسطچین شدن)
|
|
||||||
final correctedStartAngle = startAngles[i] + (gapInRadians / 2);
|
final correctedStartAngle = startAngles[i] + (gapInRadians / 2);
|
||||||
|
|
||||||
canvas.drawArc(
|
canvas.drawArc(
|
||||||
Rect.fromCircle(center: center, radius: radius),
|
Rect.fromCircle(center: center, radius: radius),
|
||||||
correctedStartAngle, // استفاده از نقطه شروع اصلاح شده
|
correctedStartAngle,
|
||||||
arcAngle, // استفاده از طول قوس جدید
|
arcAngle,
|
||||||
false,
|
false,
|
||||||
paint,
|
paint,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -66,10 +66,9 @@ class _ReservedListItemCardState extends State<ReservedListItemCard> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// ویجت اصلی به Column تغییر کرده است
|
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
// بخش اول: کارت اصلی که فقط شامل اطلاعات محصول است
|
|
||||||
Card(
|
Card(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
|
|
@ -79,16 +78,14 @@ class _ReservedListItemCardState extends State<ReservedListItemCard> {
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
clipBehavior: Clip.antiAlias,
|
clipBehavior: Clip.antiAlias,
|
||||||
margin: EdgeInsets.zero,
|
margin: EdgeInsets.zero,
|
||||||
child: _buildOfferPrimaryDetails(), // فقط اطلاعات اصلی داخل کارت
|
child: _buildOfferPrimaryDetails(),
|
||||||
),
|
),
|
||||||
_buildActionsRow(),
|
_buildActionsRow(),
|
||||||
// بخش سوم: پنل باز شونده QR کد
|
|
||||||
_buildExpansionPanel(),
|
_buildExpansionPanel(),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// این متد فقط اطلاعات اصلی داخل کارت را میسازد
|
|
||||||
Widget _buildOfferPrimaryDetails() {
|
Widget _buildOfferPrimaryDetails() {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(15, 25, 15, 25),
|
padding: const EdgeInsets.fromLTRB(15, 25, 15, 25),
|
||||||
|
|
@ -138,7 +135,6 @@ class _ReservedListItemCardState extends State<ReservedListItemCard> {
|
||||||
children: [
|
children: [
|
||||||
SvgPicture.asset(Assets.icons.location.path),
|
SvgPicture.asset(Assets.icons.location.path),
|
||||||
SizedBox(width: 8),
|
SizedBox(width: 8),
|
||||||
// برای جلوگیری از سرریز شدن متن، از Flexible استفاده میکنیم
|
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Text(
|
child: Text(
|
||||||
"${widget.offer.address} (${widget.offer.distanceInMeters} متر تا تخفیف)",
|
"${widget.offer.address} (${widget.offer.distanceInMeters} متر تا تخفیف)",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
// import 'dart:async';
|
||||||
|
// import 'dart:math';
|
||||||
|
// import 'package:mqtt_client/mqtt_client.dart';
|
||||||
|
// import 'package:mqtt_client/mqtt_server_client.dart';
|
||||||
|
|
||||||
|
// class MqttService {
|
||||||
|
// late MqttServerClient client;
|
||||||
|
// final String server = '5.75.200.241';
|
||||||
|
// final int port = 1883;
|
||||||
|
|
||||||
|
// Future<void> connect(String token) async {
|
||||||
|
// // 1. معادلسازی پارامترها
|
||||||
|
// final String clientId = 'nest-' + Random().nextInt(0xFFFFFF).toRadixString(16).padLeft(6, '0');
|
||||||
|
// final String username = 'ignored';
|
||||||
|
// final String password = token; // ✨ توکن شما مستقیماً به عنوان پسورد در نظر گرفته میشود
|
||||||
|
|
||||||
|
// // 2. ساخت کلاینت
|
||||||
|
// client = MqttServerClient.withPort(server, clientId, port);
|
||||||
|
// client.logging(on: true);
|
||||||
|
// client.keepAlivePeriod = 60;
|
||||||
|
// client.autoReconnect = false; // معادل reconnectPeriod: 0
|
||||||
|
// client.setProtocolV311();
|
||||||
|
|
||||||
|
// // 3. ساخت پیام اتصال با پارامترهای تعریف شده
|
||||||
|
// final connMessage = MqttConnectMessage()
|
||||||
|
// .withClientIdentifier(clientId)
|
||||||
|
// .startClean()
|
||||||
|
// .authenticateAs(username, password); // ارسال نام کاربری و رمز عبور (توکن)
|
||||||
|
|
||||||
|
// client.connectionMessage = connMessage;
|
||||||
|
|
||||||
|
// // 4. تعریف Callbackها و اتصال
|
||||||
|
// client.onConnected = () {
|
||||||
|
// print('✅ MQTT Connected');
|
||||||
|
// client.updates!.listen((List<MqttReceivedMessage<MqttMessage>> c) {
|
||||||
|
// final MqttPublishMessage recMess = c[0].payload as MqttPublishMessage;
|
||||||
|
// final String payload =
|
||||||
|
// MqttPublishPayload.bytesToStringAsString(recMess.payload.message);
|
||||||
|
// print('Received message: "$payload" from topic: ${c[0].topic}');
|
||||||
|
// });
|
||||||
|
|
||||||
|
// client.subscribe('test/topic', MqttQos.atLeastOnce);
|
||||||
|
// };
|
||||||
|
|
||||||
|
// client.onDisconnected = () {
|
||||||
|
// print('❌ MQTT Disconnected');
|
||||||
|
// };
|
||||||
|
|
||||||
|
// client.onSubscribed = (String topic) {
|
||||||
|
// print('✅ Subscribed to $topic');
|
||||||
|
// };
|
||||||
|
|
||||||
|
// try {
|
||||||
|
// await client.connect();
|
||||||
|
// } catch (e) {
|
||||||
|
// print('Exception: $e');
|
||||||
|
// client.disconnect();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
#include <file_selector_linux/file_selector_plugin.h>
|
#include <file_selector_linux/file_selector_plugin.h>
|
||||||
#include <flutter_localization/flutter_localization_plugin.h>
|
#include <flutter_localization/flutter_localization_plugin.h>
|
||||||
|
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
|
||||||
#include <maps_launcher/maps_launcher_plugin.h>
|
#include <maps_launcher/maps_launcher_plugin.h>
|
||||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||||
|
|
||||||
|
|
@ -18,6 +19,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
|
||||||
g_autoptr(FlPluginRegistrar) flutter_localization_registrar =
|
g_autoptr(FlPluginRegistrar) flutter_localization_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterLocalizationPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterLocalizationPlugin");
|
||||||
flutter_localization_plugin_register_with_registrar(flutter_localization_registrar);
|
flutter_localization_plugin_register_with_registrar(flutter_localization_registrar);
|
||||||
|
g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar =
|
||||||
|
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin");
|
||||||
|
flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar);
|
||||||
g_autoptr(FlPluginRegistrar) maps_launcher_registrar =
|
g_autoptr(FlPluginRegistrar) maps_launcher_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "MapsLauncherPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "MapsLauncherPlugin");
|
||||||
maps_launcher_plugin_register_with_registrar(maps_launcher_registrar);
|
maps_launcher_plugin_register_with_registrar(maps_launcher_registrar);
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
file_selector_linux
|
file_selector_linux
|
||||||
flutter_localization
|
flutter_localization
|
||||||
|
flutter_secure_storage_linux
|
||||||
maps_launcher
|
maps_launcher
|
||||||
url_launcher_linux
|
url_launcher_linux
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,13 @@
|
||||||
import FlutterMacOS
|
import FlutterMacOS
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
import cloud_firestore
|
||||||
import file_selector_macos
|
import file_selector_macos
|
||||||
|
import firebase_auth
|
||||||
|
import firebase_core
|
||||||
|
import firebase_storage
|
||||||
import flutter_localization
|
import flutter_localization
|
||||||
|
import flutter_secure_storage_macos
|
||||||
import geolocator_apple
|
import geolocator_apple
|
||||||
import maps_launcher
|
import maps_launcher
|
||||||
import path_provider_foundation
|
import path_provider_foundation
|
||||||
|
|
@ -15,8 +20,13 @@ import sqflite_darwin
|
||||||
import url_launcher_macos
|
import url_launcher_macos
|
||||||
|
|
||||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
|
FLTFirebaseFirestorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseFirestorePlugin"))
|
||||||
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
||||||
|
FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin"))
|
||||||
|
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
|
||||||
|
FLTFirebaseStoragePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseStoragePlugin"))
|
||||||
FlutterLocalizationPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalizationPlugin"))
|
FlutterLocalizationPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalizationPlugin"))
|
||||||
|
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
||||||
GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin"))
|
GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin"))
|
||||||
MapsLauncherPlugin.register(with: registry.registrar(forPlugin: "MapsLauncherPlugin"))
|
MapsLauncherPlugin.register(with: registry.registrar(forPlugin: "MapsLauncherPlugin"))
|
||||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||||
|
|
|
||||||
180
pubspec.lock
180
pubspec.lock
|
|
@ -9,6 +9,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "82.0.0"
|
version: "82.0.0"
|
||||||
|
_flutterfire_internals:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: _flutterfire_internals
|
||||||
|
sha256: ff0a84a2734d9e1089f8aedd5c0af0061b82fb94e95260d943404e0ef2134b11
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.59"
|
||||||
analyzer:
|
analyzer:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -169,6 +177,30 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.2"
|
version: "1.1.2"
|
||||||
|
cloud_firestore:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: cloud_firestore
|
||||||
|
sha256: "2d33da4465bdb81b6685c41b535895065adcb16261beb398f5f3bbc623979e9c"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.6.12"
|
||||||
|
cloud_firestore_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: cloud_firestore_platform_interface
|
||||||
|
sha256: "413c4e01895cf9cb3de36fa5c219479e06cd4722876274ace5dfc9f13ab2e39b"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.6.12"
|
||||||
|
cloud_firestore_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: cloud_firestore_web
|
||||||
|
sha256: c1e30fc4a0fcedb08723fb4b1f12ee4e56d937cbf9deae1bda43cbb6367bb4cf
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.4.12"
|
||||||
code_builder:
|
code_builder:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -273,6 +305,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.7"
|
version: "2.0.7"
|
||||||
|
event_bus:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: event_bus
|
||||||
|
sha256: "1a55e97923769c286d295240048fc180e7b0768902c3c2e869fe059aafa15304"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.1"
|
||||||
fake_async:
|
fake_async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -329,6 +369,78 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.9.3+4"
|
version: "0.9.3+4"
|
||||||
|
firebase_auth:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: firebase_auth
|
||||||
|
sha256: "0fed2133bee1369ee1118c1fef27b2ce0d84c54b7819a2b17dada5cfec3b03ff"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.7.0"
|
||||||
|
firebase_auth_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: firebase_auth_platform_interface
|
||||||
|
sha256: "871c9df4ec9a754d1a793f7eb42fa3b94249d464cfb19152ba93e14a5966b386"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "7.7.3"
|
||||||
|
firebase_auth_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: firebase_auth_web
|
||||||
|
sha256: d9ada769c43261fd1b18decf113186e915c921a811bd5014f5ea08f4cf4bc57e
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.15.3"
|
||||||
|
firebase_core:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: firebase_core
|
||||||
|
sha256: "7be63a3f841fc9663342f7f3a011a42aef6a61066943c90b1c434d79d5c995c5"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.15.2"
|
||||||
|
firebase_core_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: firebase_core_platform_interface
|
||||||
|
sha256: "5dbc900677dcbe5873d22ad7fbd64b047750124f1f9b7ebe2a33b9ddccc838eb"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.0.0"
|
||||||
|
firebase_core_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: firebase_core_web
|
||||||
|
sha256: "0ed0dc292e8f9ac50992e2394e9d336a0275b6ae400d64163fdf0a8a8b556c37"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.24.1"
|
||||||
|
firebase_storage:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: firebase_storage
|
||||||
|
sha256: "958fc88a7ef0b103e694d30beed515c8f9472dde7e8459b029d0e32b8ff03463"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "12.4.10"
|
||||||
|
firebase_storage_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: firebase_storage_platform_interface
|
||||||
|
sha256: d2661c05293c2a940c8ea4bc0444e1b5566c79dd3202c2271140c082c8cd8dd4
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.2.10"
|
||||||
|
firebase_storage_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: firebase_storage_web
|
||||||
|
sha256: "629a557c5e1ddb97a3666cbf225e97daa0a66335dbbfdfdce113ef9f881e833f"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.10.17"
|
||||||
fixnum:
|
fixnum:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -419,6 +531,54 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.28"
|
version: "2.0.28"
|
||||||
|
flutter_secure_storage:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_secure_storage
|
||||||
|
sha256: "9cad52d75ebc511adfae3d447d5d13da15a55a92c9410e50f67335b6d21d16ea"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "9.2.4"
|
||||||
|
flutter_secure_storage_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_secure_storage_linux
|
||||||
|
sha256: be76c1d24a97d0b98f8b54bce6b481a380a6590df992d0098f868ad54dc8f688
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.3"
|
||||||
|
flutter_secure_storage_macos:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_secure_storage_macos
|
||||||
|
sha256: "6c0a2795a2d1de26ae202a0d78527d163f4acbb11cde4c75c670f3a0fc064247"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.3"
|
||||||
|
flutter_secure_storage_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_secure_storage_platform_interface
|
||||||
|
sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.2"
|
||||||
|
flutter_secure_storage_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_secure_storage_web
|
||||||
|
sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.1"
|
||||||
|
flutter_secure_storage_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_secure_storage_windows
|
||||||
|
sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.2"
|
||||||
flutter_shaders:
|
flutter_shaders:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -649,10 +809,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: js
|
name: js
|
||||||
sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc"
|
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.2"
|
version: "0.6.7"
|
||||||
json_annotation:
|
json_annotation:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -741,6 +901,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.0"
|
version: "2.0.0"
|
||||||
|
mqtt_client:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: mqtt_client
|
||||||
|
sha256: "85fa7e9aad03fbd6a54fcaf46127579f3e049b4834557a06e49aea7869b04c94"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "10.8.0"
|
||||||
nested:
|
nested:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -1346,6 +1514,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.3"
|
version: "3.0.3"
|
||||||
|
win32:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: win32
|
||||||
|
sha256: "329edf97fdd893e0f1e3b9e88d6a0e627128cc17cc316a8d67fda8f1451178ba"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.13.0"
|
||||||
xdg_directories:
|
xdg_directories:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,12 @@ dependencies:
|
||||||
qr_flutter: ^4.1.0
|
qr_flutter: ^4.1.0
|
||||||
flutter_staggered_grid_view: ^0.7.0
|
flutter_staggered_grid_view: ^0.7.0
|
||||||
image_picker: ^1.1.2
|
image_picker: ^1.1.2
|
||||||
|
mqtt_client: ^10.8.0
|
||||||
|
firebase_core: ^3.15.2
|
||||||
|
firebase_auth: ^5.7.0
|
||||||
|
cloud_firestore: ^5.6.12
|
||||||
|
firebase_storage: ^12.4.10
|
||||||
|
flutter_secure_storage: ^9.2.4
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
|
||||||
|
|
@ -6,18 +6,33 @@
|
||||||
|
|
||||||
#include "generated_plugin_registrant.h"
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
|
#include <cloud_firestore/cloud_firestore_plugin_c_api.h>
|
||||||
#include <file_selector_windows/file_selector_windows.h>
|
#include <file_selector_windows/file_selector_windows.h>
|
||||||
|
#include <firebase_auth/firebase_auth_plugin_c_api.h>
|
||||||
|
#include <firebase_core/firebase_core_plugin_c_api.h>
|
||||||
|
#include <firebase_storage/firebase_storage_plugin_c_api.h>
|
||||||
#include <flutter_localization/flutter_localization_plugin_c_api.h>
|
#include <flutter_localization/flutter_localization_plugin_c_api.h>
|
||||||
|
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
|
||||||
#include <geolocator_windows/geolocator_windows.h>
|
#include <geolocator_windows/geolocator_windows.h>
|
||||||
#include <maps_launcher/maps_launcher_plugin.h>
|
#include <maps_launcher/maps_launcher_plugin.h>
|
||||||
#include <permission_handler_windows/permission_handler_windows_plugin.h>
|
#include <permission_handler_windows/permission_handler_windows_plugin.h>
|
||||||
#include <url_launcher_windows/url_launcher_windows.h>
|
#include <url_launcher_windows/url_launcher_windows.h>
|
||||||
|
|
||||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||||
|
CloudFirestorePluginCApiRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("CloudFirestorePluginCApi"));
|
||||||
FileSelectorWindowsRegisterWithRegistrar(
|
FileSelectorWindowsRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
||||||
|
FirebaseAuthPluginCApiRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("FirebaseAuthPluginCApi"));
|
||||||
|
FirebaseCorePluginCApiRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("FirebaseCorePluginCApi"));
|
||||||
|
FirebaseStoragePluginCApiRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("FirebaseStoragePluginCApi"));
|
||||||
FlutterLocalizationPluginCApiRegisterWithRegistrar(
|
FlutterLocalizationPluginCApiRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("FlutterLocalizationPluginCApi"));
|
registry->GetRegistrarForPlugin("FlutterLocalizationPluginCApi"));
|
||||||
|
FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
|
||||||
GeolocatorWindowsRegisterWithRegistrar(
|
GeolocatorWindowsRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("GeolocatorWindows"));
|
registry->GetRegistrarForPlugin("GeolocatorWindows"));
|
||||||
MapsLauncherPluginRegisterWithRegistrar(
|
MapsLauncherPluginRegisterWithRegistrar(
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,13 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
|
cloud_firestore
|
||||||
file_selector_windows
|
file_selector_windows
|
||||||
|
firebase_auth
|
||||||
|
firebase_core
|
||||||
|
firebase_storage
|
||||||
flutter_localization
|
flutter_localization
|
||||||
|
flutter_secure_storage_windows
|
||||||
geolocator_windows
|
geolocator_windows
|
||||||
maps_launcher
|
maps_launcher
|
||||||
permission_handler_windows
|
permission_handler_windows
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue