diff --git a/Frontend/assets/money.png b/Frontend/assets/money.png new file mode 100644 index 0000000..d010af1 Binary files /dev/null and b/Frontend/assets/money.png differ diff --git a/Frontend/lib/login.dart b/Frontend/lib/login.dart index 493551d..ce2f912 100644 --- a/Frontend/lib/login.dart +++ b/Frontend/lib/login.dart @@ -1,126 +1,13 @@ -// // ignore_for_file: use_build_context_synchronously - -// import 'package:flutter/material.dart'; -// import 'package:google_sign_in/google_sign_in.dart'; -// import 'package:http/http.dart' as http; -// import 'package:monarch/email.dart'; -// import 'package:monarch/other_pages/enviroment.dart'; -// import 'dart:convert'; -// import 'package:shared_preferences/shared_preferences.dart'; -// import 'package:monarch/main_pages/HomePage/homepage.dart'; - -// class LoginScreen extends StatefulWidget { -// const LoginScreen({super.key}); - -// @override -// State createState() => _LoginScreenState(); -// } - -// class _LoginScreenState extends State { -// // GoogleSignIn instance -// final GoogleSignIn _googleSignIn = GoogleSignIn( -// scopes: ['email', 'profile', 'openid'], -// serverClientId: '${Environment.serverClientId}', -// ); - -// bool _isLoading = false; - -// Future _handleGoogleSignIn() async { -// setState(() => _isLoading = true); - -// try { -// // Step 1: Trigger the sign-in flow -// final GoogleSignInAccount? account = await _googleSignIn.signIn(); - -// if (account == null) { -// // User cancelled -// setState(() => _isLoading = false); -// return; -// } - -// // Step 2: Get authentication tokens -// final GoogleSignInAuthentication auth = await account.authentication; -// final String? idToken = auth.idToken; - -// if (idToken == null) { -// debugPrint("❌ Failed to get ID token"); -// return; -// } - -// // Step 3: Send token to backend -// final uri = Uri.parse("${Environment.baseUrl}/auth/google/token"); -// final res = await http.post( -// uri, -// headers: {"Content-Type": "application/json"}, -// body: jsonEncode({"idToken": idToken}), -// ); - -// if (res.statusCode == 200) { -// final data = jsonDecode(res.body); -// if (data['success'] == true) { -// debugPrint("✅ User logged in: ${data['user']}"); -// final prefs = await SharedPreferences.getInstance(); -// prefs.setString('userId', data['user']['_id']); -// Navigator.pushReplacement( -// context, -// MaterialPageRoute(builder: (_) => const EmailsScreen(accessToken: '', userId: 'userId',)), -// ); -// } else { -// debugPrint("❌ Login failed: ${data['error']}"); -// } -// } else { -// debugPrint("❌ Backend error: ${res.body}"); -// } -// } catch (e) { -// debugPrint("⚠ Google Sign-In error: $e"); -// } finally { -// setState(() => _isLoading = false); -// } -// } - -// @override -// Widget build(BuildContext context) { -// return Scaffold( -// backgroundColor: Colors.white, -// body: Center( -// child: -// _isLoading -// ? const CircularProgressIndicator() -// : ElevatedButton.icon( -// style: ElevatedButton.styleFrom( -// backgroundColor: Colors.white, -// foregroundColor: Colors.black, -// padding: const EdgeInsets.symmetric( -// vertical: 12, -// horizontal: 20, -// ), -// shape: RoundedRectangleBorder( -// borderRadius: BorderRadius.circular(30), -// side: const BorderSide(color: Colors.grey), -// ), -// ), - -// label: const Text( -// 'Sign in with Google', -// style: TextStyle(fontSize: 16), -// ), -// onPressed: _handleGoogleSignIn, -// ), -// ), -// ); -// } -// } -// ignore_for_file: use_build_context_synchronously, unused_import +// ignore_for_file: use_build_context_synchronously, deprecated_member_use, unused_import import 'package:flutter/material.dart'; import 'package:google_sign_in/google_sign_in.dart'; import 'package:http/http.dart' as http; import 'package:monarch/main_pages/HomePage/homepage.dart'; - import 'package:monarch/main_pages/HomePage/Components/Voice/voice_dialog.dart'; -//import 'package:monarch/email.dart'; import 'package:monarch/other_pages/enviroment.dart'; import 'dart:convert'; +import 'dart:math' as math; import 'package:shared_preferences/shared_preferences.dart'; class LoginScreen extends StatefulWidget { @@ -130,7 +17,8 @@ class LoginScreen extends StatefulWidget { State createState() => _LoginScreenState(); } -class _LoginScreenState extends State { +class _LoginScreenState extends State + with TickerProviderStateMixin { final GoogleSignIn _googleSignIn = GoogleSignIn( scopes: [ 'email', @@ -142,6 +30,88 @@ class _LoginScreenState extends State { ); bool _isLoading = false; + late AnimationController _fadeController; + late AnimationController _slideController; + late AnimationController _scaleController; + late AnimationController _rotationController; + late Animation _fadeAnimation; + late Animation _slideAnimation; + late Animation _scaleAnimation; + late Animation _rotationAnimation; + + @override + void initState() { + super.initState(); + _initAnimations(); + _startAnimations(); + } + + void _initAnimations() { + _fadeController = AnimationController( + duration: const Duration(milliseconds: 1500), + vsync: this, + ); + + _slideController = AnimationController( + duration: const Duration(milliseconds: 1200), + vsync: this, + ); + + _scaleController = AnimationController( + duration: const Duration(milliseconds: 1000), + vsync: this, + ); + + _rotationController = AnimationController( + duration: const Duration(seconds: 20), + vsync: this, + ); + + _fadeAnimation = Tween(begin: 0.0, end: 1.0).animate( + CurvedAnimation(parent: _fadeController, curve: Curves.easeInOut), + ); + + _slideAnimation = Tween( + begin: const Offset(0, 1), + end: Offset.zero, + ).animate( + CurvedAnimation(parent: _slideController, curve: Curves.elasticOut), + ); + + _scaleAnimation = Tween(begin: 0.0, end: 1.0).animate( + CurvedAnimation(parent: _scaleController, curve: Curves.elasticOut), + ); + + _rotationAnimation = Tween( + begin: 0.0, + end: 2 * math.pi, + ).animate(_rotationController); + } + + void _startAnimations() { + Future.delayed(const Duration(milliseconds: 300), () { + _fadeController.forward(); + }); + + Future.delayed(const Duration(milliseconds: 500), () { + _slideController.forward(); + }); + + Future.delayed(const Duration(milliseconds: 700), () { + _scaleController.forward(); + }); + + _rotationController.repeat(); + } + + @override + void dispose() { + _fadeController.dispose(); + _slideController.dispose(); + _scaleController.dispose(); + _rotationController.dispose(); + super.dispose(); + } Future _handleGoogleSignIn() async { setState(() => _isLoading = true); @@ -164,7 +134,6 @@ class _LoginScreenState extends State { return; } - // Send idToken to backend for login final uri = Uri.parse("${Environment.baseUrl}/auth/google/token"); final res = await http.post( uri, @@ -179,18 +148,9 @@ class _LoginScreenState extends State { prefs.setString('userId', data['user']['_id']); prefs.setString('accessToken', accessToken); - // Navigate directly to EmailsScreen with accessToken Navigator.pushReplacement( context, - MaterialPageRoute( - builder: (_) => FinTrackHomePage(), - // builder: - // (_) => EmailsScreen( - // accessToken: accessToken, - // userId: data['user']['_id'], - // ), - //builder: (_) => AutoSyncEmailsScreen(accessToken: accessToken,userId: data['user']['_id'],), - ), + MaterialPageRoute(builder: (_) => FinTrackHomePage()), ); } else { debugPrint("❌ Login failed: ${data['error']}"); @@ -207,31 +167,346 @@ class _LoginScreenState extends State { @override Widget build(BuildContext context) { + final size = MediaQuery.of(context).size; + return Scaffold( - backgroundColor: Colors.white, - body: Center( - child: - _isLoading - ? const CircularProgressIndicator() - : ElevatedButton.icon( - style: ElevatedButton.styleFrom( - backgroundColor: Colors.white, - foregroundColor: Colors.black, - padding: const EdgeInsets.symmetric( - vertical: 12, - horizontal: 20, + body: Container( + width: double.infinity, + height: double.infinity, + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + Color(0xFF1DB584), // Your app's primary green + Color(0xFF0FA968), + Color(0xFF059649), + ], + stops: [0.0, 0.5, 1.0], + ), + ), + child: Stack( + children: [ + // Animated Background Elements + ...List.generate(6, (index) { + return AnimatedBuilder( + animation: _rotationController, + builder: (context, child) { + return Positioned( + top: size.height * (0.1 + (index * 0.15)), + left: size.width * (0.1 + (index * 0.12)), + child: Transform.rotate( + angle: _rotationAnimation.value + (index * 0.5), + child: Container( + width: 60 + (index * 10), + height: 60 + (index * 10), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.white.withOpacity(0.05), + border: Border.all( + color: Colors.white.withOpacity(0.1), + width: 2, + ), + ), + ), ), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(30), - side: const BorderSide(color: Colors.grey), + ); + }, + ); + }), + + // Floating Particles + ...List.generate(12, (index) { + return AnimatedBuilder( + animation: _rotationController, + builder: (context, child) { + return Positioned( + top: size.height * (0.2 + (index * 0.06)), + right: size.width * (0.1 + (index * 0.05)), + child: Transform.translate( + offset: Offset( + math.sin(_rotationAnimation.value + index) * 20, + math.cos(_rotationAnimation.value + index) * 15, + ), + child: Container( + width: 8 + (index % 3) * 4, + height: 8 + (index % 3) * 4, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.white.withOpacity(0.3), + boxShadow: [ + BoxShadow( + color: Colors.white.withOpacity(0.1), + blurRadius: 10, + spreadRadius: 2, + ), + ], + ), + ), ), - ), - label: const Text( - 'Sign in with Google', - style: TextStyle(fontSize: 16), - ), - onPressed: _handleGoogleSignIn, + ); + }, + ); + }), + + // Main Content + SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 24.0), + child: Column( + children: [ + const Spacer(), + + // Logo Section + FadeTransition( + opacity: _fadeAnimation, + child: Column( + children: [ + ScaleTransition( + scale: _scaleAnimation, + child: Container( + width: 120, + height: 120, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 30, + spreadRadius: 5, + offset: const Offset(0, 10), + ), + ], + ), + child: const Icon( + Icons.account_balance_wallet_rounded, + size: 60, + color: Color(0xFF1DB584), + ), + ), + ), + const SizedBox(height: 32), + + // App Title + const Text( + 'FinTrack', + style: TextStyle( + fontFamily: 'Inter', + fontSize: 42, + fontWeight: FontWeight.w800, + color: Colors.white, + letterSpacing: -1.5, + ), + ), + const SizedBox(height: 12), + + // Subtitle + Text( + 'Your Smart Financial Companion', + style: TextStyle( + fontFamily: 'Inter', + fontSize: 16, + color: Colors.white.withOpacity(0.9), + fontWeight: FontWeight.w400, + letterSpacing: 0.3, + ), + ), + ], + ), + ), + + const Spacer(), + + // Features Section + SlideTransition( + position: _slideAnimation, + child: Container( + padding: const EdgeInsets.all(24), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.1), + borderRadius: BorderRadius.circular(24), + border: Border.all( + color: Colors.white.withOpacity(0.2), + width: 1, + ), + ), + child: Column( + children: [ + Row( + children: [ + _buildFeatureItem( + Icons.trending_up_rounded, + 'Track Expenses', + ), + const SizedBox(width: 24), + _buildFeatureItem( + Icons.insights_rounded, + 'Smart Analytics', + ), + ], + ), + const SizedBox(height: 16), + Row( + children: [ + _buildFeatureItem( + Icons.security_rounded, + 'Secure & Private', + ), + const SizedBox(width: 24), + _buildFeatureItem( + Icons.sync_rounded, + 'Auto Sync', + ), + ], + ), + ], + ), + ), + ), + + const SizedBox(height: 48), + + // Google Sign In Button + SlideTransition( + position: _slideAnimation, + child: + _isLoading + ? Container( + width: double.infinity, + height: 60, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(30), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.15), + blurRadius: 20, + spreadRadius: 2, + offset: const Offset(0, 8), + ), + ], + ), + child: const Center( + child: SizedBox( + width: 24, + height: 24, + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: AlwaysStoppedAnimation( + Color(0xFF1DB584), + ), + ), + ), + ), + ) + : Container( + width: double.infinity, + height: 60, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(30), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.15), + blurRadius: 20, + spreadRadius: 2, + offset: const Offset(0, 8), + ), + ], + ), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: _handleGoogleSignIn, + borderRadius: BorderRadius.circular(30), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 24, + ), + child: Row( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Image.network( + 'https://developers.google.com/identity/images/g-logo.png', + width: 24, + height: 24, + ), + const SizedBox(width: 16), + const Text( + 'Continue with Google', + style: TextStyle( + fontFamily: 'Inter', + fontSize: 18, + fontWeight: FontWeight.w600, + color: Color(0xFF1F2937), + letterSpacing: 0.2, + ), + ), + ], + ), + ), + ), + ), + ), + ), + + const SizedBox(height: 24), + + // Terms Text + FadeTransition( + opacity: _fadeAnimation, + child: Text( + 'By continuing, you agree to our Terms of Service\nand Privacy Policy', + textAlign: TextAlign.center, + style: TextStyle( + fontFamily: 'Inter', + fontSize: 12, + color: Colors.white.withOpacity(0.7), + fontWeight: FontWeight.w400, + height: 1.5, + ), + ), + ), + + const SizedBox(height: 48), + ], ), + ), + ), + ], + ), + ), + ); + } + + Widget _buildFeatureItem(IconData icon, String text) { + return Expanded( + child: Row( + children: [ + Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.2), + borderRadius: BorderRadius.circular(12), + ), + child: Icon(icon, color: Colors.white, size: 20), + ), + const SizedBox(width: 12), + Expanded( + child: Text( + text, + style: const TextStyle( + fontFamily: 'Inter', + color: Colors.white, + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + ), + ], ), ); } diff --git a/Frontend/lib/main.dart b/Frontend/lib/main.dart index 958f312..7f77371 100644 --- a/Frontend/lib/main.dart +++ b/Frontend/lib/main.dart @@ -6,7 +6,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:http/http.dart' as http; import 'package:monarch/login.dart'; -import 'package:monarch/speech.dart'; +import 'package:monarch/testing_pages/speech.dart'; import 'package:monarch/support/add.dart'; import 'package:monarch/main_pages/Statistics/Budget/update_budget.dart'; import 'package:monarch/main_pages/HomePage/homepage.dart'; diff --git a/Frontend/lib/main_pages/HomePage/Components/Email/e_dialog.dart b/Frontend/lib/main_pages/HomePage/Components/Email/e_dialog.dart index 2e0d0af..1fc91c6 100644 --- a/Frontend/lib/main_pages/HomePage/Components/Email/e_dialog.dart +++ b/Frontend/lib/main_pages/HomePage/Components/Email/e_dialog.dart @@ -1,15 +1,31 @@ // ignore_for_file: deprecated_member_use, unused_import - import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:http/http.dart' as http; import 'package:monarch/main_pages/HomePage/homepage.dart'; import 'dart:convert'; - +import 'dart:math' as math; import 'package:monarch/other_pages/colors.dart'; import 'package:monarch/other_pages/enviroment.dart'; import 'package:shared_preferences/shared_preferences.dart'; +extension ResponsiveExtension on BuildContext { + double responsiveWidth(double size) { + return size * (MediaQuery.of(this).size.width / 375); + } + + double responsiveHeight(double size) { + return size * (MediaQuery.of(this).size.height / 812); + } + + double responsiveText(double size) { + final width = MediaQuery.of(this).size.width; + if (width < 350) return size * 0.9; + if (width > 600) return size * 1.2; + return size; + } +} + class EmailTransactionDialog extends StatefulWidget { const EmailTransactionDialog({super.key}); @@ -17,7 +33,8 @@ class EmailTransactionDialog extends StatefulWidget { State createState() => _EmailTransactionDialogState(); } -class _EmailTransactionDialogState extends State { +class _EmailTransactionDialogState extends State + with TickerProviderStateMixin { final List _emails = []; bool _loading = true; String? _status; @@ -25,13 +42,98 @@ class _EmailTransactionDialogState extends State { String? _error; String? _userId; String? _accessToken; - @override + + late AnimationController _slideController; + late AnimationController _fadeController; + late AnimationController _scaleController; + late AnimationController _rotationController; + late AnimationController _progressController; + + late Animation _slideAnimation; + late Animation _fadeAnimation; + late Animation _scaleAnimation; + late Animation _rotationAnimation; + late Animation _progressAnimation; + @override void initState() { super.initState(); + _initAnimations(); + _startEntryAnimation(); _autoSyncEmails(); } + void _initAnimations() { + _slideController = AnimationController( + duration: const Duration(milliseconds: 800), + vsync: this, + ); + + _fadeController = AnimationController( + duration: const Duration(milliseconds: 1000), + vsync: this, + ); + + _scaleController = AnimationController( + duration: const Duration(milliseconds: 600), + vsync: this, + ); + + _rotationController = AnimationController( + duration: const Duration(seconds: 2), + vsync: this, + ); + + _progressController = AnimationController( + duration: const Duration(milliseconds: 1500), + vsync: this, + ); + + _slideAnimation = Tween( + begin: const Offset(0, 1), + end: Offset.zero, + ).animate( + CurvedAnimation(parent: _slideController, curve: Curves.elasticOut), + ); + + _fadeAnimation = Tween(begin: 0.0, end: 1.0).animate( + CurvedAnimation(parent: _fadeController, curve: Curves.easeInOut), + ); + + _scaleAnimation = Tween(begin: 0.0, end: 1.0).animate( + CurvedAnimation(parent: _scaleController, curve: Curves.elasticOut), + ); + + _rotationAnimation = Tween( + begin: 0.0, + end: 2 * math.pi, + ).animate(_rotationController); + + _progressAnimation = Tween(begin: 0.0, end: 1.0).animate( + CurvedAnimation(parent: _progressController, curve: Curves.easeInOut), + ); + } + + void _startEntryAnimation() { + _slideController.forward(); + Future.delayed(const Duration(milliseconds: 200), () { + _fadeController.forward(); + }); + Future.delayed(const Duration(milliseconds: 400), () { + _scaleController.forward(); + }); + } + + @override + void dispose() { + _slideController.dispose(); + _fadeController.dispose(); + _scaleController.dispose(); + _rotationController.dispose(); + _progressController.dispose(); + super.dispose(); + } + Future _autoSyncEmails() async { setState(() { _loading = true; @@ -39,6 +141,9 @@ class _EmailTransactionDialogState extends State { _syncedCount = 0; }); + _rotationController.repeat(); + _progressController.forward(); + try { final prefs = await SharedPreferences.getInstance(); final userId = prefs.getString('userId'); @@ -52,7 +157,6 @@ class _EmailTransactionDialogState extends State { if (response.statusCode == 200) { final data = jsonDecode(response.body); - if (data['success'] == true && data['count'] != null) { setState(() { _loading = false; @@ -60,215 +164,441 @@ class _EmailTransactionDialogState extends State { _status = "✅ Synced $_syncedCount transactions successfully!"; }); - // // Optional: navigate back or to another screen after a delay - // Future.delayed(const Duration(seconds: 2), () { - // Navigator.pushReplacement( - // context, - // // MaterialPageRoute(builder: (_) => const FinTrackHomePage()), - // ); - // }); + _rotationController.stop(); + _progressController.reset(); + + // Success animation + _scaleController.reset(); + _scaleController.forward(); } else { setState(() { _loading = false; _status = "❌ Sync failed: Unexpected response format"; }); + _rotationController.stop(); } } else { setState(() { _loading = false; _status = "❌ Backend error: ${response.body}"; }); + _rotationController.stop(); } } catch (error) { setState(() { _loading = false; _status = "⚠️ Network or unexpected error: $error"; }); + _rotationController.stop(); } } @override Widget build(BuildContext context) { - return Container( - height: MediaQuery.of(context).size.height * 0.7, - decoration: BoxDecoration( - color: cardColor, - borderRadius: const BorderRadius.vertical(top: Radius.circular(28)), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.1), - blurRadius: 32, - offset: const Offset(0, -8), + return SlideTransition( + position: _slideAnimation, + child: Container( + height: context.responsiveHeight(560), + decoration: BoxDecoration( + color: cardColor, + borderRadius: BorderRadius.vertical( + top: Radius.circular(context.responsiveWidth(28)), ), - ], - ), - child: Column( - children: [ - // Top drag handle - Container( - width: 48, - height: 4, - margin: const EdgeInsets.only(top: 16, bottom: 24), - decoration: BoxDecoration( - color: primaryColor.withOpacity(0.2), - borderRadius: BorderRadius.circular(2), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.08), + blurRadius: 32, + offset: const Offset(0, -8), + spreadRadius: 2, ), - ), - - // Header - Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: Row( - children: [ - Container( - width: 48, - height: 48, - decoration: BoxDecoration( - color: accentColor.withOpacity(0.1), - borderRadius: BorderRadius.circular(12), - border: Border.all( - color: accentColor.withOpacity(0.2), - width: 1, - ), - ), - child: Icon(Icons.mail_outline, color: accentColor, size: 24), + BoxShadow( + color: primaryColor.withOpacity(0.05), + blurRadius: 16, + offset: const Offset(0, -4), + ), + ], + ), + child: Column( + children: [ + // Drag Handle with Animation + FadeTransition( + opacity: _fadeAnimation, + child: Container( + width: context.responsiveWidth(48), + height: context.responsiveHeight(4), + margin: EdgeInsets.only( + top: context.responsiveHeight(16), + bottom: context.responsiveHeight(24), ), - const SizedBox(width: 16), - Column( - crossAxisAlignment: CrossAxisAlignment.start, + decoration: BoxDecoration( + color: primaryColor.withOpacity(0.2), + borderRadius: BorderRadius.circular( + context.responsiveWidth(2), + ), + ), + ), + ), + + // Animated Header + FadeTransition( + opacity: _fadeAnimation, + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: context.responsiveWidth(24), + ), + child: Row( children: [ - Text( - 'Email Transactions', - style: GoogleFonts.inter( - color: primaryColor, - fontSize: 20, - fontWeight: FontWeight.w700, + // Animated Icon Container + ScaleTransition( + scale: _scaleAnimation, + child: Container( + width: context.responsiveWidth(56), + height: context.responsiveWidth(56), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + accentColor.withOpacity(0.1), + accentColor.withOpacity(0.05), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular( + context.responsiveWidth(16), + ), + border: Border.all( + color: accentColor.withOpacity(0.2), + width: 1.5, + ), + boxShadow: [ + BoxShadow( + color: accentColor.withOpacity(0.1), + blurRadius: 12, + offset: const Offset(0, 4), + ), + ], + ), + child: Icon( + Icons.email_rounded, + color: accentColor, + size: context.responsiveWidth(28), + ), ), ), - Text( - 'Forward receipts to auto-add transactions', - style: GoogleFonts.inter( - color: primaryColor.withOpacity(0.6), - fontSize: 14, - fontWeight: FontWeight.w500, + SizedBox(width: context.responsiveWidth(16)), + + // Title Section + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Email Transactions', + style: GoogleFonts.inter( + color: primaryColor, + fontSize: context.responsiveText(20), + fontWeight: FontWeight.w700, + letterSpacing: -0.5, + ), + ), + SizedBox(height: context.responsiveHeight(2)), + Text( + 'Forward receipts to auto-add transactions', + style: GoogleFonts.inter( + color: primaryColor.withOpacity(0.6), + fontSize: context.responsiveText(13), + fontWeight: FontWeight.w500, + height: 1.2, + ), + ), + ], + ), + ), + + // Animated Refresh Button + ScaleTransition( + scale: _scaleAnimation, + child: Container( + width: context.responsiveWidth(44), + height: context.responsiveWidth(44), + decoration: BoxDecoration( + color: accentColor.withOpacity(0.1), + borderRadius: BorderRadius.circular( + context.responsiveWidth(12), + ), + border: Border.all( + color: accentColor.withOpacity(0.2), + width: 1, + ), + ), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: _autoSyncEmails, + borderRadius: BorderRadius.circular( + context.responsiveWidth(12), + ), + child: AnimatedBuilder( + animation: _rotationAnimation, + builder: (context, child) { + return Transform.rotate( + angle: + _loading ? _rotationAnimation.value : 0, + child: Icon( + Icons.refresh_rounded, + color: accentColor, + size: context.responsiveWidth(22), + ), + ); + }, + ), + ), + ), ), ), ], ), - const Spacer(), - IconButton( - icon: Icon(Icons.refresh, color: accentColor), - onPressed: _autoSyncEmails, + ), + ), + + SizedBox(height: context.responsiveHeight(32)), + + // Main Content Area + Expanded( + child: _loading ? _buildLoadingContent() : _buildResultContent(), + ), + ], + ), + ), + ); + } + + Widget _buildLoadingContent() { + return FadeTransition( + opacity: _fadeAnimation, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // Animated Loading Circle + ScaleTransition( + scale: _scaleAnimation, + child: Container( + width: context.responsiveWidth(80), + height: context.responsiveWidth(80), + decoration: BoxDecoration( + shape: BoxShape.circle, + gradient: LinearGradient( + colors: [ + accentColor.withOpacity(0.1), + accentColor.withOpacity(0.05), + ], ), - ], + border: Border.all( + color: accentColor.withOpacity(0.2), + width: 2, + ), + ), + child: AnimatedBuilder( + animation: _rotationAnimation, + builder: (context, child) { + return Transform.rotate( + angle: _rotationAnimation.value, + child: Icon( + Icons.sync_rounded, + color: accentColor, + size: context.responsiveWidth(36), + ), + ); + }, + ), ), ), - const SizedBox(height: 16), - // Body (loading / error / empty / list) - Expanded( - child: - _loading - ? const Center(child: CircularProgressIndicator()) - : _error != null - ? Center( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: Text( - _error!, - textAlign: TextAlign.center, - style: GoogleFonts.inter( - color: Colors.redAccent, - fontSize: 14, - fontWeight: FontWeight.w500, - ), + SizedBox(height: context.responsiveHeight(32)), + + // Progress Bar + Container( + width: context.responsiveWidth(200), + height: context.responsiveHeight(6), + decoration: BoxDecoration( + color: primaryColor.withOpacity(0.1), + borderRadius: BorderRadius.circular(context.responsiveWidth(3)), + ), + child: AnimatedBuilder( + animation: _progressAnimation, + builder: (context, child) { + return FractionallySizedBox( + alignment: Alignment.centerLeft, + widthFactor: _progressAnimation.value, + child: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [accentColor, accentColor.withOpacity(0.7)], + ), + borderRadius: BorderRadius.circular( + context.responsiveWidth(3), + ), + ), + ), + ); + }, + ), + ), + + SizedBox(height: context.responsiveHeight(24)), + + // Status Text + Text( + _status ?? "Processing...", + textAlign: TextAlign.center, + style: GoogleFonts.inter( + fontSize: context.responsiveText(16), + fontWeight: FontWeight.w600, + color: primaryColor.withOpacity(0.8), + height: 1.4, + ), + ), + ], + ), + ); + } + + Widget _buildResultContent() { + final isSuccess = _status?.contains('✅') == true; + final isError = + _status?.contains('❌') == true || _status?.contains('⚠️') == true; + + return FadeTransition( + opacity: _fadeAnimation, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // Result Icon + ScaleTransition( + scale: _scaleAnimation, + child: Container( + width: context.responsiveWidth(80), + height: context.responsiveWidth(80), + decoration: BoxDecoration( + shape: BoxShape.circle, + gradient: LinearGradient( + colors: + isSuccess + ? [ + Colors.green.withOpacity(0.1), + Colors.green.withOpacity(0.05), + ] + : isError + ? [ + Colors.red.withOpacity(0.1), + Colors.red.withOpacity(0.05), + ] + : [ + accentColor.withOpacity(0.1), + accentColor.withOpacity(0.05), + ], + ), + border: Border.all( + color: + isSuccess + ? Colors.green.withOpacity(0.3) + : isError + ? Colors.red.withOpacity(0.3) + : accentColor.withOpacity(0.3), + width: 2, + ), + ), + child: Icon( + isSuccess + ? Icons.check_circle_rounded + : isError + ? Icons.error_rounded + : Icons.info_rounded, + color: + isSuccess + ? Colors.green + : isError + ? Colors.red + : accentColor, + size: context.responsiveWidth(36), + ), + ), + ), + + SizedBox(height: context.responsiveHeight(24)), + + // Status Text + Padding( + padding: EdgeInsets.symmetric( + horizontal: context.responsiveWidth(32), + ), + child: Text( + _status ?? "Done", + textAlign: TextAlign.center, + style: GoogleFonts.inter( + fontSize: context.responsiveText(16), + fontWeight: FontWeight.w600, + color: primaryColor.withOpacity(0.9), + height: 1.4, + ), + ), + ), + + SizedBox(height: context.responsiveHeight(32)), + + // Action Button + ScaleTransition( + scale: _scaleAnimation, + child: Container( + width: context.responsiveWidth(180), + height: context.responsiveHeight(48), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [accentColor, accentColor.withOpacity(0.8)], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular( + context.responsiveWidth(24), + ), + boxShadow: [ + BoxShadow( + color: accentColor.withOpacity(0.3), + blurRadius: 12, + offset: const Offset(0, 4), + ), + ], + ), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: _autoSyncEmails, + borderRadius: BorderRadius.circular( + context.responsiveWidth(24), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.refresh_rounded, + color: Colors.white, + size: context.responsiveWidth(20), + ), + SizedBox(width: context.responsiveWidth(8)), + Text( + "Retry Sync", + style: GoogleFonts.inter( + color: Colors.white, + fontSize: context.responsiveText(14), + fontWeight: FontWeight.w600, + letterSpacing: 0.2, ), ), - ) - : _emails.isEmpty - ? Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - Icons.inbox_outlined, - size: 48, - color: primaryColor.withOpacity(0.4), - ), - const SizedBox(height: 12), - Text( - "No transactions found", - style: GoogleFonts.inter( - fontSize: 16, - fontWeight: FontWeight.w600, - color: primaryColor, - ), - ), - const SizedBox(height: 4), - Text( - "Forward your receipts to see them here.", - style: GoogleFonts.inter( - fontSize: 13, - color: primaryColor.withOpacity(0.6), - ), - textAlign: TextAlign.center, - ), - ], - ), - ) - : ListView.separated( - padding: const EdgeInsets.symmetric( - horizontal: 20, - vertical: 12, - ), - itemCount: _emails.length, - separatorBuilder: - (_, __) => Divider( - height: 1, - color: primaryColor.withOpacity(0.1), - ), - itemBuilder: (context, index) { - final email = _emails[index]; - return ListTile( - contentPadding: EdgeInsets.zero, - leading: Container( - height: 40, - width: 40, - decoration: BoxDecoration( - color: accentColor.withOpacity(0.1), - borderRadius: BorderRadius.circular(8), - ), - child: Icon( - Icons.receipt_long, - color: accentColor, - size: 22, - ), - ), - title: Text( - email['subject'] ?? "No Subject", - style: GoogleFonts.inter( - fontSize: 15, - fontWeight: FontWeight.w600, - color: primaryColor, - ), - overflow: TextOverflow.ellipsis, - ), - subtitle: Text( - email['snippet'] ?? "No details available", - style: GoogleFonts.inter( - fontSize: 13, - color: primaryColor.withOpacity(0.6), - ), - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - trailing: Icon( - Icons.chevron_right, - color: primaryColor.withOpacity(0.4), - ), - ); - }, - ), + ], + ), + ), + ), + ), ), ], ), diff --git a/Frontend/lib/main_pages/HomePage/Components/Home/navbar.dart b/Frontend/lib/main_pages/HomePage/Components/Home/navbar.dart index 3e30d40..0788d84 100644 --- a/Frontend/lib/main_pages/HomePage/Components/Home/navbar.dart +++ b/Frontend/lib/main_pages/HomePage/Components/Home/navbar.dart @@ -1,10 +1,14 @@ // ignore_for_file: deprecated_member_use +import 'dart:ui'; import 'package:flutter/material.dart'; +import 'package:monarch/support/add.dart'; +import 'package:monarch/main_pages/HomePage/homepage.dart'; +import 'package:monarch/main_pages/Statistics/statistics.dart'; class CustomNavBar extends StatelessWidget { final int currentIndex; - final Function(int) onTap; + final void Function(int) onTap; final Color backgroundColor; final Color accentColor; final Color primaryColor; @@ -18,88 +22,103 @@ class CustomNavBar extends StatelessWidget { required this.accentColor, required this.primaryColor, required this.cardColor, + required FloatingActionButtonLocation floatingActionButtonLocation, }); + // Navigation items + final List _navItems = const [ + NavItem(icon: Icons.home_rounded, index: 0, page: FinTrackHomePage()), + NavItem(icon: Icons.bar_chart_rounded, index: 1, page: Statistics()), + NavItem(icon: Icons.settings_rounded, index: 2, page: AddExpenseScreen()), + ]; + @override Widget build(BuildContext context) { - return Container( - height: 80, // Fixed height for bottom navigation - decoration: BoxDecoration( - color: backgroundColor, - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.1), - blurRadius: 10, - offset: const Offset(0, -2), - ), - ], - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - _buildNavItem( - icon: Icons.home_outlined, - activeIcon: Icons.home_rounded, - label: 'Home', - index: 0, - ), - _buildNavItem( - icon: Icons.analytics_outlined, - activeIcon: Icons.analytics_rounded, - label: 'Analytics', - index: 1, - ), - _buildNavItem( - icon: Icons.account_balance_wallet_outlined, - activeIcon: Icons.account_balance_wallet_rounded, - label: 'Budget', - index: 2, - ), - _buildNavItem( - icon: Icons.person_outlined, - activeIcon: Icons.person_rounded, - label: 'Profile', - index: 3, - ), - ], - ), - ); - } - - Widget _buildNavItem({ - required IconData icon, - required IconData activeIcon, - required String label, - required int index, - }) { - final isActive = currentIndex == index; - - return Expanded( - child: Material( - color: Colors.transparent, - child: InkWell( - onTap: () => onTap(index), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - isActive ? activeIcon : icon, - color: isActive ? accentColor : primaryColor.withOpacity(0.6), - size: 24, - ), - const SizedBox(height: 4), - Text( - label, - style: TextStyle( - color: isActive ? accentColor : primaryColor.withOpacity(0.6), - fontSize: 12, - fontWeight: isActive ? FontWeight.w600 : FontWeight.w500, - ), - ), - ], + return Align( + alignment: Alignment.bottomCenter, + child: Container( + margin: const EdgeInsets.only(bottom: 20), + padding: const EdgeInsets.symmetric(horizontal: 20), + height: 72, + width: MediaQuery.of(context).size.width * 0.7, // centered & responsive + decoration: BoxDecoration( + color: cardColor.withOpacity(0.65), + borderRadius: BorderRadius.circular(40), + boxShadow: [ + BoxShadow( + color: primaryColor.withOpacity(0.15), + blurRadius: 30, + offset: const Offset(0, 8), + ), + ], + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(40), + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 12, sigmaY: 12), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: + _navItems + .map((item) => _buildNavItem(context, item)) + .toList(), + ), ), ), ), ); } + + Widget _buildNavItem(BuildContext context, NavItem item) { + final isActive = currentIndex == item.index; + return GestureDetector( + onTap: () { + onTap(item.index); + Navigator.pushReplacement( + context, + MaterialPageRoute(builder: (context) => item.page), + ); + }, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + AnimatedContainer( + duration: const Duration(milliseconds: 250), + width: isActive ? 38 : 30, + height: isActive ? 38 : 30, + decoration: BoxDecoration( + color: + isActive + ? primaryColor.withOpacity(0.15) + : Colors.transparent, + borderRadius: BorderRadius.circular(16), + ), + child: Icon( + item.icon, + color: isActive ? primaryColor : primaryColor.withOpacity(0.4), + size: 24, + ), + ), + const SizedBox(height: 4), + AnimatedContainer( + duration: const Duration(milliseconds: 250), + width: 6, + height: 6, + decoration: BoxDecoration( + color: isActive ? accentColor : Colors.transparent, + shape: BoxShape.circle, + ), + ), + ], + ), + ); + } +} + +// Nav item model +class NavItem { + final IconData icon; + final int index; + final Widget page; + const NavItem({required this.icon, required this.index, required this.page}); } diff --git a/Frontend/lib/main_pages/HomePage/Components/Home/quick_actions.dart b/Frontend/lib/main_pages/HomePage/Components/Home/quick_actions.dart index a97f572..6136838 100644 --- a/Frontend/lib/main_pages/HomePage/Components/Home/quick_actions.dart +++ b/Frontend/lib/main_pages/HomePage/Components/Home/quick_actions.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:google_fonts/google_fonts.dart'; +import 'package:monarch/main_pages/Statistics/statistics.dart'; import 'package:monarch/support/add.dart'; import 'package:monarch/other_pages/colors.dart'; @@ -65,9 +66,14 @@ class QuickActionsPage extends StatelessWidget { const SizedBox(width: 16), Expanded( child: GestureDetector( - onTap: () { - HapticFeedback.lightImpact(); - // Navigate to Statistics +onTap: () { + HapticFeedback.heavyImpact(); + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) => const Statistics(), + ), + ); }, child: Container( padding: const EdgeInsets.all(20), diff --git a/Frontend/lib/main_pages/HomePage/Components/Voice/confirm.dart b/Frontend/lib/main_pages/HomePage/Components/Voice/confirm.dart index 7ef322d..824d471 100644 --- a/Frontend/lib/main_pages/HomePage/Components/Voice/confirm.dart +++ b/Frontend/lib/main_pages/HomePage/Components/Voice/confirm.dart @@ -9,14 +9,50 @@ import 'package:monarch/other_pages/colors.dart'; import 'package:monarch/other_pages/enviroment.dart'; import 'package:shared_preferences/shared_preferences.dart'; -class ConfirmTransactionPage extends StatelessWidget { +class ConfirmTransactionPage extends StatefulWidget { final Map data; const ConfirmTransactionPage({super.key, required this.data}); + @override + State createState() => _ConfirmTransactionPageState(); +} + +class _ConfirmTransactionPageState extends State + with SingleTickerProviderStateMixin { + late AnimationController _controller; + late Animation _slideAnimation; + late Animation _fadeAnimation; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 600), + ); + + _slideAnimation = Tween( + begin: const Offset(0, 0.2), + end: Offset.zero, + ).animate(CurvedAnimation(parent: _controller, curve: Curves.easeOutCubic)); + + _fadeAnimation = CurvedAnimation( + parent: _controller, + curve: Curves.easeInOut, + ); + + _controller.forward(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + Future _saveTransaction(BuildContext context) async { - const String saveUrl = - '${Environment.baseUrl}/api/transaction/add'; // ✅ make sure this matches backend + const String saveUrl = '${Environment.baseUrl}/api/transaction/add'; try { final prefs = await SharedPreferences.getInstance(); @@ -31,12 +67,10 @@ class ConfirmTransactionPage extends StatelessWidget { headers: {'Content-Type': 'application/json'}, body: jsonEncode({ 'userId': userId, - 'description': data['description'] ?? '', - 'amount': - double.tryParse(data['amount'].toString()) ?? - 0.0, // ✅ force number - 'category': data['category'] ?? 'Uncategorized', - 'date': data['date'] ?? DateTime.now().toIso8601String(), + 'description': widget.data['description'] ?? '', + 'amount': double.tryParse(widget.data['amount'].toString()) ?? 0.0, + 'category': widget.data['category'] ?? 'Uncategorized', + 'date': widget.data['date'] ?? DateTime.now().toIso8601String(), }), ); @@ -47,11 +81,9 @@ class ConfirmTransactionPage extends StatelessWidget { icon: Icons.check_circle, backgroundColor: const Color(0xFF4CAF50), iconColor: Colors.white, - duration: const Duration(seconds: 3), ); Navigator.popUntil(context, (route) => route.isFirst); } else { - // 👇 Log backend response throw Exception('Save failed: ${response.statusCode} ${response.body}'); } } catch (e) { @@ -61,7 +93,6 @@ class ConfirmTransactionPage extends StatelessWidget { icon: Icons.error_outline, backgroundColor: const Color(0xFFFF6B6B), iconColor: Colors.white, - duration: const Duration(seconds: 4), ); } } @@ -76,47 +107,29 @@ class ConfirmTransactionPage extends StatelessWidget { }) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Container( - padding: EdgeInsets.symmetric(vertical: context.responsiveHeight(4)), - child: Row( - children: [ - Container( - padding: EdgeInsets.all(context.responsiveWidth(8)), - decoration: BoxDecoration( - color: Colors.white.withOpacity(0.2), - borderRadius: BorderRadius.circular( - context.responsiveWidth(8), - ), - ), - child: Icon( - icon, - color: iconColor, - size: context.responsiveText(20), + content: Row( + children: [ + Icon(icon, color: iconColor, size: context.responsiveText(20)), + SizedBox(width: context.responsiveWidth(8)), + Expanded( + child: Text( + message, + style: GoogleFonts.inter( + fontWeight: FontWeight.w600, + color: Colors.white, + fontSize: context.responsiveText(14), ), ), - SizedBox(width: context.responsiveWidth(12)), - Expanded( - child: Text( - message, - style: GoogleFonts.inter( - fontWeight: FontWeight.w500, - color: Colors.white, - fontSize: context.responsiveText(14), - height: 1.4, - ), - ), - ), - ], - ), + ), + ], ), backgroundColor: backgroundColor, behavior: SnackBarBehavior.floating, + margin: EdgeInsets.all(context.responsiveWidth(12)), + duration: duration, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(context.responsiveWidth(12)), ), - margin: EdgeInsets.all(context.responsiveWidth(16)), - duration: duration, - elevation: 8, ), ); } @@ -130,15 +143,19 @@ class ConfirmTransactionPage extends StatelessWidget { return Row( children: [ Container( - width: 40, - height: 40, + width: context.responsiveWidth(40), + height: context.responsiveWidth(40), decoration: BoxDecoration( - color: accentColor.withOpacity(0.1), - borderRadius: BorderRadius.circular(10), + color: accentColor.withOpacity(0.08), + borderRadius: BorderRadius.circular(context.responsiveWidth(10)), + ), + child: Icon( + icon, + color: accentColor, + size: context.responsiveText(20), ), - child: Icon(icon, color: accentColor, size: 20), ), - const SizedBox(width: 16), + SizedBox(width: context.responsiveWidth(14)), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -147,17 +164,16 @@ class ConfirmTransactionPage extends StatelessWidget { label, style: GoogleFonts.inter( color: primaryColor.withOpacity(0.6), - fontSize: 12, - fontWeight: FontWeight.w600, - letterSpacing: 0.5, + fontSize: context.responsiveText(12), + fontWeight: FontWeight.w500, ), ), - const SizedBox(height: 4), + SizedBox(height: context.responsiveHeight(2)), Text( value, style: GoogleFonts.inter( color: isAmount ? Colors.green : primaryColor, - fontSize: 16, + fontSize: context.responsiveText(16), fontWeight: FontWeight.w700, ), ), @@ -170,12 +186,12 @@ class ConfirmTransactionPage extends StatelessWidget { @override Widget build(BuildContext context) { - final amount = data['amount']; - final description = data['description']; - final category = data['category']; + final amount = widget.data['amount']; + final description = widget.data['description']; + final category = widget.data['category']; final date = - data['date'] != null - ? DateTime.parse(data['date']).toLocal() + widget.data['date'] != null + ? DateTime.parse(widget.data['date']).toLocal() : DateTime.now(); return Scaffold( @@ -184,11 +200,10 @@ class ConfirmTransactionPage extends StatelessWidget { backgroundColor: Colors.transparent, elevation: 0, leading: Container( - margin: const EdgeInsets.all(8), + margin: EdgeInsets.all(context.responsiveWidth(6)), decoration: BoxDecoration( color: cardColor, - borderRadius: BorderRadius.circular(12), - border: Border.all(color: primaryColor.withOpacity(0.1), width: 1), + borderRadius: BorderRadius.circular(context.responsiveWidth(12)), ), child: IconButton( icon: Icon(Icons.arrow_back, color: primaryColor), @@ -199,223 +214,206 @@ class ConfirmTransactionPage extends StatelessWidget { 'Confirm Transaction', style: GoogleFonts.inter( color: primaryColor, - fontSize: 20, + fontSize: context.responsiveText(18), fontWeight: FontWeight.w700, ), ), centerTitle: true, ), body: SafeArea( - child: Padding( - padding: const EdgeInsets.all(24), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Header Card - Container( - width: double.infinity, - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - color: cardColor, - borderRadius: BorderRadius.circular(20), - boxShadow: [ - BoxShadow( - color: accentColor.withOpacity(0.05), - blurRadius: 20, - offset: const Offset(0, 8), - ), - ], - border: Border.all( - color: accentColor.withOpacity(0.1), - width: 1.5, - ), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Container( - width: 48, - height: 48, - decoration: BoxDecoration( - color: Colors.green.withOpacity(0.1), - borderRadius: BorderRadius.circular(12), - border: Border.all( - color: Colors.green.withOpacity(0.2), - width: 1.5, - ), - ), - child: const Icon( - Icons.check_circle_outline, - color: Colors.green, - size: 24, - ), - ), - const SizedBox(width: 16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Review Transaction', - style: GoogleFonts.inter( - color: primaryColor, - fontSize: 18, - fontWeight: FontWeight.w700, - ), - ), - Text( - 'Please confirm the details below', - style: GoogleFonts.inter( - color: primaryColor.withOpacity(0.6), - fontSize: 14, - fontWeight: FontWeight.w500, - ), - ), - ], - ), - ), - ], - ), - ], - ), - ), - - const SizedBox(height: 24), - - // Transaction Details - Container( - width: double.infinity, - padding: const EdgeInsets.all(24), - decoration: BoxDecoration( - color: backgroundColor, - borderRadius: BorderRadius.circular(20), - border: Border.all( - color: primaryColor.withOpacity(0.1), - width: 1, - ), - ), - child: Column( - children: [ - _buildDetailItem( - icon: Icons.description_outlined, - label: 'Description', - value: description?.toString() ?? 'No description', - ), - - const SizedBox(height: 20), - - _buildDetailItem( - icon: Icons.payments_outlined, - label: 'Amount', - value: '₹$amount', - isAmount: true, - ), - - const SizedBox(height: 20), - - _buildDetailItem( - icon: Icons.category_outlined, - label: 'Category', - value: category?.toString() ?? 'Uncategorized', - ), - - const SizedBox(height: 20), - - _buildDetailItem( - icon: Icons.calendar_today_outlined, - label: 'Date', - value: '${date.day}/${date.month}/${date.year}', - ), - ], - ), - ), - - const Spacer(), - - // Action Buttons - Column( + child: SlideTransition( + position: _slideAnimation, + child: FadeTransition( + opacity: _fadeAnimation, + child: Padding( + padding: EdgeInsets.all(context.responsiveWidth(20)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Confirm Button + // Header Container( - width: double.infinity, - height: 56, + padding: EdgeInsets.all(context.responsiveWidth(18)), decoration: BoxDecoration( - gradient: LinearGradient( - colors: [Colors.green, Colors.green.shade600], - begin: Alignment.topLeft, - end: Alignment.bottomRight, + color: cardColor, + borderRadius: BorderRadius.circular( + context.responsiveWidth(18), ), - borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( - color: Colors.green.withOpacity(0.3), - blurRadius: 12, - offset: const Offset(0, 4), + color: accentColor.withOpacity(0.08), + blurRadius: 18, + offset: const Offset(0, 6), ), ], ), - child: Material( - color: Colors.transparent, - borderRadius: BorderRadius.circular(16), - child: InkWell( - borderRadius: BorderRadius.circular(16), - onTap: () => _saveTransaction(context), - child: Center( - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon( - Icons.check, - color: Colors.white, - size: 20, - ), - const SizedBox(width: 8), - Text( - 'Confirm & Save Transaction', - style: GoogleFonts.inter( - color: Colors.white, - fontSize: 16, - fontWeight: FontWeight.w700, - ), - ), - ], + child: Row( + children: [ + Container( + padding: EdgeInsets.all(context.responsiveWidth(10)), + decoration: BoxDecoration( + color: Colors.green.withOpacity(0.1), + borderRadius: BorderRadius.circular( + context.responsiveWidth(10), + ), + ), + child: Icon( + Icons.check_circle_outline, + color: Colors.green, + size: context.responsiveText(22), ), ), - ), + SizedBox(width: context.responsiveWidth(14)), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Review Transaction", + style: GoogleFonts.inter( + fontSize: context.responsiveText(16), + fontWeight: FontWeight.w700, + color: primaryColor, + ), + ), + Text( + "Please confirm the details below", + style: GoogleFonts.inter( + fontSize: context.responsiveText(13), + fontWeight: FontWeight.w500, + color: primaryColor.withOpacity(0.6), + ), + ), + ], + ), + ], ), ), - const SizedBox(height: 16), + SizedBox(height: context.responsiveHeight(24)), - // Cancel Button + // Details Card Container( - width: double.infinity, - height: 48, + padding: EdgeInsets.all(context.responsiveWidth(20)), decoration: BoxDecoration( color: backgroundColor, - borderRadius: BorderRadius.circular(12), + borderRadius: BorderRadius.circular( + context.responsiveWidth(18), + ), border: Border.all( - color: primaryColor.withOpacity(0.2), + color: primaryColor.withOpacity(0.08), width: 1, ), ), - child: TextButton( - onPressed: () => Navigator.pop(context), - child: Text( - 'Edit or Cancel', - style: GoogleFonts.inter( - color: primaryColor.withOpacity(0.7), - fontSize: 16, - fontWeight: FontWeight.w600, + child: Column( + children: [ + _buildDetailItem( + icon: Icons.description_outlined, + label: "Description", + value: description?.toString() ?? "No description", + ), + SizedBox(height: context.responsiveHeight(16)), + _buildDetailItem( + icon: Icons.payments_outlined, + label: "Amount", + value: "₹$amount", + isAmount: true, + ), + SizedBox(height: context.responsiveHeight(16)), + _buildDetailItem( + icon: Icons.category_outlined, + label: "Category", + value: category?.toString() ?? "Uncategorized", + ), + SizedBox(height: context.responsiveHeight(16)), + _buildDetailItem( + icon: Icons.calendar_today_outlined, + label: "Date", + value: "${date.day}/${date.month}/${date.year}", + ), + ], + ), + ), + + const Spacer(), + + // Buttons + Column( + children: [ + // Confirm + GestureDetector( + onTapDown: (_) => _controller.forward(), + onTapUp: (_) => _saveTransaction(context), + child: AnimatedScale( + scale: 1, + duration: const Duration(milliseconds: 150), + child: Container( + width: double.infinity, + padding: EdgeInsets.symmetric( + vertical: context.responsiveHeight(14), + ), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [Colors.green, Colors.green.shade700], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular( + context.responsiveWidth(14), + ), + boxShadow: [ + BoxShadow( + color: Colors.green.withOpacity(0.3), + blurRadius: 10, + offset: const Offset(0, 4), + ), + ], + ), + child: Center( + child: Text( + "Confirm & Save Transaction", + style: GoogleFonts.inter( + fontSize: context.responsiveText(15), + fontWeight: FontWeight.w700, + color: Colors.white, + ), + ), + ), + ), ), ), - ), + + SizedBox(height: context.responsiveHeight(14)), + + // Cancel + OutlinedButton( + onPressed: () => Navigator.pop(context), + style: OutlinedButton.styleFrom( + minimumSize: Size( + double.infinity, + context.responsiveHeight(48), + ), + side: BorderSide( + color: primaryColor.withOpacity(0.3), + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + context.responsiveWidth(12), + ), + ), + ), + child: Text( + "Edit or Cancel", + style: GoogleFonts.inter( + fontSize: context.responsiveText(14), + fontWeight: FontWeight.w600, + color: primaryColor.withOpacity(0.8), + ), + ), + ), + ], ), ], ), - ], + ), ), ), ), diff --git a/Frontend/lib/main_pages/HomePage/Components/Voice/voice_dialog.dart b/Frontend/lib/main_pages/HomePage/Components/Voice/voice_dialog.dart index e1a6bd4..471aef2 100644 --- a/Frontend/lib/main_pages/HomePage/Components/Voice/voice_dialog.dart +++ b/Frontend/lib/main_pages/HomePage/Components/Voice/voice_dialog.dart @@ -1,15 +1,31 @@ // ignore_for_file: depend_on_referenced_packages, library_private_types_in_public_api, use_build_context_synchronously, deprecated_member_use - import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:http/http.dart' as http; -import 'package:monarch/speech.dart'; +import 'package:monarch/testing_pages/speech.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:speech_to_text/speech_to_text.dart' as stt; import 'package:monarch/other_pages/colors.dart'; import 'package:monarch/other_pages/enviroment.dart'; +extension ResponsiveExtension on BuildContext { + double responsiveWidth(double size) { + return size * (MediaQuery.of(this).size.width / 375); + } + + double responsiveHeight(double size) { + return size * (MediaQuery.of(this).size.height / 812); + } + + double responsiveText(double size) { + final width = MediaQuery.of(this).size.width; + if (width < 350) return size * 0.9; + if (width > 600) return size * 1.2; + return size; + } +} + class VoiceTransactionDialog extends StatefulWidget { const VoiceTransactionDialog({super.key}); @@ -18,32 +34,82 @@ class VoiceTransactionDialog extends StatefulWidget { } class _VoiceTransactionDialogState extends State - with SingleTickerProviderStateMixin { + with TickerProviderStateMixin { late stt.SpeechToText _speech; bool _isListening = false; String _spokenText = ''; bool _processing = false; + + // Animation controllers late AnimationController _pulseController; + late AnimationController _waveController; + late AnimationController _slideController; + late AnimationController _fadeController; + + // Animations late Animation _pulseAnimation; + late Animation _waveAnimation; + late Animation _slideAnimation; + late Animation _fadeAnimation; @override void initState() { super.initState(); _speech = stt.SpeechToText(); + // Initialize animation controllers _pulseController = AnimationController( vsync: this, - duration: const Duration(milliseconds: 1200), + duration: const Duration(milliseconds: 1500), ); - _pulseAnimation = Tween(begin: 1.0, end: 1.15).animate( + _waveController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 2000), + ); + + _slideController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 800), + ); + + _fadeController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 600), + ); + + // Initialize animations + _pulseAnimation = Tween(begin: 1.0, end: 1.2).animate( CurvedAnimation(parent: _pulseController, curve: Curves.easeInOut), ); + + _waveAnimation = Tween(begin: 0.0, end: 1.0).animate( + CurvedAnimation(parent: _waveController, curve: Curves.elasticOut), + ); + + _slideAnimation = Tween( + begin: const Offset(0, 0.3), + end: Offset.zero, + ).animate( + CurvedAnimation(parent: _slideController, curve: Curves.easeOutCubic), + ); + + _fadeAnimation = Tween( + begin: 0.0, + end: 1.0, + ).animate(CurvedAnimation(parent: _fadeController, curve: Curves.easeOut)); + + // Start initial animations + _slideController.forward(); + _fadeController.forward(); } @override void dispose() { _pulseController.dispose(); + _waveController.dispose(); + _slideController.dispose(); + _fadeController.dispose(); super.dispose(); } @@ -57,10 +123,13 @@ class _VoiceTransactionDialogState extends State _processing = true; }); _pulseController.stop(); + _waveController.stop(); await _speech.stop(); + if (_spokenText.isNotEmpty) { await _sendToBackend(); } + setState(() { _processing = false; }); @@ -69,10 +138,7 @@ class _VoiceTransactionDialogState extends State onError: (val) { _showCustomSnackBar( message: 'Speech recognition error: $val', - icon: Icons.mic_off, - backgroundColor: const Color(0xFFFF6B6B), - iconColor: Colors.white, - duration: const Duration(seconds: 3), + isError: true, ); }, ); @@ -82,7 +148,9 @@ class _VoiceTransactionDialogState extends State _isListening = true; _spokenText = ''; }); + _pulseController.repeat(reverse: true); + _waveController.repeat(); _speech.listen( onResult: (val) { @@ -97,7 +165,9 @@ class _VoiceTransactionDialogState extends State _isListening = false; _processing = true; }); + _pulseController.stop(); + _waveController.stop(); _speech.stop(); } } @@ -132,335 +202,522 @@ class _VoiceTransactionDialogState extends State throw Exception('Save failed with status: ${response.statusCode}'); } } catch (e) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('Error: ${e.toString()}'))); + _showCustomSnackBar(message: 'Error: ${e.toString()}', isError: true); } } - void _showCustomSnackBar({ - required String message, - required IconData icon, - required Color backgroundColor, - required Color iconColor, - required Duration duration, - }) { + void _showCustomSnackBar({required String message, bool isError = false}) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Row( children: [ - Icon(icon, color: iconColor), - const SizedBox(width: 12), + Icon( + isError ? Icons.error_outline : Icons.check_circle_outline, + color: Colors.white, + size: context.responsiveWidth(20), + ), + SizedBox(width: context.responsiveWidth(12)), Expanded( child: Text( message, style: GoogleFonts.inter( color: Colors.white, + fontSize: context.responsiveText(14), fontWeight: FontWeight.w500, ), ), ), ], ), - backgroundColor: backgroundColor, - duration: duration, + backgroundColor: + isError ? const Color(0xFFFF6B6B) : const Color(0xFF4CAF50), + duration: const Duration(seconds: 3), behavior: SnackBarBehavior.floating, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - margin: const EdgeInsets.all(16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(context.responsiveWidth(16)), + ), + margin: EdgeInsets.all(context.responsiveWidth(16)), ), ); } + Widget _buildWaveIndicator() { + return AnimatedBuilder( + animation: _waveAnimation, + builder: (context, child) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: List.generate(5, (index) { + final delay = index * 0.2; + final animValue = (_waveAnimation.value - delay).clamp(0.0, 1.0); + final height = 4 + (animValue * 20); + + return Container( + margin: EdgeInsets.symmetric( + horizontal: context.responsiveWidth(2), + ), + width: context.responsiveWidth(4), + height: context.responsiveHeight(height), + decoration: BoxDecoration( + color: accentColor.withOpacity(0.8), + borderRadius: BorderRadius.circular(context.responsiveWidth(2)), + ), + ); + }), + ); + }, + ); + } + @override Widget build(BuildContext context) { - return Container( - height: MediaQuery.of(context).size.height * 0.85, // Reduced height - decoration: BoxDecoration( - color: cardColor, - borderRadius: const BorderRadius.vertical(top: Radius.circular(28)), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.15), - blurRadius: 32, - offset: const Offset(0, -12), - ), - ], - ), - child: Column( - children: [ - // Handle Bar - Container( - width: 48, - height: 4, - margin: const EdgeInsets.only(top: 16, bottom: 8), - decoration: BoxDecoration( - color: primaryColor.withOpacity(0.2), - borderRadius: BorderRadius.circular(2), - ), - ), - - // Header - Padding( - padding: const EdgeInsets.fromLTRB( - 24, - 16, - 24, - 16, - ), // Added bottom padding - child: Row( - children: [ - Container( - width: 52, - height: 52, - decoration: BoxDecoration( - color: accentColor.withOpacity(0.1), - borderRadius: BorderRadius.circular(16), - border: Border.all( - color: accentColor.withOpacity(0.2), - width: 1.5, - ), - ), - child: Icon(Icons.mic_outlined, color: accentColor, size: 28), + return AnimatedBuilder( + animation: Listenable.merge([_slideController, _fadeController]), + builder: (context, child) { + return SlideTransition( + position: _slideAnimation, + child: FadeTransition( + opacity: _fadeAnimation, + child: Container( + height: context.responsiveHeight(680), + decoration: BoxDecoration( + color: cardColor, + borderRadius: BorderRadius.vertical( + top: Radius.circular(context.responsiveWidth(32)), ), - const SizedBox(width: 16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Voice Transaction', - style: GoogleFonts.inter( - color: primaryColor, - fontSize: 20, // Reduced font size - fontWeight: FontWeight.w700, - ), - ), - const SizedBox(height: 4), // Added spacing - Text( - 'Speak naturally to record your transaction', - style: GoogleFonts.inter( - color: primaryColor.withOpacity(0.6), - fontSize: 12, // Reduced font size - fontWeight: FontWeight.w500, - ), - ), - ], + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: context.responsiveWidth(40), + offset: Offset(0, -context.responsiveHeight(8)), ), - ), - ], - ), - ), - - // Main content with SingleChildScrollView to prevent overflow - Expanded( - child: SingleChildScrollView( - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), + ], + ), child: Column( - mainAxisSize: MainAxisSize.min, children: [ - // Status Card + // Handle Bar Container( - width: double.infinity, - padding: const EdgeInsets.all(20), + width: context.responsiveWidth(48), + height: context.responsiveHeight(4), + margin: EdgeInsets.only( + top: context.responsiveHeight(20), + bottom: context.responsiveHeight(8), + ), decoration: BoxDecoration( - color: - _isListening - ? accentColor.withOpacity(0.08) - : backgroundColor, - borderRadius: BorderRadius.circular(20), - border: Border.all( - color: - _isListening - ? accentColor.withOpacity(0.2) - : primaryColor.withOpacity(0.1), - width: 1.5, + color: primaryColor.withOpacity(0.2), + borderRadius: BorderRadius.circular( + context.responsiveWidth(2), ), ), - child: Column( + ), + + // Header + Padding( + padding: EdgeInsets.fromLTRB( + context.responsiveWidth(24), + context.responsiveHeight(20), + context.responsiveWidth(24), + context.responsiveHeight(20), + ), + child: Row( children: [ - // Microphone Button - AnimatedBuilder( - animation: _pulseAnimation, - builder: (context, child) { - return Transform.scale( - scale: _isListening ? _pulseAnimation.value : 1.0, - child: GestureDetector( - onTap: _processing ? null : _listen, - child: Container( - width: 80, // Reduced size - height: 80, // Reduced size - decoration: BoxDecoration( - color: - _isListening - ? accentColor.withOpacity(0.15) - : primaryColor.withOpacity(0.05), - shape: BoxShape.circle, - border: Border.all( - color: - _isListening - ? accentColor.withOpacity(0.4) - : primaryColor.withOpacity(0.15), - width: 2.5, - ), - boxShadow: - _isListening - ? [ - BoxShadow( - color: accentColor.withOpacity( - 0.2, - ), - blurRadius: 20, - spreadRadius: 0, - ), - ] - : null, - ), - child: - _processing - ? const CircularProgressIndicator( - strokeWidth: 3, - valueColor: - AlwaysStoppedAnimation( - Colors.blue, - ), - ) - : Icon( - _isListening - ? Icons.mic - : Icons.mic_none, - size: 36, // Reduced size - color: - _isListening - ? accentColor - : primaryColor.withOpacity( - 0.6, - ), - ), + Container( + width: context.responsiveWidth(56), + height: context.responsiveWidth(56), + decoration: BoxDecoration( + color: accentColor.withOpacity(0.1), + borderRadius: BorderRadius.circular( + context.responsiveWidth(18), + ), + border: Border.all( + color: accentColor.withOpacity(0.3), + width: 1.5, + ), + ), + child: Icon( + Icons.mic_outlined, + color: accentColor, + size: context.responsiveWidth(28), + ), + ), + SizedBox(width: context.responsiveWidth(16)), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Voice Transaction', + style: GoogleFonts.inter( + color: primaryColor, + fontSize: context.responsiveText(22), + fontWeight: FontWeight.w700, + letterSpacing: -0.5, ), ), - ); - }, - ), - const SizedBox(height: 16), // Reduced spacing - // Status Text - Text( - _processing - ? 'Processing your request...' - : _isListening - ? 'Listening... Tap to stop' - : 'Tap microphone to start recording', - style: GoogleFonts.inter( - color: - _isListening || _processing - ? accentColor - : primaryColor.withOpacity(0.7), - fontSize: 14, // Reduced font size - fontWeight: FontWeight.w600, + SizedBox(height: context.responsiveHeight(6)), + Text( + 'Speak naturally to record your transaction', + style: GoogleFonts.inter( + color: primaryColor.withOpacity(0.65), + fontSize: context.responsiveText(14), + fontWeight: FontWeight.w500, + ), + ), + ], ), - textAlign: TextAlign.center, ), ], ), ), - const SizedBox(height: 20), // Reduced spacing - // Spoken Text Display - if (_spokenText.isNotEmpty) ...[ - Container( - width: double.infinity, - padding: const EdgeInsets.all(16), // Reduced padding - decoration: BoxDecoration( - color: cardColor, - borderRadius: BorderRadius.circular( - 16, - ), // Reduced radius - border: Border.all( - color: accentColor.withOpacity(0.15), - width: 1.5, - ), - boxShadow: [ - BoxShadow( - color: accentColor.withOpacity(0.05), - blurRadius: 8, // Reduced blur - offset: const Offset(0, 2), // Reduced offset - ), - ], + // Main Content + Expanded( + child: SingleChildScrollView( + physics: const BouncingScrollPhysics(), + padding: EdgeInsets.symmetric( + horizontal: context.responsiveWidth(24), + vertical: context.responsiveHeight(8), ), child: Column( - crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - children: [ - Icon(Icons.email, color: accentColor, size: 16), - const SizedBox(width: 8), - Text( - 'Transcript', - style: GoogleFonts.inter( - color: accentColor, - fontSize: 12, - fontWeight: FontWeight.w700, - letterSpacing: 0.5, + // Voice Input Card + Container( + width: double.infinity, + padding: EdgeInsets.all( + context.responsiveWidth(24), + ), + decoration: BoxDecoration( + gradient: + _isListening + ? LinearGradient( + colors: [ + accentColor.withOpacity(0.05), + accentColor.withOpacity(0.12), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ) + : LinearGradient( + colors: [ + backgroundColor, + backgroundColor.withOpacity(0.8), + ], + ), + borderRadius: BorderRadius.circular( + context.responsiveWidth(24), + ), + border: Border.all( + color: + _isListening + ? accentColor.withOpacity(0.3) + : primaryColor.withOpacity(0.08), + width: 1.5, + ), + ), + child: Column( + children: [ + // Microphone Button + AnimatedBuilder( + animation: _pulseAnimation, + builder: (context, child) { + return Transform.scale( + scale: + _isListening + ? _pulseAnimation.value + : 1.0, + child: GestureDetector( + onTap: _processing ? null : _listen, + child: Container( + width: context.responsiveWidth(96), + height: context.responsiveWidth(96), + decoration: BoxDecoration( + gradient: + _isListening + ? RadialGradient( + colors: [ + accentColor.withOpacity( + 0.2, + ), + accentColor.withOpacity( + 0.05, + ), + ], + ) + : RadialGradient( + colors: [ + primaryColor + .withOpacity(0.08), + primaryColor + .withOpacity(0.02), + ], + ), + shape: BoxShape.circle, + border: Border.all( + color: + _isListening + ? accentColor.withOpacity( + 0.4, + ) + : primaryColor + .withOpacity(0.2), + width: 2.5, + ), + boxShadow: + _isListening + ? [ + BoxShadow( + color: accentColor + .withOpacity(0.3), + blurRadius: context + .responsiveWidth( + 24, + ), + spreadRadius: 0, + ), + ] + : [ + BoxShadow( + color: primaryColor + .withOpacity(0.1), + blurRadius: context + .responsiveWidth( + 12, + ), + offset: Offset( + 0, + context + .responsiveHeight( + 4, + ), + ), + ), + ], + ), + child: + _processing + ? SizedBox( + width: context + .responsiveWidth(32), + height: context + .responsiveWidth(32), + child: CircularProgressIndicator( + strokeWidth: 3, + valueColor: + AlwaysStoppedAnimation< + Color + >(accentColor), + ), + ) + : Icon( + _isListening + ? Icons.mic + : Icons.mic_none, + size: context + .responsiveWidth(42), + color: + _isListening + ? accentColor + : primaryColor + .withOpacity( + 0.7, + ), + ), + ), + ), + ); + }, + ), + + SizedBox(height: context.responsiveHeight(20)), + + // Wave Indicator (only when listening) + if (_isListening) ...[ + _buildWaveIndicator(), + SizedBox( + height: context.responsiveHeight(16), + ), + ], + + // Status Text + AnimatedSwitcher( + duration: const Duration(milliseconds: 300), + child: Text( + _processing + ? 'Processing your request...' + : _isListening + ? 'Listening... Tap to stop' + : 'Tap microphone to start recording', + key: ValueKey( + _processing + ? 'processing' + : _isListening + ? 'listening' + : 'idle', + ), + style: GoogleFonts.inter( + color: + _isListening || _processing + ? accentColor + : primaryColor.withOpacity(0.7), + fontSize: context.responsiveText(16), + fontWeight: FontWeight.w600, + letterSpacing: 0.2, + ), + textAlign: TextAlign.center, + ), + ), + ], + ), + ), + + SizedBox(height: context.responsiveHeight(24)), + + // Transcript Display + AnimatedContainer( + duration: const Duration(milliseconds: 500), + curve: Curves.easeOutCubic, + height: _spokenText.isNotEmpty ? null : 0, + child: + _spokenText.isNotEmpty + ? Container( + width: double.infinity, + padding: EdgeInsets.all( + context.responsiveWidth(20), + ), + decoration: BoxDecoration( + color: cardColor, + borderRadius: BorderRadius.circular( + context.responsiveWidth(20), + ), + border: Border.all( + color: accentColor.withOpacity(0.2), + width: 1.5, + ), + boxShadow: [ + BoxShadow( + color: accentColor.withOpacity( + 0.08, + ), + blurRadius: context.responsiveWidth( + 16, + ), + offset: Offset( + 0, + context.responsiveHeight(4), + ), + ), + ], + ), + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon( + Icons.transcribe_outlined, + color: accentColor, + size: context.responsiveWidth( + 18, + ), + ), + SizedBox( + width: context.responsiveWidth( + 8, + ), + ), + Text( + 'Transcript', + style: GoogleFonts.inter( + color: accentColor, + fontSize: context + .responsiveText(13), + fontWeight: FontWeight.w700, + letterSpacing: 0.8, + ), + ), + ], + ), + SizedBox( + height: context.responsiveHeight( + 12, + ), + ), + Text( + _spokenText, + style: GoogleFonts.inter( + color: primaryColor, + fontSize: context.responsiveText( + 15, + ), + fontWeight: FontWeight.w500, + height: 1.5, + letterSpacing: 0.1, + ), + ), + ], + ), + ) + : const SizedBox.shrink(), + ), + + SizedBox(height: context.responsiveHeight(32)), + + // Close Button + Container( + width: double.infinity, + height: context.responsiveHeight(56), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + primaryColor.withOpacity(0.06), + primaryColor.withOpacity(0.12), + ], + ), + borderRadius: BorderRadius.circular( + context.responsiveWidth(16), + ), + border: Border.all( + color: primaryColor.withOpacity(0.15), + width: 1, + ), + ), + child: Material( + color: Colors.transparent, + borderRadius: BorderRadius.circular( + context.responsiveWidth(16), + ), + child: InkWell( + borderRadius: BorderRadius.circular( + context.responsiveWidth(16), + ), + onTap: () => Navigator.pop(context), + child: Center( + child: Text( + 'Close', + style: GoogleFonts.inter( + color: primaryColor.withOpacity(0.8), + fontSize: context.responsiveText(16), + fontWeight: FontWeight.w600, + letterSpacing: 0.3, + ), + ), ), ), - ], - ), - const SizedBox(height: 8), - Text( - _spokenText, - style: GoogleFonts.inter( - color: primaryColor, - fontSize: 14, // Reduced font size - fontWeight: FontWeight.w500, - height: 1.4, // Reduced line height ), - maxLines: 5, // Limit lines - overflow: TextOverflow.ellipsis, ), - ], - ), - ), - const SizedBox(height: 20), // Reduced spacing - ], - // Action Button - Container( - width: double.infinity, - height: 50, // Reduced height - decoration: BoxDecoration( - color: primaryColor.withOpacity(0.08), - borderRadius: BorderRadius.circular(12), // Reduced radius - border: Border.all( - color: primaryColor.withOpacity(0.15), - width: 1, - ), - ), - child: Material( - color: Colors.transparent, - borderRadius: BorderRadius.circular(12), - child: InkWell( - borderRadius: BorderRadius.circular(12), - onTap: () => Navigator.pop(context), - child: Center( - child: Text( - 'Close', - style: GoogleFonts.inter( - color: primaryColor.withOpacity(0.8), - fontSize: 14, // Reduced font size - fontWeight: FontWeight.w600, - ), - ), - ), + SizedBox(height: context.responsiveHeight(20)), + ], ), ), ), - - const SizedBox(height: 16), // Added bottom spacing for safety ], ), ), ), - ], - ), + ); + }, ); } } diff --git a/Frontend/lib/main_pages/HomePage/homepage.dart b/Frontend/lib/main_pages/HomePage/homepage.dart index 723aaba..29a8395 100644 --- a/Frontend/lib/main_pages/HomePage/homepage.dart +++ b/Frontend/lib/main_pages/HomePage/homepage.dart @@ -227,7 +227,7 @@ class _FinTrackHomePageState extends State ), ), // Use bottomNavigationBar instead of floatingActionButton - bottomNavigationBar: CustomNavBar( + floatingActionButton: CustomNavBar( currentIndex: _selectedIndex, onTap: (index) { setState(() => _selectedIndex = index); @@ -236,6 +236,7 @@ class _FinTrackHomePageState extends State accentColor: accentColor, primaryColor: primaryColor, cardColor: cardColor, + floatingActionButtonLocation: FloatingActionButtonLocation.centerTop, ), ); } diff --git a/Frontend/lib/main_pages/Statistics/statistics.dart b/Frontend/lib/main_pages/Statistics/statistics.dart index ff8ab21..f354d19 100644 --- a/Frontend/lib/main_pages/Statistics/statistics.dart +++ b/Frontend/lib/main_pages/Statistics/statistics.dart @@ -767,6 +767,7 @@ class StatisticsState extends State with TickerProviderStateMixin { accentColor: accentColor, primaryColor: primaryColor, cardColor: cardColor, + floatingActionButtonLocation: FloatingActionButtonLocation.centerTop, ), ); } diff --git a/Frontend/lib/emai_screen.dart b/Frontend/lib/testing_pages/emai_screen.dart similarity index 100% rename from Frontend/lib/emai_screen.dart rename to Frontend/lib/testing_pages/emai_screen.dart diff --git a/Frontend/lib/email.dart b/Frontend/lib/testing_pages/email.dart similarity index 100% rename from Frontend/lib/email.dart rename to Frontend/lib/testing_pages/email.dart diff --git a/Frontend/lib/speech.dart b/Frontend/lib/testing_pages/speech.dart similarity index 100% rename from Frontend/lib/speech.dart rename to Frontend/lib/testing_pages/speech.dart