firebase ssh updated

This commit is contained in:
mohamadmahdi jebeli 2025-09-24 08:38:07 +03:30
parent 87ec98def4
commit 08d6e4e9e9
9 changed files with 681 additions and 46 deletions

View File

@ -9,7 +9,7 @@ plugins {
android {
namespace = "com.example.lba"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
ndkVersion = "27.0.12077973"
compileOptions {
// استفاده از جاوا ۸ برای سازگاری بهتر

View File

@ -28,5 +28,5 @@ plugins {
// The Kotlin plugin version is also managed.
id("org.jetbrains.kotlin.android") apply false
// اضافه کردن این خط برای تعریف پلاگین گوگل سرویسز
id("com.google.gms.google-services") version "4.4.1" apply false
id("com.google.gms.google-services") version "4.3.15" apply false
}

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
// Updated Kotlin version to the latest stable release
id("org.jetbrains.kotlin.android") version "2.0.0" apply false
}

1
firebase.json Normal file
View File

@ -0,0 +1 @@
{"flutter":{"platforms":{"android":{"default":{"projectId":"lba-app-c4a7e","appId":"1:81202355575:android:6d4c7db49b6120f8239572","fileOutput":"android/app/google-services.json"}},"dart":{"lib/firebase_options.dart":{"projectId":"lba-app-c4a7e","configurations":{"android":"1:81202355575:android:6d4c7db49b6120f8239572","ios":"1:81202355575:ios:d40682afd403fc5b239572","macos":"1:81202355575:ios:d40682afd403fc5b239572","web":"1:81202355575:web:0cf25bf19cb4a8a1239572","windows":"1:81202355575:web:03aeac4a6dbda4d3239572"}}}}}}

View File

@ -0,0 +1,93 @@
// 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:
return macos;
case TargetPlatform.windows:
return windows;
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: 'AIzaSyCeRwWv-Pep_624hoUVIw-SSlicDVRHGiY',
appId: '1:81202355575:web:0cf25bf19cb4a8a1239572',
messagingSenderId: '81202355575',
projectId: 'lba-app-c4a7e',
authDomain: 'lba-app-c4a7e.firebaseapp.com',
storageBucket: 'lba-app-c4a7e.firebasestorage.app',
measurementId: 'G-8D14ETTE9T',
);
static const FirebaseOptions android = FirebaseOptions(
apiKey: 'AIzaSyB22NqmwB_PpI1s37xbc2ABJ5_COEHeC8g',
appId: '1:81202355575:android:6d4c7db49b6120f8239572',
messagingSenderId: '81202355575',
projectId: 'lba-app-c4a7e',
storageBucket: 'lba-app-c4a7e.firebasestorage.app',
);
static const FirebaseOptions ios = FirebaseOptions(
apiKey: 'AIzaSyCJsr8Baaw0CbucB0zrB_kWv_7fcBNRwIk',
appId: '1:81202355575:ios:d40682afd403fc5b239572',
messagingSenderId: '81202355575',
projectId: 'lba-app-c4a7e',
storageBucket: 'lba-app-c4a7e.firebasestorage.app',
androidClientId: '81202355575-fchlrrp4fu3irskh6co1ep8i04oi3adr.apps.googleusercontent.com',
iosClientId: '81202355575-hcgc97f7glhbpa8h7iqq4d57efcsrtqo.apps.googleusercontent.com',
iosBundleId: 'com.example.lba',
);
static const FirebaseOptions macos = FirebaseOptions(
apiKey: 'AIzaSyCJsr8Baaw0CbucB0zrB_kWv_7fcBNRwIk',
appId: '1:81202355575:ios:d40682afd403fc5b239572',
messagingSenderId: '81202355575',
projectId: 'lba-app-c4a7e',
storageBucket: 'lba-app-c4a7e.firebasestorage.app',
androidClientId: '81202355575-fchlrrp4fu3irskh6co1ep8i04oi3adr.apps.googleusercontent.com',
iosClientId: '81202355575-hcgc97f7glhbpa8h7iqq4d57efcsrtqo.apps.googleusercontent.com',
iosBundleId: 'com.example.lba',
);
static const FirebaseOptions windows = FirebaseOptions(
apiKey: 'AIzaSyCeRwWv-Pep_624hoUVIw-SSlicDVRHGiY',
appId: '1:81202355575:web:03aeac4a6dbda4d3239572',
messagingSenderId: '81202355575',
projectId: 'lba-app-c4a7e',
authDomain: 'lba-app-c4a7e.firebaseapp.com',
storageBucket: 'lba-app-c4a7e.firebasestorage.app',
measurementId: 'G-2EZ542PHK0',
);
}

View File

@ -48,6 +48,7 @@ class _ItemState extends State<Item> with TickerProviderStateMixin {
"yesCount": 2,
"noCount": 0,
"date": "Jun 09, 2025",
"userLiked": null,
},
{
"name": "Khalid A",
@ -57,6 +58,7 @@ class _ItemState extends State<Item> with TickerProviderStateMixin {
"yesCount": 5,
"noCount": 10,
"date": "Dec 26, 2024",
"userLiked": null,
},
{
"name": "Khalid A",
@ -66,6 +68,7 @@ class _ItemState extends State<Item> with TickerProviderStateMixin {
"yesCount": 5,
"noCount": 10,
"date": "Dec 26, 2024",
"userLiked": null,
},
{
"name": "Sara M",
@ -75,6 +78,7 @@ class _ItemState extends State<Item> with TickerProviderStateMixin {
"yesCount": 2,
"noCount": 0,
"date": "Jun 09, 2025",
"userLiked": null,
},
];
@ -246,6 +250,32 @@ class _ItemState extends State<Item> with TickerProviderStateMixin {
yesCount: review['yesCount'],
noCount: review['noCount'],
date: review['date'],
initialLikeState: review['userLiked'],
onLikeDislike: (isLike) {
setState(() {
if (isLike) {
if (review['userLiked'] == true) {
review['userLiked'] = null;
} else {
if (review['userLiked'] == false) {
review['noCount'] = (review['noCount'] as int) - 1;
}
review['userLiked'] = true;
review['yesCount'] = (review['yesCount'] as int) + 1;
}
} else {
if (review['userLiked'] == false) {
review['userLiked'] = null;
} else {
if (review['userLiked'] == true) {
review['yesCount'] = (review['yesCount'] as int) - 1;
}
review['userLiked'] = false;
review['noCount'] = (review['noCount'] as int) + 1;
}
}
});
},
),
),
);
@ -279,7 +309,7 @@ class _ItemState extends State<Item> with TickerProviderStateMixin {
),
],
),
const SizedBox(height: 20),
const SizedBox(height: 7),
const PriceReserveWidget(),
],
),

View File

@ -2,4 +2,7 @@ class SharedPreferencesKey {
static const String token = 'token';
static const String isDarkMode = 'isDarkMode';
static const String hasManualThemeOverride = 'hasManualThemeOverride';
static const String hasSeenOnboarding = 'hasSeenOnboarding';
static const String selectedLanguage = 'selectedLanguage';
static const String selectedFlag = 'selectedFlag';
}

View File

@ -4,6 +4,7 @@ import 'package:lba/gen/assets.gen.dart';
import 'package:lba/res/colors.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:lba/screens/auth/login_page.dart';
Future<void> showLogoutDialog(BuildContext context) async {
showDialog(
@ -121,11 +122,22 @@ class _AnimatedLogoutDialogState extends State<_AnimatedLogoutDialog>
await GoogleSignIn().signOut();
await FirebaseAuth.instance.signOut();
print('✅ Logout successful');
if (context.mounted) {
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (context) => const LoginPage(),
),
(Route<dynamic> route) => false,
);
}
} catch (e) {
print('❌ Logout error: $e');
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('خطا در خروج: $e')),
);
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('خطا در خروج: $e')),
);
}
}
},
child: Text(

View File

@ -5,14 +5,17 @@ import 'package:lba/res/colors.dart';
import 'package:lba/widgets/buildWarpedInfo.dart';
import 'package:lba/widgets/rate.dart';
class Reviews extends StatelessWidget {
enum LikeState { none, liked, disliked }
class Reviews extends StatefulWidget {
final String name;
final String comment;
final double rate;
final int yesCount;
final int noCount;
final String date;
final Function(bool isLike)? onLikeDislike;
final bool? initialLikeState;
const Reviews({
super.key,
@ -22,7 +25,483 @@ class Reviews extends StatelessWidget {
required this.yesCount,
required this.noCount,
required this.date,
});
this.onLikeDislike,
this.initialLikeState,
});
@override
State<Reviews> createState() => _ReviewsState();
}
class _ReviewsState extends State<Reviews> with TickerProviderStateMixin {
LikeState _likeState = LikeState.none;
int _currentYesCount = 0;
int _currentNoCount = 0;
late AnimationController _likeScaleController;
late AnimationController _dislikeScaleController;
late AnimationController _particleController;
late AnimationController _pulseController;
late AnimationController _countController;
late AnimationController _likeIconController;
late AnimationController _dislikeIconController;
late Animation<double> _likeScaleAnimation;
late Animation<double> _dislikeScaleAnimation;
late Animation<double> _particleAnimation;
late Animation<double> _pulseAnimation;
late Animation<double> _countAnimation;
late Animation<Color?> _likeColorAnimation;
late Animation<Color?> _dislikeColorAnimation;
late Animation<double> _likeIconRotationAnimation;
late Animation<double> _dislikeIconRotationAnimation;
late Animation<double> _likeIconScaleAnimation;
late Animation<double> _dislikeIconScaleAnimation;
@override
void initState() {
super.initState();
_currentYesCount = widget.yesCount;
_currentNoCount = widget.noCount;
if (widget.initialLikeState == true) {
_likeState = LikeState.liked;
} else if (widget.initialLikeState == false) {
_likeState = LikeState.disliked;
}
_likeScaleController = AnimationController(
duration: const Duration(milliseconds: 600),
vsync: this,
);
_dislikeScaleController = AnimationController(
duration: const Duration(milliseconds: 600),
vsync: this,
);
_particleController = AnimationController(
duration: const Duration(milliseconds: 1200),
vsync: this,
);
_pulseController = AnimationController(
duration: const Duration(milliseconds: 800),
vsync: this,
);
_countController = AnimationController(
duration: const Duration(milliseconds: 400),
vsync: this,
);
_likeIconController = AnimationController(
duration: const Duration(milliseconds: 500),
vsync: this,
);
_dislikeIconController = AnimationController(
duration: const Duration(milliseconds: 500),
vsync: this,
);
_likeScaleAnimation = Tween<double>(begin: 1.0, end: 1.08).animate(
CurvedAnimation(parent: _likeScaleController, curve: Curves.easeOutBack),
);
_dislikeScaleAnimation = Tween<double>(begin: 1.0, end: 1.08).animate(
CurvedAnimation(
parent: _dislikeScaleController,
curve: Curves.easeOutBack,
),
);
_particleAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _particleController, curve: Curves.easeOutCubic),
);
_pulseAnimation = Tween<double>(begin: 1.0, end: 1.15).animate(
CurvedAnimation(parent: _pulseController, curve: Curves.easeInOut),
);
_countAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _countController, curve: Curves.bounceOut),
);
_likeColorAnimation = ColorTween(
begin: AppColors.hint,
end: const Color(0xFF4CAF50),
).animate(_likeScaleController);
_dislikeColorAnimation = ColorTween(
begin: AppColors.hint,
end: const Color(0xFFF44336),
).animate(_dislikeScaleController);
_likeIconRotationAnimation = Tween<double>(begin: 0.0, end: 0.1).animate(
CurvedAnimation(parent: _likeIconController, curve: Curves.elasticOut),
);
_dislikeIconRotationAnimation = Tween<double>(
begin: 0.0,
end: -0.1,
).animate(
CurvedAnimation(parent: _dislikeIconController, curve: Curves.elasticOut),
);
_likeIconScaleAnimation = Tween<double>(begin: 1.0, end: 1.3).animate(
CurvedAnimation(parent: _likeIconController, curve: Curves.elasticOut),
);
_dislikeIconScaleAnimation = Tween<double>(begin: 1.0, end: 1.3).animate(
CurvedAnimation(parent: _dislikeIconController, curve: Curves.elasticOut),
);
}
@override
void dispose() {
_likeScaleController.dispose();
_dislikeScaleController.dispose();
_particleController.dispose();
_pulseController.dispose();
_countController.dispose();
_likeIconController.dispose();
_dislikeIconController.dispose();
super.dispose();
}
void _onLikeTap() async {
if (_likeState == LikeState.liked) {
setState(() {
_likeState = LikeState.none;
_currentYesCount--;
});
_likeScaleController.reverse();
} else {
if (_likeState == LikeState.disliked) {
_currentNoCount--;
_dislikeScaleController.reverse();
}
setState(() {
_likeState = LikeState.liked;
_currentYesCount++;
});
_likeScaleController.forward();
_likeIconController.forward().then((_) => _likeIconController.reverse());
_particleController.forward().then((_) => _particleController.reset());
_pulseController.forward().then((_) => _pulseController.reverse());
_countController.forward().then((_) => _countController.reset());
}
widget.onLikeDislike?.call(true);
}
void _onDislikeTap() async {
if (_likeState == LikeState.disliked) {
setState(() {
_likeState = LikeState.none;
_currentNoCount--;
});
_dislikeScaleController.reverse();
} else {
if (_likeState == LikeState.liked) {
_currentYesCount--;
_likeScaleController.reverse();
}
setState(() {
_likeState = LikeState.disliked;
_currentNoCount++;
});
_dislikeScaleController.forward();
_dislikeIconController.forward().then(
(_) => _dislikeIconController.reverse(),
);
_particleController.forward().then((_) => _particleController.reset());
_pulseController.forward().then((_) => _pulseController.reverse());
_countController.forward().then((_) => _countController.reset());
}
widget.onLikeDislike?.call(false);
}
Widget _buildParticleEffect({required bool isLike}) {
return AnimatedBuilder(
animation: _particleAnimation,
builder: (context, child) {
return CustomPaint(
painter: ParticleEffectPainter(
progress: _particleAnimation.value,
color: isLike ? const Color(0xFF4CAF50) : const Color(0xFFF44336),
),
size: const Size(10, 10),
);
},
);
}
Widget _buildAnimatedLikeButton() {
return AnimatedBuilder(
animation: Listenable.merge([
_likeScaleController,
_pulseController,
_countController,
]),
builder: (context, child) {
final isLiked = _likeState == LikeState.liked;
return GestureDetector(
onTap: _onLikeTap,
child: Stack(
clipBehavior: Clip.none,
children: [
if (isLiked)
Positioned(
child: Transform.scale(
scale: _pulseAnimation.value,
child: Container(
width: 30,
height: 25,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
color: _likeColorAnimation.value?.withOpacity(0.2),
),
),
),
),
Transform.scale(
scale: _likeScaleAnimation.value,
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 4,
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
color:
isLiked
? _likeColorAnimation.value?.withOpacity(0.1)
: null,
border:
isLiked
? Border.all(
color:
_likeColorAnimation.value ??
Colors.transparent,
width: 1.5,
)
: null,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
AnimatedBuilder(
animation: Listenable.merge([
_likeColorAnimation,
_likeIconController,
]),
builder: (context, child) {
return Transform.rotate(
angle: _likeIconRotationAnimation.value,
child: Transform.scale(
scale:
isLiked ? _likeIconScaleAnimation.value : 1.0,
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 200),
child: SvgPicture.asset(
isLiked
? Assets.icons.like.path
: Assets.icons.like.path,
key: ValueKey(isLiked ? 'liked' : 'normal'),
color:
isLiked
? _likeColorAnimation.value
: AppColors.hint,
width: 16,
height: 16,
),
),
),
);
},
),
const SizedBox(width: 4),
AnimatedBuilder(
animation: _countAnimation,
builder: (context, child) {
return Transform.scale(
scale: 1.0 + (_countAnimation.value * 0.1),
child: AnimatedBuilder(
animation: _likeColorAnimation,
builder: (context, child) {
return Text(
"Yes ($_currentYesCount)",
style: TextStyle(
color:
isLiked
? _likeColorAnimation.value
: AppColors.hint,
fontWeight:
isLiked
? FontWeight.w600
: FontWeight.normal,
fontSize: 12 + (_countAnimation.value * 1),
),
);
},
),
);
},
),
],
),
),
),
if (isLiked)
Positioned(
top: -5,
right: -5,
child: _buildParticleEffect(isLike: true),
),
],
),
);
},
);
}
Widget _buildAnimatedDislikeButton() {
return AnimatedBuilder(
animation: Listenable.merge([
_dislikeScaleController,
_pulseController,
_countController,
]),
builder: (context, child) {
final isDisliked = _likeState == LikeState.disliked;
return GestureDetector(
onTap: _onDislikeTap,
child: Stack(
clipBehavior: Clip.none,
children: [
if (isDisliked)
Positioned(
child: Transform.scale(
scale: _pulseAnimation.value,
child: Container(
width: 30,
height: 25,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
color: _dislikeColorAnimation.value?.withOpacity(0.2),
),
),
),
),
Transform.scale(
scale: _dislikeScaleAnimation.value,
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 4,
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
color:
isDisliked
? _dislikeColorAnimation.value?.withOpacity(0.1)
: null,
border:
isDisliked
? Border.all(
color:
_dislikeColorAnimation.value ??
Colors.transparent,
width: 1.5,
)
: null,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
AnimatedBuilder(
animation: Listenable.merge([
_dislikeColorAnimation,
_dislikeIconController,
]),
builder: (context, child) {
return Transform.rotate(
angle: _dislikeIconRotationAnimation.value,
child: Transform.scale(
scale:
isDisliked
? _dislikeIconScaleAnimation.value
: 1.0,
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 200),
child: SvgPicture.asset(
isDisliked
? Assets.icons.dislike.path
: Assets.icons.dislike.path,
key: ValueKey(
isDisliked ? 'disliked' : 'normal',
),
color:
isDisliked
? _dislikeColorAnimation.value
: AppColors.hint,
width: 16,
height: 16,
),
),
),
);
},
),
const SizedBox(width: 4),
AnimatedBuilder(
animation: _countAnimation,
builder: (context, child) {
return Transform.scale(
scale: 1.0 + (_countAnimation.value * 0.1),
child: AnimatedBuilder(
animation: _dislikeColorAnimation,
builder: (context, child) {
return Text(
"No ($_currentNoCount)",
style: TextStyle(
color:
isDisliked
? _dislikeColorAnimation.value
: AppColors.hint,
fontWeight:
isDisliked
? FontWeight.w600
: FontWeight.normal,
fontSize: 12 + (_countAnimation.value * 1),
),
);
},
),
);
},
),
],
),
),
),
if (isDisliked)
Positioned(
top: -5,
right: -5,
child: _buildParticleEffect(isLike: false),
),
],
),
);
},
);
}
@override
Widget build(BuildContext context) {
@ -38,8 +517,8 @@ class Reviews extends StatelessWidget {
children: [
Row(
children: [
Text(name),
SizedBox(width: 2),
Text(widget.name),
const SizedBox(width: 2),
Image.asset(Assets.images.usa.path),
],
),
@ -49,54 +528,68 @@ class Reviews extends StatelessWidget {
),
],
),
CustomStarRating(rating: rate),
CustomStarRating(rating: widget.rate),
],
),
SizedBox(height: 7),
buildWrappedInfo(
"",
comment,
),
SizedBox(height: 10,),
const SizedBox(height: 7),
buildWrappedInfo("", widget.comment),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
InkWell(
child: Row(
children: [
SvgPicture.asset(Assets.icons.like.path),
SizedBox(width: 1,),
Text("Yes (${yesCount.toString()})",style: TextStyle(
color: AppColors.hint,
),),
],
),
),
SizedBox(width: 10,),
InkWell(
child: Row(
children: [
SvgPicture.asset(Assets.icons.dislike.path),
SizedBox(width: 1,),
Text("No (${noCount.toString()})",style: TextStyle(
color: AppColors.hint,
),),
],
),
),
_buildAnimatedLikeButton(),
const SizedBox(width: 7),
_buildAnimatedDislikeButton(),
],
),
Text(date,style: TextStyle(
color: AppColors.hint
),)
Text(widget.date, style: TextStyle(color: AppColors.hint)),
],
),
SizedBox(height: 5,),
Divider(thickness: 1.2, height: 2),
const SizedBox(height: 5),
const Divider(thickness: 1.2, height: 2),
],
),
);
}
}
class ParticleEffectPainter extends CustomPainter {
final double progress;
final Color color;
ParticleEffectPainter({required this.progress, required this.color});
@override
void paint(Canvas canvas, Size size) {
if (progress == 0.0) return;
final paint =
Paint()
..color = color.withOpacity(1.0 - progress)
..style = PaintingStyle.fill;
final particles = [
[0.2, -0.3, 3.0],
[0.5, -0.5, 2.5],
[0.8, -0.2, 2.0],
[0.1, -0.6, 1.5],
[0.9, -0.4, 2.0],
];
for (final particle in particles) {
final x =
size.width * particle[0] + (progress * 20 * (particle[0] - 0.5));
final y = size.height * particle[1] * progress;
final particleSize = particle[2] * (1.0 - progress * 0.5);
canvas.drawCircle(Offset(x, y), particleSize, paint);
}
}
@override
bool shouldRepaint(ParticleEffectPainter oldDelegate) {
return oldDelegate.progress != progress;
}
}