connect to firebase

This commit is contained in:
mohamadmahdi jebeli 2025-07-21 16:34:52 +03:30
parent 3dc25e64a8
commit 21aa7bd30f
16 changed files with 319 additions and 92 deletions

View File

@ -1,5 +1,8 @@
plugins {
id("com.android.application")
// START: FlutterFire Configuration
id("com.google.gms.google-services")
// END: FlutterFire Configuration
id("kotlin-android")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin")

View File

@ -0,0 +1,29 @@
{
"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": []
}
}
}
],
"configuration_version": "1"
}

View File

@ -1,5 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

View File

@ -19,6 +19,9 @@ pluginManagement {
plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
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
}

1
firebase.json Normal file
View File

@ -0,0 +1 @@
{"flutter":{"platforms":{"android":{"default":{"projectId":"proxibuy-3b5e0","appId":"1:800272350428:android:d6af1e013bae09d9c78819","fileOutput":"android/app/google-services.json"}},"dart":{"lib/firebase_options.dart":{"projectId":"proxibuy-3b5e0","configurations":{"android":"1:800272350428:android:d6af1e013bae09d9c78819","ios":"1:800272350428:ios:715e52af5f7d922ac78819","web":"1:800272350428:web:0d3454e8d3783408c78819"}}}}}}

75
lib/firebase_options.dart Normal file
View File

@ -0,0 +1,75 @@
// 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) {
return web;
}
switch (defaultTargetPlatform) {
case TargetPlatform.android:
return android;
case TargetPlatform.iOS:
return ios;
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 web = FirebaseOptions(
apiKey: 'AIzaSyBR7LPmu-0_pG3aRwP0SAg9Xh1DKFMl76s',
appId: '1:800272350428:web:0d3454e8d3783408c78819',
messagingSenderId: '800272350428',
projectId: 'proxibuy-3b5e0',
authDomain: 'proxibuy-3b5e0.firebaseapp.com',
storageBucket: 'proxibuy-3b5e0.firebasestorage.app',
measurementId: 'G-XVGY4HZ5M6',
);
static const FirebaseOptions android = FirebaseOptions(
apiKey: 'AIzaSyCMGweIbZBsFNXabKRJJJcVLPwcmqhuSwg',
appId: '1:800272350428:android:d6af1e013bae09d9c78819',
messagingSenderId: '800272350428',
projectId: 'proxibuy-3b5e0',
storageBucket: 'proxibuy-3b5e0.firebasestorage.app',
);
static const FirebaseOptions ios = FirebaseOptions(
apiKey: 'AIzaSyCspVOPpX4shZThL5jaqy-BwOtveVDUsQs',
appId: '1:800272350428:ios:715e52af5f7d922ac78819',
messagingSenderId: '800272350428',
projectId: 'proxibuy-3b5e0',
storageBucket: 'proxibuy-3b5e0.firebasestorage.app',
iosBundleId: 'com.example.businessPanel',
);
}

View File

@ -7,8 +7,16 @@ import 'package:flutter_bloc/flutter_bloc.dart';
// ignore: depend_on_referenced_packages
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:persian_datetime_picker/persian_datetime_picker.dart';
import 'package:firebase_core/firebase_core.dart';
import 'firebase_options.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
void main() {
runApp(const MyApp());
}

View File

@ -1,4 +1,4 @@
import 'dart:io'; // کتابخانه برای SocketException اضافه شد
import 'dart:io';
import 'package:bloc/bloc.dart';
import 'package:business_panel/core/config/api_config.dart';
import 'package:business_panel/core/services/token_storage_service.dart';
@ -30,24 +30,24 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
return;
}
await _dio.get(
final response = await _dio.get(
ApiConfig.checkShopStatus,
options: Options(
headers: {'Authorization': 'Bearer $token'},
),
);
emit(ShopExists());
// *** CHANGE IS HERE: Parse shop name from response ***
final shopName = response.data?['data']?['Name'] as String? ?? 'فروشگاه شما';
emit(ShopExists(shopName));
} on DioException catch (e) {
// --- منطق تشخیص آفلاین بودن بهبود یافت ---
final isOffline = e.error is SocketException ||
e.type == DioExceptionType.connectionError ||
e.type == DioExceptionType.sendTimeout ||
e.type == DioExceptionType.receiveTimeout;
if (isOffline) {
// اگر کاربر آفلاین باشد، این حالت صادر میشود
emit(AuthOffline());
} else if (e.response?.statusCode == 404) {
emit(NoShop());

View File

@ -6,20 +6,18 @@ class AuthInitial extends AuthState {}
class AuthLoading extends AuthState {}
// ADDED: State to indicate the result of the token check
class AuthChecked extends AuthState {
final bool hasToken;
AuthChecked(this.hasToken);
}
// ADDED: State for when the user has a shop
class ShopExists extends AuthState {}
class ShopExists extends AuthState {
final String shopName;
ShopExists(this.shopName);
}
// ADDED: State for when the user is logged in but has no shop
class NoShop extends AuthState {}
// *** CHANGE IS HERE: Added state for offline mode ***
// ADDED: State for when the user is authenticated but offline
class AuthOffline extends AuthState {}
class AuthCodeSentSuccess extends AuthState {}

View File

@ -5,6 +5,8 @@ import 'package:business_panel/gen/assets.gen.dart';
import 'package:business_panel/presentation/discount/bloc/discount_bloc.dart';
import 'package:business_panel/presentation/discount/bloc/discount_event.dart';
import 'package:business_panel/presentation/discount/bloc/discount_state.dart';
import 'package:business_panel/presentation/home/bloc/home_bloc.dart';
import 'package:business_panel/presentation/pages/home_page.dart';
import 'package:business_panel/presentation/widgets/custom_app_bar.dart';
import 'package:business_panel/presentation/widgets/info_popup.dart';
import 'package:flutter/material.dart';
@ -124,7 +126,16 @@ class _AddDiscountViewState extends State<_AddDiscountView> {
"تخفیف با موفقیت ${_isEditMode ? 'ویرایش' : 'ثبت'} شد!"),
backgroundColor: Colors.green),
);
Navigator.of(context).pop(true);
// HIGHLIGHT: مسیریابی به صفحهی اصلی و حذف تمام صفحات قبلی
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (_) => BlocProvider(
create: (context) => HomeBloc()..add(FetchDiscounts()),
child: const HomePage(),
),
),
(route) => false,
);
}
if (state.errorMessage != null && !state.isLoadingDetails) {
ScaffoldMessenger.of(context).showSnackBar(

View File

@ -9,7 +9,9 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
class HomePage extends StatelessWidget {
const HomePage({super.key});
// *** CHANGE IS HERE: Added shopName parameter ***
final String? shopName;
const HomePage({super.key, this.shopName});
@override
Widget build(BuildContext context) {
@ -17,7 +19,6 @@ class HomePage extends StatelessWidget {
appBar: const CustomAppBar(),
body: BlocBuilder<HomeBloc, HomeState>(
builder: (context, state) {
// --- منطق اصلاحشده برای مدیریت وضعیت ---
if (state is HomeError) {
return Center(
child: Padding(
@ -29,7 +30,8 @@ class HomePage extends StatelessWidget {
if (state is HomeLoaded) {
if (state.discounts.isEmpty) {
return _buildEmptyState(context);
// *** CHANGE IS HERE: Pass shopName to empty state ***
return _buildEmptyState(context, shopName);
}
return RefreshIndicator(
onRefresh: () async {
@ -55,14 +57,17 @@ class HomePage extends StatelessWidget {
);
}
Widget _buildEmptyState(BuildContext context) {
Widget _buildEmptyState(BuildContext context, String? shopName) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset(Assets.icons.emptyHome, height: 300),
const SizedBox(height: 35),
const Text("سلام!", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 17)),
Text(
"سلام ${shopName ?? 'کاربر'}",
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 17),
),
const SizedBox(height: 15),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24.0),

View File

@ -144,8 +144,6 @@ class _OtpPageState extends State<OtpPage> {
)
else
const SizedBox(height: 32),
// *** CHANGE IS HERE ***
BlocConsumer<AuthBloc, AuthState>(
listener: (context, state) {
if (state is AuthFailure) {
@ -158,25 +156,21 @@ class _OtpPageState extends State<OtpPage> {
if (state is ShopExists) {
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder:
(_) => BlocProvider(
// بلافاصله پس از ایجاد، رویداد دریافت تخفیفها را ارسال میکند
create:
(context) =>
HomeBloc()..add(FetchDiscounts()),
child: const HomePage(),
),
builder: (_) => BlocProvider(
create: (context) => HomeBloc()..add(FetchDiscounts()),
// *** CHANGE IS HERE: Pass shopName to HomePage constructor ***
child: HomePage(shopName: state.shopName),
),
),
);
}
if (state is NoShop) {
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder:
(_) => BlocProvider(
create: (context) => StoreInfoBloc(),
child: const StoreInfoPage(),
),
builder: (_) => BlocProvider(
create: (context) => StoreInfoBloc(),
child: const StoreInfoPage(),
),
),
(route) => false,
);
@ -212,20 +206,19 @@ class _OtpPageState extends State<OtpPage> {
builder: (context, canResend, child) {
return canResend
? TextButton(
onPressed: _resendOtp,
child: const Text(
"ارسال مجدد کد",
style: TextStyle(color: AppColors.active),
),
)
onPressed: _resendOtp,
child: const Text(
"ارسال مجدد کد",
style: TextStyle(color: AppColors.active),
),
)
: ValueListenableBuilder<int>(
valueListenable: _otpTimer.remainingSeconds,
builder:
(context, seconds, child) => Text(
"${_otpTimer.formatTime()} تا دریافت مجدد",
style: const TextStyle(color: Colors.grey),
),
);
valueListenable: _otpTimer.remainingSeconds,
builder: (context, seconds, child) => Text(
"${_otpTimer.formatTime()} تا دریافت مجدد",
style: const TextStyle(color: Colors.grey),
),
);
},
),
],
@ -263,15 +256,14 @@ class _OtpPageState extends State<OtpPage> {
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(
color:
_hasError
? Colors.red
: (Theme.of(context)
.inputDecorationTheme
.enabledBorder
?.borderSide
.color ??
Colors.grey),
color: _hasError
? Colors.red
: (Theme.of(context)
.inputDecorationTheme
.enabledBorder
?.borderSide
.color ??
Colors.grey),
),
),
focusedBorder: OutlineInputBorder(
@ -311,12 +303,12 @@ class _OtpPageState extends State<OtpPage> {
final otpCode = _controllers.map((c) => c.text).join();
if (otpCode.length == 5) {
context.read<AuthBloc>().add(
VerifyOTPEvent(
otp: otpCode,
phoneNumber: widget.phoneNumber,
countryCode: widget.countryCode,
),
);
VerifyOTPEvent(
otp: otpCode,
phoneNumber: widget.phoneNumber,
countryCode: widget.countryCode,
),
);
}
}
@ -330,11 +322,11 @@ class _OtpPageState extends State<OtpPage> {
_isOtpComplete = false;
});
context.read<AuthBloc>().add(
SendOTPEvent(
phoneNumber: widget.phoneNumber,
countryCode: widget.countryCode,
),
);
SendOTPEvent(
phoneNumber: widget.phoneNumber,
countryCode: widget.countryCode,
),
);
_otpTimer.resetTimer();
}
}

View File

@ -36,36 +36,29 @@ class _SplashPageState extends State<SplashPage> {
} else if (state is ShopExists) {
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder:
(_) => BlocProvider(
// بلافاصله پس از ایجاد، رویداد دریافت تخفیفها را ارسال میکند
create: (context) => HomeBloc()..add(FetchDiscounts()),
child: const HomePage(),
),
builder: (_) => BlocProvider(
create: (context) => HomeBloc()..add(FetchDiscounts()),
// *** CHANGE IS HERE: Pass shopName to HomePage constructor ***
child: HomePage(shopName: state.shopName),
),
),
);
}
else if (state is AuthOffline) {
// کاربر توکن دارد ولی آفلاین است، به HomePage میرود
// HomePage خودش خطای شبکه را مدیریت خواهد کرد
} else if (state is AuthOffline) {
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder:
(_) => BlocProvider(
create: (context) => HomeBloc(),
child: const HomePage(),
),
builder: (_) => BlocProvider(
create: (context) => HomeBloc(),
child: const HomePage(),
),
),
);
} else if (state is NoShop) {
// کاربر توکن دارد ولی فروشگاه نساخته، به صفحه ساخت فروشگاه میرود
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder:
(_) => BlocProvider(
create: (context) => StoreInfoBloc(),
child: const StoreInfoPage(),
),
builder: (_) => BlocProvider(
create: (context) => StoreInfoBloc(),
child: const StoreInfoPage(),
),
),
);
} else if (state is AuthFailure) {

View File

@ -53,12 +53,12 @@ class _WorkingHoursDialogState extends State<WorkingHoursDialog> {
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildTimePicker("پایان", _endTime, (time) {
setState(() => _endTime = time);
}),
_buildTimePicker("شروع", _startTime, (time) {
setState(() => _startTime = time);
}),
_buildTimePicker("پایان", _endTime, (time) {
setState(() => _endTime = time);
}),
],
),
],

View File

@ -9,6 +9,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "85.0.0"
_flutterfire_internals:
dependency: transitive
description:
name: _flutterfire_internals
sha256: ff0a84a2734d9e1089f8aedd5c0af0061b82fb94e95260d943404e0ef2134b11
url: "https://pub.dev"
source: hosted
version: "1.3.59"
analyzer:
dependency: transitive
description:
@ -145,6 +153,30 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: transitive
description:
@ -321,6 +353,78 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: transitive
description:

View File

@ -49,6 +49,10 @@ dependencies:
dio: ^5.8.0+1
flutter_secure_storage: ^9.2.4
slide_countdown: ^2.0.2
firebase_core: ^3.15.2
firebase_auth: ^5.7.0
cloud_firestore: ^5.6.12
firebase_storage: ^12.4.10
dev_dependencies: