firebase ssh updated
This commit is contained in:
parent
87ec98def4
commit
08d6e4e9e9
|
|
@ -9,7 +9,7 @@ plugins {
|
||||||
android {
|
android {
|
||||||
namespace = "com.example.lba"
|
namespace = "com.example.lba"
|
||||||
compileSdk = flutter.compileSdkVersion
|
compileSdk = flutter.compileSdkVersion
|
||||||
ndkVersion = flutter.ndkVersion
|
ndkVersion = "27.0.12077973"
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
// استفاده از جاوا ۸ برای سازگاری بهتر
|
// استفاده از جاوا ۸ برای سازگاری بهتر
|
||||||
|
|
|
||||||
|
|
@ -28,5 +28,5 @@ plugins {
|
||||||
// The Kotlin plugin version is also managed.
|
// The Kotlin plugin version is also managed.
|
||||||
id("org.jetbrains.kotlin.android") apply false
|
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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
// Updated Kotlin version to the latest stable release
|
// Updated Kotlin version to the latest stable release
|
||||||
id("org.jetbrains.kotlin.android") version "2.0.0" apply false
|
id("org.jetbrains.kotlin.android") version "2.0.0" apply false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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"}}}}}}
|
||||||
|
|
@ -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',
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -48,6 +48,7 @@ class _ItemState extends State<Item> with TickerProviderStateMixin {
|
||||||
"yesCount": 2,
|
"yesCount": 2,
|
||||||
"noCount": 0,
|
"noCount": 0,
|
||||||
"date": "Jun 09, 2025",
|
"date": "Jun 09, 2025",
|
||||||
|
"userLiked": null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Khalid A",
|
"name": "Khalid A",
|
||||||
|
|
@ -57,6 +58,7 @@ class _ItemState extends State<Item> with TickerProviderStateMixin {
|
||||||
"yesCount": 5,
|
"yesCount": 5,
|
||||||
"noCount": 10,
|
"noCount": 10,
|
||||||
"date": "Dec 26, 2024",
|
"date": "Dec 26, 2024",
|
||||||
|
"userLiked": null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Khalid A",
|
"name": "Khalid A",
|
||||||
|
|
@ -66,6 +68,7 @@ class _ItemState extends State<Item> with TickerProviderStateMixin {
|
||||||
"yesCount": 5,
|
"yesCount": 5,
|
||||||
"noCount": 10,
|
"noCount": 10,
|
||||||
"date": "Dec 26, 2024",
|
"date": "Dec 26, 2024",
|
||||||
|
"userLiked": null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Sara M",
|
"name": "Sara M",
|
||||||
|
|
@ -75,6 +78,7 @@ class _ItemState extends State<Item> with TickerProviderStateMixin {
|
||||||
"yesCount": 2,
|
"yesCount": 2,
|
||||||
"noCount": 0,
|
"noCount": 0,
|
||||||
"date": "Jun 09, 2025",
|
"date": "Jun 09, 2025",
|
||||||
|
"userLiked": null,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
@ -246,6 +250,32 @@ class _ItemState extends State<Item> with TickerProviderStateMixin {
|
||||||
yesCount: review['yesCount'],
|
yesCount: review['yesCount'],
|
||||||
noCount: review['noCount'],
|
noCount: review['noCount'],
|
||||||
date: review['date'],
|
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(),
|
const PriceReserveWidget(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -2,4 +2,7 @@ class SharedPreferencesKey {
|
||||||
static const String token = 'token';
|
static const String token = 'token';
|
||||||
static const String isDarkMode = 'isDarkMode';
|
static const String isDarkMode = 'isDarkMode';
|
||||||
static const String hasManualThemeOverride = 'hasManualThemeOverride';
|
static const String hasManualThemeOverride = 'hasManualThemeOverride';
|
||||||
|
static const String hasSeenOnboarding = 'hasSeenOnboarding';
|
||||||
|
static const String selectedLanguage = 'selectedLanguage';
|
||||||
|
static const String selectedFlag = 'selectedFlag';
|
||||||
}
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ import 'package:lba/gen/assets.gen.dart';
|
||||||
import 'package:lba/res/colors.dart';
|
import 'package:lba/res/colors.dart';
|
||||||
import 'package:firebase_auth/firebase_auth.dart';
|
import 'package:firebase_auth/firebase_auth.dart';
|
||||||
import 'package:google_sign_in/google_sign_in.dart';
|
import 'package:google_sign_in/google_sign_in.dart';
|
||||||
|
import 'package:lba/screens/auth/login_page.dart';
|
||||||
|
|
||||||
Future<void> showLogoutDialog(BuildContext context) async {
|
Future<void> showLogoutDialog(BuildContext context) async {
|
||||||
showDialog(
|
showDialog(
|
||||||
|
|
@ -121,12 +122,23 @@ class _AnimatedLogoutDialogState extends State<_AnimatedLogoutDialog>
|
||||||
await GoogleSignIn().signOut();
|
await GoogleSignIn().signOut();
|
||||||
await FirebaseAuth.instance.signOut();
|
await FirebaseAuth.instance.signOut();
|
||||||
print('✅ Logout successful');
|
print('✅ Logout successful');
|
||||||
|
|
||||||
|
if (context.mounted) {
|
||||||
|
Navigator.of(context).pushAndRemoveUntil(
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => const LoginPage(),
|
||||||
|
),
|
||||||
|
(Route<dynamic> route) => false,
|
||||||
|
);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('❌ Logout error: $e');
|
print('❌ Logout error: $e');
|
||||||
|
if (context.mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text('خطا در خروج: $e')),
|
SnackBar(content: Text('خطا در خروج: $e')),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
"Log Out",
|
"Log Out",
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,17 @@ import 'package:lba/res/colors.dart';
|
||||||
import 'package:lba/widgets/buildWarpedInfo.dart';
|
import 'package:lba/widgets/buildWarpedInfo.dart';
|
||||||
import 'package:lba/widgets/rate.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 name;
|
||||||
final String comment;
|
final String comment;
|
||||||
final double rate;
|
final double rate;
|
||||||
final int yesCount;
|
final int yesCount;
|
||||||
final int noCount;
|
final int noCount;
|
||||||
final String date;
|
final String date;
|
||||||
|
final Function(bool isLike)? onLikeDislike;
|
||||||
|
final bool? initialLikeState;
|
||||||
|
|
||||||
const Reviews({
|
const Reviews({
|
||||||
super.key,
|
super.key,
|
||||||
|
|
@ -22,8 +25,484 @@ class Reviews extends StatelessWidget {
|
||||||
required this.yesCount,
|
required this.yesCount,
|
||||||
required this.noCount,
|
required this.noCount,
|
||||||
required this.date,
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Padding(
|
return Padding(
|
||||||
|
|
@ -38,8 +517,8 @@ class Reviews extends StatelessWidget {
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Text(name),
|
Text(widget.name),
|
||||||
SizedBox(width: 2),
|
const SizedBox(width: 2),
|
||||||
Image.asset(Assets.images.usa.path),
|
Image.asset(Assets.images.usa.path),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -49,54 +528,68 @@ class Reviews extends StatelessWidget {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
CustomStarRating(rating: rate),
|
CustomStarRating(rating: widget.rate),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
SizedBox(height: 7),
|
const SizedBox(height: 7),
|
||||||
buildWrappedInfo(
|
buildWrappedInfo("", widget.comment),
|
||||||
"",
|
const SizedBox(height: 10),
|
||||||
comment,
|
|
||||||
),
|
|
||||||
SizedBox(height: 10,),
|
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
InkWell(
|
_buildAnimatedLikeButton(),
|
||||||
child: Row(
|
const SizedBox(width: 7),
|
||||||
children: [
|
_buildAnimatedDislikeButton(),
|
||||||
SvgPicture.asset(Assets.icons.like.path),
|
|
||||||
SizedBox(width: 1,),
|
|
||||||
Text("Yes (${yesCount.toString()})",style: TextStyle(
|
|
||||||
color: AppColors.hint,
|
|
||||||
),),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
Text(widget.date, 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,
|
|
||||||
),),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 5),
|
||||||
],
|
const Divider(thickness: 1.2, height: 2),
|
||||||
),
|
|
||||||
Text(date,style: TextStyle(
|
|
||||||
color: AppColors.hint
|
|
||||||
),)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
SizedBox(height: 5,),
|
|
||||||
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue