Compare commits

...

10 commits

37 changed files with 2854 additions and 555 deletions

View file

@ -160,7 +160,7 @@ app.post('/api/transaction/add', async (req, res) => {
const { description, amount, userId } = req.body;
if (!userId) return res.status(400).json({ error: 'Missing userId' });
const predictRes = await axios.post('http://10.231.41.181:5000/api/predict', {
const predictRes = await axios.post('http://10.231.7.150:5000/api/predict', {
description,
});
const category = predictRes.data.category || 'Other';
@ -212,13 +212,18 @@ app.get('/api/transactions/recent', async (req, res) => {
//VOICE
app.post('/api/transactions/voice', async (req, res) => {
try {
const { voiceInput } = req.body;
const { voiceInput, userId } = req.body; // ✅ take userId from body
if (!voiceInput) {
return res.status(400).json({ error: 'No voice input provided' });
}
if (!userId) {
return res.status(400).json({ error: 'Missing userId' }); // ✅ enforce userId like others
}
// Step 1: Use regex to extract amount
const amountMatch = voiceInput.match(/(?:\₹|\$)?(\d+(?:\.\d{1,2})?)/);
const amount = amountMatch ? parseFloat(amountMatch[1]) : null;
// Step 2: Extract description (rough logic: remove common verbs + amount)
const cleaned = voiceInput
.toLowerCase()
@ -226,19 +231,24 @@ app.post('/api/transactions/voice', async (req, res) => {
.replace(/₹?\d+/, '')
.trim();
const description = cleaned || 'misc';
// Step 3: Predict category using your Flask API
const predictRes = await axios.post('http://192.168.1.101:5000/api/predict', {
// Step 3: Predict category using Flask API
const predictRes = await axios.post('http://10.231.7.150:5000/api/predict', {
description,
});
const category = predictRes.data?.category || 'Other';
// Step 4: Save to DB
const newTransaction = new Transaction({
userId, // ✅ store userId
description,
amount,
category,
source: 'voice'
});
await newTransaction.save();
res.status(200).json({
message: '✅ Voice transaction saved',
data: newTransaction,
@ -429,7 +439,7 @@ app.post('/api/sync-gmail', async (req, res) => {
let category = 'Other';
try {
const predictRes = await axios.post('http://10.231.41.181:5000/api/predict', {
const predictRes = await axios.post('http://10.231.7.150:5000/api/predict', {
description: parsed.vendor || "Unknown"
}, { timeout: 5000 }); // Optional timeout

View file

@ -7,6 +7,9 @@
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
analyzer:
errors:
unused_field: ignore
include: package:flutter_lints/flutter.yaml
linter:

BIN
Frontend/assets/money.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

View file

@ -1,124 +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<LoginScreen> createState() => _LoginScreenState();
// }
// class _LoginScreenState extends State<LoginScreen> {
// // GoogleSignIn instance
// final GoogleSignIn _googleSignIn = GoogleSignIn(
// scopes: ['email', 'profile', 'openid'],
// serverClientId: '${Environment.serverClientId}',
// );
// bool _isLoading = false;
// Future<void> _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
// 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/emai_screen.dart';
//import 'package:monarch/email.dart';
import 'package:monarch/main_pages/HomePage/homepage.dart';
import 'package:monarch/main_pages/HomePage/Components/Voice/voice_dialog.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 {
@ -128,7 +17,8 @@ class LoginScreen extends StatefulWidget {
State<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
class _LoginScreenState extends State<LoginScreen>
with TickerProviderStateMixin {
final GoogleSignIn _googleSignIn = GoogleSignIn(
scopes: [
'email',
@ -140,6 +30,88 @@ class _LoginScreenState extends State<LoginScreen> {
);
bool _isLoading = false;
late AnimationController _fadeController;
late AnimationController _slideController;
late AnimationController _scaleController;
late AnimationController _rotationController;
late Animation<double> _fadeAnimation;
late Animation<Offset> _slideAnimation;
late Animation<double> _scaleAnimation;
late Animation<double> _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<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _fadeController, curve: Curves.easeInOut),
);
_slideAnimation = Tween<Offset>(
begin: const Offset(0, 1),
end: Offset.zero,
).animate(
CurvedAnimation(parent: _slideController, curve: Curves.elasticOut),
);
_scaleAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _scaleController, curve: Curves.elasticOut),
);
_rotationAnimation = Tween<double>(
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<void> _handleGoogleSignIn() async {
setState(() => _isLoading = true);
@ -162,7 +134,6 @@ class _LoginScreenState extends State<LoginScreen> {
return;
}
// Send idToken to backend for login
final uri = Uri.parse("${Environment.baseUrl}/auth/google/token");
final res = await http.post(
uri,
@ -177,17 +148,9 @@ class _LoginScreenState extends State<LoginScreen> {
prefs.setString('userId', data['user']['_id']);
prefs.setString('accessToken', accessToken);
// Navigate directly to EmailsScreen with accessToken
Navigator.pushReplacement(
context,
MaterialPageRoute(
// 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']}");
@ -204,31 +167,346 @@ class _LoginScreenState extends State<LoginScreen> {
@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>(
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,
),
),
),
],
),
);
}

View file

@ -6,9 +6,9 @@ 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/update_budget.dart';
import 'package:monarch/main_pages/Statistics/Budget/update_budget.dart';
import 'package:monarch/main_pages/HomePage/homepage.dart';
import 'package:monarch/main_pages/Statistics/statistics.dart';
@ -31,103 +31,3 @@ class MyApp extends StatelessWidget {
);
}
}
class AddTransactionPage extends StatefulWidget {
const AddTransactionPage({super.key});
@override
_AddTransactionPageState createState() => _AddTransactionPageState();
}
class _AddTransactionPageState extends State<AddTransactionPage> {
final TextEditingController descriptionController = TextEditingController();
final TextEditingController amountController = TextEditingController();
final baseUrl = dotenv.env['BASE_URL'];
Future<void> addTransaction() async {
// Show loading indicator
showDialog(
context: context,
barrierDismissible: false,
builder: (_) => const Center(child: CircularProgressIndicator()),
);
try {
final response = await http.post(
Uri.parse('$baseUrl/api/transaction/add'),
headers: {'Content-Type': 'application/json'},
body: json.encode({
'description': descriptionController.text,
'amount': double.tryParse(amountController.text),
}),
);
Navigator.of(context).pop(); // Close loading dialog
if (response.statusCode == 200) {
final responseData = json.decode(response.body);
final predictedCategory = responseData['data']['category'];
// Clear the input fields
descriptionController.clear();
amountController.clear();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'✅ Transaction added and categorized as: $predictedCategory',
),
),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('❌ Failed to add transaction: ${response.body}'),
),
);
}
} catch (e) {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const Statistics()),
);
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text('⚠️ Error: $e')));
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Add Transaction')),
body: Padding(
padding: EdgeInsets.all(16),
child: Column(
children: [
TextField(
controller: descriptionController,
decoration: InputDecoration(labelText: 'Description'),
),
TextField(
controller: amountController,
keyboardType: TextInputType.number,
decoration: InputDecoration(labelText: 'Amount'),
),
SizedBox(height: 20),
ElevatedButton(onPressed: addTransaction, child: Text('Submit')),
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const Statistics()),
);
},
child: const Text('NextPage'),
),
],
),
),
);
}
}

View file

@ -0,0 +1,598 @@
// 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});
@override
State<EmailTransactionDialog> createState() => _EmailTransactionDialogState();
}
class _EmailTransactionDialogState extends State<EmailTransactionDialog>
with TickerProviderStateMixin {
final List<dynamic> _emails = [];
bool _loading = true;
String? _status;
int _syncedCount = 0;
String? _error;
String? _userId;
String? _accessToken;
late AnimationController _slideController;
late AnimationController _fadeController;
late AnimationController _scaleController;
late AnimationController _rotationController;
late AnimationController _progressController;
late Animation<Offset> _slideAnimation;
late Animation<double> _fadeAnimation;
late Animation<double> _scaleAnimation;
late Animation<double> _rotationAnimation;
late Animation<double> _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<Offset>(
begin: const Offset(0, 1),
end: Offset.zero,
).animate(
CurvedAnimation(parent: _slideController, curve: Curves.elasticOut),
);
_fadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _fadeController, curve: Curves.easeInOut),
);
_scaleAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _scaleController, curve: Curves.elasticOut),
);
_rotationAnimation = Tween<double>(
begin: 0.0,
end: 2 * math.pi,
).animate(_rotationController);
_progressAnimation = Tween<double>(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<void> _autoSyncEmails() async {
setState(() {
_loading = true;
_status = "Syncing your bank emails...";
_syncedCount = 0;
});
_rotationController.repeat();
_progressController.forward();
try {
final prefs = await SharedPreferences.getInstance();
final userId = prefs.getString('userId');
final accessToken = prefs.getString('accessToken');
final response = await http.post(
Uri.parse("${Environment.baseUrl}/api/sync-gmail"),
headers: {"Content-Type": "application/json"},
body: jsonEncode({"accessToken": accessToken, "userId": userId}),
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
if (data['success'] == true && data['count'] != null) {
setState(() {
_loading = false;
_syncedCount = data['count'];
_status = "Synced";
});
_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 error: $error";
});
_rotationController.stop();
}
}
@override
Widget build(BuildContext context) {
return SlideTransition(
position: _slideAnimation,
child: Container(
height: context.responsiveHeight(560),
decoration: BoxDecoration(
color: cardColor,
borderRadius: BorderRadius.vertical(
top: Radius.circular(context.responsiveWidth(28)),
),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.08),
blurRadius: 32,
offset: const Offset(0, -8),
spreadRadius: 2,
),
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),
),
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: [
// 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),
),
),
),
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),
),
);
},
),
),
),
),
),
],
),
),
),
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),
),
);
},
),
),
),
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(
Icons.check_circle_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: [
SizedBox(width: context.responsiveWidth(8)),
Text(
"Update",
style: GoogleFonts.inter(
color: Colors.white,
fontSize: context.responsiveText(14),
fontWeight: FontWeight.w600,
letterSpacing: 0.2,
),
),
],
),
),
),
),
),
],
),
);
}
}

View file

@ -0,0 +1,105 @@
// ignore_for_file: use_super_parameters, deprecated_member_use
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:monarch/main_pages/HomePage/Components/Home/transaction_button.dart';
import 'package:monarch/other_pages/colors.dart';
class GreetingHeader extends StatelessWidget {
final String greetingText;
final Animation<double> scaleAnimation;
final VoidCallback onEmailPressed;
final VoidCallback onVoicePressed;
const GreetingHeader({
Key? key,
required this.greetingText,
required this.scaleAnimation,
required this.onEmailPressed,
required this.onVoicePressed,
}) : super(key: key);
String _getFormattedDate() {
final now = DateTime.now();
final weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
final months = [
'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec',
];
return '${weekdays[now.weekday - 1]}, ${now.day} ${months[now.month - 1]}';
}
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.only(bottom: 28),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: AnimatedBuilder(
animation: scaleAnimation,
builder: (context, child) {
return Transform.scale(
scale: scaleAnimation.value,
alignment: Alignment.centerLeft,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
'$greetingText ',
style: GoogleFonts.inter(
color: primaryColor,
fontSize: 28,
fontWeight: FontWeight.w800,
height: 1.1,
letterSpacing: -0.5,
),
),
],
),
const SizedBox(height: 6),
const SizedBox(height: 8),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 6,
),
decoration: BoxDecoration(
color: accentColor.withOpacity(0.08),
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: accentColor.withOpacity(0.15),
width: 1,
),
),
child: Text(
_getFormattedDate(),
style: GoogleFonts.inter(
color: accentColor,
fontSize: 12,
fontWeight: FontWeight.w600,
),
),
),
],
),
);
},
),
),
const SizedBox(width: 16),
TransactionButtons(
scaleAnimation: scaleAnimation,
onEmailPressed: onEmailPressed,
onVoicePressed: onVoicePressed,
),
],
),
);
}
}

View file

@ -0,0 +1,124 @@
// 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 void Function(int) onTap;
final Color backgroundColor;
final Color accentColor;
final Color primaryColor;
final Color cardColor;
const CustomNavBar({
super.key,
required this.currentIndex,
required this.onTap,
required this.backgroundColor,
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 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});
}

View file

@ -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),

View file

@ -0,0 +1,88 @@
// ignore_for_file: deprecated_member_use
import 'package:flutter/material.dart';
import 'package:monarch/other_pages/colors.dart';
class TransactionButtons extends StatelessWidget {
final Animation<double> scaleAnimation;
final VoidCallback onEmailPressed;
final VoidCallback onVoicePressed;
const TransactionButtons({
super.key,
required this.scaleAnimation,
required this.onEmailPressed,
required this.onVoicePressed,
});
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: scaleAnimation,
builder: (context, child) {
return Transform.scale(
scale: scaleAnimation.value,
child: Row(
children: [
_buildButton(
icon: Icons.mail_outline,
onPressed: onEmailPressed,
),
const SizedBox(width: 12),
_buildButton(
icon: Icons.mic_outlined,
onPressed: onVoicePressed,
),
],
),
);
},
);
}
Widget _buildButton({required IconData icon, required VoidCallback onPressed}) {
return Container(
width: 56,
height: 56,
decoration: BoxDecoration(
color: cardColor,
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: accentColor.withOpacity(0.08),
blurRadius: 20,
offset: const Offset(0, 8),
spreadRadius: 0,
),
BoxShadow(
color: Colors.black.withOpacity(0.04),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
border: Border.all(
color: accentColor.withOpacity(0.08),
width: 1.5,
),
),
child: Material(
color: Colors.transparent,
borderRadius: BorderRadius.circular(20),
child: InkWell(
borderRadius: BorderRadius.circular(20),
onTap: onPressed,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
),
child: Icon(
icon,
color: accentColor,
size: 28,
),
),
),
),
);
}
}

View file

@ -0,0 +1,422 @@
// ignore_for_file: 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/other_pages/reponsive.dart';
import 'package:monarch/other_pages/colors.dart';
import 'package:monarch/other_pages/enviroment.dart';
import 'package:shared_preferences/shared_preferences.dart';
class ConfirmTransactionPage extends StatefulWidget {
final Map<String, dynamic> data;
const ConfirmTransactionPage({super.key, required this.data});
@override
State<ConfirmTransactionPage> createState() => _ConfirmTransactionPageState();
}
class _ConfirmTransactionPageState extends State<ConfirmTransactionPage>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<Offset> _slideAnimation;
late Animation<double> _fadeAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 600),
);
_slideAnimation = Tween<Offset>(
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<void> _saveTransaction(BuildContext context) async {
const String saveUrl = '${Environment.baseUrl}/api/transaction/add';
try {
final prefs = await SharedPreferences.getInstance();
final userId = prefs.getString('userId');
if (userId == null) {
throw Exception('User ID not found. Please log in again.');
}
final response = await http.post(
Uri.parse(saveUrl),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'userId': userId,
'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(),
}),
);
if (response.statusCode == 200) {
_showCustomSnackBar(
context: context,
message: '✅ Transaction saved successfully!',
icon: Icons.check_circle,
backgroundColor: const Color(0xFF4CAF50),
iconColor: Colors.white,
);
Navigator.popUntil(context, (route) => route.isFirst);
} else {
throw Exception('Save failed: ${response.statusCode} ${response.body}');
}
} catch (e) {
_showCustomSnackBar(
context: context,
message: 'Error saving transaction: ${e.toString()}',
icon: Icons.error_outline,
backgroundColor: const Color(0xFFFF6B6B),
iconColor: Colors.white,
);
}
}
void _showCustomSnackBar({
required String message,
required IconData icon,
required Color backgroundColor,
required Color iconColor,
Duration duration = const Duration(seconds: 3),
required BuildContext context,
}) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
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),
),
),
),
],
),
backgroundColor: backgroundColor,
behavior: SnackBarBehavior.floating,
margin: EdgeInsets.all(context.responsiveWidth(12)),
duration: duration,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(context.responsiveWidth(12)),
),
),
);
}
Widget _buildDetailItem({
required IconData icon,
required String label,
required String value,
bool isAmount = false,
}) {
return Row(
children: [
Container(
width: context.responsiveWidth(40),
height: context.responsiveWidth(40),
decoration: BoxDecoration(
color: accentColor.withOpacity(0.08),
borderRadius: BorderRadius.circular(context.responsiveWidth(10)),
),
child: Icon(
icon,
color: accentColor,
size: context.responsiveText(20),
),
),
SizedBox(width: context.responsiveWidth(14)),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: GoogleFonts.inter(
color: primaryColor.withOpacity(0.6),
fontSize: context.responsiveText(12),
fontWeight: FontWeight.w500,
),
),
SizedBox(height: context.responsiveHeight(2)),
Text(
value,
style: GoogleFonts.inter(
color: isAmount ? Colors.green : primaryColor,
fontSize: context.responsiveText(16),
fontWeight: FontWeight.w700,
),
),
],
),
),
],
);
}
@override
Widget build(BuildContext context) {
final amount = widget.data['amount'];
final description = widget.data['description'];
final category = widget.data['category'];
final date =
widget.data['date'] != null
? DateTime.parse(widget.data['date']).toLocal()
: DateTime.now();
return Scaffold(
backgroundColor: backgroundColor,
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
leading: Container(
margin: EdgeInsets.all(context.responsiveWidth(6)),
decoration: BoxDecoration(
color: cardColor,
borderRadius: BorderRadius.circular(context.responsiveWidth(12)),
),
child: IconButton(
icon: Icon(Icons.arrow_back, color: primaryColor),
onPressed: () => Navigator.pop(context),
),
),
title: Text(
'Confirm Transaction',
style: GoogleFonts.inter(
color: primaryColor,
fontSize: context.responsiveText(18),
fontWeight: FontWeight.w700,
),
),
centerTitle: true,
),
body: SafeArea(
child: SlideTransition(
position: _slideAnimation,
child: FadeTransition(
opacity: _fadeAnimation,
child: Padding(
padding: EdgeInsets.all(context.responsiveWidth(20)),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header
Container(
padding: EdgeInsets.all(context.responsiveWidth(18)),
decoration: BoxDecoration(
color: cardColor,
borderRadius: BorderRadius.circular(
context.responsiveWidth(18),
),
boxShadow: [
BoxShadow(
color: accentColor.withOpacity(0.08),
blurRadius: 18,
offset: const Offset(0, 6),
),
],
),
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),
),
),
],
),
],
),
),
SizedBox(height: context.responsiveHeight(24)),
// Details Card
Container(
padding: EdgeInsets.all(context.responsiveWidth(20)),
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: BorderRadius.circular(
context.responsiveWidth(18),
),
border: Border.all(
color: primaryColor.withOpacity(0.08),
width: 1,
),
),
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),
),
),
),
],
),
],
),
),
),
),
),
);
}
}

View file

@ -0,0 +1,723 @@
// 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/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});
@override
_VoiceTransactionDialogState createState() => _VoiceTransactionDialogState();
}
class _VoiceTransactionDialogState extends State<VoiceTransactionDialog>
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<double> _pulseAnimation;
late Animation<double> _waveAnimation;
late Animation<Offset> _slideAnimation;
late Animation<double> _fadeAnimation;
@override
void initState() {
super.initState();
_speech = stt.SpeechToText();
// Initialize animation controllers
_pulseController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 1500),
);
_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<double>(begin: 1.0, end: 1.2).animate(
CurvedAnimation(parent: _pulseController, curve: Curves.easeInOut),
);
_waveAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _waveController, curve: Curves.elasticOut),
);
_slideAnimation = Tween<Offset>(
begin: const Offset(0, 0.3),
end: Offset.zero,
).animate(
CurvedAnimation(parent: _slideController, curve: Curves.easeOutCubic),
);
_fadeAnimation = Tween<double>(
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();
}
void _listen() async {
if (!_isListening) {
bool available = await _speech.initialize(
onStatus: (val) async {
if (val == 'done') {
setState(() {
_isListening = false;
_processing = true;
});
_pulseController.stop();
_waveController.stop();
await _speech.stop();
if (_spokenText.isNotEmpty) {
await _sendToBackend();
}
setState(() {
_processing = false;
});
}
},
onError: (val) {
_showCustomSnackBar(
message: 'Speech recognition error: $val',
isError: true,
);
},
);
if (available) {
setState(() {
_isListening = true;
_spokenText = '';
});
_pulseController.repeat(reverse: true);
_waveController.repeat();
_speech.listen(
onResult: (val) {
setState(() {
_spokenText = val.recognizedWords;
});
},
);
}
} else {
setState(() {
_isListening = false;
_processing = true;
});
_pulseController.stop();
_waveController.stop();
_speech.stop();
}
}
Future<void> _sendToBackend() async {
try {
final prefs = await SharedPreferences.getInstance();
final userId = prefs.getString('userId');
if (userId == null) {
throw Exception('User ID not found. Please log in again.');
}
final response = await http.post(
Uri.parse('${Environment.baseUrl}/api/transactions/voice'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({'userId': userId, 'voiceInput': _spokenText}),
);
if (response.statusCode == 200) {
final Map<String, dynamic> responseData = jsonDecode(response.body);
final transactionData = responseData['data'];
Navigator.pop(context);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ConfirmTransactionPage(data: transactionData),
),
);
} else {
throw Exception('Save failed with status: ${response.statusCode}');
}
} catch (e) {
_showCustomSnackBar(message: 'Error: ${e.toString()}', isError: true);
}
}
void _showCustomSnackBar({required String message, bool isError = false}) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Row(
children: [
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:
isError ? const Color(0xFFFF6B6B) : const Color(0xFF4CAF50),
duration: const Duration(seconds: 3),
behavior: SnackBarBehavior.floating,
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 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)),
),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: context.responsiveWidth(40),
offset: Offset(0, -context.responsiveHeight(8)),
),
],
),
child: Column(
children: [
// Handle Bar
Container(
width: context.responsiveWidth(48),
height: context.responsiveHeight(4),
margin: EdgeInsets.only(
top: context.responsiveHeight(20),
bottom: context.responsiveHeight(8),
),
decoration: BoxDecoration(
color: primaryColor.withOpacity(0.2),
borderRadius: BorderRadius.circular(
context.responsiveWidth(2),
),
),
),
// Header
Padding(
padding: EdgeInsets.fromLTRB(
context.responsiveWidth(24),
context.responsiveHeight(20),
context.responsiveWidth(24),
context.responsiveHeight(20),
),
child: Row(
children: [
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,
),
),
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,
),
),
],
),
),
],
),
),
// Main Content
Expanded(
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
padding: EdgeInsets.symmetric(
horizontal: context.responsiveWidth(24),
vertical: context.responsiveHeight(8),
),
child: Column(
children: [
// 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,
),
),
),
),
),
),
SizedBox(height: context.responsiveHeight(20)),
],
),
),
),
],
),
),
),
);
},
);
}
}

View file

@ -0,0 +1,37 @@
import 'package:flutter/material.dart';
class AnimatedSection extends StatelessWidget {
final int delay;
final Widget child;
const AnimatedSection({
super.key,
required this.delay,
required this.child,
});
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: Future.delayed(Duration(milliseconds: delay)),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Opacity(opacity: 0, child: child);
}
return AnimatedOpacity(
opacity: 1,
duration: const Duration(milliseconds: 600),
curve: Curves.easeOut,
child: AnimatedSlide(
offset: snapshot.connectionState == ConnectionState.done
? Offset.zero
: const Offset(0, 0.1),
duration: const Duration(milliseconds: 600),
curve: Curves.easeOutCubic,
child: child,
),
);
},
);
}
}

View file

@ -1,16 +1,19 @@
// ignore_for_file: deprecated_member_use, unused_local_variable, use_super_parameters, avoid_print
// ignore_for_file: deprecated_member_use
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:monarch/main_pages/HomePage/Components/Email/e_dialog.dart';
import 'package:monarch/main_pages/HomePage/Components/Home/hero_card.dart';
import 'package:monarch/main_pages/HomePage/animated.dart';
import 'package:monarch/main_pages/HomePage/Components/Home/greeting.dart';
import 'package:monarch/main_pages/HomePage/Components/Home/navbar.dart';
import 'package:monarch/main_pages/HomePage/Components/Voice/voice_dialog.dart';
import 'package:monarch/other_pages/colors.dart';
import 'package:monarch/support/fetch_service.dart';
import 'package:monarch/main_pages/HomePage/hero_card.dart';
import 'package:monarch/main_pages/HomePage/navbar.dart';
import 'package:monarch/main_pages/HomePage/quick_actions.dart';
import 'package:monarch/main_pages/HomePage/Components/Home/quick_actions.dart';
import 'package:monarch/support/transcations_recent.dart';
class FinTrackHomePage extends StatefulWidget {
const FinTrackHomePage({Key? key}) : super(key: key);
const FinTrackHomePage({super.key});
@override
State<FinTrackHomePage> createState() => _FinTrackHomePageState();
@ -18,171 +21,140 @@ class FinTrackHomePage extends StatefulWidget {
class _FinTrackHomePageState extends State<FinTrackHomePage>
with TickerProviderStateMixin {
// Animation Controllers
late AnimationController _fadeController;
late AnimationController _slideController;
late AnimationController _scaleController;
late Animation<double> _fadeAnimation;
late Animation<Offset> _slideAnimation;
late Animation<double> _scaleAnimation;
// Navigation State
int _selectedIndex = 0;
String _greetingText = '';
@override
void initState() {
super.initState();
_initializeData();
_setupAnimations();
_updateGreeting();
}
@override
void dispose() {
_fadeController.dispose();
_slideController.dispose();
_scaleController.dispose();
super.dispose();
}
// Initialize data fetching
void _initializeData() {
fetchRecentTransactions().then((transactions) {
// Handle transactions data
});
}
// Setup animation controllers and animations
void _updateGreeting() {
final now = DateTime.now();
final hour = now.hour;
if (hour >= 5 && hour < 12) {
_greetingText = 'Good Morning';
} else if (hour >= 12 && hour < 17) {
_greetingText = 'Good Afternoon';
} else if (hour >= 17 && hour < 21) {
_greetingText = 'Good Evening';
} else {
_greetingText = 'Good Night';
}
}
void _setupAnimations() {
_fadeController = AnimationController(
duration: const Duration(milliseconds: 1200),
duration: const Duration(milliseconds: 1500),
vsync: this,
);
_slideController = AnimationController(
duration: const Duration(milliseconds: 1200),
vsync: this,
);
_scaleController = AnimationController(
duration: const Duration(milliseconds: 800),
vsync: this,
);
_fadeAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _fadeController,
curve: Curves.easeOut,
));
_fadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _fadeController, curve: Curves.easeOutQuart),
);
_slideAnimation = Tween<Offset>(
begin: const Offset(0, 0.3),
begin: const Offset(0, 0.2),
end: Offset.zero,
).animate(CurvedAnimation(
parent: _slideController,
curve: Curves.easeOutCubic,
));
// Start animations
_fadeController.forward();
_slideController.forward();
}
// Build greeting header with enhanced styling
Widget _buildGreetingHeader() {
return Container(
margin: const EdgeInsets.only(bottom: 24),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// Greeting Section
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Good Morning! 👋',
style: GoogleFonts.inter(
color: primaryColor,
fontSize: 28,
fontWeight: FontWeight.w700,
height: 1.2,
),
),
const SizedBox(height: 4),
Text(
'Welcome back to your financial dashboard',
style: GoogleFonts.inter(
color: primaryColor.withOpacity(0.7),
fontSize: 14,
fontWeight: FontWeight.w400,
),
),
],
),
),
// Notification Button with enhanced design
Container(
width: 52,
height: 52,
decoration: BoxDecoration(
color: cardColor,
borderRadius: BorderRadius.circular(18),
boxShadow: [
BoxShadow(
color: accentColor.withOpacity(0.1),
blurRadius: 12,
offset: const Offset(0, 4),
),
],
border: Border.all(
color: accentColor.withOpacity(0.1),
width: 1,
),
),
child: Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.circular(18),
onTap: () {
// Handle notification tap
},
child: Icon(
Icons.notifications_outlined,
color: accentColor,
size: 26,
),
),
),
),
],
),
).animate(
CurvedAnimation(parent: _slideController, curve: Curves.easeOutCubic),
);
_scaleAnimation = Tween<double>(begin: 0.8, end: 1.0).animate(
CurvedAnimation(parent: _scaleController, curve: Curves.elasticOut),
);
_fadeController.forward();
Future.delayed(const Duration(milliseconds: 200), () {
_slideController.forward();
});
Future.delayed(const Duration(milliseconds: 400), () {
_scaleController.forward();
});
}
void _showTransactionEntryDialog(String type) {
if (type == 'voice') {
showModalBottomSheet(
context: context,
backgroundColor: Colors.transparent,
isScrollControlled: true,
builder: (context) => VoiceTransactionDialog(),
);
} else {
showModalBottomSheet(
context: context,
backgroundColor: Colors.transparent,
isScrollControlled: true,
builder: (context) => EmailTransactionDialog(),
);
}
}
// Build main content sections
Widget _buildMainContent() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Balance Card Section
Container(
margin: const EdgeInsets.only(bottom: 28),
child: const BalanceCardPage(),
),
// Quick Actions Section
Container(
margin: const EdgeInsets.only(bottom: 28),
child: QuickActionsPage(),
),
// Recent Transactions Section
Container(
margin: const EdgeInsets.only(bottom: 32),
child: RecentTransactionsWidget(
recentTransactions: fetchRecentTransactions(),
AnimatedSection(
delay: 600,
child: Container(
margin: const EdgeInsets.only(bottom: 32),
child: const BalanceCardPage(),
),
),
// Bottom spacing for FAB
const SizedBox(height: 100),
AnimatedSection(
delay: 800,
child: Container(
margin: const EdgeInsets.only(bottom: 32),
child: QuickActionsPage(),
),
),
AnimatedSection(
delay: 1000,
child: Container(
margin: const EdgeInsets.only(bottom: 32),
child: RecentTransactionsWidget(
recentTransactions: fetchRecentTransactions(),
),
),
),
const SizedBox(height: 120),
],
);
}
@ -196,28 +168,65 @@ class _FinTrackHomePageState extends State<FinTrackHomePage>
opacity: _fadeAnimation,
child: SlideTransition(
position: _slideAnimation,
child: CustomScrollView(
physics: const BouncingScrollPhysics(),
slivers: [
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.fromLTRB(20, 16, 20, 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildGreetingHeader(),
_buildMainContent(),
],
child: RefreshIndicator(
onRefresh: () async {
_initializeData();
_updateGreeting();
setState(() {});
},
color: accentColor,
backgroundColor: cardColor,
strokeWidth: 2.5,
displacement: 60,
child: CustomScrollView(
physics: const BouncingScrollPhysics(
parent: AlwaysScrollableScrollPhysics(),
),
slivers: [
SliverToBoxAdapter(
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
backgroundColor,
backgroundColor.withOpacity(0.95),
],
stops: const [0.0, 1.0],
),
),
child: Padding(
padding: const EdgeInsets.fromLTRB(
24,
20,
24,
80,
), // Added bottom padding for navbar
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
GreetingHeader(
greetingText: _greetingText,
scaleAnimation: _scaleAnimation,
onEmailPressed:
() => _showTransactionEntryDialog('email'),
onVoicePressed:
() => _showTransactionEntryDialog('voice'),
),
_buildMainContent(),
],
),
),
),
),
),
],
],
),
),
),
),
),
// Enhanced Floating Action Button
// Use bottomNavigationBar instead of floatingActionButton
floatingActionButton: CustomNavBar(
currentIndex: _selectedIndex,
onTap: (index) {

View file

@ -1,149 +0,0 @@
// ignore_for_file: deprecated_member_use
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 void Function(int) onTap;
final Color backgroundColor;
final Color accentColor;
final Color primaryColor;
final Color cardColor;
const CustomNavBar({
super.key,
required this.currentIndex,
required this.onTap,
required this.backgroundColor,
required this.accentColor,
required this.primaryColor,
required this.cardColor, required FloatingActionButtonLocation floatingActionButtonLocation,
});
// Define navigation items configuration
final List<NavItem> _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(
margin: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
height: 72,
decoration: BoxDecoration(
color: cardColor,
borderRadius: BorderRadius.circular(36),
boxShadow: [
BoxShadow(
color: primaryColor.withOpacity(0.1),
blurRadius: 30,
offset: const Offset(0, 8),
),
],
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
..._navItems.map((item) => _buildNavItem(context, item)),
_buildAddButton(context),
],
),
);
}
Widget _buildNavItem(BuildContext context, NavItem item) {
final isActive = currentIndex == item.index;
return Material(
color: Colors.transparent,
borderRadius: BorderRadius.circular(20),
child: InkWell(
borderRadius: BorderRadius.circular(20),
onTap: () {
onTap(item.index);
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => item.page),
);
},
child: Container(
width: 48,
height: 48,
decoration: BoxDecoration(
color: isActive ? primaryColor.withOpacity(0.1) : Colors.transparent,
borderRadius: BorderRadius.circular(20),
),
child: Icon(
item.icon,
color: isActive ? primaryColor : primaryColor.withOpacity(0.4),
size: 24,
),
),
),
);
}
Widget _buildAddButton(BuildContext context) {
return Container(
width: 48,
height: 48,
decoration: BoxDecoration(
color: accentColor,
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: accentColor.withOpacity(0.3),
blurRadius: 12,
offset: const Offset(0, 4),
),
],
),
child: Material(
color: Colors.transparent,
borderRadius: BorderRadius.circular(20),
child: InkWell(
onTap: () => Navigator.push(
context,
MaterialPageRoute(builder: (context) => const AddExpenseScreen()),
),
borderRadius: BorderRadius.circular(20),
child: const Icon(
Icons.add_rounded,
color: Colors.white,
size: 24,
),
),
),
);
}
}
// Navigation item model
class NavItem {
final IconData icon;
final int index;
final Widget page;
const NavItem({
required this.icon,
required this.index,
required this.page,
});
}

View file

@ -7,12 +7,13 @@ import 'package:fl_chart/fl_chart.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:http/http.dart' as http;
import 'package:monarch/other_pages/colors.dart';
import 'package:monarch/support/add.dart';
import 'package:monarch/other_pages/enviroment.dart';
import 'package:monarch/main_pages/Statistics/update_budget.dart';
import 'package:monarch/main_pages/Statistics/budget_manager.dart';
import 'package:monarch/main_pages/Statistics/Budget/update_budget.dart';
import 'package:monarch/main_pages/Statistics/Budget/budget_manager.dart';
import 'package:monarch/main_pages/HomePage/homepage.dart';
import 'package:monarch/main_pages/HomePage/navbar.dart';
import 'package:monarch/main_pages/HomePage/Components/Home/navbar.dart';
import 'package:shared_preferences/shared_preferences.dart';
class Transaction {
@ -62,12 +63,6 @@ class StatisticsState extends State<Statistics> with TickerProviderStateMixin {
String selectedCategory = 'All';
Map<String, double> totalAmountPerCategory = {};
// Color Scheme
final Color backgroundColor = const Color(0xFFF8F9FA);
final Color primaryColor = const Color(0xFF2D3436);
final Color accentColor = const Color(0xFF00B894);
final Color cardColor = const Color(0xFFFFFFFF);
final Color textSecondary = const Color(0xFF636E72);
final Color surfaceColor = const Color(0xFFF1F2F6);
// Responsive methods
double responsiveWidth(double size) =>
@ -772,7 +767,7 @@ class StatisticsState extends State<Statistics> with TickerProviderStateMixin {
accentColor: accentColor,
primaryColor: primaryColor,
cardColor: cardColor,
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
floatingActionButtonLocation: FloatingActionButtonLocation.centerTop,
),
);
}

View file

@ -1,5 +1,5 @@
class Environment {
static const String baseUrl = 'http://10.231.41.181:3000';
static const String baseUrl = 'http://10.231.7.150:3000';
static const String serverClientId =
"278433619849-cg6f7gk5cc45rgu1ojrlkf794lm4udgn.apps.googleusercontent.com";
}

View file

@ -0,0 +1,18 @@
import 'package:flutter/material.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;
}
}

View file

@ -0,0 +1,29 @@
package io.flutter.plugins;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import io.flutter.Log;
import io.flutter.embedding.engine.FlutterEngine;
/**
* Generated file. Do not edit.
* This file is generated by the Flutter tool based on the
* plugins that support the Android platform.
*/
@Keep
public final class GeneratedPluginRegistrant {
private static final String TAG = "GeneratedPluginRegistrant";
public static void registerWith(@NonNull FlutterEngine flutterEngine) {
try {
flutterEngine.getPlugins().add(new io.flutter.plugins.pathprovider.PathProviderPlugin());
} catch (Exception e) {
Log.e(TAG, "Error registering plugin path_provider_android, io.flutter.plugins.pathprovider.PathProviderPlugin", e);
}
try {
flutterEngine.getPlugins().add(new io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin());
} catch (Exception e) {
Log.e(TAG, "Error registering plugin shared_preferences_android, io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin", e);
}
}
}

View file

@ -0,0 +1,2 @@
sdk.dir=C:\\Developmenting\\SDK_and
flutter.sdk=C:\\Developmenting\\Flutter\\flutter

View file

@ -0,0 +1,14 @@
// This is a generated file; do not edit or check into version control.
FLUTTER_ROOT=C:\Developmenting\Flutter\flutter
FLUTTER_APPLICATION_PATH=D:\MeAPI\Monarch\Frontend\monarch
COCOAPODS_PARALLEL_CODE_SIGN=true
FLUTTER_TARGET=lib\main.dart
FLUTTER_BUILD_DIR=build
FLUTTER_BUILD_NAME=1.0.0
FLUTTER_BUILD_NUMBER=1
EXCLUDED_ARCHS[sdk=iphonesimulator*]=i386
EXCLUDED_ARCHS[sdk=iphoneos*]=armv7
DART_OBFUSCATION=false
TRACK_WIDGET_CREATION=true
TREE_SHAKE_ICONS=false
PACKAGE_CONFIG=.dart_tool/package_config.json

View file

@ -0,0 +1,13 @@
#!/bin/sh
# This is a generated file; do not edit or check into version control.
export "FLUTTER_ROOT=C:\Developmenting\Flutter\flutter"
export "FLUTTER_APPLICATION_PATH=D:\MeAPI\Monarch\Frontend\monarch"
export "COCOAPODS_PARALLEL_CODE_SIGN=true"
export "FLUTTER_TARGET=lib\main.dart"
export "FLUTTER_BUILD_DIR=build"
export "FLUTTER_BUILD_NAME=1.0.0"
export "FLUTTER_BUILD_NUMBER=1"
export "DART_OBFUSCATION=false"
export "TRACK_WIDGET_CREATION=true"
export "TREE_SHAKE_ICONS=false"
export "PACKAGE_CONFIG=.dart_tool/package_config.json"

View file

@ -0,0 +1,19 @@
//
// Generated file. Do not edit.
//
// clang-format off
#ifndef GeneratedPluginRegistrant_h
#define GeneratedPluginRegistrant_h
#import <Flutter/Flutter.h>
NS_ASSUME_NONNULL_BEGIN
@interface GeneratedPluginRegistrant : NSObject
+ (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry;
@end
NS_ASSUME_NONNULL_END
#endif /* GeneratedPluginRegistrant_h */

View file

@ -0,0 +1,28 @@
//
// Generated file. Do not edit.
//
// clang-format off
#import "GeneratedPluginRegistrant.h"
#if __has_include(<path_provider_foundation/PathProviderPlugin.h>)
#import <path_provider_foundation/PathProviderPlugin.h>
#else
@import path_provider_foundation;
#endif
#if __has_include(<shared_preferences_foundation/SharedPreferencesPlugin.h>)
#import <shared_preferences_foundation/SharedPreferencesPlugin.h>
#else
@import shared_preferences_foundation;
#endif
@implementation GeneratedPluginRegistrant
+ (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry {
[PathProviderPlugin registerWithRegistrar:[registry registrarForPlugin:@"PathProviderPlugin"]];
[SharedPreferencesPlugin registerWithRegistrar:[registry registrarForPlugin:@"SharedPreferencesPlugin"]];
}
@end

View file

@ -0,0 +1 @@
C:/Users/loram/AppData/Local/Pub/Cache/hosted/pub.dev/path_provider_linux-2.2.1/

View file

@ -0,0 +1 @@
C:/Users/loram/AppData/Local/Pub/Cache/hosted/pub.dev/shared_preferences_linux-2.4.1/

View file

@ -0,0 +1,11 @@
// This is a generated file; do not edit or check into version control.
FLUTTER_ROOT=C:\Developmenting\Flutter\flutter
FLUTTER_APPLICATION_PATH=D:\MeAPI\Monarch\Frontend\monarch
COCOAPODS_PARALLEL_CODE_SIGN=true
FLUTTER_BUILD_DIR=build
FLUTTER_BUILD_NAME=1.0.0
FLUTTER_BUILD_NUMBER=1
DART_OBFUSCATION=false
TRACK_WIDGET_CREATION=true
TREE_SHAKE_ICONS=false
PACKAGE_CONFIG=.dart_tool/package_config.json

View file

@ -0,0 +1,12 @@
#!/bin/sh
# This is a generated file; do not edit or check into version control.
export "FLUTTER_ROOT=C:\Developmenting\Flutter\flutter"
export "FLUTTER_APPLICATION_PATH=D:\MeAPI\Monarch\Frontend\monarch"
export "COCOAPODS_PARALLEL_CODE_SIGN=true"
export "FLUTTER_BUILD_DIR=build"
export "FLUTTER_BUILD_NAME=1.0.0"
export "FLUTTER_BUILD_NUMBER=1"
export "DART_OBFUSCATION=false"
export "TRACK_WIDGET_CREATION=true"
export "TREE_SHAKE_ICONS=false"
export "PACKAGE_CONFIG=.dart_tool/package_config.json"

View file

@ -0,0 +1 @@
C:/Users/loram/AppData/Local/Pub/Cache/hosted/pub.dev/path_provider_windows-2.3.0/

View file

@ -0,0 +1 @@
C:/Users/loram/AppData/Local/Pub/Cache/hosted/pub.dev/shared_preferences_windows-2.4.1/