firebase ssh updated
This commit is contained in:
parent
87ec98def4
commit
08d6e4e9e9
|
|
@ -9,7 +9,7 @@ plugins {
|
|||
android {
|
||||
namespace = "com.example.lba"
|
||||
compileSdk = flutter.compileSdkVersion
|
||||
ndkVersion = flutter.ndkVersion
|
||||
ndkVersion = "27.0.12077973"
|
||||
|
||||
compileOptions {
|
||||
// استفاده از جاوا ۸ برای سازگاری بهتر
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
"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(),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
}
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue